Add notepad text import export
All checks were successful
Deploy Sokko G / deploy (push) Successful in 7s

This commit is contained in:
Shinuwa 2026-08-14 08:49:26 +02:00
parent 323e1b266d
commit f76cddcc95
8 changed files with 165 additions and 19 deletions

View file

@ -19,6 +19,7 @@ export function parseColonImportLines(text) {
}
const SUPPORTED_TEXT_EXCHANGE_TYPES = new Set([
"notepad",
"checklist",
"links",
"counters",
@ -42,6 +43,7 @@ export function supportsTextExchange(type) {
}
export function hasTextExchangeContent(type, data) {
if (type === "notepad") return Boolean(data?.text || data?.html || data?.drawings?.strokes?.length);
if (type === "checklist") return Boolean(data?.sections?.some((section) => section.items?.length));
if (type === "links") return Boolean(data?.links?.length);
if (type === "counters") return Boolean(data?.counters?.length);
@ -57,6 +59,7 @@ export function hasTextExchangeContent(type, data) {
}
export function exportModuleText(type, data) {
if (type === "notepad") return exportNotepadText(data);
if (type === "checklist") return exportChecklistText(data);
if (type === "links") return exportLinksText(data);
if (type === "counters") return exportCountersText(data);
@ -76,7 +79,8 @@ export function importModuleText(type, text, context) {
const value = String(text || "");
if (!value.trim()) return fail("Le texte à importer est vide.");
let data = null;
if (type === "checklist") data = importChecklistText(value, context);
if (type === "notepad") data = importNotepadText(value, context);
else if (type === "checklist") data = importChecklistText(value, context);
else if (type === "links") data = importLinksText(value, context);
else if (type === "counters") data = importCountersText(value, context);
else if (type === "combos") data = importCombosText(value, context);
@ -157,6 +161,97 @@ function cloneDrawingData(drawings) {
};
}
function escapeHtml(value) {
return String(value || "")
.replaceAll("&", "&")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;");
}
function textToNotepadExchangeHtml(value) {
return String(value || "")
.split(/\r?\n/)
.map((line) => line ? `<p>${escapeHtml(line)}</p>` : "<p><br></p>")
.join("");
}
function exportNotepadText(data) {
const html = String(data?.html || "").trim() || textToNotepadExchangeHtml(data?.text || "");
const lines = ["@notepad", "@html", html, "@endhtml"];
if (data?.drawingMode === "permanent") lines.push("@drawingMode: permanent");
if (data?.drawings?.strokes?.length) lines.push(`@drawings: ${JSON.stringify(cloneDrawingData(data.drawings))}`);
return lines.join("\n");
}
function cloneImportedNotepadDrawings(drawings, context) {
if (!drawings || typeof drawings !== "object" || !Array.isArray(drawings.strokes)) throw new Error("dessins invalides.");
return {
strokes: drawings.strokes.map((stroke) => {
if (!stroke || typeof stroke !== "object" || !Array.isArray(stroke.points) || stroke.points.length < 2) throw new Error("trait de dessin invalide.");
return {
...stroke,
id: context.uid("stroke"),
points: stroke.points.map((point) => {
const x = Number(point?.x);
const y = Number(point?.y);
if (!Number.isFinite(x) || !Number.isFinite(y)) throw new Error("point de dessin invalide.");
return { x, y };
})
};
})
};
}
function importNotepadText(text, context) {
const lines = splitLines(text);
const hasStructuredMarker = lines.some((line) => ["@notepad", "@html", "@endhtml"].includes(line.trim()) || parseMetaLine(line));
if (!hasStructuredMarker) return context.normalizeNotepadData({ text });
let readingHtml = false;
let htmlWasOpened = false;
let htmlWasClosed = false;
const htmlLines = [];
let drawingMode = "temporary";
let drawings = { strokes: [] };
lines.forEach((line, index) => {
const cleanLine = line.trim();
if (readingHtml) {
if (cleanLine === "@endhtml") {
readingHtml = false;
htmlWasClosed = true;
return;
}
htmlLines.push(line);
return;
}
if (!cleanLine || cleanLine === "@notepad") return;
if (cleanLine === "@html") {
if (htmlWasOpened) throw lineError(index, "bloc html dupliqué.");
readingHtml = true;
htmlWasOpened = true;
return;
}
const meta = parseMetaLine(cleanLine);
if (meta?.key === "drawingMode") {
if (!["temporary", "permanent"].includes(meta.value)) throw lineError(index, "mode de dessin invalide.");
drawingMode = meta.value;
return;
}
if (meta?.key === "drawings") {
try {
drawings = cloneImportedNotepadDrawings(JSON.parse(meta.value), context);
} catch (error) {
throw lineError(index, error?.message || "dessins invalides.");
}
if (drawings.strokes.length) drawingMode = "permanent";
return;
}
throw lineError(index, `ligne bloc notes invalide: ${cleanLine}`);
});
if (readingHtml || (htmlWasOpened && !htmlWasClosed)) throw new Error("Bloc HTML non terminé.");
return context.normalizeNotepadData({ html: htmlLines.join("\n").trim(), drawingMode, drawings });
}
function exportChecklistText(data) {
return (data.sections || []).flatMap((section) => [
section.title ? `# ${section.title}` : "",