feat(create): wizard uploads render as images in display and exports (imageUrl/fileUrl)
This commit is contained in:
@@ -6,7 +6,10 @@ import type {
|
||||
function isLabeledBlock(x: unknown): x is CommunityRuleLabeledBlock {
|
||||
if (!x || typeof x !== "object") return false;
|
||||
const o = x as Record<string, unknown>;
|
||||
return typeof o.label === "string" && typeof o.body === "string";
|
||||
if (typeof o.label !== "string" || typeof o.body !== "string") return false;
|
||||
if (o.imageUrl !== undefined && typeof o.imageUrl !== "string") return false;
|
||||
if (o.fileUrl !== undefined && typeof o.fileUrl !== "string") return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Shared by publish payload parsing and template body parsing — keep in sync. */
|
||||
|
||||
@@ -27,7 +27,19 @@ function entryToMarkdown(entry: CommunityRuleEntry): string {
|
||||
const lines: string[] = [`### ${entry.title}`, ""];
|
||||
if (entry.blocks && entry.blocks.length > 0) {
|
||||
for (const b of entry.blocks) {
|
||||
lines.push(`#### ${b.label}`, "", b.body, "");
|
||||
lines.push(`#### ${b.label}`, "");
|
||||
const img = b.imageUrl?.trim();
|
||||
const file = b.fileUrl?.trim();
|
||||
if (img) {
|
||||
lines.push(
|
||||
``,
|
||||
"",
|
||||
);
|
||||
} else if (file) {
|
||||
lines.push(`[${b.body.trim() || "file"}](${file})`, "");
|
||||
} else {
|
||||
lines.push(b.body, "");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const body = (entry.body ?? "").trim();
|
||||
@@ -86,7 +98,17 @@ export function sectionsToCsv(
|
||||
for (const ent of sec.entries) {
|
||||
if (ent.blocks && ent.blocks.length > 0) {
|
||||
for (const b of ent.blocks) {
|
||||
rows.push([sec.categoryName, ent.title, b.label, b.body]);
|
||||
const img = b.imageUrl?.trim();
|
||||
const file = b.fileUrl?.trim();
|
||||
const content =
|
||||
img != null && img.length > 0
|
||||
? b.body.trim().length > 0
|
||||
? `${b.body}\n${img}`
|
||||
: img
|
||||
: file != null && file.length > 0
|
||||
? `${b.body}\n${file}`
|
||||
: b.body;
|
||||
rows.push([sec.categoryName, ent.title, b.label, content]);
|
||||
}
|
||||
} else {
|
||||
rows.push([sec.categoryName, ent.title, "", ent.body ?? ""]);
|
||||
@@ -136,7 +158,18 @@ function entryToPrintHtml(entry: CommunityRuleEntry): string {
|
||||
if (entry.blocks && entry.blocks.length > 0) {
|
||||
for (const b of entry.blocks) {
|
||||
inner += `<h4 class="block-label">${escapeHtml(b.label)}</h4>`;
|
||||
inner += paragraphsHtml(b.body);
|
||||
const img = b.imageUrl?.trim();
|
||||
const file = b.fileUrl?.trim();
|
||||
if (img) {
|
||||
inner += `<p><img src="${escapeHtml(img)}" alt="${escapeHtml(b.body.trim() || b.label)}" /></p>`;
|
||||
if (b.body.trim().length > 0) {
|
||||
inner += paragraphsHtml(b.body);
|
||||
}
|
||||
} else if (file) {
|
||||
inner += `<p><a href="${escapeHtml(file)}">${escapeHtml(b.body.trim() || file)}</a></p>`;
|
||||
} else {
|
||||
inner += paragraphsHtml(b.body);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
inner += paragraphsHtml(entry.body ?? "");
|
||||
|
||||
@@ -7,6 +7,15 @@ import type { PublishedMethodSelections } from "./buildPublishPayload";
|
||||
import type { CustomMethodCardFieldBlock } from "./customMethodCardFieldBlocks";
|
||||
import { templateCategoryToGroupKey } from "./templateReviewMapping";
|
||||
|
||||
/** Uses filename extension and/or URL path so uploads render as `<img>` vs file link on read-only surfaces. */
|
||||
export function wizardUploadDisplaysAsImage(
|
||||
fileName: string | null,
|
||||
assetUrl: string | null,
|
||||
): boolean {
|
||||
if (fileName && /\.(jpe?g|png|gif|webp)$/i.test(fileName)) return true;
|
||||
if (assetUrl && /\.(jpe?g|png|gif|webp)(\?|#|$)/i.test(assetUrl)) return true;
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Serialize wizard-authored field blocks into Community Rule labeled rows for
|
||||
* read-only surfaces (completed step, exported views). Matches how those blocks
|
||||
@@ -32,8 +41,23 @@ export function labeledBlocksFromCustomMethodCardFieldBlocks(
|
||||
case "upload": {
|
||||
const name = nonEmptyTrimmed(b.fileName);
|
||||
const url = nonEmptyTrimmed(b.assetUrl);
|
||||
const body = name ?? url;
|
||||
if (body) out.push({ label: b.blockTitle, body });
|
||||
if (url) {
|
||||
if (wizardUploadDisplaysAsImage(name, url)) {
|
||||
out.push({
|
||||
label: b.blockTitle,
|
||||
body: "",
|
||||
imageUrl: url,
|
||||
});
|
||||
} else {
|
||||
out.push({
|
||||
label: b.blockTitle,
|
||||
body: name ?? url,
|
||||
fileUrl: url,
|
||||
});
|
||||
}
|
||||
} else if (name) {
|
||||
out.push({ label: b.blockTitle, body: name });
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "proportion":
|
||||
|
||||
Reference in New Issue
Block a user