import { USER_NOTES_MODULE_ID, USER_NOTES_WINDOW_ID, USER_NOTES_DEFAULT_POSITION, USER_NOTES_MIN_WIDTH, USER_NOTES_MIN_HEIGHT, USER_NOTES_VIEWPORT_MARGIN } from "./user-notes-constants.js"; import { userNotesLoadNotes, userNotesLoadPosition, userNotesSaveNotes, userNotesSavePositionData, userNotesRemoveSavedPosition } from "./user-notes-storage.js"; import { userNotesApplyWindowSettings } from "./user-notes-settings.js"; import { userNotesNormalizeStoredNotesForEditor, userNotesSanitizeHtml } from "./user-notes-sanitize.js"; let userNotesSaveTimer = null; export function userNotesSetStatus(text) { const status = document.querySelector( `#${USER_NOTES_WINDOW_ID} .user-notes-status` ); if (status) { status.textContent = text; } } export function userNotesDebouncedSave(value) { window.clearTimeout(userNotesSaveTimer); userNotesSetStatus("Ungespeichert …"); userNotesSaveTimer = window.setTimeout(() => { userNotesSaveNotes(userNotesSanitizeHtml(value)); userNotesSetStatus("Gespeichert"); }, 250); } export function userNotesApplyPosition(win, position) { win.style.left = `${position.left}px`; win.style.top = `${position.top}px`; win.style.width = `${position.width}px`; win.style.height = `${position.height}px`; } export function userNotesClampPosition(position) { const viewportWidth = Math.max( window.innerWidth, USER_NOTES_MIN_WIDTH + USER_NOTES_VIEWPORT_MARGIN ); const viewportHeight = Math.max( window.innerHeight, USER_NOTES_MIN_HEIGHT + USER_NOTES_VIEWPORT_MARGIN ); const maxWidth = Math.max( USER_NOTES_MIN_WIDTH, viewportWidth - USER_NOTES_VIEWPORT_MARGIN ); const maxHeight = Math.max( USER_NOTES_MIN_HEIGHT, viewportHeight - USER_NOTES_VIEWPORT_MARGIN ); const width = Math.max( USER_NOTES_MIN_WIDTH, Math.min(position.width, maxWidth) ); const height = Math.max( USER_NOTES_MIN_HEIGHT, Math.min(position.height, maxHeight) ); const maxLeft = Math.max(0, viewportWidth - width - 20); const maxTop = Math.max(0, viewportHeight - height - 20); const left = Math.max(0, Math.min(position.left, maxLeft)); const top = Math.max(0, Math.min(position.top, maxTop)); return { left, top, width, height }; } export function userNotesRestorePosition(win) { try { const pos = userNotesLoadPosition(null); if (!pos) { userNotesApplyPosition( win, userNotesClampPosition(USER_NOTES_DEFAULT_POSITION) ); return; } const restoredPosition = { left: Number.isFinite(pos.left) ? pos.left : USER_NOTES_DEFAULT_POSITION.left, top: Number.isFinite(pos.top) ? pos.top : USER_NOTES_DEFAULT_POSITION.top, width: Number.isFinite(pos.width) ? pos.width : USER_NOTES_DEFAULT_POSITION.width, height: Number.isFinite(pos.height) ? pos.height : USER_NOTES_DEFAULT_POSITION.height }; userNotesApplyPosition( win, userNotesClampPosition(restoredPosition) ); } catch (err) { console.warn( `${USER_NOTES_MODULE_ID} | Could not restore note window position`, err ); userNotesApplyPosition( win, userNotesClampPosition(USER_NOTES_DEFAULT_POSITION) ); } } export function userNotesSavePosition(win) { if (!win || win.hidden) { return; } const rect = win.getBoundingClientRect(); if (rect.width < USER_NOTES_MIN_WIDTH || rect.height < USER_NOTES_MIN_HEIGHT) { return; } const position = userNotesClampPosition({ left: Math.round(rect.left), top: Math.round(rect.top), width: Math.round(rect.width), height: Math.round(rect.height) }); userNotesSavePositionData(position); } export function userNotesResetPositionAndSize() { userNotesRemoveSavedPosition(); const win = document.getElementById(USER_NOTES_WINDOW_ID); if (win) { userNotesApplyPosition( win, userNotesClampPosition(USER_NOTES_DEFAULT_POSITION) ); userNotesSavePosition(win); const editor = win.__userNotesEditor; if (editor?.events) { editor.events.fire("resize"); } } } export function userNotesBringToFront(win) { const currentTop = Number.parseInt(win.style.zIndex || "100000", 10); win.style.zIndex = String(Math.max(currentTop + 1, 100000)); } export function userNotesMakeDraggable(win) { const handle = win.querySelector(".user-notes-titlebar"); if (!handle) { return; } let drag = null; handle.addEventListener("pointerdown", event => { const target = event.target; if (target instanceof HTMLElement && target.closest("button")) { return; } const rect = win.getBoundingClientRect(); drag = { pointerId: event.pointerId, startX: event.clientX, startY: event.clientY, left: rect.left, top: rect.top }; userNotesBringToFront(win); handle.setPointerCapture(event.pointerId); }); handle.addEventListener("pointermove", event => { if (!drag || drag.pointerId !== event.pointerId) { return; } const rect = win.getBoundingClientRect(); const clamped = userNotesClampPosition({ left: drag.left + event.clientX - drag.startX, top: drag.top + event.clientY - drag.startY, width: rect.width, height: rect.height }); win.style.left = `${clamped.left}px`; win.style.top = `${clamped.top}px`; }); handle.addEventListener("pointerup", event => { if (!drag || drag.pointerId !== event.pointerId) { return; } drag = null; userNotesSavePosition(win); const editor = win.__userNotesEditor; if (editor?.events) { editor.events.fire("resize"); } }); handle.addEventListener("pointercancel", event => { if (!drag || drag.pointerId !== event.pointerId) { return; } drag = null; userNotesSavePosition(win); const editor = win.__userNotesEditor; if (editor?.events) { editor.events.fire("resize"); } }); } function userNotesGetEditorValue(win) { const editor = win.__userNotesEditor; if (editor) { return userNotesSanitizeHtml(editor.value); } const textarea = win.querySelector(".user-notes-editor"); if (textarea instanceof HTMLTextAreaElement) { return userNotesSanitizeHtml(textarea.value); } return ""; } function userNotesDestroyEditor(win) { const editor = win.__userNotesEditor; if (editor && typeof editor.destruct === "function") { editor.destruct(); } win.__userNotesEditor = null; } function userNotesInsertSymbol(editor, symbol) { editor.s.insertHTML(`${symbol} `); } function userNotesCreateEditor(win, editorElement) { const initialContent = userNotesNormalizeStoredNotesForEditor(userNotesLoadNotes()); if (!globalThis.Jodit) { console.warn("User Notes | Jodit is not available. Falling back to plain textarea."); editorElement.value = initialContent .replace(//gi, "\n") .replace(/<\/p>/gi, "\n") .replace(/<[^>]+>/g, ""); editorElement.addEventListener("input", event => { userNotesDebouncedSave(event.currentTarget.value); }); return null; } const editor = globalThis.Jodit.make(editorElement, { height: "100%", minHeight: 150, allowResizeX: false, allowResizeY: false, toolbarAdaptive: false, askBeforePasteHTML: false, askBeforePasteFromWord: false, defaultActionOnPaste: "insert_clear_html", disablePlugins: [ "about", "add-new-line", "ai-assistant", "file", "image", "media", "paste-storage", "powered-by-jodit", "preview", "print", "source", "speech-recognize", "video" ], buttons: [ "bold", "italic", "underline", "strikethrough", "|", "ul", "ol", "|", "font", "fontsize", "brush", "|", "table", "|", "undo", "redo", "|", { name: "checkbox-empty", tooltip: "Kästchen einfügen", icon: "☐", exec: editorInstance => userNotesInsertSymbol(editorInstance, "☐") }, { name: "checkbox-checked", tooltip: "Angehaktes Kästchen einfügen", icon: "☑", exec: editorInstance => userNotesInsertSymbol(editorInstance, "☑") }, { name: "checkmark", tooltip: "Haken einfügen", icon: "✓", exec: editorInstance => userNotesInsertSymbol(editorInstance, "✓") }, { name: "crossmark", tooltip: "Kreuz einfügen", icon: "✗", exec: editorInstance => userNotesInsertSymbol(editorInstance, "✗") }, { name: "bordered-text", tooltip: "Rahmen um markierten Text", icon: "▣", exec: editorInstance => { const selected = editorInstance.s.html || " "; editorInstance.s.insertHTML( `${selected}` ); } } ] }); editor.value = initialContent; editor.events.on("change", () => { userNotesDebouncedSave(editor.value); }); win.__userNotesEditor = editor; return editor; } export function userNotesOpenNotes() { let win = document.getElementById(USER_NOTES_WINDOW_ID); if (win) { win.hidden = false; userNotesRestorePosition(win); userNotesApplyWindowSettings(win); userNotesBringToFront(win); const editor = win.__userNotesEditor; if (editor) { editor.focus(); if (editor.events) { editor.events.fire("resize"); } } else { win.querySelector(".user-notes-editor")?.focus(); } return; } win = document.createElement("section"); win.id = USER_NOTES_WINDOW_ID; win.className = "user-notes-window"; win.innerHTML = `
User Notes
Gespeichert
`; document.body.appendChild(win); userNotesApplyWindowSettings(win); userNotesRestorePosition(win); const editorElement = win.querySelector(".user-notes-editor"); if (!(editorElement instanceof HTMLTextAreaElement)) { console.error(`${USER_NOTES_MODULE_ID} | Notes editor could not be created.`); return; } userNotesCreateEditor(win, editorElement); win.querySelector(".user-notes-save")?.addEventListener("click", () => { userNotesSaveNotes(userNotesGetEditorValue(win)); userNotesSetStatus("Gespeichert"); userNotesSavePosition(win); const editor = win.__userNotesEditor; if (editor?.events) { editor.events.fire("resize"); } }); win.querySelector(".user-notes-close")?.addEventListener("click", () => { userNotesSaveNotes(userNotesGetEditorValue(win)); userNotesSavePosition(win); win.hidden = true; }); win.addEventListener("pointerdown", () => { userNotesBringToFront(win); }); const resizeObserver = new ResizeObserver(() => { if (win.hidden) { return; } const rect = win.getBoundingClientRect(); if (rect.width < USER_NOTES_MIN_WIDTH || rect.height < USER_NOTES_MIN_HEIGHT) { return; } userNotesSavePosition(win); const editor = win.__userNotesEditor; if (editor?.events) { editor.events.fire("resize"); } }); resizeObserver.observe(win); userNotesMakeDraggable(win); userNotesBringToFront(win); if (win.__userNotesEditor) { win.__userNotesEditor.focus(); if (win.__userNotesEditor.events) { win.__userNotesEditor.events.fire("resize"); } } else { editorElement.focus(); } } export function userNotesRefreshOpenWindow(options = {}) { const saveCurrentContent = options.saveCurrentContent ?? true; const oldWin = document.getElementById(USER_NOTES_WINDOW_ID); if (!oldWin) { return; } if (saveCurrentContent) { userNotesSaveNotes(userNotesGetEditorValue(oldWin)); } userNotesSavePosition(oldWin); userNotesDestroyEditor(oldWin); oldWin.remove(); userNotesOpenNotes(); }