Add notepad text import export
All checks were successful
Deploy Sokko G / deploy (push) Successful in 7s
All checks were successful
Deploy Sokko G / deploy (push) Successful in 7s
This commit is contained in:
parent
323e1b266d
commit
f76cddcc95
8 changed files with 165 additions and 19 deletions
|
|
@ -3,6 +3,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|||
import { CompactDropdown } from "../../../components/CompactDropdown.jsx";
|
||||
import { DrawingControls, FormattingColorDropdown } from "./DrawingControls.jsx";
|
||||
import { DrawingOverlay } from "./DrawingOverlay.jsx";
|
||||
import { TextExchangeActions } from "./TextExchangeActions.jsx";
|
||||
|
||||
const TEXT_COLORS = ["#f5f7ff", "#b4bdd3", "#f6c453", "#22d3ee", "#8b5cf6", "#d946ef"];
|
||||
const HIGHLIGHT_COLORS = ["#f6c453", "#22d3ee", "#8b5cf6", "#d946ef", "#202745"];
|
||||
|
|
@ -107,7 +108,7 @@ function ListTypeDropdown({ textContent, activeFormats, onCommand }) {
|
|||
);
|
||||
}
|
||||
|
||||
export function NotepadModule({ toolboxId, moduleId, context }) {
|
||||
export function NotepadModule({ toolboxId, moduleId, context, editing = false }) {
|
||||
const storedData = context.getModuleData(toolboxId, moduleId, { text: "" });
|
||||
const normalizedData = useMemo(() => context.normalizeNotepadData(storedData), [context, storedData]);
|
||||
const textContent = context.moduleText?.notepad || {};
|
||||
|
|
@ -142,7 +143,7 @@ export function NotepadModule({ toolboxId, moduleId, context }) {
|
|||
setDrawingMode(normalizedData.drawingMode);
|
||||
setDrawingActive(false);
|
||||
setEraseActive(false);
|
||||
}, [toolboxId, moduleId]);
|
||||
}, [toolboxId, moduleId, normalizedData.html, normalizedData.text, normalizedData.updatedAt, normalizedData.drawingMode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!editorRef.current || editorRef.current.innerHTML === html) return;
|
||||
|
|
@ -302,6 +303,11 @@ export function NotepadModule({ toolboxId, moduleId, context }) {
|
|||
|
||||
return (
|
||||
<div className="notepad-module">
|
||||
{editing && (
|
||||
<div className="module-add-panel notepad-exchange-panel">
|
||||
<TextExchangeActions type="notepad" data={normalizedData} context={context} textContent={textContent} onImport={(nextData) => context.setModuleData(toolboxId, moduleId, nextData, "notepad")} />
|
||||
</div>
|
||||
)}
|
||||
<div className="notepad-toolbar" aria-label={textContent.toolbarLabel || "Mise en page du bloc notes"}>
|
||||
<div className="notepad-format-controls">
|
||||
<div className="notepad-toolbar-group" role="group" aria-label={textContent.textFormatLabel || "Style du texte"}>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import { TaskPlannerModule } from "./TaskPlannerModule.jsx";
|
|||
import { TimerModule } from "./TimerModule.jsx";
|
||||
|
||||
const MODULE_COMPONENTS = {
|
||||
notepad: { label: "Bloc notes", icon: "notepad", Component: NotepadModule, editable: false },
|
||||
notepad: { label: "Bloc notes", icon: "notepad", Component: NotepadModule, editable: true },
|
||||
checklist: { label: "Checklist", icon: "checklist", Component: ChecklistModule, editable: true, scrollable: true },
|
||||
images: { label: "Images", icon: "picture", Component: ImagesModule, editable: true, scrollable: true },
|
||||
links: { label: "Liens", icon: "link", Component: LinksModule, editable: true, scrollable: true },
|
||||
|
|
|
|||
|
|
@ -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("<", "<")
|
||||
.replaceAll(">", ">");
|
||||
}
|
||||
|
||||
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}` : "",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue