initial commit after split
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
import type { CampaignVersionDetail } from "../../../api/campaigns";
|
||||
import { asRecord, getCampaignJson, isRecord } from "./campaignView";
|
||||
|
||||
export type DraftPatch = (draft: Record<string, unknown>) => Record<string, unknown>;
|
||||
|
||||
export function cloneJson<T>(value: T): T {
|
||||
return JSON.parse(JSON.stringify(value ?? {})) as T;
|
||||
}
|
||||
|
||||
export function ensureCampaignDraft(version: CampaignVersionDetail | null): Record<string, unknown> {
|
||||
const raw = cloneJson(getCampaignJson(version));
|
||||
raw.version = typeof raw.version === "string" ? raw.version : "1";
|
||||
raw.campaign = {
|
||||
id: "",
|
||||
name: "",
|
||||
description: "",
|
||||
mode: "draft",
|
||||
...asRecord(raw.campaign)
|
||||
};
|
||||
raw.fields = Array.isArray(raw.fields) ? raw.fields : [];
|
||||
raw.global_values = isRecord(raw.global_values) ? raw.global_values : {};
|
||||
raw.server = isRecord(raw.server) ? raw.server : {};
|
||||
raw.recipients = isRecord(raw.recipients) ? raw.recipients : {};
|
||||
raw.template = isRecord(raw.template) ? raw.template : { subject: "", text: "" };
|
||||
raw.attachments = {
|
||||
base_path: ".",
|
||||
allow_individual: false,
|
||||
send_without_attachments: true,
|
||||
global: [],
|
||||
missing_behavior: "ask",
|
||||
ambiguous_behavior: "ask",
|
||||
...asRecord(raw.attachments)
|
||||
};
|
||||
raw.entries = isRecord(raw.entries) ? raw.entries : { inline: [] };
|
||||
raw.validation_policy = {
|
||||
unsent_attachment_files: "warn",
|
||||
...asRecord(raw.validation_policy)
|
||||
};
|
||||
raw.delivery = isRecord(raw.delivery) ? raw.delivery : {};
|
||||
raw.status_tracking = isRecord(raw.status_tracking) ? raw.status_tracking : { enabled: true };
|
||||
return raw;
|
||||
}
|
||||
|
||||
export function updateNested(
|
||||
draft: Record<string, unknown>,
|
||||
path: string[],
|
||||
value: unknown
|
||||
): Record<string, unknown> {
|
||||
const next = cloneJson(draft);
|
||||
let current: Record<string, unknown> = next;
|
||||
path.forEach((segment, index) => {
|
||||
if (index === path.length - 1) {
|
||||
current[segment] = value;
|
||||
return;
|
||||
}
|
||||
const existing = current[segment];
|
||||
if (!isRecord(existing)) {
|
||||
current[segment] = {};
|
||||
}
|
||||
current = current[segment] as Record<string, unknown>;
|
||||
});
|
||||
return next;
|
||||
}
|
||||
|
||||
export function parseJsonTextarea<T>(text: string, fallback: T): { value: T; error: string } {
|
||||
if (!text.trim()) return { value: fallback, error: "" };
|
||||
try {
|
||||
return { value: JSON.parse(text) as T, error: "" };
|
||||
} catch (error) {
|
||||
return { value: fallback, error: error instanceof Error ? error.message : String(error) };
|
||||
}
|
||||
}
|
||||
|
||||
export function stringifyJson(value: unknown): string {
|
||||
return JSON.stringify(value ?? {}, null, 2);
|
||||
}
|
||||
|
||||
export function getBool(record: Record<string, unknown>, key: string, fallback = false): boolean {
|
||||
const value = record[key];
|
||||
return typeof value === "boolean" ? value : fallback;
|
||||
}
|
||||
|
||||
export function getNumber(record: Record<string, unknown>, key: string, fallback = 0): number {
|
||||
const value = record[key];
|
||||
return typeof value === "number" ? value : fallback;
|
||||
}
|
||||
|
||||
export function getText(record: Record<string, unknown>, key: string, fallback = ""): string {
|
||||
const value = record[key];
|
||||
if (typeof value === "string") return value;
|
||||
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
||||
return fallback;
|
||||
}
|
||||
|
||||
export function downloadJson(filename: string, data: Record<string, unknown>) {
|
||||
const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
export function safeFileStem(value?: string | null): string {
|
||||
const stem = (value || "campaign").replace(/[^a-zA-Z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "");
|
||||
return stem || "campaign";
|
||||
}
|
||||
Reference in New Issue
Block a user