diff --git a/shaarli-pro/css/custom_views.css b/shaarli-pro/css/custom_views.css
index 4068ef6..d19391c 100644
--- a/shaarli-pro/css/custom_views.css
+++ b/shaarli-pro/css/custom_views.css
@@ -13,14 +13,18 @@
/* --- TODO VIEW --- */
body.view-todo .content-container {
- max-width: 100%;
- padding: 0;
- margin: 0;
+ padding: 2rem;
+ background-color: var(--bg-body);
+ min-height: 100vh;
+}
+
+[data-theme="dark"] body.view-todo .content-container {
+ background-color: var(--bg-body);
}
body.view-todo #linklist {
- display: none;
- /* Hide default list when wrapper is active */
+ display: block;
+ /* Show default list when wrapper is not active */
}
/* Sidebar */
@@ -636,6 +640,8 @@ body.view-notes .content-container {
display: flex;
flex-direction: column;
gap: 8px;
+ position: relative;
+ z-index: 2;
}
/* Title */
@@ -1487,6 +1493,13 @@ body.view-notes .content-container {
background-position: center bottom;
}
+.note-card.todo-card.note-has-bg[data-font-color="auto"] {
+ --note-card-fg: rgba(255, 255, 255, 0.92);
+ color: var(--note-card-fg);
+ background-image: linear-gradient(rgba(0, 0, 0, 0.42), rgba(0, 0, 0, 0.42)), var(--note-bg-image);
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55);
+}
+
.note-card .note-title,
.note-card .note-body,
.note-card .note-tag,
@@ -2179,4 +2192,226 @@ body.view-archive .content-container {
right: auto;
top: auto;
transform: none;
+}
+
+body.view-todo .note-card.todo-card .note-body {
+ padding-top: 0.25rem;
+}
+
+body.view-todo .note-card.todo-card .todo-checklist-preview-wrap {
+ display: block;
+ -webkit-line-clamp: initial;
+ line-clamp: initial;
+ -webkit-box-orient: initial;
+ overflow: visible;
+ max-height: none;
+}
+
+.todo-checklist-preview {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 0.35rem;
+}
+
+.todo-checklist-preview-item {
+ display: flex;
+ align-items: center;
+ gap: 0.6rem;
+ font-size: 0.95rem;
+ line-height: 1.35;
+}
+
+.todo-checklist-preview-box {
+ width: 16px;
+ height: 16px;
+ border-radius: 4px;
+ border: 2px solid currentColor;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ flex: 0 0 auto;
+ opacity: 0.7;
+}
+
+.todo-checklist-preview-box i {
+ font-size: 14px;
+ opacity: 0;
+}
+
+.todo-checklist-preview-item.is-checked {
+ opacity: 0.7;
+}
+
+.todo-checklist-preview-item.is-checked .todo-checklist-preview-box i {
+ opacity: 1;
+}
+
+.todo-checklist-preview-item.is-checked .todo-checklist-preview-text {
+ text-decoration: line-through;
+}
+
+.todo-checklist-preview-more {
+ opacity: 0.7;
+}
+
+.todo-modal .note-modal-content {
+ padding-top: 6px;
+}
+
+.todo-checklist {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ padding-bottom: 6px;
+}
+
+.todo-checklist-row {
+ display: grid;
+ grid-template-columns: 26px 26px minmax(0, 1fr) 34px;
+ align-items: center;
+ gap: 8px;
+ padding: 6px 2px;
+ border-radius: 8px;
+}
+
+.todo-checklist-row.is-checked {
+ opacity: 0.75;
+}
+
+.todo-checklist-row.is-dragging {
+ opacity: 0.6;
+}
+
+.todo-drag-handle {
+ width: 26px;
+ height: 26px;
+ border: none;
+ background: none;
+ color: inherit;
+ opacity: 0.7;
+ cursor: grab;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0;
+}
+
+.todo-drag-handle:active {
+ cursor: grabbing;
+}
+
+.todo-drag-handle i {
+ font-size: 18px;
+}
+
+.todo-checklist-box {
+ width: 26px;
+ height: 26px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+}
+
+.todo-checklist-box input {
+ position: absolute;
+ opacity: 0;
+ pointer-events: none;
+}
+
+.todo-checklist-box-ui {
+ width: 16px;
+ height: 16px;
+ border-radius: 4px;
+ border: 2px solid currentColor;
+ display: inline-block;
+ opacity: 0.75;
+}
+
+.todo-item-checkbox:checked + .todo-checklist-box-ui {
+ background: currentColor;
+ box-shadow: inset 0 0 0 3px rgba(0, 0, 0, 0.22);
+}
+
+[data-theme="dark"] .todo-item-checkbox:checked + .todo-checklist-box-ui {
+ box-shadow: inset 0 0 0 3px rgba(0, 0, 0, 0.35);
+}
+
+.todo-item-text {
+ width: 100%;
+ border: none;
+ background: transparent;
+ color: inherit;
+ font-size: 1rem;
+ line-height: 1.4;
+ padding: 6px 6px;
+ border-radius: 6px;
+ outline: none;
+ min-width: 0;
+}
+
+.todo-checklist-row.is-checked .todo-item-text {
+ text-decoration: line-through;
+}
+
+.todo-item-text:focus {
+ background: rgba(0, 0, 0, 0.06);
+}
+
+[data-theme="dark"] .todo-item-text:focus {
+ background: rgba(255, 255, 255, 0.08);
+}
+
+.todo-item-delete {
+ width: 34px;
+ height: 34px;
+ border: none;
+ background: none;
+ color: inherit;
+ opacity: 0.7;
+ cursor: pointer;
+ border-radius: 50%;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0;
+}
+
+.todo-item-delete:hover {
+ opacity: 1;
+ background: rgba(0, 0, 0, 0.08);
+}
+
+[data-theme="dark"] .todo-item-delete:hover {
+ background: rgba(255, 255, 255, 0.12);
+}
+
+.todo-item-delete i {
+ font-size: 20px;
+}
+
+.todo-add-item-btn {
+ border: none;
+ background: none;
+ color: inherit;
+ opacity: 0.8;
+ cursor: pointer;
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+ padding: 8px 0;
+ font-size: 1rem;
+ justify-content: flex-start;
+ width: 100%;
+}
+
+.todo-add-item-btn i {
+ font-size: 18px;
+}
+
+.todo-add-item-btn:hover {
+ opacity: 1;
}
\ No newline at end of file
diff --git a/shaarli-pro/js/custom_views.js b/shaarli-pro/js/custom_views.js
index 75c2f03..1a04f64 100644
--- a/shaarli-pro/js/custom_views.js
+++ b/shaarli-pro/js/custom_views.js
@@ -6,6 +6,22 @@ document.addEventListener("DOMContentLoaded", function () {
const linkList = document.getElementById("links-list");
const container = document.querySelector(".content-container");
+ const restoreDefaultListView = () => {
+ try {
+ document.body.classList.remove("view-todo", "view-notes", "view-archive");
+ } catch (e) {
+ // noop
+ }
+
+ const toolbar = document.querySelector(".content-toolbar");
+ if (toolbar) toolbar.style.display = "";
+
+ const list = document.getElementById("links-list");
+ if (list) list.style.display = "";
+
+ document.querySelectorAll(".notes-wrapper.todo-wrapper").forEach((el) => el.remove());
+ };
+
const startViewInitialization = function () {
if (typeof initBookmarkPaletteButtons === "function") {
initBookmarkPaletteButtons();
@@ -24,7 +40,12 @@ document.addEventListener("DOMContentLoaded", function () {
if (!linkList || !container) return;
if (searchTags === "shaarli-todo") {
- initTodoView(linkList, container);
+ try {
+ initTodoView(linkList, container);
+ } catch (err) {
+ console.error("Erreur lors de l'initialisation de la vue Todo:", err);
+ restoreDefaultListView();
+ }
} else if (searchTags === "note") {
// Pour la vue notes, parser les notes AVANT de supprimer les tags techniques
// afin que les propriétés visuelles (couleur, fond, etc.) soient correctement extraites
@@ -179,6 +200,7 @@ function isTechnicalTag(tag) {
if (!t) return false;
if (t === "note") return true;
+ if (t === "shaarli-todo") return true;
if (t === "shaarli-pin") return true;
if (t.startsWith(NOTE_FONT_COLOR_TAG_PREFIX)) return true;
if (t.startsWith(NOTE_COLOR_TAG_PREFIX)) return true;
@@ -237,7 +259,7 @@ function removeTagFromEntity(editUrl, tag) {
if (input.type === "checkbox") {
if (input.checked) formData.append(input.name, input.value || "on");
} else if (input.name) {
- formData.append(input.name, input.value);
+ formData.append(input.name, getFormFieldValue(input));
}
});
@@ -317,11 +339,12 @@ function initTagDisplayAndRemoval() {
tags = tags.filter((t) => t !== tag);
card.dataset.tags = tags.join("||");
- const modal = document.querySelector(".note-modal-overlay");
- if (modal && modal.currentNote && String(modal.currentNote.id) === String(card.dataset.id)) {
- modal.currentNote.tags = (modal.currentNote.tags || []).filter((t) => t !== tag);
- const tagsContainer = modal.querySelector("#note-modal-tags");
- renderModalTags(tagsContainer, modal.currentNote.tags);
+ const modal = getOpenModalOverlay();
+ const entity = getModalCurrentEntity(modal);
+ if (modal && entity && String(entity.id) === String(card.dataset.id)) {
+ entity.tags = (entity.tags || []).filter((t) => t !== tag);
+ const tagsContainer = modal.querySelector(".note-modal-tags");
+ renderModalTags(tagsContainer, entity.tags);
}
}
@@ -330,22 +353,23 @@ function initTagDisplayAndRemoval() {
tags = tags.filter((t) => t !== tag);
card.dataset.tags = tags.join("||");
- const modal = document.querySelector(".note-modal-overlay");
- if (modal && modal.currentNote) {
- modal.currentNote.tags = (modal.currentNote.tags || []).filter((t) => t !== tag);
- const tagsContainer = modal.querySelector("#note-modal-tags");
- renderModalTags(tagsContainer, modal.currentNote.tags);
+ const modal = getOpenModalOverlay();
+ const entity = getModalCurrentEntity(modal);
+ if (modal && entity) {
+ entity.tags = (entity.tags || []).filter((t) => t !== tag);
+ const tagsContainer = modal.querySelector(".note-modal-tags");
+ renderModalTags(tagsContainer, entity.tags);
}
// Also update corresponding card if visible
- const noteId = card.dataset.noteId;
- const noteCard = noteId ? document.querySelector(`.note-card[data-id="${noteId}"]`) : null;
- if (noteCard) {
- const pill = noteCard.querySelector(`.note-tag[data-tag="${CSS.escape(tag)}"]`);
+ const entityId = card.dataset.noteId || card.dataset.todoId || (entity && entity.id ? String(entity.id) : "");
+ const entityCard = entityId ? document.querySelector(`.note-card[data-id="${entityId}"]`) : null;
+ if (entityCard) {
+ const pill = entityCard.querySelector(`.note-tag[data-tag="${CSS.escape(tag)}"]`);
if (pill) pill.remove();
- let noteTags = (noteCard.dataset.tags || "").split("||").filter((t) => t);
- noteTags = noteTags.filter((t) => t !== tag);
- noteCard.dataset.tags = noteTags.join("||");
+ let entityTags = (entityCard.dataset.tags || "").split("||").filter((t) => t);
+ entityTags = entityTags.filter((t) => t !== tag);
+ entityCard.dataset.tags = entityTags.join("||");
}
}
})
@@ -358,6 +382,15 @@ function initTagDisplayAndRemoval() {
tagDisplayRemovalInitialized = true;
}
+function getOpenModalOverlay() {
+ return document.querySelector(".note-modal-overlay.open");
+}
+
+function getModalCurrentEntity(modal) {
+ if (!modal) return null;
+ return modal.currentTodo || modal.currentNote || null;
+}
+
function resolveThemeAssetBasePath() {
const cssLink = Array.from(document.querySelectorAll('link[rel="stylesheet"]')).find(
(link) => link.href && link.href.includes("/custom_views.css"),
@@ -743,18 +776,19 @@ function refreshBackgroundPalettes() {
);
});
- const modal = document.querySelector(".note-modal-overlay");
- if (!modal || !modal.currentNote) return;
+ const modal = getOpenModalOverlay();
+ const entity = getModalCurrentEntity(modal);
+ if (!modal || !entity) return;
const modalCard = modal.querySelector(".note-modal");
if (modalCard) {
- modal.currentNote.color = getElementVisualColor(modalCard);
- modal.currentNote.background = getElementVisualBackground(modalCard);
+ entity.color = getElementVisualColor(modalCard);
+ entity.background = getElementVisualBackground(modalCard);
}
- const modalColorPopup = modal.querySelector("#note-modal-color-popup");
+ const modalColorPopup = modal.querySelector(".note-modal-palette");
if (modalColorPopup) {
- modalColorPopup.innerHTML = generateModalPaletteButtons(modal.currentNote);
+ modalColorPopup.innerHTML = generateModalPaletteButtons(entity);
}
}
@@ -1572,211 +1606,858 @@ function syncNoteFromCardElement(note, card) {
* Initialize the Google Tasks-like view
*/
function initTodoView(linkList, container) {
- document.body.classList.add("view-todo");
+ document.body.classList.add("view-todo", "view-notes");
- // Extract task data from existing DOM
- const rawLinks = Array.from(linkList.querySelectorAll(".link-outer"));
- const tasks = rawLinks.map((link) => parseTaskFromLink(link)).filter((t) => t !== null);
+ const basePath = typeof shaarli !== "undefined" && shaarli.basePath ? shaarli.basePath : "";
- // Create new Layout
- const wrapper = document.createElement("div");
- wrapper.className = "special-view-wrapper";
-
- // 1. Sidebar
- const sidebar = document.createElement("div");
- sidebar.className = "todo-sidebar";
-
- // Extract unique groups for the sidebar
- const groups = new Set();
- tasks.forEach((t) => {
- if (t.group) groups.add(t.group);
- });
-
- const groupsList = Array.from(groups)
- .map((g) => `
${g}
`)
- .join("");
-
- sidebar.innerHTML = `
-
-
- Mes tâches
- ${tasks.length}
-
- ${groups.size > 0 ? `${groupsList}` : ""}
- `;
-
- // 2. Main Content
- const main = document.createElement("div");
- main.className = "todo-main";
-
- const mainHeader = document.createElement("div");
- mainHeader.className = "todo-main-header";
- mainHeader.innerHTML = `
- Mes tâches
-
-
-
- `;
-
- const itemsContainer = document.createElement("div");
- itemsContainer.className = "todo-items-container";
-
- // Sort Tasks: Pinned items first
- tasks.sort((a, b) => {
- const aPinned = a.tags && a.tags.includes("shaarli-pin");
- const bPinned = b.tags && b.tags.includes("shaarli-pin");
- return bPinned - aPinned;
- });
-
- // Render Tasks
- tasks.forEach((task) => {
- itemsContainer.appendChild(renderTaskItem(task));
- });
-
- main.appendChild(mainHeader);
- main.appendChild(itemsContainer);
-
- wrapper.appendChild(sidebar);
- wrapper.appendChild(main);
-
- // Inject and Hide original
- linkList.style.display = "none";
-
- // Remove pagination/toolbar if present to clean up view
const toolbar = document.querySelector(".content-toolbar");
if (toolbar) toolbar.style.display = "none";
+ const wrapper = document.createElement("div");
+ wrapper.className = "notes-wrapper todo-wrapper";
+
+ const topBar = document.createElement("div");
+ topBar.className = "notes-top-bar";
+
+ const inputContainer = document.createElement("div");
+ inputContainer.className = "note-input-container";
+ inputContainer.innerHTML = `
+
+ `;
+ topBar.appendChild(inputContainer);
+
+ const tools = document.createElement("div");
+ tools.className = "notes-tools";
+ tools.innerHTML = `
+
+
+ `;
+ topBar.appendChild(tools);
+ wrapper.appendChild(topBar);
+
+ const contentArea = document.createElement("div");
+ contentArea.className = "notes-content-area";
+
+ const links = Array.from(linkList.querySelectorAll(".link-outer"));
+ const todos = links.map((link) => parseTodoFromLink(link)).filter((t) => t);
+
+ wrapper.appendChild(contentArea);
+
+ linkList.style.display = "none";
if (linkList.parentNode) {
linkList.parentNode.insertBefore(wrapper, linkList);
} else {
container.appendChild(wrapper);
}
- // Global filter function
- window.filterTasksByGroup = function (group) {
- const title = document.getElementById("todo-list-title");
- const items = document.querySelectorAll(".todo-item");
+ renderTodos(contentArea, todos, "grid");
- // Update Sidebar Active State
- document.querySelectorAll(".todo-list-item").forEach((el) => el.classList.remove("active"));
- if (event && event.currentTarget) event.currentTarget.classList.add("active");
+ const modalOverlay = document.createElement("div");
+ modalOverlay.className = "note-modal-overlay todo-modal-overlay";
+ modalOverlay.innerHTML = `
+
+ `;
+ document.body.appendChild(modalOverlay);
- if (group === "all") {
- title.textContent = "Mes tâches";
- items.forEach((item) => (item.style.display = "flex"));
+ const btnGrid = wrapper.querySelector("#btn-view-grid");
+ const btnList = wrapper.querySelector("#btn-view-list");
+
+ btnGrid.addEventListener("click", () => {
+ btnGrid.classList.add("active");
+ btnList.classList.remove("active");
+ renderTodos(contentArea, todos, "grid");
+ });
+
+ btnList.addEventListener("click", () => {
+ btnList.classList.add("active");
+ btnGrid.classList.remove("active");
+ renderTodos(contentArea, todos, "list");
+ });
+
+ modalOverlay.querySelector("#todo-modal-close").addEventListener("click", () => {
+ modalOverlay.classList.remove("open");
+ });
+ modalOverlay.addEventListener("click", (e) => {
+ if (e.target === modalOverlay) modalOverlay.classList.remove("open");
+ });
+
+ const modalPinBtn = modalOverlay.querySelector("#todo-modal-pin");
+ const modalColorBtn = modalOverlay.querySelector("#todo-modal-color-btn");
+ const modalColorPopup = modalOverlay.querySelector("#todo-modal-color-popup");
+
+ modalColorBtn.addEventListener("click", (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ const modalCard = modalOverlay.querySelector(".note-modal");
+ openBackgroundStudioPanel({
+ anchorEl: modalColorBtn,
+ mode: "modal",
+ entityId: modalCard ? modalCard.dataset.todoId || "" : "",
+ editUrl: modalCard ? modalCard.dataset.editUrl || "" : "",
+ currentColor: modalCard ? getElementVisualColor(modalCard) : "default",
+ currentFilter: modalCard ? getElementVisualFilter(modalCard) : "none",
+ currentBackground: modalCard ? getElementVisualBackground(modalCard) : "none",
+ currentFontColor: modalCard ? getElementVisualFontColor(modalCard) : "auto",
+ title: "Mes images & couleurs",
+ });
+ });
+
+ modalPinBtn.addEventListener("click", (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+
+ const modalCard = modalOverlay.querySelector(".note-modal");
+ const todoId = modalCard.dataset.todoId;
+ const editUrl = modalCard.dataset.editUrl;
+ if (!todoId || !editUrl) return;
+
+ togglePinTag(todoId, editUrl, modalPinBtn);
+
+ const isPinned = modalPinBtn.classList.contains("active");
+ let tags = (modalCard.dataset.tags || "").split("||").filter((t) => t);
+ if (isPinned) {
+ if (!tags.includes("shaarli-pin")) tags.push("shaarli-pin");
} else {
- title.textContent = group;
- items.forEach((item) => {
- if (item.dataset.group === group) item.style.display = "flex";
- else item.style.display = "none";
- });
+ tags = tags.filter((t) => t !== "shaarli-pin");
}
- };
+
+ modalCard.dataset.tags = tags.join("||");
+ renderModalTags(modalOverlay.querySelector("#todo-modal-tags"), tags);
+
+ if (modalOverlay.currentTodo) {
+ modalOverlay.currentTodo.isPinned = isPinned;
+ modalOverlay.currentTodo.tags = tags;
+ }
+ });
+
+ modalOverlay.querySelector("#todo-modal-delete").addEventListener("click", () => {
+ const modalCard = modalOverlay.querySelector(".note-modal");
+ const deleteUrl = modalCard.dataset.deleteUrl;
+ if (deleteUrl && deleteUrl !== "#") {
+ window.location.href = deleteUrl;
+ }
+ });
+
+ modalOverlay.addEventListener("click", (e) => {
+ if (!e.target.closest(".note-modal-color-picker")) {
+ modalColorPopup.classList.remove("open");
+ }
+ });
+
+ modalOverlay.querySelector("#todo-modal-add-item").addEventListener("click", (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ if (!modalOverlay.currentTodo) return;
+ addTodoItem(modalOverlay, modalOverlay.currentTodo);
+ });
+
+ if (typeof initTagDisplayAndRemoval === "function") {
+ initTagDisplayAndRemoval();
+ }
}
-function parseTaskFromLink(linkEl) {
+function parseTodoFromLink(linkEl) {
+ if (!linkEl) return null;
+
const id = linkEl.dataset.id;
- const titleEl = linkEl.querySelector(".link-title"); // "Title" normally
- // For Todos, the Bookmark Title is the Task Title?
- // Or is the title inside the description?
- // Mental model says: "Todo: reste un bookmark privé... LinkEntity.title" is used.
+ const titleEl = linkEl.querySelector(".link-title");
+ const title = titleEl ? titleEl.textContent.trim() : "";
- const title = titleEl ? titleEl.textContent.trim() : "Task";
const descEl = linkEl.querySelector(".link-description");
- const rawDesc = descEl ? descEl.innerHTML : "";
- const textDesc = descEl ? descEl.textContent : "";
+ const descHtml = descEl ? descEl.innerHTML : "";
+ const descText = descEl ? descEl.textContent : "";
- // Check if it's really a todo (should be if we are in ?searchtags=todo, but double check)
- // We assume yes.
+ const rawTags = [];
+ linkEl.querySelectorAll(".link-tag-list a").forEach((tag) => {
+ const t = (tag.textContent || "").trim();
+ if (t) rawTags.push(t);
+ });
- // Parse Metadata from Description text
- // Format: 📅 **Échéance :**
- // Format: 🏷️ **Groupe :**
- // Format: - [ ] Subtask or Main task status?
+ const { color, filter, background, fontColor } = extractNoteVisualStateFromTags(rawTags);
- // Status
- // If [x] is found in the first few lines, maybe completed?
- // User says: "Puis une checkbox markdown: - [ ] Titre"
- // Wait, if the Description contains the checkbox and title, then Bookmark Title is ignored?
- // Let's assume Bookmark Title is the master Display Title.
- // And "Checkbox status" can be parsed from description.
+ const tags = rawTags.filter((t) => {
+ if (t.startsWith(NOTE_COLOR_TAG_PREFIX)) return false;
+ if (t.startsWith("note-custom-")) return false;
+ if (t.startsWith(NOTE_FILTER_TAG_PREFIX)) return false;
+ if (t.startsWith(NOTE_BACKGROUND_TAG_PREFIX)) return false;
+ if (t.startsWith(NOTE_FONT_COLOR_TAG_PREFIX)) return false;
+ return true;
+ });
- let isCompleted = false;
- if (textDesc.includes("[x]")) isCompleted = true;
+ const actionsEl = linkEl.querySelector(".link-actions");
+ const editUrl = actionsEl && actionsEl.querySelector('a[href*="admin/shaare"]') ? actionsEl.querySelector('a[href*="admin/shaare"]').href : "#";
+ const deleteUrl = actionsEl && actionsEl.querySelector('a[href*="delete"]') ? actionsEl.querySelector('a[href*="delete"]').href : "#";
+ const pinUrl = actionsEl && actionsEl.querySelector('a[href*="pin"]') ? actionsEl.querySelector('a[href*="pin"]').href : "#";
+ const isPinned = tags.includes("shaarli-pin");
- // Due Date
- let dueDate = null;
- const dateMatch = textDesc.match(/Échéance\s*:\s*\*+([^*]+)\*+/); // varies by markdown regex
- // Text might be "📅 **Échéance :** 2023-10-10"
- // Regex: Échéance\s*:\s*(.*?)(\n|$)
- const dueMatch = textDesc.match(/Échéance\s*[:]\s*(.*?)(\n|$)/);
- if (dueMatch) dueDate = dueMatch[1].trim();
-
- // Group
- let group = null;
- const groupMatch = textDesc.match(/Groupe\s*[:]\s*(.*?)(\n|$)/);
- if (groupMatch) group = groupMatch[1].trim();
+ const items = extractChecklistItemsFromDescription(descEl, descText);
+ const parsed = parseTodoMarkdown(descText);
return {
id,
title,
- isCompleted,
- dueDate,
- group,
- originalUrl: linkEl.querySelector(".link-url") ? linkEl.querySelector(".link-url").textContent : "",
- editUrl: linkEl.querySelector('a[href*="admin/shaare/"]') ? linkEl.querySelector('a[href*="admin/shaare/"]').href : "#",
+ descHtml,
+ descText,
+ items,
+ _todoHeaderLines: parsed.headerLines,
+ _todoFooterLines: parsed.footerLines,
+ tags,
+ color,
+ filter,
+ background,
+ fontColor,
+ editUrl,
+ deleteUrl,
+ pinUrl,
+ isPinned,
};
}
-function renderTaskItem(task) {
- const el = document.createElement("div");
- el.className = `todo-item ${task.isCompleted ? "completed" : ""}`;
- el.dataset.group = task.group || "";
+function extractChecklistItemsFromDescription(descEl, descText) {
+ const items = [];
- /*
- We cannot easily toggle state (AJAX) without a backend API that supports partial updates efficiently.
- But we can provide a link to Edit.
- Or simulate it.
- */
+ if (descEl) {
+ const checkboxEls = Array.from(descEl.querySelectorAll('input[type="checkbox"]'));
+ if (checkboxEls.length > 0) {
+ checkboxEls.forEach((cb) => {
+ const li = cb.closest("li") || cb.parentElement;
+ let text = "";
+ if (li) {
+ const clone = li.cloneNode(true);
+ clone.querySelectorAll('input[type="checkbox"]').forEach((i) => i.remove());
+ text = (clone.textContent || "").trim();
+ }
+ items.push({ checked: !!cb.checked, text });
+ });
+ return items;
+ }
+ }
- el.innerHTML = `
-
- ${task.isCompleted ? '' : ""}
-
-
- `;
-
- // Simple click handler to open edit (since we can't sync state easily)
- el.querySelector(".todo-checkbox").addEventListener("click", (e) => {
- e.stopPropagation();
- // Open edit page or toggle visually
- // window.location.href = task.editUrl;
- });
-
- return el;
+ const lines = String(descText || "").split(/\r?\n/);
+ const fallbackSource = descEl ? descEl.innerHTML : lines.join("\n");
+ const parsed = parseTodoMarkdown(fallbackSource);
+ return parsed.items;
}
-function isOverdue(dateStr) {
+function getFormFieldValue(input) {
+ if (!input) return "";
+ const tag = (input.tagName || "").toUpperCase();
+ if (tag === "TEXTAREA") {
+ const v = typeof input.value === "string" ? input.value : "";
+ if (v && v.trim() !== "") return v;
+ const t = typeof input.textContent === "string" ? input.textContent : "";
+ return t;
+ }
+ return input.value;
+}
+
+async function hydrateTodoFromEditForm(todo) {
+ if (!todo || !todo.editUrl || todo.editUrl === "#") return;
+ if (todo._todoHydrated) return;
+ if (todo._todoHydratePromise) return todo._todoHydratePromise;
+
+ todo._todoHydratePromise = (async () => {
+ try {
+ const response = await fetch(todo.editUrl, { credentials: "same-origin" });
+ if (!response.ok) {
+ console.error("Todo hydration failed (edit form fetch not ok).", { id: todo && todo.id, url: todo && todo.editUrl, status: response && response.status });
+ return;
+ }
+ const html = await response.text();
+ const parser = new DOMParser();
+ const doc = parser.parseFromString(html, "text/html");
+ const form = doc.querySelector('form[name="linkform"]');
+ if (!form) {
+ console.error("Todo hydration failed (edit form not found).", { id: todo && todo.id, url: todo && todo.editUrl, status: response && response.status });
+ return;
+ }
+
+ const formData = new URLSearchParams();
+ const inputs = form.querySelectorAll("input, textarea");
+ inputs.forEach((input) => {
+ if (input.type === "checkbox") {
+ if (input.checked) formData.append(input.name, input.value || "on");
+ } else if (input.name) {
+ formData.append(input.name, getFormFieldValue(input));
+ }
+ });
+
+ todo._todoEditAction = form.action;
+ todo._todoEditBaseData = formData;
+
+ const rawMarkdown = formData.get("lf_description") || "";
+ const parsed = parseTodoMarkdown(rawMarkdown);
+ todo._todoHeaderLines = parsed.headerLines;
+ todo._todoFooterLines = parsed.footerLines;
+ todo.items = parsed.items;
+
+ if (!todo._todoDebugLogged && rawMarkdown && String(rawMarkdown).trim() && (!parsed.items || parsed.items.length === 0)) {
+ todo._todoDebugLogged = true;
+ console.warn("Todo parsed 0 items from lf_description; sample follows.", {
+ id: todo && todo.id,
+ url: todo && todo.editUrl,
+ sample: String(rawMarkdown).slice(0, 240),
+ });
+ }
+
+ updateTodoCardPreview(todo);
+ todo._todoHydrated = true;
+ } catch (e) {
+ console.error("Error hydrating todo markdown:", e);
+ } finally {
+ todo._todoHydratePromise = null;
+ }
+ })();
+
+ return todo._todoHydratePromise;
+}
+
+function renderTodos(container, todos, viewMode) {
+ if (!container) return;
+ container.innerHTML = "";
+ container.className = viewMode === "grid" ? "notes-masonry" : "notes-list-view";
+
+ const visibleTodos = (todos || []).slice();
+ visibleTodos.sort((a, b) => {
+ const aPinned = (a.tags || []).includes("shaarli-pin");
+ const bPinned = (b.tags || []).includes("shaarli-pin");
+ return bPinned - aPinned;
+ });
+
+ visibleTodos.forEach((todo) => {
+ const card = document.createElement("div");
+ card.className = "note-card todo-card";
+ card.dataset.id = todo.id;
+ card.dataset.editUrl = todo.editUrl || "";
+ card.dataset.tags = (todo.tags || []).filter((t) => t).join("||");
+ todo._todoCardEl = card;
+ applyNoteVisualState(card, todo);
+ if (viewMode === "list") card.classList.add("list-mode");
+
+ card.addEventListener("click", (e) => {
+ if (e.target.closest("button") || e.target.closest("a") || e.target.closest(".note-hover-actions")) return;
+ syncNoteFromCardElement(todo, card);
+ openTodoModal(todo);
+ });
+
+ const inner = document.createElement("div");
+ inner.className = "note-inner";
+
+ if (todo.title) {
+ const h3 = document.createElement("h3");
+ h3.className = "note-title";
+ h3.textContent = todo.title;
+ inner.appendChild(h3);
+ }
+
+ const body = document.createElement("div");
+ body.className = "note-body todo-checklist-preview-wrap";
+ body.innerHTML = buildTodoPreviewHtml(todo.items || []);
+ inner.appendChild(body);
+
+ if ((todo.tags || []).length > 0) {
+ const tagContainer = document.createElement("div");
+ tagContainer.className = "note-tags";
+ todo.tags.forEach((t) => {
+ if (isTechnicalTag(t)) return;
+ const pill = createTagPill({ tag: t, onRemoveClass: "note-tag-remove-btn", tagClass: "note-tag", canRemove: !!todo.editUrl && todo.editUrl !== "#" });
+ tagContainer.appendChild(pill);
+ });
+ inner.appendChild(tagContainer);
+ }
+
+ const actions = document.createElement("div");
+ actions.className = "note-hover-actions";
+
+ const paletteBtnId = `palette-${todo.id}`;
+
+ actions.innerHTML = `
+
+
+
+
+
+
+
+ `;
+
+ const paletteBtn = actions.querySelector(`#${paletteBtnId}`);
+ paletteBtn.addEventListener("click", (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ openBackgroundStudioPanel({
+ anchorEl: paletteBtn,
+ mode: "entity",
+ entityId: todo.id,
+ editUrl: todo.editUrl,
+ currentColor: getElementVisualColor(card),
+ currentFilter: getElementVisualFilter(card),
+ currentBackground: getElementVisualBackground(card),
+ title: "Mes images & couleurs",
+ });
+ });
+
+ inner.appendChild(actions);
+ card.appendChild(inner);
+ container.appendChild(card);
+
+ hydrateTodoFromEditForm(todo);
+ });
+}
+
+function buildTodoPreviewHtml(items) {
+ const safeItems = Array.isArray(items) ? items : [];
+ const maxItems = 8;
+ const visible = safeItems.slice(0, maxItems);
+ const hasMore = safeItems.length > maxItems;
+
+ const rows = visible
+ .map((it) => {
+ const checked = !!it.checked;
+ const text = escapeHtml(String(it.text || ""));
+ return `${text}`;
+ })
+ .join("");
+
+ return `${rows}${hasMore ? '- ...
' : ""}
`;
+}
+
+function escapeHtml(str) {
+ return String(str)
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/\"/g, """)
+ .replace(/'/g, "'");
+}
+
+function parseTodoMarkdown(markdown) {
+ const raw = String(markdown || "");
+ const normalized = raw
+ .replace(/
/gi, "\n")
+ .replace(/<\/(p|div|li|h\d)\s*>/gi, "\n");
+
+ let text = normalized;
try {
- return new Date(dateStr) < new Date();
+ const tmp = document.createElement("div");
+ tmp.innerHTML = normalized;
+ const checkboxEls = Array.from(tmp.querySelectorAll('input[type="checkbox"]'));
+ if (checkboxEls.length > 0) {
+ const items = [];
+ checkboxEls.forEach((cb) => {
+ const li = cb.closest("li") || cb.parentElement;
+ let itemText = "";
+ if (li) {
+ const clone = li.cloneNode(true);
+ clone.querySelectorAll('input[type="checkbox"]').forEach((i) => i.remove());
+ itemText = (clone.textContent || "").trim();
+ }
+ items.push({ checked: !!cb.checked, text: itemText });
+ });
+ return { headerLines: [], footerLines: [], items };
+ }
+
+ const listItemEls = Array.from(tmp.querySelectorAll("li"));
+ if (listItemEls.length > 0) {
+ const items = listItemEls
+ .map((li) => ({ checked: false, text: (li.textContent || "").trim() }))
+ .filter((it) => it.text);
+ if (items.length > 0) {
+ return { headerLines: [], footerLines: [], items };
+ }
+ }
+ text = tmp.textContent || normalized;
} catch (e) {
- return false;
+ text = normalized;
+ }
+
+ const lines = text.split(/\r?\n/);
+ const taskRegexes = [
+ /^\s*[-*+]\s*\[([ xX])\]\s*(.*)$/,
+ /^\s*\[([ xX])\]\s*(.*)$/,
+ /^\s*☐\s*(.*)$/,
+ /^\s*☑\s*(.*)$/,
+ ];
+
+ let firstTaskIndex = -1;
+ let lastTaskIndex = -1;
+ const items = [];
+
+ lines.forEach((line, idx) => {
+ let matched = false;
+
+ for (let i = 0; i < taskRegexes.length; i++) {
+ const rx = taskRegexes[i];
+ const m = line.match(rx);
+ if (!m) continue;
+
+ matched = true;
+ if (firstTaskIndex === -1) firstTaskIndex = idx;
+ lastTaskIndex = idx;
+
+ if (i === 2) {
+ items.push({ checked: false, text: (m[1] || "").trim() });
+ } else if (i === 3) {
+ items.push({ checked: true, text: (m[1] || "").trim() });
+ } else {
+ items.push({ checked: String(m[1] || "").toLowerCase() === "x", text: (m[2] || "").trim() });
+ }
+ break;
+ }
+
+ if (!matched) return;
+ });
+
+ const headerLines = firstTaskIndex === -1 ? lines.slice() : lines.slice(0, firstTaskIndex);
+ const footerLines = lastTaskIndex === -1 ? [] : lines.slice(lastTaskIndex + 1);
+
+ return { headerLines, footerLines, items };
+}
+
+function buildTodoMarkdown(todo) {
+ const headerLines = Array.isArray(todo._todoHeaderLines) ? todo._todoHeaderLines : [];
+ const footerLines = Array.isArray(todo._todoFooterLines) ? todo._todoFooterLines : [];
+ const items = Array.isArray(todo.items) ? todo.items : [];
+ const taskLines = items.map((it) => `- [${it.checked ? "x" : " "}] ${String(it.text || "").trim()}`);
+
+ const out = [];
+ headerLines.forEach((l) => out.push(l));
+ taskLines.forEach((l) => out.push(l));
+ footerLines.forEach((l) => out.push(l));
+
+ return out.join("\n").replace(/\s+$/g, "");
+}
+
+function renderTodoModalChecklist(modal, todo) {
+ const list = modal.querySelector("#todo-modal-checklist");
+ if (!list) return;
+
+ list.innerHTML = "";
+ const items = Array.isArray(todo.items) ? todo.items : [];
+
+ items.forEach((item, idx) => {
+ const row = document.createElement("div");
+ row.className = "todo-checklist-row";
+ row.dataset.index = String(idx);
+ row.setAttribute("draggable", "true");
+ row.classList.toggle("is-checked", !!item.checked);
+ row.innerHTML = `
+
+
+
+
+ `;
+
+ const cb = row.querySelector(".todo-item-checkbox");
+ const textInput = row.querySelector(".todo-item-text");
+ const delBtn = row.querySelector(".todo-item-delete");
+
+ cb.addEventListener("change", () => {
+ todo.items[idx].checked = cb.checked;
+ scheduleTodoSave(modal, todo);
+ row.classList.toggle("is-checked", cb.checked);
+ });
+
+ textInput.addEventListener("input", () => {
+ todo.items[idx].text = textInput.value;
+ scheduleTodoSave(modal, todo);
+ });
+
+ delBtn.addEventListener("click", (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ todo.items.splice(idx, 1);
+ renderTodoModalChecklist(modal, todo);
+ scheduleTodoSave(modal, todo);
+ });
+
+ row.addEventListener("dragstart", (e) => {
+ row.classList.add("is-dragging");
+ e.dataTransfer.effectAllowed = "move";
+ e.dataTransfer.setData("text/plain", String(idx));
+ });
+ row.addEventListener("dragend", () => {
+ row.classList.remove("is-dragging");
+ });
+
+ list.appendChild(row);
+ });
+
+ if (!list.dataset.dndInit) {
+ list.addEventListener(
+ "dragover",
+ (e) => {
+ e.preventDefault();
+ const dragging = list.querySelector(".todo-checklist-row.is-dragging");
+ if (!dragging) return;
+
+ const afterElement = getDragAfterElement(list, e.clientY);
+ if (afterElement == null) {
+ list.appendChild(dragging);
+ } else {
+ list.insertBefore(dragging, afterElement);
+ }
+ },
+ { passive: false },
+ );
+
+ list.addEventListener("drop", (e) => {
+ e.preventDefault();
+ const current = modal && modal.currentTodo ? modal.currentTodo : null;
+ if (!current) return;
+ const rows = Array.from(list.querySelectorAll(".todo-checklist-row"));
+ const newItems = [];
+ rows.forEach((r) => {
+ const oldIndex = Number(r.dataset.index);
+ if (!Number.isNaN(oldIndex) && current.items && current.items[oldIndex]) {
+ newItems.push(current.items[oldIndex]);
+ }
+ });
+ current.items = newItems;
+ renderTodoModalChecklist(modal, current);
+ scheduleTodoSave(modal, current);
+ });
+
+ list.dataset.dndInit = "1";
}
}
-function formatDate(dateStr) {
- try {
- const d = new Date(dateStr);
- return d.toLocaleDateString();
- } catch (e) {
- return dateStr;
+function getDragAfterElement(container, y) {
+ const draggableElements = [...container.querySelectorAll(".todo-checklist-row:not(.is-dragging)")];
+
+ return draggableElements.reduce(
+ (closest, child) => {
+ const box = child.getBoundingClientRect();
+ const offset = y - box.top - box.height / 2;
+ if (offset < 0 && offset > closest.offset) {
+ return { offset, element: child };
+ }
+ return closest;
+ },
+ { offset: Number.NEGATIVE_INFINITY, element: null },
+ ).element;
+}
+
+async function openTodoModal(todo) {
+ const modal = document.querySelector(".todo-modal-overlay");
+ if (!modal) return;
+
+ const modalCard = modal.querySelector(".note-modal");
+ const title = modal.querySelector("#todo-modal-title");
+ const tagsContainer = modal.querySelector("#todo-modal-tags");
+ const editLink = modal.querySelector("#todo-modal-edit");
+ const pinButton = modal.querySelector("#todo-modal-pin");
+ const modalColorPopup = modal.querySelector("#todo-modal-color-popup");
+
+ modal.currentTodo = todo;
+
+ modalCard.className = "note-modal todo-modal";
+ applyNoteVisualState(modalCard, todo);
+ modalCard.dataset.todoId = todo.id || "";
+ modalCard.dataset.editUrl = todo.editUrl || "";
+ modalCard.dataset.deleteUrl = todo.deleteUrl || "";
+ modalCard.dataset.background = todo.background || "none";
+
+ const visibleTags = (todo.tags || []).filter((tag) => tag && !isTechnicalTag(tag));
+ modalCard.dataset.tags = visibleTags.join("||");
+
+ title.textContent = todo.title || "Sans titre";
+ renderModalTags(tagsContainer, visibleTags);
+
+ if (editLink) {
+ editLink.href = todo.editUrl || "#";
+ }
+
+ if (modalColorPopup) {
+ modalColorPopup.innerHTML = generateUnifiedPaletteMenu({
+ entityId: todo && todo.id ? todo.id : "",
+ editUrl: todo && todo.editUrl ? todo.editUrl : "",
+ currentColor: todo && todo.color ? todo.color : "default",
+ currentFilter: todo && todo.filter ? todo.filter : "none",
+ mode: "modal",
+ });
+ modalColorPopup.classList.remove("open");
+ }
+
+ setModalPinButtonState(pinButton, !!todo.isPinned);
+
+ modal.classList.add("open");
+ renderTodoModalChecklist(modal, todo);
+
+ const loadKey = `${todo.id}-${Date.now()}`;
+ modal._todoLoadKey = loadKey;
+
+ if (todo.editUrl && todo.editUrl !== "#") {
+ try {
+ const response = await fetch(todo.editUrl, { credentials: "same-origin" });
+ if (!response.ok) {
+ console.error("Todo modal load failed (edit form fetch not ok).", { id: todo && todo.id, url: todo && todo.editUrl, status: response && response.status });
+ return;
+ }
+ const html = await response.text();
+ if (modal._todoLoadKey !== loadKey) return;
+
+ const parser = new DOMParser();
+ const doc = parser.parseFromString(html, "text/html");
+ const form = doc.querySelector('form[name="linkform"]');
+ if (!form) {
+ console.error("Todo modal load failed (edit form not found).", { id: todo && todo.id, url: todo && todo.editUrl, status: response && response.status });
+ return;
+ }
+
+ const formData = new URLSearchParams();
+ const inputs = form.querySelectorAll("input, textarea");
+ inputs.forEach((input) => {
+ if (input.type === "checkbox") {
+ if (input.checked) formData.append(input.name, input.value || "on");
+ } else if (input.name) {
+ formData.append(input.name, getFormFieldValue(input));
+ }
+ });
+
+ todo._todoEditAction = form.action;
+ todo._todoEditBaseData = formData;
+
+ const rawMarkdown = formData.get("lf_description") || "";
+ const parsed = parseTodoMarkdown(rawMarkdown);
+ todo._todoHeaderLines = parsed.headerLines;
+ todo._todoFooterLines = parsed.footerLines;
+ todo.items = parsed.items;
+
+ renderTodoModalChecklist(modal, todo);
+ } catch (e) {
+ console.error("Error loading todo markdown:", e);
+ }
+ }
+}
+
+function addTodoItem(modal, todo) {
+ if (!todo.items) todo.items = [];
+ todo.items.push({ checked: false, text: "" });
+ renderTodoModalChecklist(modal, todo);
+ const inputs = modal.querySelectorAll(".todo-item-text");
+ const last = inputs && inputs.length ? inputs[inputs.length - 1] : null;
+ if (last) last.focus();
+ scheduleTodoSave(modal, todo);
+}
+
+function scheduleTodoSave(modal, todo) {
+ if (!modal || !todo) return;
+ if (modal._todoSaveTimer) {
+ clearTimeout(modal._todoSaveTimer);
+ }
+
+ updateTodoCardPreview(todo);
+
+ modal._todoSaveTimer = setTimeout(() => {
+ persistTodoChanges(todo).catch((err) => {
+ console.error("Error saving todo:", err);
+ alert("Erreur lors de la sauvegarde de la tâche. Veuillez rafraîchir la page.");
+ });
+ }, 600);
+}
+
+function updateTodoCardPreview(todo) {
+ if (!todo || !todo.id) return;
+ let card = todo._todoCardEl && todo._todoCardEl.isConnected ? todo._todoCardEl : null;
+ if (!card) {
+ card = document.querySelector(`.note-card.todo-card[data-id="${String(todo.id)}"]`);
+ }
+ if (!card) {
+ const all = Array.from(document.querySelectorAll(".note-card.todo-card"));
+ card = all.find((c) => String(c.dataset.id || "") === String(todo.id)) || null;
+ }
+ if (!card) return;
+ const wrap = card.querySelector(".todo-checklist-preview-wrap");
+ if (!wrap) return;
+ wrap.innerHTML = buildTodoPreviewHtml(todo.items || []);
+}
+
+async function persistTodoChanges(todo, retryCount = 0) {
+ if (!todo || !todo.editUrl || todo.editUrl === "#") return;
+
+ const refreshEditForm = async () => {
+ const response = await fetch(todo.editUrl, { credentials: "same-origin" });
+ const html = await response.text();
+ const parser = new DOMParser();
+ const doc = parser.parseFromString(html, "text/html");
+ const form = doc.querySelector('form[name="linkform"]');
+ if (!form) throw new Error("Could not find edit form");
+
+ const baseData = new URLSearchParams();
+ const inputs = form.querySelectorAll("input, textarea");
+ inputs.forEach((input) => {
+ if (input.type === "checkbox") {
+ if (input.checked) baseData.append(input.name, input.value || "on");
+ } else if (input.name) {
+ baseData.append(input.name, getFormFieldValue(input));
+ }
+ });
+
+ todo._todoEditAction = form.action;
+ todo._todoEditBaseData = baseData;
+
+ const rawMarkdown = baseData.get("lf_description") || "";
+ const parsed = parseTodoMarkdown(rawMarkdown);
+ todo._todoHeaderLines = parsed.headerLines;
+ todo._todoFooterLines = parsed.footerLines;
+ };
+
+ if (!todo._todoEditAction || !todo._todoEditBaseData || !(todo._todoEditBaseData.get && todo._todoEditBaseData.get("token"))) {
+ await refreshEditForm();
+ }
+
+ const description = buildTodoMarkdown(todo);
+ const formData = new URLSearchParams(todo._todoEditBaseData.toString());
+ formData.set("lf_description", description);
+ formData.append("save_edit", "1");
+
+ const response = await fetch(todo._todoEditAction, {
+ method: "POST",
+ credentials: "same-origin",
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
+ body: formData.toString(),
+ });
+
+ if (!response.ok) {
+ if (response.status === 403 && retryCount < 1) {
+ todo._todoEditAction = "";
+ todo._todoEditBaseData = null;
+ await refreshEditForm();
+ return persistTodoChanges(todo, retryCount + 1);
+ }
+ throw new Error("Failed to save todo");
}
}
@@ -2476,8 +3157,9 @@ function renderModalTags(container, tags) {
container.innerHTML = "";
const visibleTags = (tags || []).filter((tag) => tag && !isTechnicalTag(tag));
- const modal = document.querySelector(".note-modal-overlay");
- const canRemove = !!(modal && modal.currentNote && modal.currentNote.editUrl && modal.currentNote.editUrl !== "#");
+ const modal = container.closest(".note-modal-overlay") || getOpenModalOverlay();
+ const entity = getModalCurrentEntity(modal);
+ const canRemove = !!(entity && entity.editUrl && entity.editUrl !== "#");
if (visibleTags.length === 0) {
container.classList.add("is-empty");
@@ -2544,43 +3226,64 @@ function generateModalPaletteButtons(note) {
}
window.setModalNoteColor = function (color) {
- const modal = document.querySelector(".note-modal-overlay");
- if (!modal || !modal.currentNote) return;
+ const modal = getOpenModalOverlay();
+ const entity = getModalCurrentEntity(modal);
+ if (!modal || !entity) return;
- const currentNote = modal.currentNote;
- setNoteColor(currentNote.id, color, currentNote.editUrl);
+ setNoteColor(entity.id, color, entity.editUrl);
- currentNote.color = color;
+ entity.color = color;
const modalCard = modal.querySelector(".note-modal");
if (modalCard) {
- applyNoteVisualState(modalCard, currentNote);
+ applyNoteVisualState(modalCard, entity);
}
- const modalColorPopup = modal.querySelector("#note-modal-color-popup");
+ const modalColorPopup = modal.querySelector(".note-modal-palette");
if (modalColorPopup) {
- modalColorPopup.innerHTML = generateModalPaletteButtons(currentNote);
+ modalColorPopup.innerHTML = generateModalPaletteButtons(entity);
+ modalColorPopup.classList.add("open");
+ positionPalettePopup(modalColorPopup);
+ }
+};
+
+window.setModalNoteFontColor = function (fontColorKey) {
+ const modal = getOpenModalOverlay();
+ const entity = getModalCurrentEntity(modal);
+ if (!modal || !entity) return;
+
+ setNoteFontColor(entity.id, fontColorKey, entity.editUrl);
+
+ entity.fontColor = fontColorKey;
+ const modalCard = modal.querySelector(".note-modal");
+ if (modalCard) {
+ applyNoteVisualState(modalCard, entity);
+ }
+
+ const modalColorPopup = modal.querySelector(".note-modal-palette");
+ if (modalColorPopup) {
+ modalColorPopup.innerHTML = generateModalPaletteButtons(entity);
modalColorPopup.classList.add("open");
positionPalettePopup(modalColorPopup);
}
};
window.setModalNoteFilter = function (filterKey) {
- const modal = document.querySelector(".note-modal-overlay");
- if (!modal || !modal.currentNote) return;
+ const modal = getOpenModalOverlay();
+ const entity = getModalCurrentEntity(modal);
+ if (!modal || !entity) return;
- const currentNote = modal.currentNote;
const normalizedFilterKey = normalizeFilterKey(filterKey) || "none";
- setNoteFilter(currentNote.id, normalizedFilterKey, currentNote.editUrl);
+ setNoteFilter(entity.id, normalizedFilterKey, entity.editUrl);
- currentNote.filter = normalizedFilterKey;
+ entity.filter = normalizedFilterKey;
const modalCard = modal.querySelector(".note-modal");
if (modalCard) {
- applyNoteVisualState(modalCard, currentNote);
+ applyNoteVisualState(modalCard, entity);
}
- const modalColorPopup = modal.querySelector("#note-modal-color-popup");
+ const modalColorPopup = modal.querySelector(".note-modal-palette");
if (modalColorPopup) {
- modalColorPopup.innerHTML = generateModalPaletteButtons(currentNote);
+ modalColorPopup.innerHTML = generateModalPaletteButtons(entity);
modalColorPopup.classList.add("open");
positionPalettePopup(modalColorPopup);
}
@@ -2610,16 +3313,17 @@ window.setNoteColor = function (noteId, color, editUrl) {
}
}
- const modal = document.querySelector(".note-modal-overlay");
- if (modal && modal.currentNote && String(modal.currentNote.id) === String(noteId)) {
- modal.currentNote.color = color;
+ const modal = getOpenModalOverlay();
+ const entity = getModalCurrentEntity(modal);
+ if (modal && entity && String(entity.id) === String(noteId)) {
+ entity.color = color;
const modalCard = modal.querySelector(".note-modal");
if (modalCard) {
- applyNoteVisualState(modalCard, modal.currentNote);
+ applyNoteVisualState(modalCard, entity);
}
- const modalColorPopup = modal.querySelector("#note-modal-color-popup");
+ const modalColorPopup = modal.querySelector(".note-modal-palette");
if (modalColorPopup) {
- modalColorPopup.innerHTML = generateModalPaletteButtons(modal.currentNote);
+ modalColorPopup.innerHTML = generateModalPaletteButtons(entity);
}
}
@@ -2641,7 +3345,7 @@ window.setNoteColor = function (noteId, color, editUrl) {
if (input.type === "checkbox") {
if (input.checked) formData.append(input.name, input.value || "on");
} else if (input.name) {
- formData.append(input.name, input.value);
+ formData.append(input.name, getFormFieldValue(input));
}
});
@@ -2718,16 +3422,17 @@ window.setNoteFilter = function (noteId, filterKey, editUrl) {
}
}
- const modal = document.querySelector(".note-modal-overlay");
- if (modal && modal.currentNote && String(modal.currentNote.id) === String(noteId)) {
- modal.currentNote.filter = normalizedFilterKey;
+ const modal = getOpenModalOverlay();
+ const entity = getModalCurrentEntity(modal);
+ if (modal && entity && String(entity.id) === String(noteId)) {
+ entity.filter = normalizedFilterKey;
const modalCard = modal.querySelector(".note-modal");
if (modalCard) {
- applyNoteVisualState(modalCard, modal.currentNote);
+ applyNoteVisualState(modalCard, entity);
}
- const modalColorPopup = modal.querySelector("#note-modal-color-popup");
+ const modalColorPopup = modal.querySelector(".note-modal-palette");
if (modalColorPopup) {
- modalColorPopup.innerHTML = generateModalPaletteButtons(modal.currentNote);
+ modalColorPopup.innerHTML = generateModalPaletteButtons(entity);
}
}
@@ -2747,7 +3452,7 @@ window.setNoteFilter = function (noteId, filterKey, editUrl) {
if (input.type === "checkbox") {
if (input.checked) formData.append(input.name, input.value || "on");
} else if (input.name) {
- formData.append(input.name, input.value);
+ formData.append(input.name, getFormFieldValue(input));
}
});
@@ -2800,16 +3505,17 @@ window.setNoteBackground = function (noteId, backgroundKey, editUrl) {
applyNoteVisualState(bookmarkCard, { color, filter, background: normalizedBackgroundKey });
}
- const modal = document.querySelector(".note-modal-overlay");
- if (modal && modal.currentNote && String(modal.currentNote.id) === String(noteId)) {
- modal.currentNote.background = normalizedBackgroundKey;
+ const modal = getOpenModalOverlay();
+ const entity = getModalCurrentEntity(modal);
+ if (modal && entity && String(entity.id) === String(noteId)) {
+ entity.background = normalizedBackgroundKey;
const modalCard = modal.querySelector(".note-modal");
if (modalCard) {
- applyNoteVisualState(modalCard, modal.currentNote);
+ applyNoteVisualState(modalCard, entity);
}
- const modalColorPopup = modal.querySelector("#note-modal-color-popup");
+ const modalColorPopup = modal.querySelector(".note-modal-palette");
if (modalColorPopup) {
- modalColorPopup.innerHTML = generateModalPaletteButtons(modal.currentNote);
+ modalColorPopup.innerHTML = generateModalPaletteButtons(entity);
}
}
@@ -2829,7 +3535,7 @@ window.setNoteBackground = function (noteId, backgroundKey, editUrl) {
if (input.type === "checkbox") {
if (input.checked) formData.append(input.name, input.value || "on");
} else if (input.name) {
- formData.append(input.name, input.value);
+ formData.append(input.name, getFormFieldValue(input));
}
});
@@ -2864,22 +3570,22 @@ window.setNoteBackground = function (noteId, backgroundKey, editUrl) {
};
window.setModalNoteBackground = function (backgroundKey) {
- const modal = document.querySelector(".note-modal-overlay");
- if (!modal || !modal.currentNote) return;
+ const modal = getOpenModalOverlay();
+ const entity = getModalCurrentEntity(modal);
+ if (!modal || !entity) return;
- const currentNote = modal.currentNote;
const normalizedBackgroundKey = backgroundKey === "none" ? "none" : normalizeBackgroundKey(backgroundKey) || "none";
- setNoteBackground(currentNote.id, normalizedBackgroundKey, currentNote.editUrl);
+ setNoteBackground(entity.id, normalizedBackgroundKey, entity.editUrl);
- currentNote.background = normalizedBackgroundKey;
+ entity.background = normalizedBackgroundKey;
const modalCard = modal.querySelector(".note-modal");
if (modalCard) {
- applyNoteVisualState(modalCard, currentNote);
+ applyNoteVisualState(modalCard, entity);
}
- const modalColorPopup = modal.querySelector("#note-modal-color-popup");
+ const modalColorPopup = modal.querySelector(".note-modal-palette");
if (modalColorPopup) {
- modalColorPopup.innerHTML = generateModalPaletteButtons(currentNote);
+ modalColorPopup.innerHTML = generateModalPaletteButtons(entity);
modalColorPopup.classList.add("open");
positionPalettePopup(modalColorPopup);
}
@@ -2901,7 +3607,7 @@ function addTagToNote(editUrl, tag) {
if (input.type === "checkbox") {
if (input.checked) formData.append(input.name, input.value || "on");
} else if (input.name) {
- formData.append(input.name, input.value);
+ formData.append(input.name, getFormFieldValue(input));
}
});
@@ -3179,7 +3885,7 @@ function togglePinTag(id, editUrl, btn) {
if (input.type === "checkbox") {
if (input.checked) formData.append(input.name, input.value || "on");
} else if (input.name) {
- formData.append(input.name, input.value);
+ formData.append(input.name, getFormFieldValue(input));
}
});
@@ -3302,7 +4008,7 @@ function openColorPickerPanel({ mode, entityId, editUrl, type }) {
if ((type || "background") === "font") {
let prevFontColorKey = "auto";
if ((mode || "entity") === "modal") {
- const modal = document.querySelector(".note-modal-overlay");
+ const modal = getOpenModalOverlay();
const modalCard = modal ? modal.querySelector(".note-modal") : null;
prevFontColorKey = (modalCard && modalCard.dataset.fontColor) ? modalCard.dataset.fontColor : "auto";
} else {
@@ -3318,7 +4024,7 @@ function openColorPickerPanel({ mode, entityId, editUrl, type }) {
if ((type || "background") === "background") {
let prevColorKey = "default";
if ((mode || "entity") === "modal") {
- const modal = document.querySelector(".note-modal-overlay");
+ const modal = getOpenModalOverlay();
const modalCard = modal ? modal.querySelector(".note-modal") : null;
if (modalCard) {
const isCustom = modalCard.dataset.color === "custom";
@@ -3635,17 +4341,19 @@ function setNoteFontColorVisual(noteId, fontColorKey) {
applyTo(card);
applyTo(bookmarkCard);
- const modal = document.querySelector(".note-modal-overlay");
- if (modal && modal.currentNote && String(modal.currentNote.id) === String(noteId)) {
+ const modal = getOpenModalOverlay();
+ const entity = getModalCurrentEntity(modal);
+ if (modal && entity && String(entity.id) === String(noteId)) {
const modalCard = modal.querySelector(".note-modal");
applyTo(modalCard);
}
}
function setModalNoteFontColorVisual(fontColorKey) {
- const modal = document.querySelector(".note-modal-overlay");
- if (!modal || !modal.currentNote) return;
- setNoteFontColorVisual(modal.currentNote.id, fontColorKey);
+ const modal = getOpenModalOverlay();
+ const entity = getModalCurrentEntity(modal);
+ if (!modal || !entity) return;
+ setNoteFontColorVisual(entity.id, fontColorKey);
}
function setNoteColorVisual(noteId, colorKey) {
@@ -3683,19 +4391,20 @@ function setNoteColorVisual(noteId, colorKey) {
applyTo(card);
applyTo(bookmarkCard);
- const modal = document.querySelector(".note-modal-overlay");
- if (modal && modal.currentNote && String(modal.currentNote.id) === String(noteId)) {
+ const modal = getOpenModalOverlay();
+ const entity = getModalCurrentEntity(modal);
+ if (modal && entity && String(entity.id) === String(noteId)) {
const modalCard = modal.querySelector(".note-modal");
applyTo(modalCard);
}
}
function setModalNoteColorVisual(colorKey) {
- const modal = document.querySelector(".note-modal-overlay");
- if (!modal || !modal.currentNote) return;
- setNoteColorVisual(modal.currentNote.id, colorKey);
+ const modal = getOpenModalOverlay();
+ const entity = getModalCurrentEntity(modal);
+ if (!modal || !entity) return;
+ setNoteColorVisual(entity.id, colorKey);
}
-
/* ==========================================================
FONT COLOR FUNCTIONS
========================================================== */
@@ -3720,7 +4429,7 @@ function setNoteFontColor(noteId, fontColorKey, editUrl) {
if (input.type === "checkbox") {
if (input.checked) formData.append(input.name, input.value || "on");
} else if (input.name) {
- formData.append(input.name, input.value);
+ formData.append(input.name, getFormFieldValue(input));
}
});
@@ -3764,18 +4473,18 @@ function setNoteFontColor(noteId, fontColorKey, editUrl) {
}
function setModalNoteFontColor(fontColorKey) {
- const modal = document.querySelector(".note-modal-overlay");
- if (!modal || !modal.currentNote) return;
+ const modal = getOpenModalOverlay();
+ const entity = getModalCurrentEntity(modal);
+ if (!modal || !entity) return;
- const currentNote = modal.currentNote;
- setNoteFontColor(currentNote.id, fontColorKey, currentNote.editUrl);
+ setNoteFontColor(entity.id, fontColorKey, entity.editUrl);
- currentNote.fontColor = fontColorKey;
+ entity.fontColor = fontColorKey;
const modalCard = modal.querySelector(".note-modal");
if (modalCard) {
let colorValue = "auto";
- if (fontColorKey.startsWith("custom:")) {
- colorValue = fontColorKey.substring(7);
+ if (String(fontColorKey || "").startsWith("custom:")) {
+ colorValue = String(fontColorKey).substring(7);
} else if (fontColorKey !== "auto") {
const option = NOTE_FONT_COLOR_OPTIONS.find((opt) => opt.key === fontColorKey);
colorValue = option ? option.value : "auto";
@@ -3798,10 +4507,9 @@ function setModalCustomFontColor(color) {
CUSTOM NOTE COLOR FUNCTIONS
========================================================== */
function setModalCustomNoteColor(color) {
- const modal = document.querySelector(".note-modal-overlay");
- if (!modal || !modal.currentNote) return;
-
- const currentNote = modal.currentNote;
+ const modal = getOpenModalOverlay();
+ const entity = getModalCurrentEntity(modal);
+ if (!modal || !entity || !entity.editUrl) return;
// Apply custom color visually
const modalCard = modal.querySelector(".note-modal");
@@ -3813,7 +4521,7 @@ function setModalCustomNoteColor(color) {
}
// Save via AJAX
- fetch(currentNote.editUrl)
+ fetch(entity.editUrl)
.then((response) => response.text())
.then((html) => {
const parser = new DOMParser();
@@ -3829,7 +4537,7 @@ function setModalCustomNoteColor(color) {
if (input.type === "checkbox") {
if (input.checked) formData.append(input.name, input.value || "on");
} else if (input.name) {
- formData.append(input.name, input.value);
+ formData.append(input.name, getFormFieldValue(input));
}
});
@@ -3864,7 +4572,7 @@ function setModalCustomNoteColor(color) {
})
.then((response) => {
if (!response.ok) throw new Error("Failed to save custom color");
- console.log(`Custom color ${color} saved for note ${currentNote.id}`);
+ console.log(`Custom color ${color} saved for note ${entity.id}`);
})
.catch((err) => {
console.error("Error saving custom color:", err);