Fix notepad cursor and calculator linked formulas
All checks were successful
Deploy Sokko G / deploy (push) Successful in 16s

This commit is contained in:
Shinuwa 2026-08-20 16:16:24 +02:00
parent 992665f4ea
commit 49ba0c4cb2
10 changed files with 223 additions and 45 deletions

View file

@ -1,5 +1,5 @@
// Rôle : fournit l'outil bloc-notes riche avec annotations dessinées.
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { CompactDropdown } from "../../../components/CompactDropdown.jsx";
import { DrawingControls, FormattingColorDropdown } from "./DrawingControls.jsx";
import { DrawingOverlay } from "./DrawingOverlay.jsx";
@ -13,6 +13,43 @@ function getPlainText(element) {
return (element?.innerText || "").replace(/\n{3,}/g, "\n\n").trim();
}
function getSelectionTextOffset(container) {
const selection = document.getSelection();
if (!container || !selection?.rangeCount || !container.contains(selection.anchorNode)) return null;
const range = selection.getRangeAt(0);
const prefix = range.cloneRange();
prefix.selectNodeContents(container);
prefix.setEnd(range.startContainer, range.startOffset);
return prefix.toString().length;
}
function restoreSelectionTextOffset(container, offset) {
if (!container || offset == null) return;
const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT);
let remaining = offset;
let node = walker.nextNode();
while (node) {
const length = node.textContent.length;
if (remaining <= length) {
const range = document.createRange();
range.setStart(node, remaining);
range.collapse(true);
const selection = document.getSelection();
selection.removeAllRanges();
selection.addRange(range);
return;
}
remaining -= length;
node = walker.nextNode();
}
const range = document.createRange();
range.selectNodeContents(container);
range.collapse(false);
const selection = document.getSelection();
selection.removeAllRanges();
selection.addRange(range);
}
function isHtmlEmpty(html, text) {
return !text && !String(html || "").replace(/<br\s*\/?>/gi, "").replace(/<[^>]*>/g, "").replace(/&nbsp;/gi, " ").trim();
}
@ -114,6 +151,9 @@ export function NotepadModule({ toolboxId, moduleId, context, editing = false })
const textContent = context.moduleText?.notepad || {};
const editorRef = useRef(null);
const surfaceRef = useRef(null);
const selectionOffsetRef = useRef(null);
const localSaveUpdatedAtRef = useRef("");
const moduleKeyRef = useRef(`${toolboxId}:${moduleId}`);
const [html, setHtml] = useState(normalizedData.html);
const [text, setText] = useState(normalizedData.text);
const [updatedAt, setUpdatedAt] = useState(normalizedData.updatedAt);
@ -137,6 +177,19 @@ export function NotepadModule({ toolboxId, moduleId, context, editing = false })
const [temporaryStrokes, setTemporaryStrokes] = useState([]);
useEffect(() => {
const moduleKey = `${toolboxId}:${moduleId}`;
if (moduleKeyRef.current !== moduleKey) {
moduleKeyRef.current = moduleKey;
localSaveUpdatedAtRef.current = "";
}
if (normalizedData.updatedAt && normalizedData.updatedAt === localSaveUpdatedAtRef.current) return;
const editor = editorRef.current;
if (document.activeElement === editor && getPlainText(editor) === normalizedData.text) {
setText(normalizedData.text);
setUpdatedAt(normalizedData.updatedAt);
setDrawingMode(normalizedData.drawingMode);
return;
}
setHtml(normalizedData.html);
setText(normalizedData.text);
setUpdatedAt(normalizedData.updatedAt);
@ -145,10 +198,12 @@ export function NotepadModule({ toolboxId, moduleId, context, editing = false })
setEraseActive(false);
}, [toolboxId, moduleId, normalizedData.html, normalizedData.text, normalizedData.updatedAt, normalizedData.drawingMode]);
useEffect(() => {
useLayoutEffect(() => {
if (!editorRef.current || editorRef.current.innerHTML === html) return;
if (document.activeElement === editorRef.current && getPlainText(editorRef.current) === text) return;
editorRef.current.innerHTML = html;
}, [html]);
if (document.activeElement === editorRef.current) restoreSelectionTextOffset(editorRef.current, selectionOffsetRef.current);
}, [html, text]);
useEffect(() => {
function updateActiveFormats() {
@ -199,6 +254,7 @@ export function NotepadModule({ toolboxId, moduleId, context, editing = false })
function saveContent(nextHtml, nextText) {
const nextUpdatedAt = new Date().toISOString();
localSaveUpdatedAtRef.current = nextUpdatedAt;
setHtml(nextHtml);
setText(nextText);
setUpdatedAt(nextUpdatedAt);
@ -241,6 +297,7 @@ export function NotepadModule({ toolboxId, moduleId, context, editing = false })
}
function handleEditorInput() {
selectionOffsetRef.current = getSelectionTextOffset(editorRef.current);
const nextHtml = editorRef.current?.innerHTML || "";
const nextText = getPlainText(editorRef.current);
saveContent(nextHtml, nextText);