diff --git a/shaarli-pro/css/custom_views.css b/shaarli-pro/css/custom_views.css
index d19391c..c50c168 100644
--- a/shaarli-pro/css/custom_views.css
+++ b/shaarli-pro/css/custom_views.css
@@ -229,8 +229,15 @@ body.view-notes .content-container {
align-items: flex-start;
margin-bottom: 2rem;
position: relative;
- padding-right: 60px;
- /* Space for toggle */
+ padding-right: 0;
+}
+
+.notes-top-bar-inner {
+ width: 600px;
+ max-width: 100%;
+ display: flex;
+ align-items: flex-start;
+ gap: 12px;
}
.note-input-container {
@@ -243,10 +250,14 @@ body.view-notes .content-container {
overflow: hidden;
}
+.note-input-container.is-editing {
+ max-height: 72vh;
+}
+
[data-theme="dark"] .note-input-container {
- background-color: var(--bg-sidebar);
- border: 1px solid var(--border);
- box-shadow: none;
+ background-color: #202124;
+ border: none;
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.6), 0 2px 6px 2px rgba(0, 0, 0, 0.4);
}
.note-input-collapsed {
@@ -259,6 +270,188 @@ body.view-notes .content-container {
font-size: 1rem;
}
+.note-input-container.is-editing {
+ cursor: default;
+}
+
+.note-input-expanded {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ padding: 12px 16px 10px;
+ max-height: 72vh;
+}
+
+.note-input-title {
+ width: 100%;
+ border: 0;
+ outline: none;
+ background: transparent;
+ color: inherit;
+ font-size: 1.05rem;
+ font-weight: 500;
+ padding: 0;
+}
+
+.note-input-description-source {
+ width: 100%;
+ border: none;
+ background: transparent;
+ color: inherit;
+ padding: 0;
+ resize: none;
+ min-height: 120px;
+ max-height: 44vh;
+ overflow: auto;
+ outline: none;
+ display: block;
+ font-size: 0.95rem;
+ line-height: 1.5;
+}
+
+.note-input-description-source:focus {
+ outline: none;
+}
+
+[data-theme="dark"] .note-input-description-source {
+ border: none;
+}
+
+.note-input-container.is-enhanced .note-input-description-source {
+ display: none;
+}
+
+.note-formatting-bar {
+ display: none;
+ align-items: center;
+ gap: 4px;
+ padding: 6px 12px;
+ border-radius: 24px;
+ background: rgba(0, 0, 0, 0.04);
+ border: 1px solid rgba(0, 0, 0, 0.08);
+ width: fit-content;
+ max-width: 100%;
+ overflow-x: auto;
+ margin: 4px 0;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
+}
+
+[data-theme="dark"] .note-formatting-bar {
+ background: rgba(255, 255, 255, 0.06);
+ border-color: rgba(255, 255, 255, 0.12);
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
+}
+
+.note-formatting-bar.open {
+ display: inline-flex;
+ animation: fadeIn 0.15s ease;
+}
+
+@keyframes fadeIn {
+ from { opacity: 0; transform: translateY(-4px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+
+.note-format-btn {
+ appearance: none;
+ background: transparent;
+ border: none;
+ color: inherit;
+ height: 32px;
+ min-width: 32px;
+ padding: 0 8px;
+ border-radius: 16px;
+ cursor: pointer;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: 600;
+ font-size: 0.85rem;
+ opacity: 0.85;
+ flex: 0 0 auto;
+ transition: all 0.15s ease;
+}
+
+.note-format-btn:hover {
+ opacity: 1;
+ background: rgba(0, 0, 0, 0.08);
+ transform: scale(1.05);
+}
+
+[data-theme="dark"] .note-format-btn:hover {
+ background: rgba(255, 255, 255, 0.14);
+}
+
+.note-format-btn:active {
+ transform: scale(0.95);
+}
+
+.note-format-sep {
+ width: 1px;
+ height: 16px;
+ background: rgba(0, 0, 0, 0.12);
+ margin: 0 4px;
+ flex: 0 0 auto;
+}
+
+[data-theme="dark"] .note-format-sep {
+ background: rgba(255, 255, 255, 0.15);
+}
+
+.note-input-actions-left {
+ display: flex;
+ align-items: center;
+ gap: 2px;
+ flex-wrap: wrap;
+}
+
+.note-input-actions-left > button {
+ background: transparent;
+ border: none;
+ color: inherit;
+ opacity: 0.9;
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ padding: 0;
+}
+
+.note-input-actions-left > button:disabled {
+ cursor: default;
+ opacity: 0.35;
+}
+
+.note-input-actions-left > button:hover:not(:disabled) {
+ opacity: 1;
+ background: rgba(0, 0, 0, 0.08);
+}
+
+[data-theme="dark"] .note-input-actions-left > button:hover:not(:disabled) {
+ background: rgba(255, 255, 255, 0.14);
+}
+
+.note-input-close-btn {
+ background: transparent;
+ border: none;
+ color: inherit;
+ font-weight: 600;
+ border-radius: 8px;
+ padding: 6px 10px;
+ cursor: pointer;
+}
+
+.note-input-close-btn:hover {
+ background: rgba(136, 136, 136, 0.1);
+}
+
+[data-theme="dark"] .note-input-close-btn:hover {
+ background: rgba(255, 255, 255, 0.14);
+}
+
.note-input-placeholder {
flex: 1;
}
@@ -329,7 +522,6 @@ body.view-notes .content-container {
color: #e8eaed;
}
-
/* --- LOGIC: Masonry vs List --- */
/* Masonry Grid */
@@ -391,17 +583,23 @@ body.view-notes .content-container {
width: min(720px, 92vw);
max-height: 86vh;
overflow: hidden;
- border-radius: 12px;
+ border-radius: 8px;
background: var(--background-secondary, #ffffff);
border: 1px solid rgba(0, 0, 0, 0.12);
+ box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
color: inherit;
display: flex;
flex-direction: column;
}
+body.note-modal-open {
+ overflow: hidden;
+}
+
[data-theme="dark"] .note-modal {
background: #202124;
- border-color: rgba(255, 255, 255, 0.12);
+ border-color: transparent;
+ box-shadow: 0 14px 28px rgba(0,0,0,0.5), 0 10px 10px rgba(0,0,0,0.4);
}
.note-modal-header {
@@ -421,22 +619,47 @@ body.view-notes .content-container {
font-weight: 500;
}
+.note-modal-title-input {
+ width: 100%;
+ border: 0;
+ outline: none;
+ background: transparent;
+ color: inherit;
+ padding: 0;
+}
+
.note-modal-content {
flex: 1;
overflow-y: auto;
- padding: 0 20px 18px;
+ padding: 0;
}
-.note-modal .note-body {
- font-size: 1rem;
- line-height: 1.75;
- display: block;
- -webkit-line-clamp: initial;
- line-clamp: initial;
- -webkit-box-orient: initial;
- max-height: none;
- overflow: visible;
+.note-modal-description-source {
+ width: 100%;
+ border: none;
+ background: transparent;
color: inherit;
+ padding: 0;
+ resize: none;
+ min-height: 200px;
+ max-height: 58vh;
+ overflow: auto;
+ outline: none;
+ display: block;
+ font-size: 0.95rem;
+ line-height: 1.5;
+}
+
+.note-modal-description-source:focus {
+ outline: none;
+}
+
+[data-theme="dark"] .note-modal-description-source {
+ border: none;
+}
+
+.note-modal-overlay.is-enhanced .note-modal-description-source {
+ display: none;
}
.note-modal .note-body * {
@@ -447,15 +670,10 @@ body.view-notes .content-container {
display: flex;
flex-wrap: wrap;
gap: 8px;
- padding: 10px 20px 12px;
- border-top: 1px solid rgba(0, 0, 0, 0.12);
+ padding: 0 20px 12px;
flex-shrink: 0;
}
-[data-theme="dark"] .note-modal-tags {
- border-top-color: rgba(255, 255, 255, 0.12);
-}
-
.note-modal-tags.is-empty {
display: none;
}
@@ -501,16 +719,14 @@ body.view-notes .content-container {
align-items: center;
justify-content: space-between;
gap: 6px;
- padding: 8px 12px;
- border-top: 1px solid rgba(0, 0, 0, 0.12);
- background: rgba(0, 0, 0, 0.03);
+ padding: 8px 16px 16px;
+ background: transparent;
flex-shrink: 0;
flex-wrap: wrap;
}
[data-theme="dark"] .note-modal-actions {
- border-top-color: rgba(255, 255, 255, 0.12);
- background: rgba(255, 255, 255, 0.06);
+ background: transparent;
}
.note-modal-actions-left {
@@ -597,28 +813,31 @@ body.view-notes .content-container {
/* --- CARD STYLING --- */
.note-card {
background-color: var(--background-secondary, #ffffff);
- border: 1px solid #e0e0e0;
+ border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 8px;
margin-bottom: 16px;
break-inside: avoid;
position: relative;
- transition: box-shadow 0.2s, transform 0.2s, background-color 0.2s;
+ transition: box-shadow 0.2s cubic-bezier(0.4, 0.0, 0.2, 1), transform 0.2s cubic-bezier(0.4, 0.0, 0.2, 1), border-color 0.2s cubic-bezier(0.4, 0.0, 0.2, 1);
overflow: visible;
color: var(--note-card-fg, #202124);
}
[data-theme="dark"] .note-card {
background-color: #202124;
- border: 1px solid #5f6368;
+ border-color: #5f6368;
color: #e8eaed;
}
.note-card:hover {
box-shadow: 0 1px 2px 0 rgba(60, 64, 67, 0.3), 0 1px 3px 1px rgba(60, 64, 67, 0.15);
+ border-color: transparent;
}
[data-theme="dark"] .note-card:hover {
background-color: #202124;
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.6), 0 1px 3px 1px rgba(0, 0, 0, 0.4);
+ border-color: transparent;
}
/* Cover Image */
@@ -687,17 +906,17 @@ body.view-notes .content-container {
display: inline-flex;
align-items: center;
gap: 0.25rem;
- background: var(--tag-bg);
+ background: var(--tag-bg, #f1f3f4);
padding: 0.25rem 0.5rem;
border-radius: 999px;
font-size: 0.75rem;
font-weight: 500;
- color: var(--tag-text);
+ color: var(--tag-text, #3c4043);
}
[data-theme="dark"] .note-tag {
- background: var(--tag-bg);
- color: var(--tag-text);
+ background: var(--tag-bg, #3c4043);
+ color: var(--tag-text, #e8eaed);
}
.note-tag-text {
@@ -738,18 +957,15 @@ body.view-notes .content-container {
.note-hover-actions {
display: flex;
align-items: center;
- gap: 0px;
- /* evenly spaced */
- margin-top: 8px;
- margin-left: -8px;
- /* Alignment fix */
+ gap: 2px;
+ margin-top: 12px;
+ margin-left: -6px;
color: var(--text-light, #5f6368);
opacity: 0;
- /* Hidden by default */
- transition: opacity 0.2s;
+ transition: opacity 0.2s cubic-bezier(0.4, 0.0, 0.2, 1);
position: relative;
- /* For palette popup */
z-index: 2;
+ min-height: 34px;
}
[data-theme="dark"] .note-hover-actions {
@@ -771,27 +987,35 @@ body.view-notes .content-container {
.note-hover-actions > button,
.note-hover-actions > a,
.note-hover-actions > div > button {
- background: none;
+ background: transparent;
border: none;
- width: 34px;
- /* Touch target */
- height: 34px;
+ width: 32px;
+ height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: inherit;
cursor: pointer;
- font-size: 1.1rem;
- transition: background-color 0.2s, color 0.2s;
+ font-size: 1.15rem;
+ transition: background-color 0.15s, color 0.15s, opacity 0.15s;
text-decoration: none;
+ opacity: 0.7;
}
.note-hover-actions > button:hover,
.note-hover-actions > a:hover,
.note-hover-actions > div > button:hover {
- background-color: rgba(136, 136, 136, 0.2);
- color: var(--text-color);
+ background-color: rgba(0, 0, 0, 0.08);
+ color: var(--text-color, #202124);
+ opacity: 1;
+}
+
+[data-theme="dark"] .note-hover-actions > button:hover,
+[data-theme="dark"] .note-hover-actions > a:hover,
+[data-theme="dark"] .note-hover-actions > div > button:hover {
+ background-color: rgba(255, 255, 255, 0.14);
+ color: #e8eaed;
}
.bookmark-palette {
@@ -1480,22 +1704,25 @@ body.view-notes .content-container {
/* Grey */
.note-card.note-color-grey {
- background-color: #e8eaed;
- border-color: transparent;
- --note-card-fg: #2a2d31;
+background-color: #e8eaed;
+border-color: transparent;
+--note-card-fg: #2a2d31;
}
.note-card.note-has-bg,
.note-modal.note-has-bg,
+.note-input-container.note-has-bg,
.link-outer.note-has-bg {
- background-image: linear-gradient(rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0.16)), var(--note-bg-image);
- background-size: cover;
- background-position: center bottom;
+background-image: linear-gradient(rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0.16)), var(--note-bg-image);
+background-size: cover;
+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);
+--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);
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);
}
diff --git a/shaarli-pro/includes.html b/shaarli-pro/includes.html
index 7ee90a8..3d6e235 100644
--- a/shaarli-pro/includes.html
+++ b/shaarli-pro/includes.html
@@ -24,13 +24,13 @@
-
+
{if="$pageName=='editlink' || $pageName=='addlink' || $pageName=='editlinkbatch'"}
{/if}
-{if="$pageName=='editlink' || $pageName=='editlinkbatch'"}
+{if="$pageName=='editlink' || $pageName=='editlinkbatch' || ($pageName=='linklist' && isset($search_tags) && preg_match('/(^|[\s,])note([\s,]|$)/i', (string) $search_tags))"}
@@ -61,14 +61,14 @@ var shaarli = {
basePath: '{$base_path}',
rootPath: '{$root_path}',
assetPath: '{$base_path}{$asset_path}',
-isAuth: {if="$is_logged_in"}true{else}false{/if},
+isAuth: (function(){/*{if="$is_logged_in"}*/return true;/*{else}*/return false;/*{/if}*/})(),
pageName: '{$pageName}',
visibility: '{$visibility}',
-untaggedonly: {if="$untaggedonly"}true{else}false{/if}
+untaggedonly: (function(){/*{if="$untaggedonly"}*/return true;/*{else}*/return false;/*{/if}*/})()
};
-
+
{if="file_exists('tpl/shaarli-pro/extra.html')"}
{include="extra"}
diff --git a/shaarli-pro/js/custom_views.js b/shaarli-pro/js/custom_views.js
index 1a04f64..5e56696 100644
--- a/shaarli-pro/js/custom_views.js
+++ b/shaarli-pro/js/custom_views.js
@@ -1,11 +1,38 @@
document.addEventListener("DOMContentLoaded", function () {
// Check URL parameters for custom views
const urlParams = new URLSearchParams(window.location.search);
- const searchTags = urlParams.get("searchtags");
+ const searchTagsRaw = urlParams.get("searchtags") || urlParams.get("searchTags") || "";
+
+ // Also check URL path for tag format (e.g., /tag/note)
+ const urlPath = window.location.pathname;
+ const pathMatch = urlPath.match(/\/tag\/(.+)$/);
+ const pathTagRaw = pathMatch ? decodeURIComponent(pathMatch[1]) : "";
+
+ // Parse all active tags to safely detect the view
+ const activeTags = (searchTagsRaw + " " + pathTagRaw).toLowerCase().split(/[\s,]+/).filter(t => t);
+
+ // Foolproof detection using sidebar active state and DOM rendered tags
+ const hasNoteActiveMenu = !!document.querySelector('.sidebar-link[aria-label="Notes"].active, .header-nav-link[aria-label="Notes"].active, .sidebar-link[href*="searchtags=note"].active');
+ const hasTodoActiveMenu = !!document.querySelector('.sidebar-link[aria-label="Mes tâches"].active, .header-nav-link[aria-label="Mes tâches"].active, .sidebar-link[href*="searchtags=shaarli-todo"].active');
+ const hasArchiveActiveMenu = !!document.querySelector('.sidebar-link[aria-label="Archive"].active, .header-nav-link[aria-label="Archive"].active, .sidebar-link[href*="searchtags=shaarli-archive"].active');
+
+ const domChipTags = Array.from(document.querySelectorAll('.search-tag-chip')).map(el => (el.textContent || "").trim().toLowerCase());
+
+ const isNoteView = activeTags.includes("note") || hasNoteActiveMenu || domChipTags.includes("note");
+ const isTodoView = activeTags.includes("shaarli-todo") || hasTodoActiveMenu || domChipTags.includes("shaarli-todo");
+ const isArchiveView = activeTags.includes("shaarli-archive") || hasArchiveActiveMenu || domChipTags.includes("shaarli-archive");
const linkList = document.getElementById("links-list");
const container = document.querySelector(".content-container");
+ const showErrorBanner = (msg, err) => {
+ const banner = document.createElement("div");
+ banner.style.cssText = "background: #ffebee; color: #c62828; padding: 16px; margin: 16px; border-radius: 8px; border: 1px solid #ef9a9a; z-index: 9999; position: relative;";
+ banner.innerHTML = `Erreur Custom Views: ${msg}
${err ? err.stack || err : ''}`;
+ if (container) container.prepend(banner);
+ else document.body.prepend(banner);
+ };
+
const restoreDefaultListView = () => {
try {
document.body.classList.remove("view-todo", "view-notes", "view-archive");
@@ -22,6 +49,102 @@ document.addEventListener("DOMContentLoaded", function () {
document.querySelectorAll(".notes-wrapper.todo-wrapper").forEach((el) => el.remove());
};
+ // Fonction de rendu Markdown basique pour l'affichage des notes
+ function renderMarkdown(markdown) {
+ if (!markdown) return "";
+ let html = markdown
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/^### (.*$)/gim, "$1
")
+ .replace(/^## (.*$)/gim, "$1
")
+ .replace(/^# (.*$)/gim, "$1
")
+ .replace(/\*\*(.*?)\*\*/gim, "$1")
+ .replace(/\*(.*?)\*/gim, "$1")
+ .replace(/(.*?)<\/u>/gim, "$1")
+ .replace(/\n/g, "
");
+ return html;
+ }
+
+ function applyKeepNoteFormatting(textarea, format) {
+ if (!textarea || !format) return;
+ const start = textarea.selectionStart || 0;
+ const end = textarea.selectionEnd || 0;
+ const value = String(textarea.value || "");
+ const selected = value.slice(start, end);
+
+ const replaceRange = (from, to, replacement) => {
+ const next = value.slice(0, from) + replacement + value.slice(to);
+ textarea.value = next;
+ const cursor = from + replacement.length;
+ textarea.focus({ preventScroll: true });
+ textarea.setSelectionRange(cursor, cursor);
+ };
+
+ const wrapSelection = (prefix, suffix) => {
+ const body = selected || "";
+ const replacement = `${prefix}${body}${suffix}`;
+ const next = value.slice(0, start) + replacement + value.slice(end);
+ textarea.value = next;
+ const selStart = start + prefix.length;
+ const selEnd = selStart + body.length;
+ textarea.focus({ preventScroll: true });
+ textarea.setSelectionRange(selStart, selEnd);
+ };
+
+ const lineRangeForSelection = () => {
+ const before = value.slice(0, start);
+ const after = value.slice(end);
+ const lineStart = before.lastIndexOf("\n") + 1;
+ const nextNl = after.indexOf("\n");
+ const lineEnd = nextNl === -1 ? value.length : end + nextNl;
+ return { lineStart, lineEnd };
+ };
+
+ if (format === "bold") return wrapSelection("**", "**");
+ if (format === "italic") return wrapSelection("*", "*");
+ if (format === "underline") return wrapSelection("", "");
+
+ if (format === "h1" || format === "h2" || format === "p") {
+ const { lineStart, lineEnd } = lineRangeForSelection();
+ const block = value.slice(lineStart, lineEnd);
+ const lines = block.split(/\r?\n/);
+ const prefix = format === "h1" ? "# " : format === "h2" ? "## " : "";
+ const nextLines = lines.map((ln) => {
+ const stripped = ln.replace(/^\s*(#{1,6}\s+)/, "");
+ return prefix ? prefix + stripped : stripped;
+ });
+ const replacement = nextLines.join("\n");
+ const next = value.slice(0, lineStart) + replacement + value.slice(lineEnd);
+ textarea.value = next;
+ textarea.focus({ preventScroll: true });
+ textarea.setSelectionRange(lineStart, lineStart + replacement.length);
+ return;
+ }
+
+ if (format === "clear") {
+ if (!selected) {
+ const { lineStart, lineEnd } = lineRangeForSelection();
+ const block = value.slice(lineStart, lineEnd);
+ const cleaned = block
+ .replace(/^\s*(#{1,6}\s+)/gm, "")
+ .replace(/\*\*(.*?)\*\*/g, "$1")
+ .replace(/\*(.*?)\*/g, "$1")
+ .replace(/(.*?)<\/u>/g, "$1");
+ const next = value.slice(0, lineStart) + cleaned + value.slice(lineEnd);
+ textarea.value = next;
+ textarea.focus({ preventScroll: true });
+ textarea.setSelectionRange(lineStart, lineStart + cleaned.length);
+ return;
+ }
+ const cleaned = selected
+ .replace(/\*\*(.*?)\*\*/g, "$1")
+ .replace(/\*(.*?)\*/g, "$1")
+ .replace(/(.*?)<\/u>/g, "$1");
+ replaceRange(start, end, cleaned);
+ }
+ }
+
const startViewInitialization = function () {
if (typeof initBookmarkPaletteButtons === "function") {
initBookmarkPaletteButtons();
@@ -39,27 +162,40 @@ document.addEventListener("DOMContentLoaded", function () {
if (!linkList || !container) return;
- if (searchTags === "shaarli-todo") {
+ if (isTodoView) {
try {
+ console.log("[custom_views] Initializing Todo view");
initTodoView(linkList, container);
} catch (err) {
- console.error("Erreur lors de l'initialisation de la vue Todo:", err);
+ console.error("[custom_views] 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
- initNoteView(linkList, container);
- // Puis supprimer les tags techniques de l'affichage
- if (typeof initTagDisplayAndRemoval === "function") {
- initTagDisplayAndRemoval();
+ } else if (isNoteView) {
+ try {
+ console.log("[custom_views] Initializing Note view");
+ // 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
+ initNoteView(linkList, container);
+ // Puis supprimer les tags techniques de l'affichage
+ if (typeof initTagDisplayAndRemoval === "function") {
+ initTagDisplayAndRemoval();
+ }
+ } catch (err) {
+ console.error("[custom_views] Erreur lors de l'initialisation de la vue Note:", err);
+ restoreDefaultListView();
}
- } else if (searchTags === "shaarli-archive") {
- // Vue Archive - similaire à Notes mais pour les notes archivées
- initArchiveView(linkList, container);
- // Puis supprimer les tags techniques de l'affichage
- if (typeof initTagDisplayAndRemoval === "function") {
- initTagDisplayAndRemoval();
+ } else if (isArchiveView) {
+ try {
+ console.log("[custom_views] Initializing Archive view");
+ // Vue Archive - similaire à Notes mais pour les notes archivées
+ initArchiveView(linkList, container);
+ // Puis supprimer les tags techniques de l'affichage
+ if (typeof initTagDisplayAndRemoval === "function") {
+ initTagDisplayAndRemoval();
+ }
+ } catch (err) {
+ console.error("[custom_views] Erreur lors de l'initialisation de la vue Archive:", err);
+ restoreDefaultListView();
}
} else {
// Vue standard : supprimer les tags techniques
@@ -912,6 +1048,12 @@ function ensureBackgroundStudioPanel() {
const entityId = panelEl.dataset.entityId || "";
const editUrl = panelEl.dataset.editUrl || "";
+ const applyDraft = (next) => {
+ if (typeof panelEl.__draftApply === "function") {
+ panelEl.__draftApply(next);
+ }
+ };
+
const action = actionBtn.dataset.bgStudioAction;
if (action === "close") {
closeBackgroundStudioPanel();
@@ -920,22 +1062,44 @@ function ensureBackgroundStudioPanel() {
if (action === "set-color") {
const key = actionBtn.dataset.colorKey || "default";
- if (mode === "modal") setModalNoteColor(key);
- else setNoteColor(entityId, key, editUrl);
+ if (mode === "draft") {
+ panelEl.dataset.color = key;
+ applyDraft({ color: key });
+ renderBackgroundStudioPanel(panelEl);
+ } else if (mode === "modal") {
+ setModalNoteColor(key);
+ } else {
+ setNoteColor(entityId, key, editUrl);
+ }
return;
}
if (action === "set-background") {
const key = actionBtn.dataset.bgKey || "none";
- if (mode === "modal") setModalNoteBackground(key);
- else setNoteBackground(entityId, key, editUrl);
+ if (mode === "draft") {
+ panelEl.dataset.background = key;
+ applyDraft({ background: key });
+ renderBackgroundStudioPanel(panelEl);
+ } else if (mode === "modal") {
+ setModalNoteBackground(key);
+ } else {
+ setNoteBackground(entityId, key, editUrl);
+ }
return;
}
if (action === "set-filter") {
const filterKey = actionBtn.dataset.filter || "none";
- if (mode === "modal") setModalNoteFilter(filterKey);
- else setNoteFilter(entityId, filterKey, editUrl);
+ if (mode === "draft") {
+ const normalized = normalizeFilterKey(filterKey || "") || "none";
+ panelEl.dataset.filter = normalized;
+ applyDraft({ filter: normalized });
+ renderBackgroundStudioPanel(panelEl);
+ } else if (mode === "modal") {
+ setModalNoteFilter(filterKey);
+ } else {
+ setNoteFilter(entityId, filterKey, editUrl);
+ }
return;
}
@@ -948,7 +1112,14 @@ function ensureBackgroundStudioPanel() {
}
if (action === "set-defaults") {
- if (mode === "modal") {
+ if (mode === "draft") {
+ panelEl.dataset.color = "default";
+ panelEl.dataset.filter = "none";
+ panelEl.dataset.background = "none";
+ panelEl.dataset.fontColor = "auto";
+ applyDraft({ color: "default", filter: "none", background: "none", fontColor: "auto" });
+ renderBackgroundStudioPanel(panelEl);
+ } else if (mode === "modal") {
setModalNoteColor("default");
setModalNoteFilter("none");
} else {
@@ -959,7 +1130,11 @@ function ensureBackgroundStudioPanel() {
}
if (action === "reset-font-color") {
- if (mode === "modal") {
+ if (mode === "draft") {
+ panelEl.dataset.fontColor = "auto";
+ applyDraft({ fontColor: "auto" });
+ renderBackgroundStudioPanel(panelEl);
+ } else if (mode === "modal") {
setModalNoteFontColor("auto");
} else {
setNoteFontColor(entityId, "auto", editUrl);
@@ -979,8 +1154,15 @@ function ensureBackgroundStudioPanel() {
if (action === "set-font-color") {
const fontColorKey = actionBtn.dataset.fontColorKey || "auto";
- if (mode === "modal") setModalNoteFontColor(fontColorKey);
- else setNoteFontColor(entityId, fontColorKey, editUrl);
+ if (mode === "draft") {
+ panelEl.dataset.fontColor = fontColorKey;
+ applyDraft({ fontColor: fontColorKey });
+ renderBackgroundStudioPanel(panelEl);
+ } else if (mode === "modal") {
+ setModalNoteFontColor(fontColorKey);
+ } else {
+ setNoteFontColor(entityId, fontColorKey, editUrl);
+ }
return;
}
});
@@ -1347,7 +1529,7 @@ function applyNoteVisualState(element, note) {
const background = normalizedBackground || "none";
const fontColor = note.fontColor || "auto";
- element.classList.forEach((cls) => {
+ Array.from(element.classList).forEach((cls) => {
if (cls.startsWith("note-color-")) element.classList.remove(cls);
if (cls.startsWith("note-filter-")) element.classList.remove(cls);
});
@@ -1879,6 +2061,161 @@ function getFormFieldValue(input) {
return input.value;
}
+async function fetchShaareFormBaseData(url) {
+ const response = await fetch(url, { 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));
+ }
+ });
+
+ return { action: form.action, baseData };
+}
+
+async function createNewNoteViaForm({ title, markdown, visual = null }) {
+ const basePath = typeof shaarli !== "undefined" && shaarli.basePath ? shaarli.basePath : "";
+ const formUrl = `${basePath}/admin/shaare?post=&tags=note`;
+ const { action, baseData } = await fetchShaareFormBaseData(formUrl);
+
+ const formData = new URLSearchParams(baseData.toString());
+ formData.set("lf_url", "");
+ formData.set("lf_title", title || "");
+ formData.set("lf_description", markdown || "");
+
+ const currentTags = (formData.get("lf_tags") || "").trim();
+ let tagsArray = currentTags.split(/[\s,]+/).filter((t) => t.trim() !== "");
+
+ // Ensure note tag exists
+ if (!tagsArray.includes("note")) tagsArray.push("note");
+
+ // Apply optional visual tags (note-color-*, notefilter-*, notebg-*, font-*)
+ if (visual) {
+ const colorKey = visual.color || "default";
+ const filterKey = normalizeFilterKey(visual.filter || "") || "none";
+ const bgKey = normalizeBackgroundKey(visual.background || "") || "none";
+ const fontColorKey = visual.fontColor || "auto";
+
+ tagsArray = tagsArray.filter((t) => !t.startsWith(NOTE_COLOR_TAG_PREFIX));
+ tagsArray = tagsArray.filter((t) => !t.startsWith(NOTE_FILTER_TAG_PREFIX));
+ tagsArray = tagsArray.filter((t) => !t.startsWith(NOTE_BACKGROUND_TAG_PREFIX));
+ tagsArray = tagsArray.filter((t) => !t.startsWith(NOTE_FONT_COLOR_TAG_PREFIX));
+
+ if (colorKey && colorKey !== "default") {
+ if (typeof colorKey === "string" && colorKey.startsWith("custom:")) {
+ const clean = colorKey.substring(7).replace("#", "");
+ if (clean) tagsArray.push(`${NOTE_COLOR_TAG_PREFIX}${clean}`);
+ } else {
+ tagsArray.push(`${NOTE_COLOR_TAG_PREFIX}${colorKey}`);
+ }
+ }
+
+ if (filterKey && filterKey !== "none") {
+ tagsArray.push(`${NOTE_FILTER_TAG_PREFIX}${filterKey}`);
+ }
+
+ if (bgKey && bgKey !== "none") {
+ tagsArray.push(`${NOTE_BACKGROUND_TAG_PREFIX}${bgKey}`);
+ }
+
+ if (fontColorKey && fontColorKey !== "auto") {
+ if (typeof fontColorKey === "string" && fontColorKey.startsWith("custom:")) {
+ const clean = fontColorKey.substring(7).replace("#", "");
+ if (clean) tagsArray.push(`${NOTE_FONT_COLOR_TAG_PREFIX}${clean}`);
+ } else {
+ const option = NOTE_FONT_COLOR_OPTIONS.find((opt) => opt.key === fontColorKey);
+ if (option && option.value && option.value !== "auto") {
+ tagsArray.push(`${NOTE_FONT_COLOR_TAG_PREFIX}${option.value.substring(1)}`);
+ }
+ }
+ }
+ }
+
+ formData.set("lf_tags", tagsArray.join(" "));
+
+ if (!formData.get("returnurl")) {
+ formData.set("returnurl", window.location.href);
+ }
+ formData.append("save_edit", "1");
+
+ const response = await fetch(action, {
+ method: "POST",
+ credentials: "same-origin",
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
+ body: formData.toString(),
+ });
+
+ if (!response.ok) throw new Error(`Failed to create note (status ${response.status})`);
+}
+
+async function hydrateNoteFromEditForm(note) {
+ if (!note || !note.editUrl || note.editUrl === "#") return;
+ if (note._noteHydrated) return;
+ if (note._noteHydratePromise) return note._noteHydratePromise;
+
+ note._noteHydratePromise = (async () => {
+ try {
+ const { action, baseData } = await fetchShaareFormBaseData(note.editUrl);
+ note._noteEditAction = action;
+ note._noteEditBaseData = baseData;
+ note._noteMarkdown = baseData.get("lf_description") || "";
+ note._noteTitle = baseData.get("lf_title") || "";
+ note._noteHydrated = true;
+ } catch (e) {
+ console.error("Error hydrating note edit form:", e);
+ } finally {
+ note._noteHydratePromise = null;
+ }
+ })();
+
+ return note._noteHydratePromise;
+}
+
+async function persistNoteChanges(note, { title, markdown }, retryCount = 0) {
+ if (!note || !note.editUrl || note.editUrl === "#") return;
+
+ const refreshEditForm = async () => {
+ const { action, baseData } = await fetchShaareFormBaseData(note.editUrl);
+ note._noteEditAction = action;
+ note._noteEditBaseData = baseData;
+ };
+
+ if (!note._noteEditAction || !note._noteEditBaseData || !(note._noteEditBaseData.get && note._noteEditBaseData.get("token"))) {
+ await refreshEditForm();
+ }
+
+ const formData = new URLSearchParams(note._noteEditBaseData.toString());
+ formData.set("lf_title", title || "");
+ formData.set("lf_description", markdown || "");
+ formData.append("save_edit", "1");
+
+ const response = await fetch(note._noteEditAction, {
+ 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) {
+ note._noteEditAction = "";
+ note._noteEditBaseData = null;
+ await refreshEditForm();
+ return persistNoteChanges(note, { title, markdown }, retryCount + 1);
+ }
+ throw new Error("Failed to save note");
+ }
+}
+
async function hydrateTodoFromEditForm(todo) {
if (!todo || !todo.editUrl || todo.editUrl === "#") return;
if (todo._todoHydrated) return;
@@ -2471,6 +2808,8 @@ async function persistTodoChanges(todo, retryCount = 0) {
function initNoteView(linkList, container) {
document.body.classList.add("view-notes");
+ const basePath = typeof shaarli !== "undefined" && shaarli.basePath ? shaarli.basePath : "";
+
// Hide standard toolbar
const toolbar = document.querySelector(".content-toolbar");
if (toolbar) toolbar.style.display = "none";
@@ -2483,18 +2822,215 @@ function initNoteView(linkList, container) {
const topBar = document.createElement("div");
topBar.className = "notes-top-bar";
+ const topBarInner = document.createElement("div");
+ topBarInner.className = "notes-top-bar-inner";
+
// Custom Input "Take a note..."
const inputContainer = document.createElement("div");
inputContainer.className = "note-input-container";
- inputContainer.innerHTML = `
-
+ const renderNoteInputCollapsed = () => {
+ inputContainer.classList.remove("is-editing", "is-enhanced");
+ inputContainer.innerHTML = `
+
`;
- topBar.appendChild(inputContainer);
+
+ const collapsed = inputContainer.querySelector(".note-input-collapsed");
+ const btn = inputContainer.querySelector(".note-input-actions button");
+ const openEditor = () => {
+ inputContainer.classList.add("is-editing");
+ inputContainer.innerHTML = `
+
+ `;
+
+ const titleInput = inputContainer.querySelector(".note-input-title");
+ const source = inputContainer.querySelector(".note-input-description-source");
+ const closeBtn = inputContainer.querySelector(".note-input-close-btn");
+ const paletteBtn = inputContainer.querySelector(".note-input-palette-btn");
+ const formatToggleBtn = inputContainer.querySelector(".note-input-format-btn");
+ const formattingBar = inputContainer.querySelector(".note-formatting-bar");
+
+ let hasChanges = false;
+ const initialTitle = "";
+ const initialMarkdown = "";
+ let isSaving = false;
+
+ let draftVisual = { color: "default", filter: "none", background: "none", fontColor: "auto" };
+ applyNoteVisualState(inputContainer, draftVisual);
+
+ const setFormattingVisible = (visible) => {
+ if (!formattingBar) return;
+ if (visible) {
+ formattingBar.classList.add("open");
+ formattingBar.setAttribute("aria-hidden", "false");
+ inputContainer.classList.add("show-formatting");
+ } else {
+ formattingBar.classList.remove("open");
+ formattingBar.setAttribute("aria-hidden", "true");
+ inputContainer.classList.remove("show-formatting");
+ }
+ };
+
+ formatToggleBtn?.addEventListener("click", (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ const isOpen = formattingBar && formattingBar.classList.contains("open");
+ setFormattingVisible(!isOpen);
+ });
+
+ inputContainer.querySelectorAll(".note-format-btn").forEach((btnEl) => {
+ btnEl.addEventListener("click", (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ if (!source) return;
+ applyKeepNoteFormatting(source, btnEl.dataset.noteFormat || "");
+ hasChanges = true;
+ });
+ });
+
+ source?.addEventListener("input", () => {
+ hasChanges = true;
+ });
+
+ const getValues = () => {
+ const t = titleInput && typeof titleInput.value === "string" ? titleInput.value.trim() : "";
+ const md = source && typeof source.value === "string" ? source.value.trim() : "";
+ return { title: t, markdown: md };
+ };
+
+ const saveIfNeededAndClose = async () => {
+ if (isSaving) return;
+ const { title, markdown } = getValues();
+ if (!title && !markdown) {
+ document.removeEventListener("pointerdown", onOutsidePointerDown, true);
+ renderNoteInputCollapsed();
+ return;
+ }
+
+ const changed = hasChanges || title !== initialTitle || markdown !== initialMarkdown;
+ if (!changed) {
+ document.removeEventListener("pointerdown", onOutsidePointerDown, true);
+ renderNoteInputCollapsed();
+ return;
+ }
+ isSaving = true;
+ try {
+ await createNewNoteViaForm({ title, markdown, visual: draftVisual });
+ window.location.reload();
+ } catch (e) {
+ console.error("Error creating note:", e);
+ alert("Erreur lors de la création de la note.");
+ isSaving = false;
+ }
+ };
+
+ // Attacher les listeners de formatage immédiatement
+ formatToggleBtn?.addEventListener("click", (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ const isOpen = formattingBar && formattingBar.classList.contains("open");
+ setFormattingVisible(!isOpen);
+ });
+
+ inputContainer.querySelectorAll(".note-format-btn").forEach((btnEl) => {
+ btnEl.addEventListener("click", (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ if (!source) return;
+ applyKeepNoteFormatting(source, btnEl.dataset.noteFormat || "");
+ hasChanges = true;
+ });
+ });
+
+ titleInput?.addEventListener("input", () => {
+ hasChanges = true;
+ });
+
+ closeBtn?.addEventListener("click", (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ saveIfNeededAndClose();
+ });
+
+ const onOutsidePointerDown = (e) => {
+ if (e.target && e.target.closest && e.target.closest("#shaarli-bg-studio")) return;
+ if (e.target && e.target.closest && e.target.closest(".bg-studio-panel")) return;
+ if (e.target && e.target.closest && e.target.closest(".note-formatting-bar")) return;
+ if (inputContainer.contains(e.target)) return;
+ saveIfNeededAndClose();
+ };
+ document.addEventListener("pointerdown", onOutsidePointerDown, true);
+
+ paletteBtn?.addEventListener("click", (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ openBackgroundStudioPanel({
+ anchorEl: paletteBtn,
+ mode: "draft",
+ entityId: "draft-new-note",
+ editUrl: "",
+ currentColor: draftVisual.color || "default",
+ currentFilter: draftVisual.filter || "none",
+ currentBackground: draftVisual.background || "none",
+ currentFontColor: draftVisual.fontColor || "auto",
+ title: "Mes images & couleurs",
+ });
+
+ const panel = document.getElementById("shaarli-bg-studio");
+ if (panel) {
+ panel.__draftApply = (next) => {
+ draftVisual = { ...draftVisual, ...(next || {}) };
+ applyNoteVisualState(inputContainer, draftVisual);
+ };
+ }
+ });
+
+ titleInput?.focus({ preventScroll: true });
+ };
+
+ collapsed?.addEventListener("click", (e) => {
+ if (e.target.closest("button")) return;
+ openEditor();
+ });
+ btn?.addEventListener("click", (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ openEditor();
+ });
+ };
+
+ renderNoteInputCollapsed();
+ topBarInner.appendChild(inputContainer);
+ topBar.appendChild(topBarInner);
// View Toggle and other tools
const tools = document.createElement("div");
@@ -2546,14 +3082,26 @@ function initNoteView(linkList, container) {
const modalOverlay = document.createElement("div");
modalOverlay.className = "note-modal-overlay";
modalOverlay.innerHTML = `
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2561,6 +3109,7 @@ function initNoteView(linkList, container) {
+
@@ -2590,12 +3139,30 @@ function initNoteView(linkList, container) {
renderNotes(contentArea, notes, "list");
});
- // Close Modal
- modalOverlay.querySelector("#note-modal-close").addEventListener("click", () => {
- modalOverlay.classList.remove("open");
+ const saveAndCloseModal = async () => {
+ try {
+ await saveOpenNoteEditorModal(modalOverlay);
+ modalOverlay.classList.remove("open");
+ document.body.classList.remove("note-modal-open");
+ } catch (e) {
+ console.error("Error saving note from modal:", e);
+ alert("Erreur lors de la sauvegarde de la note.");
+ }
+ };
+
+ modalOverlay.querySelector("#note-modal-close").addEventListener("click", (e) => {
+ e.preventDefault();
+ saveAndCloseModal();
});
modalOverlay.addEventListener("click", (e) => {
- if (e.target === modalOverlay) modalOverlay.classList.remove("open");
+ if (e.target === modalOverlay) saveAndCloseModal();
+ });
+
+ modalOverlay.querySelector("#note-modal-edit").addEventListener("click", (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ const titleInput = modalOverlay.querySelector("#note-modal-title");
+ titleInput?.focus({ preventScroll: true });
});
const modalPinBtn = modalOverlay.querySelector("#note-modal-pin");
@@ -2682,6 +3249,7 @@ function initNoteView(linkList, container) {
if (index > -1) notes.splice(index, 1);
modalOverlay.classList.remove("open");
+ document.body.classList.remove("note-modal-open");
})
.catch((err) => {
console.error("Error archiving note:", err);
@@ -3051,12 +3619,11 @@ function renderNotes(container, notes, viewMode, isArchiveMode = false) {
}
// Body (truncated in grid, maybe?)
- if (note.descHtml) {
+ if (note.descHtml || note._noteMarkdown) {
const body = document.createElement("div");
body.className = "note-body";
- // Start simple: use innerHTML but maybe strip big images if we used cover?
- // For now, let's just dump it and style images to fit or hide if first child
- body.innerHTML = note.descHtml;
+ const content = note._noteMarkdown || note.descHtml || "";
+ body.innerHTML = renderMarkdown(content);
inner.appendChild(body);
}
@@ -3091,10 +3658,18 @@ function renderNotes(container, notes, viewMode, isArchiveMode = false) {
-
+
`;
+ const openEditorBtn = actions.querySelector(".note-open-editor-btn");
+ openEditorBtn?.addEventListener("click", (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ syncNoteFromCardElement(note, card);
+ openNoteModal(note);
+ });
+
// Palette Toggle
const paletteBtn = actions.querySelector(`#${paletteBtnId}`);
paletteBtn.addEventListener("click", (e) => {
@@ -3178,12 +3753,14 @@ function openNoteModal(note) {
if (!modal) return;
const modalCard = modal.querySelector(".note-modal");
- const title = modal.querySelector("#note-modal-title");
- const content = modal.querySelector(".note-modal-content");
+ const titleInput = modal.querySelector("#note-modal-title");
+ const descriptionSource = modal.querySelector("#note-modal-description-source");
const tagsContainer = modal.querySelector("#note-modal-tags");
const editLink = modal.querySelector("#note-modal-edit");
const pinButton = modal.querySelector("#note-modal-pin");
const modalColorPopup = modal.querySelector("#note-modal-color-popup");
+ const formatToggleBtn = modal.querySelector("#note-modal-format-btn");
+ const formattingBar = modal.querySelector("#note-modal-formatting");
modal.currentNote = note;
@@ -3197,8 +3774,8 @@ function openNoteModal(note) {
const visibleTags = (note.tags || []).filter((tag) => tag && !isTechnicalTag(tag));
modalCard.dataset.tags = visibleTags.join("||");
- title.textContent = note.title || "Sans titre";
- content.innerHTML = `
${note.descHtml || ""}
`;
+ if (titleInput) titleInput.value = note.title || "";
+ if (descriptionSource) descriptionSource.value = (note._noteMarkdown || note.descText || "").trim();
renderModalTags(tagsContainer, visibleTags);
if (editLink) {
@@ -3212,7 +3789,131 @@ function openNoteModal(note) {
setModalPinButtonState(pinButton, !!note.isPinned);
+ if (!modal._noteEditorState) {
+ modal._noteEditorState = { hasChanges: false, isSaving: false, lastSavedTitle: "", lastSavedMarkdown: "" };
+ }
+
+ const state = modal._noteEditorState;
+ state.hasChanges = false;
+ state.lastSavedTitle = titleInput && typeof titleInput.value === "string" ? titleInput.value.trim() : "";
+ state.lastSavedMarkdown = descriptionSource && typeof descriptionSource.value === "string" ? descriptionSource.value.trim() : "";
+
+ if (!modal._noteEditorBound) {
+ modal._noteEditorBound = true;
+ titleInput?.addEventListener("input", () => {
+ if (modal._noteEditorState) modal._noteEditorState.hasChanges = true;
+ });
+ descriptionSource?.addEventListener("input", () => {
+ if (modal._noteEditorState) modal._noteEditorState.hasChanges = true;
+ });
+
+ const setFormattingVisible = (visible) => {
+ const fb = modal.querySelector("#note-modal-formatting");
+ if (!fb) return;
+ if (visible) {
+ fb.classList.add("open");
+ fb.setAttribute("aria-hidden", "false");
+ modal.classList.add("show-formatting");
+ } else {
+ fb.classList.remove("open");
+ fb.setAttribute("aria-hidden", "true");
+ modal.classList.remove("show-formatting");
+ }
+ };
+
+ modal._setNoteModalFormattingVisible = setFormattingVisible;
+
+ const ftb = modal.querySelector("#note-modal-format-btn");
+ ftb?.addEventListener("click", (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ const fb = modal.querySelector("#note-modal-formatting");
+ const isOpen = fb && fb.classList.contains("open");
+ setFormattingVisible(!isOpen);
+ });
+
+ modal.querySelectorAll(".note-format-btn").forEach((btnEl) => {
+ btnEl.addEventListener("click", (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ const src = modal.querySelector("#note-modal-description-source");
+ if (!src) return;
+ applyKeepNoteFormatting(src, btnEl.dataset.noteFormat || "");
+ if (modal._noteEditorState) modal._noteEditorState.hasChanges = true;
+ });
+ });
+ }
+
+ if (typeof modal._setNoteModalFormattingVisible === "function") {
+ modal._setNoteModalFormattingVisible(false);
+ }
+
+ hydrateNoteFromEditForm(note).then(() => {
+ if (modal.currentNote !== note) return;
+ if (titleInput && note._noteTitle) {
+ titleInput.value = note._noteTitle;
+ }
+ if (descriptionSource) {
+ descriptionSource.value = (note._noteMarkdown || "").trim();
+ }
+ state.lastSavedTitle = titleInput && typeof titleInput.value === "string" ? titleInput.value.trim() : "";
+ state.lastSavedMarkdown = descriptionSource && typeof descriptionSource.value === "string" ? descriptionSource.value.trim() : "";
+ });
+
modal.classList.add("open");
+ document.body.classList.add("note-modal-open");
+ titleInput?.focus({ preventScroll: true });
+}
+
+async function saveOpenNoteEditorModal(modal) {
+ if (!modal || !modal.currentNote) return;
+ const note = modal.currentNote;
+
+ const titleInput = modal.querySelector("#note-modal-title");
+ const descriptionSource = modal.querySelector("#note-modal-description-source");
+
+ const title = titleInput && typeof titleInput.value === "string" ? titleInput.value.trim() : "";
+ const markdown = descriptionSource && typeof descriptionSource.value === "string" ? descriptionSource.value.trim() : "";
+ const state = modal._noteEditorState || { hasChanges: false, isSaving: false, lastSavedTitle: "", lastSavedMarkdown: "" };
+
+ if (!title && !markdown) {
+ return;
+ }
+
+ const changed =
+ state.hasChanges ||
+ title !== (state.lastSavedTitle || "") ||
+ markdown !== (state.lastSavedMarkdown || "");
+
+ if (!changed || state.isSaving) return;
+
+ state.isSaving = true;
+ modal._noteEditorState = state;
+
+ await hydrateNoteFromEditForm(note);
+ await persistNoteChanges(note, { title, markdown });
+
+ note.title = title;
+ note._noteTitle = title;
+ note._noteMarkdown = markdown;
+ note.descText = markdown;
+
+ const card = document.querySelector(`.note-card[data-id="${CSS.escape(String(note.id))}"]`);
+ if (card) {
+ const titleEl = card.querySelector(".note-inner .note-title");
+ if (titleEl) titleEl.textContent = title;
+
+ const bodyEl = card.querySelector(".note-inner .note-body");
+ if (bodyEl) {
+ bodyEl.innerHTML = renderMarkdown(markdown);
+ note.descHtml = bodyEl.innerHTML;
+ }
+ }
+
+ state.lastSavedTitle = title;
+ state.lastSavedMarkdown = markdown;
+ state.hasChanges = false;
+ state.isSaving = false;
}
function generateModalPaletteButtons(note) {
@@ -3964,10 +4665,18 @@ function closeColorPickerPanel() {
const type = panel.dataset.type || "background";
const skipRestore = panel.dataset.skipRestore === "1";
+ const mode = panel.dataset.mode || "entity";
+ const entityId = panel.dataset.entityId || "";
+
+ if (!skipRestore && mode === "draft") {
+ panel.dataset.skipRestore = "0";
+ panel.classList.remove("open");
+ panel.style.display = "none";
+ panel.setAttribute("aria-hidden", "true");
+ return;
+ }
if (!skipRestore && type === "font") {
const prevFontColorKey = panel.dataset.prevFontColorKey || "auto";
- const mode = panel.dataset.mode || "entity";
- const entityId = panel.dataset.entityId || "";
if (mode === "modal") {
setModalNoteFontColorVisual(prevFontColorKey);
} else {
@@ -3977,8 +4686,6 @@ function closeColorPickerPanel() {
if (!skipRestore && type === "background") {
const prevColorKey = panel.dataset.prevColorKey || "default";
- const mode = panel.dataset.mode || "entity";
- const entityId = panel.dataset.entityId || "";
if (mode === "modal") {
setModalNoteColorVisual(prevColorKey);
} else {
@@ -4007,6 +4714,10 @@ function openColorPickerPanel({ mode, entityId, editUrl, type }) {
if ((type || "background") === "font") {
let prevFontColorKey = "auto";
+ if ((mode || "entity") === "draft") {
+ const bgPanel = document.getElementById("shaarli-bg-studio");
+ prevFontColorKey = bgPanel ? (bgPanel.dataset.fontColor || "auto") : "auto";
+ } else
if ((mode || "entity") === "modal") {
const modal = getOpenModalOverlay();
const modalCard = modal ? modal.querySelector(".note-modal") : null;
@@ -4023,6 +4734,12 @@ function openColorPickerPanel({ mode, entityId, editUrl, type }) {
if ((type || "background") === "background") {
let prevColorKey = "default";
+ if ((mode || "entity") === "draft") {
+ const bgPanel = document.getElementById("shaarli-bg-studio");
+ if (bgPanel) {
+ prevColorKey = bgPanel.dataset.color || "default";
+ }
+ } else
if ((mode || "entity") === "modal") {
const modal = getOpenModalOverlay();
const modalCard = modal ? modal.querySelector(".note-modal") : null;
@@ -4142,7 +4859,12 @@ function openColorPickerPanel({ mode, entityId, editUrl, type }) {
const previewKey = `custom:${currentHex}`;
const pMode = panel.dataset.mode || "entity";
const pEntityId = panel.dataset.entityId || "";
- if (pMode === "modal") {
+ if (pMode === "draft") {
+ const bgPanel = document.getElementById("shaarli-bg-studio");
+ if (bgPanel && typeof bgPanel.__draftApply === "function") {
+ bgPanel.__draftApply({ fontColor: previewKey });
+ }
+ } else if (pMode === "modal") {
setModalNoteFontColorVisual(previewKey);
} else {
setNoteFontColorVisual(pEntityId, previewKey);
@@ -4153,7 +4875,12 @@ function openColorPickerPanel({ mode, entityId, editUrl, type }) {
const previewKey = `custom:${currentHex}`;
const pMode = panel.dataset.mode || "entity";
const pEntityId = panel.dataset.entityId || "";
- if (pMode === "modal") {
+ if (pMode === "draft") {
+ const bgPanel = document.getElementById("shaarli-bg-studio");
+ if (bgPanel && typeof bgPanel.__draftApply === "function") {
+ bgPanel.__draftApply({ color: previewKey });
+ }
+ } else if (pMode === "modal") {
setModalNoteColorVisual(previewKey);
} else {
setNoteColorVisual(pEntityId, previewKey);
@@ -4300,6 +5027,24 @@ document.addEventListener("click", (e) => {
const hexInput = panel.querySelector("#hex-input");
const color = hexInput ? hexInput.value : "#ffffff";
+ if (mode === "draft") {
+ const bgPanel = document.getElementById("shaarli-bg-studio");
+ if (bgPanel && typeof bgPanel.__draftApply === "function") {
+ if (type === "font") {
+ bgPanel.dataset.fontColor = `custom:${color}`;
+ bgPanel.__draftApply({ fontColor: `custom:${color}` });
+ } else {
+ bgPanel.dataset.color = `custom:${color}`;
+ bgPanel.__draftApply({ color: `custom:${color}` });
+ }
+ renderBackgroundStudioPanel(bgPanel);
+ }
+
+ panel.dataset.skipRestore = "1";
+ closeColorPickerPanel();
+ return;
+ }
+
if (type === "font") {
if (mode === "modal") {
setModalCustomFontColor(color);
@@ -4371,7 +5116,7 @@ function setNoteColorVisual(noteId, colorKey) {
if (typeof colorKey === "string" && colorKey.startsWith("custom:")) {
const hex = colorKey.substring(7);
- element.classList.forEach((cls) => {
+ Array.from(element.classList).forEach((cls) => {
if (cls.startsWith("note-color-")) element.classList.remove(cls);
});
element.classList.add("note-color-custom");