feat: ajouter support pour les images de fond et les filtres CSS sur les notes avec des classes spécifiques, incluant des styles pour les éléments .link-outer et .note-card, ainsi que des ajustements pour la gestion des couleurs de texte et de fond dans le studio de personnalisation
This commit is contained in:
parent
1313f78b93
commit
c1b48ad584
@ -1592,6 +1592,13 @@ body.view-notes .notes-masonry:empty::after {
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.link-outer.note-has-bg {
|
||||
background-image: var(--note-bg-image, none) !important;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.note-modal.note-has-bg {
|
||||
background-image: var(--note-bg-image, none) !important;
|
||||
background-size: cover;
|
||||
@ -1612,7 +1619,14 @@ body.view-notes .notes-masonry:empty::after {
|
||||
.note-card.note-filter-grid::after,
|
||||
.note-card.note-filter-noise::after,
|
||||
.note-card.note-filter-dots::after,
|
||||
.note-card.note-filter-stripes::after {
|
||||
.note-card.note-filter-stripes::after,
|
||||
.link-outer.note-filter-glass::after,
|
||||
.link-outer.note-filter-vignette::after,
|
||||
.link-outer.note-filter-lined::after,
|
||||
.link-outer.note-filter-grid::after,
|
||||
.link-outer.note-filter-noise::after,
|
||||
.link-outer.note-filter-dots::after,
|
||||
.link-outer.note-filter-stripes::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
@ -1628,11 +1642,21 @@ body.view-notes .notes-masonry:empty::after {
|
||||
-webkit-backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.link-outer.note-filter-glass::after {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
backdrop-filter: blur(2px);
|
||||
-webkit-backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
/* Filter: Vignette — dark edges */
|
||||
.note-card.note-filter-vignette::after {
|
||||
background: radial-gradient(ellipse at center, transparent 45%, rgba(0, 0, 0, 0.42) 100%);
|
||||
}
|
||||
|
||||
.link-outer.note-filter-vignette::after {
|
||||
background: radial-gradient(ellipse at center, transparent 45%, rgba(0, 0, 0, 0.42) 100%);
|
||||
}
|
||||
|
||||
/* Filter: Lined — horizontal ruled lines */
|
||||
.note-card.note-filter-lined::after {
|
||||
background-image: repeating-linear-gradient(
|
||||
@ -1644,6 +1668,16 @@ body.view-notes .notes-masonry:empty::after {
|
||||
);
|
||||
}
|
||||
|
||||
.link-outer.note-filter-lined::after {
|
||||
background-image: repeating-linear-gradient(
|
||||
to bottom,
|
||||
transparent 0px,
|
||||
transparent 27px,
|
||||
rgba(128, 128, 128, 0.18) 27px,
|
||||
rgba(128, 128, 128, 0.18) 28px
|
||||
);
|
||||
}
|
||||
|
||||
/* Filter: Grid — squared grid */
|
||||
.note-card.note-filter-grid::after {
|
||||
background-image:
|
||||
@ -1651,18 +1685,34 @@ body.view-notes .notes-masonry:empty::after {
|
||||
repeating-linear-gradient(to bottom, rgba(128, 128, 128, 0.15) 0px, rgba(128, 128, 128, 0.15) 1px, transparent 1px, transparent 28px);
|
||||
}
|
||||
|
||||
.link-outer.note-filter-grid::after {
|
||||
background-image:
|
||||
repeating-linear-gradient(to right, rgba(128, 128, 128, 0.15) 0px, rgba(128, 128, 128, 0.15) 1px, transparent 1px, transparent 28px),
|
||||
repeating-linear-gradient(to bottom, rgba(128, 128, 128, 0.15) 0px, rgba(128, 128, 128, 0.15) 1px, transparent 1px, transparent 28px);
|
||||
}
|
||||
|
||||
/* Filter: Noise — subtle stipple effect */
|
||||
.note-card.note-filter-noise::after {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)' opacity='0.06'/%3E%3C/svg%3E");
|
||||
background-repeat: repeat;
|
||||
}
|
||||
|
||||
.link-outer.note-filter-noise::after {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)' opacity='0.06'/%3E%3C/svg%3E");
|
||||
background-repeat: repeat;
|
||||
}
|
||||
|
||||
/* Filter: Dots — dot pattern */
|
||||
.note-card.note-filter-dots::after {
|
||||
background-image: radial-gradient(circle, rgba(128, 128, 128, 0.3) 1px, transparent 1px);
|
||||
background-size: 14px 14px;
|
||||
}
|
||||
|
||||
.link-outer.note-filter-dots::after {
|
||||
background-image: radial-gradient(circle, rgba(128, 128, 128, 0.3) 1px, transparent 1px);
|
||||
background-size: 14px 14px;
|
||||
}
|
||||
|
||||
/* Filter: Stripes — diagonal lines */
|
||||
.note-card.note-filter-stripes::after {
|
||||
background-image: repeating-linear-gradient(
|
||||
@ -1674,9 +1724,55 @@ body.view-notes .notes-masonry:empty::after {
|
||||
);
|
||||
}
|
||||
|
||||
.link-outer.note-filter-stripes::after {
|
||||
background-image: repeating-linear-gradient(
|
||||
-45deg,
|
||||
transparent 0px,
|
||||
transparent 8px,
|
||||
rgba(128, 128, 128, 0.12) 8px,
|
||||
rgba(128, 128, 128, 0.12) 9px
|
||||
);
|
||||
}
|
||||
|
||||
/* Keep card inner content above the filter overlay */
|
||||
.note-card .note-inner,
|
||||
.note-card .note-hover-actions {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.link-outer .link-content,
|
||||
.link-outer .link-actions,
|
||||
.link-outer .link-hover-actions,
|
||||
.link-outer .link-meta,
|
||||
.link-outer .link-select-checkbox,
|
||||
.link-outer .link-visibility-badge {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* Let Background Studio font color affect bookmark tiles too */
|
||||
.link-outer[class*="note-color-"],
|
||||
.link-outer[class*="note-color-"] .link-title,
|
||||
.link-outer[class*="note-color-"] .link-title a,
|
||||
.link-outer[class*="note-color-"] .link-url,
|
||||
.link-outer[class*="note-color-"] .link-url a,
|
||||
.link-outer[class*="note-color-"] .link-description,
|
||||
.link-outer[class*="note-color-"] .link-meta,
|
||||
.link-outer[class*="note-color-"] .link-meta a,
|
||||
.link-outer[class*="note-color-"] .link-actions,
|
||||
.link-outer[class*="note-color-"] .link-actions a,
|
||||
.link-outer[class*="note-color-"] .link-actions button,
|
||||
.view-list .link-outer[class*="note-color-"],
|
||||
.view-list .link-outer[class*="note-color-"] a,
|
||||
.view-list .link-outer[class*="note-color-"] button {
|
||||
color: var(--note-card-fg, var(--tile-text));
|
||||
}
|
||||
|
||||
/* Let Background Studio font color affect note/todo body text */
|
||||
.note-card[class*="note-color-"] .note-title,
|
||||
.note-card[class*="note-color-"] .note-body,
|
||||
.note-card[class*="note-color-"] .todo-checklist-preview-item,
|
||||
.note-card[class*="note-color-"] .todo-checklist-preview-text {
|
||||
color: var(--note-card-fg, var(--special-view-strong));
|
||||
}
|
||||
@ -9,14 +9,17 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
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);
|
||||
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, .sidebar-link[href*="searchtags=shaarli-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, .sidebar-link[href*="searchtags=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, .sidebar-link[href*="searchtags=shaarli-archiver"].active');
|
||||
|
||||
const domChipTags = Array.from(document.querySelectorAll('.search-tag-chip')).map(el => (el.textContent || "").trim().toLowerCase());
|
||||
const domChipTags = Array.from(document.querySelectorAll(".search-tag-chip")).map((el) => (el.textContent || "").trim().toLowerCase());
|
||||
|
||||
const isArchiveTag = (tag) => tag === "shaarli-archive" || tag === "shaarli-archiver";
|
||||
const isNoteTag = (tag) => tag === "note" || tag === "shaarli-note" || tag === "#note";
|
||||
@ -32,7 +35,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
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 = `<strong>Erreur Custom Views:</strong> ${msg}<br><pre style="margin-top:8px; font-size: 12px; white-space: pre-wrap; word-break: break-all;">${err ? err.stack || err : ''}</pre>`;
|
||||
banner.innerHTML = `<strong>Erreur Custom Views:</strong> ${msg}<br><pre style="margin-top:8px; font-size: 12px; white-space: pre-wrap; word-break: break-all;">${err ? err.stack || err : ""}</pre>`;
|
||||
if (container) container.prepend(banner);
|
||||
else document.body.prepend(banner);
|
||||
};
|
||||
@ -58,18 +61,9 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
if (!markdown) return "";
|
||||
|
||||
const normalizeNewlines = String(markdown).replace(/\r\n?/g, "\n");
|
||||
const escape = (value) =>
|
||||
String(value)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
const escape = (value) => String(value).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||
|
||||
const escapeAttr = (value) =>
|
||||
String(value)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/"/g, """)
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">");
|
||||
const escapeAttr = (value) => String(value).replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
||||
|
||||
const sanitizeUrl = (rawUrl) => {
|
||||
const url = String(rawUrl || "").trim();
|
||||
@ -196,12 +190,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
inTodo = true;
|
||||
}
|
||||
const checked = String(todoMatch[1]).toLowerCase() === "x";
|
||||
html.push(
|
||||
`<li class="md-todo-item${checked ? " is-checked" : ""}">`
|
||||
+ `<span class="md-todo-box" aria-hidden="true"><i class="mdi mdi-check"></i></span>`
|
||||
+ `<span class="md-todo-text">${renderInline(todoMatch[2])}</span>`
|
||||
+ "</li>",
|
||||
);
|
||||
html.push(`<li class="md-todo-item${checked ? " is-checked" : ""}">` + `<span class="md-todo-box" aria-hidden="true"><i class="mdi mdi-check"></i></span>` + `<span class="md-todo-text">${renderInline(todoMatch[2])}</span>` + "</li>");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -490,8 +479,7 @@ function renderMarkdown(markdown) {
|
||||
if (typeof window !== "undefined" && typeof window.renderMarkdown === "function") {
|
||||
return window.renderMarkdown(markdown);
|
||||
}
|
||||
return String(markdown || "")
|
||||
.replace(/\n/g, "<br>");
|
||||
return String(markdown || "").replace(/\n/g, "<br>");
|
||||
}
|
||||
|
||||
function applyKeepNoteFormatting(textarea, format) {
|
||||
@ -505,80 +493,80 @@ const NOTE_COLOR_OPTIONS = [
|
||||
key: "default",
|
||||
label: "Par défaut",
|
||||
light: "#f8fafc",
|
||||
dark: "#20293A"
|
||||
dark: "#20293A",
|
||||
},
|
||||
{
|
||||
key: "red",
|
||||
label: "Rouge",
|
||||
light: "#f28b82",
|
||||
dark: "#9c2116"
|
||||
dark: "#9c2116",
|
||||
},
|
||||
{
|
||||
key: "orange",
|
||||
label: "Orange",
|
||||
light: "#fbbc04",
|
||||
dark: "#9c7a16"
|
||||
dark: "#9c7a16",
|
||||
},
|
||||
{
|
||||
key: "yellow",
|
||||
label: "Jaune",
|
||||
light: "#fff475",
|
||||
dark: "#9c9116"
|
||||
dark: "#9c9116",
|
||||
},
|
||||
{
|
||||
key: "green",
|
||||
label: "Vert",
|
||||
light: "#ccff90",
|
||||
dark: "#5e9c16"
|
||||
dark: "#5e9c16",
|
||||
},
|
||||
{
|
||||
key: "teal",
|
||||
label: "Menthe",
|
||||
light: "#a7ffeb",
|
||||
dark: "#169c7d"
|
||||
dark: "#169c7d",
|
||||
},
|
||||
{
|
||||
key: "blue",
|
||||
label: "Bleu clair",
|
||||
light: "#cbf0f8",
|
||||
dark: "#16849c"
|
||||
dark: "#16849c",
|
||||
},
|
||||
{
|
||||
key: "darkblue",
|
||||
label: "Bleu",
|
||||
light: "#aecbfa",
|
||||
dark: "#16499c"
|
||||
dark: "#16499c",
|
||||
},
|
||||
{
|
||||
key: "purple",
|
||||
label: "Violet",
|
||||
light: "#d7aefb",
|
||||
dark: "#5d169c"
|
||||
dark: "#5d169c",
|
||||
},
|
||||
{
|
||||
key: "pink",
|
||||
label: "Rose",
|
||||
light: "#fdcfe8",
|
||||
dark: "#9c165f"
|
||||
dark: "#9c165f",
|
||||
},
|
||||
{
|
||||
key: "brown",
|
||||
label: "Beige",
|
||||
light: "#e6c9a8",
|
||||
dark: "#9c5d16"
|
||||
dark: "#9c5d16",
|
||||
},
|
||||
{
|
||||
key: "grey",
|
||||
label: "Gris",
|
||||
light: "#e8eaed",
|
||||
dark: "#4e5764"
|
||||
dark: "#4e5764",
|
||||
},
|
||||
{
|
||||
key: "custom",
|
||||
label: "Personnalisé",
|
||||
light: "#custom",
|
||||
dark: "#custom"
|
||||
}
|
||||
dark: "#custom",
|
||||
},
|
||||
];
|
||||
|
||||
const NOTE_FILTER_OPTIONS = [
|
||||
@ -598,7 +586,7 @@ const NOTE_FONT_COLOR_OPTIONS = [
|
||||
{ key: "dark", label: "Sombre", value: "#202124" },
|
||||
{ key: "white", label: "Blanc", value: "#ffffff" },
|
||||
{ key: "black", label: "Noir", value: "#000000" },
|
||||
{ key: "custom", label: "Personnalisé", value: "custom" }
|
||||
{ key: "custom", label: "Personnalisé", value: "custom" },
|
||||
];
|
||||
|
||||
const NOTE_FONT_COLOR_TAG_PREFIX = "font-";
|
||||
@ -703,11 +691,10 @@ function removeTagFromEntity(editUrl, tag) {
|
||||
function deleteEntitySilently(deleteUrl) {
|
||||
if (!deleteUrl || deleteUrl === "#") return Promise.reject("Invalid delete URL");
|
||||
|
||||
return fetch(deleteUrl)
|
||||
.then((response) => {
|
||||
if (!response.ok) throw new Error("Delete request failed");
|
||||
return response;
|
||||
});
|
||||
return fetch(deleteUrl).then((response) => {
|
||||
if (!response.ok) throw new Error("Delete request failed");
|
||||
return response;
|
||||
});
|
||||
}
|
||||
|
||||
let tagDisplayRemovalInitialized = false;
|
||||
@ -818,18 +805,14 @@ function getModalCurrentEntity(modal) {
|
||||
}
|
||||
|
||||
function resolveThemeAssetBasePath() {
|
||||
const cssLink = Array.from(document.querySelectorAll('link[rel="stylesheet"]')).find(
|
||||
(link) => link.href && link.href.includes("/custom_views.css"),
|
||||
);
|
||||
const cssLink = Array.from(document.querySelectorAll('link[rel="stylesheet"]')).find((link) => link.href && link.href.includes("/custom_views.css"));
|
||||
if (cssLink && cssLink.href) {
|
||||
const cssUrl = new URL(cssLink.href, window.location.origin);
|
||||
const cssPath = cssUrl.pathname.replace(/\/css\/custom_views\.css$/, "");
|
||||
if (cssPath) return cssPath;
|
||||
}
|
||||
|
||||
const jsScript = Array.from(document.querySelectorAll("script[src]")).find(
|
||||
(script) => script.src && script.src.includes("/custom_views.js"),
|
||||
);
|
||||
const jsScript = Array.from(document.querySelectorAll("script[src]")).find((script) => script.src && script.src.includes("/custom_views.js"));
|
||||
if (jsScript && jsScript.src) {
|
||||
const jsUrl = new URL(jsScript.src, window.location.origin);
|
||||
const jsPath = jsUrl.pathname.replace(/\/js\/custom_views\.js$/, "");
|
||||
@ -1052,11 +1035,7 @@ function getReadableForegroundForBackground(colorValue, mode = getCurrentThemeMo
|
||||
}
|
||||
|
||||
function normalizeDynamicBackgroundOptions(rawOptions) {
|
||||
const rawList = Array.isArray(rawOptions)
|
||||
? rawOptions
|
||||
: rawOptions && Array.isArray(rawOptions.backgrounds)
|
||||
? rawOptions.backgrounds
|
||||
: [];
|
||||
const rawList = Array.isArray(rawOptions) ? rawOptions : rawOptions && Array.isArray(rawOptions.backgrounds) ? rawOptions.backgrounds : [];
|
||||
|
||||
const mergedByKey = {};
|
||||
|
||||
@ -1170,9 +1149,7 @@ function refreshBackgroundPalettes() {
|
||||
const noteId = card.dataset.id || "";
|
||||
if (!noteId) return;
|
||||
|
||||
const editLink = card.querySelector(
|
||||
'.note-hover-actions a[href*="/admin/shaare/"], .note-hover-actions a[href*="do=editlink"], .note-hover-actions a[title="Edit"]',
|
||||
);
|
||||
const editLink = card.querySelector('.note-hover-actions a[href*="/admin/shaare/"], .note-hover-actions a[href*="do=editlink"], .note-hover-actions a[title="Edit"]');
|
||||
|
||||
palettePopup.innerHTML = generatePaletteButtons({
|
||||
id: noteId,
|
||||
@ -1190,16 +1167,9 @@ function refreshBackgroundPalettes() {
|
||||
if (!noteId) return;
|
||||
|
||||
const actions = card.querySelector(".link-actions");
|
||||
const editLink = actions
|
||||
? actions.querySelector('a[title="Modifier"], a[aria-label="Modifier ce bookmark"], a[href*="/admin/shaare/"]')
|
||||
: null;
|
||||
const editLink = actions ? actions.querySelector('a[title="Modifier"], a[aria-label="Modifier ce bookmark"], a[href*="/admin/shaare/"]') : null;
|
||||
|
||||
palettePopup.innerHTML = generateBookmarkPaletteButtons(
|
||||
noteId,
|
||||
editLink && editLink.href ? editLink.href : "#",
|
||||
getElementVisualColor(card),
|
||||
getElementVisualBackground(card),
|
||||
);
|
||||
palettePopup.innerHTML = generateBookmarkPaletteButtons(noteId, editLink && editLink.href ? editLink.href : "#", getElementVisualColor(card), getElementVisualBackground(card));
|
||||
});
|
||||
|
||||
const modal = getOpenModalOverlay();
|
||||
@ -1236,7 +1206,6 @@ function parseBackgroundManifestPayload(rawPayload) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let isThemeModeBackgroundSyncInitialized = false;
|
||||
|
||||
function initThemeModeBackgroundSync() {
|
||||
@ -1311,22 +1280,6 @@ function ensureBackgroundStudioPanel() {
|
||||
panel.style.display = "none";
|
||||
document.body.appendChild(panel);
|
||||
|
||||
document.addEventListener("click", (e) => {
|
||||
const p = document.getElementById("shaarli-bg-studio");
|
||||
if (!p || !p.classList.contains("open")) return;
|
||||
if (e.target.closest("#shaarli-bg-studio")) return;
|
||||
const anchor = p.__anchorEl;
|
||||
if (anchor && (e.target === anchor || e.target.closest(`#${anchor.id}`))) return;
|
||||
closeBackgroundStudioPanel();
|
||||
});
|
||||
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key !== "Escape") return;
|
||||
const p = document.getElementById("shaarli-bg-studio");
|
||||
if (!p || !p.classList.contains("open")) return;
|
||||
closeBackgroundStudioPanel();
|
||||
});
|
||||
|
||||
panel.addEventListener("click", (e) => {
|
||||
const actionBtn = e.target.closest("button[data-bg-studio-action]");
|
||||
if (!actionBtn) return;
|
||||
@ -1562,17 +1515,7 @@ function closeBackgroundStudioPanel() {
|
||||
panel.__anchorEl = null;
|
||||
}
|
||||
|
||||
function openBackgroundStudioPanel({
|
||||
anchorEl,
|
||||
mode,
|
||||
entityId,
|
||||
editUrl,
|
||||
currentColor,
|
||||
currentFilter,
|
||||
currentBackground,
|
||||
currentFontColor,
|
||||
title,
|
||||
}) {
|
||||
function openBackgroundStudioPanel({ anchorEl, mode, entityId, editUrl, currentColor, currentFilter, currentBackground, currentFontColor, title }) {
|
||||
ensureBackgroundStudioPanel();
|
||||
const panel = document.getElementById("shaarli-bg-studio");
|
||||
if (!panel) return;
|
||||
@ -1693,12 +1636,7 @@ function renderBackgroundStudioPanel(panel) {
|
||||
if (!panel) return;
|
||||
|
||||
const activeEl = document.activeElement;
|
||||
const wasSearchFocused = !!(
|
||||
activeEl &&
|
||||
panel.contains(activeEl) &&
|
||||
activeEl.classList &&
|
||||
activeEl.classList.contains("bg-studio-search-input")
|
||||
);
|
||||
const wasSearchFocused = !!(activeEl && panel.contains(activeEl) && activeEl.classList && activeEl.classList.contains("bg-studio-search-input"));
|
||||
const caretStart = wasSearchFocused && typeof activeEl.selectionStart === "number" ? activeEl.selectionStart : null;
|
||||
const caretEnd = wasSearchFocused && typeof activeEl.selectionEnd === "number" ? activeEl.selectionEnd : null;
|
||||
|
||||
@ -1733,14 +1671,15 @@ function renderBackgroundStudioPanel(panel) {
|
||||
.join("");
|
||||
|
||||
// Colors row with custom color picker button
|
||||
const colorsRowHtml = colors
|
||||
.filter((c) => c.key !== "default" && c.key !== "custom")
|
||||
.map((c) => {
|
||||
const isActive = color === c.key;
|
||||
const hex = c.color || "";
|
||||
return `<button class="bg-studio-swatch ${isActive ? "is-active" : ""}" type="button" data-bg-studio-action="set-color" data-color-key="${c.key}" title="${hex}" aria-label="${c.label}" aria-pressed="${isActive}" style="background-color:${hex}"></button>`;
|
||||
})
|
||||
.join("") +
|
||||
const colorsRowHtml =
|
||||
colors
|
||||
.filter((c) => c.key !== "default" && c.key !== "custom")
|
||||
.map((c) => {
|
||||
const isActive = color === c.key;
|
||||
const hex = c.color || "";
|
||||
return `<button class="bg-studio-swatch ${isActive ? "is-active" : ""}" type="button" data-bg-studio-action="set-color" data-color-key="${c.key}" title="${hex}" aria-label="${c.label}" aria-pressed="${isActive}" style="background-color:${hex}"></button>`;
|
||||
})
|
||||
.join("") +
|
||||
// Add custom color button (opens color picker)
|
||||
`<button class="bg-studio-swatch bg-studio-swatch-custom ${isCustomColor ? "is-active" : ""}" type="button" data-bg-studio-action="open-color-picker" title="Couleur personnalisée" aria-label="Couleur personnalisée" aria-pressed="${isCustomColor}">
|
||||
<i class="mdi mdi-plus" aria-hidden="true"></i>
|
||||
@ -1748,18 +1687,15 @@ function renderBackgroundStudioPanel(panel) {
|
||||
|
||||
// Font colors row
|
||||
const isCustomFontColor = typeof currentFontColor === "string" && currentFontColor.startsWith("custom:");
|
||||
const fontColorsRowHtml = NOTE_FONT_COLOR_OPTIONS
|
||||
.filter((c) => c.key !== "custom")
|
||||
.map((c) => {
|
||||
const isActive = currentFontColor === c.key;
|
||||
const value = c.value || "auto";
|
||||
const style =
|
||||
value === "auto"
|
||||
? "background: linear-gradient(135deg, #f5f7fb 50%, #202124 50%);"
|
||||
: `background-color: ${value};`;
|
||||
return `<button class="bg-studio-swatch ${isActive ? "is-active" : ""}" type="button" data-bg-studio-action="set-font-color" data-font-color-key="${c.key}" title="${c.label}" aria-label="${c.label}" aria-pressed="${isActive}" style="${style}"></button>`;
|
||||
})
|
||||
.join("") +
|
||||
const fontColorsRowHtml =
|
||||
NOTE_FONT_COLOR_OPTIONS.filter((c) => c.key !== "custom")
|
||||
.map((c) => {
|
||||
const isActive = currentFontColor === c.key;
|
||||
const value = c.value || "auto";
|
||||
const style = value === "auto" ? "background: linear-gradient(135deg, #f5f7fb 50%, #202124 50%);" : `background-color: ${value};`;
|
||||
return `<button class="bg-studio-swatch ${isActive ? "is-active" : ""}" type="button" data-bg-studio-action="set-font-color" data-font-color-key="${c.key}" title="${c.label}" aria-label="${c.label}" aria-pressed="${isActive}" style="${style}"></button>`;
|
||||
})
|
||||
.join("") +
|
||||
// Add custom font color button
|
||||
`<button class="bg-studio-swatch bg-studio-swatch-custom ${isCustomFontColor ? "is-active" : ""}" type="button" data-bg-studio-action="open-font-color-picker" title="Couleur de texte personnalisée" aria-label="Couleur de texte personnalisée" aria-pressed="${isCustomFontColor}">
|
||||
<i class="mdi mdi-plus" aria-hidden="true"></i>
|
||||
@ -1833,7 +1769,7 @@ function applyNoteVisualState(element, note) {
|
||||
const customHex = isCustomColor ? rawColor.substring(7) : "";
|
||||
|
||||
const resolvedColorOption = isCustomColor ? null : getColorOption(rawColor);
|
||||
const color = isCustomColor ? "custom" : (resolvedColorOption ? resolvedColorOption.key : "default");
|
||||
const color = isCustomColor ? "custom" : resolvedColorOption ? resolvedColorOption.key : "default";
|
||||
const colorValue = isCustomColor ? customHex : getThemeColorValue(resolvedColorOption);
|
||||
const foregroundColor = getReadableForegroundForBackground(colorValue);
|
||||
const filter = normalizeFilterKey(note.filter || "none");
|
||||
@ -2049,15 +1985,9 @@ function generateUnifiedPaletteMenu({ entityId, editUrl, currentColor, currentFi
|
||||
const color = currentColor || "default";
|
||||
const filter = normalizeFilterKey(currentFilter || "") || "none";
|
||||
|
||||
const onColorClick =
|
||||
mode === "modal"
|
||||
? (c) => `setModalNoteColor('${c}')`
|
||||
: (c) => `setNoteColor('${entityId}', '${c}', '${editUrl}')`;
|
||||
const onColorClick = mode === "modal" ? (c) => `setModalNoteColor('${c}')` : (c) => `setNoteColor('${entityId}', '${c}', '${editUrl}')`;
|
||||
|
||||
const onFilterClick =
|
||||
mode === "modal"
|
||||
? (k) => `setModalNoteFilter('${k}')`
|
||||
: (k) => `setNoteFilter('${entityId}', '${k}', '${editUrl}')`;
|
||||
const onFilterClick = mode === "modal" ? (k) => `setModalNoteFilter('${k}')` : (k) => `setNoteFilter('${entityId}', '${k}', '${editUrl}')`;
|
||||
|
||||
const colorButtons = [
|
||||
`<button class="palette-btn palette-btn-default ${color === "default" ? "is-active" : ""}" type="button" title="Par défaut" aria-label="Par défaut" aria-pressed="${color === "default"}" onclick="${onColorClick("default")}"><i class="mdi mdi-format-color-reset" aria-hidden="true"></i></button>`,
|
||||
@ -2687,9 +2617,7 @@ async function createNewTodoViaForm({ title, items = [], visual = null }) {
|
||||
formData.set("lf_url", todoUrl);
|
||||
formData.set("lf_title", title || "");
|
||||
|
||||
const normalizedItems = (Array.isArray(items) ? items : [])
|
||||
.map((it) => ({ checked: !!(it && it.checked), text: String((it && it.text) || "").trim() }))
|
||||
.filter((it) => it.text);
|
||||
const normalizedItems = (Array.isArray(items) ? items : []).map((it) => ({ checked: !!(it && it.checked), text: String((it && it.text) || "").trim() })).filter((it) => it.text);
|
||||
const markdown = normalizedItems.map((it) => `- [${it.checked ? "x" : " "}] ${it.text}`).join("\n");
|
||||
formData.set("lf_description", markdown);
|
||||
|
||||
@ -3016,20 +2944,13 @@ function buildTodoPreviewHtml(items) {
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
return String(str)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/\"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
return String(str).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/\"/g, """).replace(/'/g, "'");
|
||||
}
|
||||
|
||||
function parseTodoMarkdown(markdown) {
|
||||
const raw = String(markdown || "");
|
||||
// Minimal normalization for parsing lines
|
||||
const normalized = raw
|
||||
.replace(/<br\s*\/?\s*>/gi, "\n")
|
||||
.replace(/<\/(p|div|li|h\d)\s*>/gi, "\n");
|
||||
const normalized = raw.replace(/<br\s*\/?\s*>/gi, "\n").replace(/<\/(p|div|li|h\d)\s*>/gi, "\n");
|
||||
|
||||
let text = normalized;
|
||||
try {
|
||||
@ -3053,9 +2974,7 @@ function parseTodoMarkdown(markdown) {
|
||||
|
||||
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);
|
||||
const items = listItemEls.map((li) => ({ checked: false, text: (li.textContent || "").trim() })).filter((it) => it.text);
|
||||
if (items.length > 0) {
|
||||
return { headerLines: [], footerLines: [], items };
|
||||
}
|
||||
@ -3066,12 +2985,7 @@ function parseTodoMarkdown(markdown) {
|
||||
}
|
||||
|
||||
const lines = text.split(/\r?\n/);
|
||||
const taskRegexes = [
|
||||
/^\s*[-*+]\s*\[([ xX])\]\s*(.*)$/,
|
||||
/^\s*\[([ xX])\]\s*(.*)$/,
|
||||
/^\s*☐\s*(.*)$/,
|
||||
/^\s*☑\s*(.*)$/,
|
||||
];
|
||||
const taskRegexes = [/^\s*[-*+]\s*\[([ xX])\]\s*(.*)$/, /^\s*\[([ xX])\]\s*(.*)$/, /^\s*☐\s*(.*)$/, /^\s*☑\s*(.*)$/];
|
||||
|
||||
let firstTaskIndex = -1;
|
||||
let lastTaskIndex = -1;
|
||||
@ -3857,9 +3771,9 @@ function initNoteView(linkList, container) {
|
||||
|
||||
// Check for hash parameter to auto-open a specific note
|
||||
const hash = window.location.hash;
|
||||
if (hash && hash.startsWith('#open-note-')) {
|
||||
const noteId = hash.replace('#open-note-', '');
|
||||
const targetNote = notes.find(n => String(n.id) === String(noteId));
|
||||
if (hash && hash.startsWith("#open-note-")) {
|
||||
const noteId = hash.replace("#open-note-", "");
|
||||
const targetNote = notes.find((n) => String(n.id) === String(noteId));
|
||||
if (targetNote) {
|
||||
// Clear the hash to avoid reopening on refresh
|
||||
history.replaceState(null, null, window.location.pathname + window.location.search);
|
||||
@ -4003,9 +3917,7 @@ function initNoteView(linkList, container) {
|
||||
togglePinTag(noteId, editUrl, modalPinBtn);
|
||||
|
||||
const isPinned = modalPinBtn.classList.contains("active");
|
||||
let tags = (modalCard.dataset.tags || "")
|
||||
.split("||")
|
||||
.filter((t) => t);
|
||||
let tags = (modalCard.dataset.tags || "").split("||").filter((t) => t);
|
||||
|
||||
if (isPinned) {
|
||||
if (!tags.includes("shaarli-pin")) tags.push("shaarli-pin");
|
||||
@ -4238,9 +4150,7 @@ function initArchiveView(linkList, container) {
|
||||
togglePinTag(noteId, editUrl, modalPinBtn);
|
||||
|
||||
const isPinned = modalPinBtn.classList.contains("active");
|
||||
let tags = (modalCard.dataset.tags || "")
|
||||
.split("||")
|
||||
.filter((t) => t);
|
||||
let tags = (modalCard.dataset.tags || "").split("||").filter((t) => t);
|
||||
|
||||
if (isPinned) {
|
||||
if (!tags.includes("shaarli-pin")) tags.push("shaarli-pin");
|
||||
@ -4449,7 +4359,7 @@ function renderNotes(container, notes, viewMode, isArchiveMode = false) {
|
||||
body.innerHTML = renderMarkdown(note._noteMarkdown);
|
||||
} else {
|
||||
const textToRender = note.descText || "";
|
||||
if (textToRender.trim().startsWith('<div class="markdown">') || textToRender.trim().startsWith('<')) {
|
||||
if (textToRender.trim().startsWith('<div class="markdown">') || textToRender.trim().startsWith("<")) {
|
||||
body.innerHTML = textToRender;
|
||||
} else {
|
||||
body.innerHTML = renderMarkdown(textToRender);
|
||||
@ -4775,10 +4685,7 @@ async function saveOpenNoteEditorModal(modal) {
|
||||
return;
|
||||
}
|
||||
|
||||
const changed =
|
||||
state.hasChanges ||
|
||||
title !== (state.lastSavedTitle || "") ||
|
||||
markdown !== (state.lastSavedMarkdown || "");
|
||||
const changed = state.hasChanges || title !== (state.lastSavedTitle || "") || markdown !== (state.lastSavedMarkdown || "");
|
||||
|
||||
if (!changed || state.isSaving) return;
|
||||
|
||||
@ -5004,14 +4911,18 @@ window.setNoteFilter = function (noteId, filterKey, editUrl) {
|
||||
if (card) {
|
||||
const colorClass = Array.from(card.classList).find((cls) => cls.startsWith("note-color-"));
|
||||
const color = colorClass ? colorClass.replace("note-color-", "") : card.dataset.color || "default";
|
||||
applyNoteVisualState(card, { color, filter: normalizedFilterKey });
|
||||
const background = getElementVisualBackground(card);
|
||||
const fontColor = getElementVisualFontColor(card);
|
||||
applyNoteVisualState(card, { color, filter: normalizedFilterKey, background, fontColor });
|
||||
}
|
||||
|
||||
const bookmarkCard = document.querySelector(`.link-outer[data-id="${noteId}"]`);
|
||||
if (bookmarkCard) {
|
||||
const colorClass = Array.from(bookmarkCard.classList).find((cls) => cls.startsWith("note-color-"));
|
||||
const color = colorClass ? colorClass.replace("note-color-", "") : bookmarkCard.dataset.color || "default";
|
||||
applyNoteVisualState(bookmarkCard, { color, filter: normalizedFilterKey });
|
||||
const background = getElementVisualBackground(bookmarkCard);
|
||||
const fontColor = getElementVisualFontColor(bookmarkCard);
|
||||
applyNoteVisualState(bookmarkCard, { color, filter: normalizedFilterKey, background, fontColor });
|
||||
const palettePopup = bookmarkCard.querySelector(".bookmark-palette .palette-popup");
|
||||
if (palettePopup) {
|
||||
palettePopup.innerHTML = generateBookmarkPaletteButtons(noteId, editUrl, color, normalizedFilterKey);
|
||||
@ -5090,7 +5001,8 @@ window.setNoteBackground = function (noteId, backgroundKey, editUrl) {
|
||||
const colorClass = Array.from(card.classList).find((cls) => cls.startsWith("note-color-"));
|
||||
const color = colorClass ? colorClass.replace("note-color-", "") : card.dataset.color || "default";
|
||||
const filter = card.dataset.filter || "none";
|
||||
applyNoteVisualState(card, { color, filter, background: normalizedBackgroundKey });
|
||||
const fontColor = getElementVisualFontColor(card);
|
||||
applyNoteVisualState(card, { color, filter, background: normalizedBackgroundKey, fontColor });
|
||||
}
|
||||
|
||||
const bookmarkCard = document.querySelector(`.link-outer[data-id="${noteId}"]`);
|
||||
@ -5098,7 +5010,8 @@ window.setNoteBackground = function (noteId, backgroundKey, editUrl) {
|
||||
const colorClass = Array.from(bookmarkCard.classList).find((cls) => cls.startsWith("note-color-"));
|
||||
const color = colorClass ? colorClass.replace("note-color-", "") : bookmarkCard.dataset.color || "default";
|
||||
const filter = bookmarkCard.dataset.filter || "none";
|
||||
applyNoteVisualState(bookmarkCard, { color, filter, background: normalizedBackgroundKey });
|
||||
const fontColor = getElementVisualFontColor(bookmarkCard);
|
||||
applyNoteVisualState(bookmarkCard, { color, filter, background: normalizedBackgroundKey, fontColor });
|
||||
}
|
||||
|
||||
const modal = getOpenModalOverlay();
|
||||
@ -5611,17 +5524,16 @@ function openColorPickerPanel({ mode, entityId, editUrl, type }) {
|
||||
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;
|
||||
prevFontColorKey = (modalCard && modalCard.dataset.fontColor) ? modalCard.dataset.fontColor : "auto";
|
||||
} else {
|
||||
const noteCard = document.querySelector(`.note-card[data-id="${entityId}"]`);
|
||||
const bookmarkCard = document.querySelector(`.link-outer[data-id="${entityId}"]`);
|
||||
prevFontColorKey = (noteCard && noteCard.dataset.fontColor) ? noteCard.dataset.fontColor : ((bookmarkCard && bookmarkCard.dataset.fontColor) ? bookmarkCard.dataset.fontColor : "auto");
|
||||
}
|
||||
prevFontColorKey = bgPanel ? bgPanel.dataset.fontColor || "auto" : "auto";
|
||||
} else if ((mode || "entity") === "modal") {
|
||||
const modal = getOpenModalOverlay();
|
||||
const modalCard = modal ? modal.querySelector(".note-modal") : null;
|
||||
prevFontColorKey = modalCard && modalCard.dataset.fontColor ? modalCard.dataset.fontColor : "auto";
|
||||
} else {
|
||||
const noteCard = document.querySelector(`.note-card[data-id="${entityId}"]`);
|
||||
const bookmarkCard = document.querySelector(`.link-outer[data-id="${entityId}"]`);
|
||||
prevFontColorKey = noteCard && noteCard.dataset.fontColor ? noteCard.dataset.fontColor : bookmarkCard && bookmarkCard.dataset.fontColor ? bookmarkCard.dataset.fontColor : "auto";
|
||||
}
|
||||
panel.dataset.prevFontColorKey = prevFontColorKey;
|
||||
} else {
|
||||
panel.dataset.prevFontColorKey = "";
|
||||
@ -5634,25 +5546,24 @@ function openColorPickerPanel({ mode, entityId, editUrl, type }) {
|
||||
if (bgPanel) {
|
||||
prevColorKey = bgPanel.dataset.color || "default";
|
||||
}
|
||||
} else
|
||||
if ((mode || "entity") === "modal") {
|
||||
const modal = getOpenModalOverlay();
|
||||
const modalCard = modal ? modal.querySelector(".note-modal") : null;
|
||||
if (modalCard) {
|
||||
const isCustom = modalCard.dataset.color === "custom";
|
||||
const cc = modalCard.dataset.customColor || "";
|
||||
prevColorKey = isCustom && cc ? `custom:${cc}` : getElementVisualColor(modalCard);
|
||||
}
|
||||
} else {
|
||||
const noteCard = document.querySelector(`.note-card[data-id="${entityId}"]`);
|
||||
const bookmarkCard = document.querySelector(`.link-outer[data-id="${entityId}"]`);
|
||||
const el = noteCard || bookmarkCard;
|
||||
if (el) {
|
||||
const isCustom = el.dataset.color === "custom";
|
||||
const cc = el.dataset.customColor || "";
|
||||
prevColorKey = isCustom && cc ? `custom:${cc}` : getElementVisualColor(el);
|
||||
}
|
||||
} else if ((mode || "entity") === "modal") {
|
||||
const modal = getOpenModalOverlay();
|
||||
const modalCard = modal ? modal.querySelector(".note-modal") : null;
|
||||
if (modalCard) {
|
||||
const isCustom = modalCard.dataset.color === "custom";
|
||||
const cc = modalCard.dataset.customColor || "";
|
||||
prevColorKey = isCustom && cc ? `custom:${cc}` : getElementVisualColor(modalCard);
|
||||
}
|
||||
} else {
|
||||
const noteCard = document.querySelector(`.note-card[data-id="${entityId}"]`);
|
||||
const bookmarkCard = document.querySelector(`.link-outer[data-id="${entityId}"]`);
|
||||
const el = noteCard || bookmarkCard;
|
||||
if (el) {
|
||||
const isCustom = el.dataset.color === "custom";
|
||||
const cc = el.dataset.customColor || "";
|
||||
prevColorKey = isCustom && cc ? `custom:${cc}` : getElementVisualColor(el);
|
||||
}
|
||||
}
|
||||
panel.dataset.prevColorKey = prevColorKey;
|
||||
} else {
|
||||
panel.dataset.prevColorKey = "";
|
||||
@ -5706,14 +5617,36 @@ function openColorPickerPanel({ mode, entityId, editUrl, type }) {
|
||||
const m = v - c;
|
||||
let r, g, b;
|
||||
|
||||
if (h < 60) { r = c; g = x; b = 0; }
|
||||
else if (h < 120) { r = x; g = c; b = 0; }
|
||||
else if (h < 180) { r = 0; g = c; b = x; }
|
||||
else if (h < 240) { r = 0; g = x; b = c; }
|
||||
else if (h < 300) { r = x; g = 0; b = c; }
|
||||
else { r = c; g = 0; b = x; }
|
||||
if (h < 60) {
|
||||
r = c;
|
||||
g = x;
|
||||
b = 0;
|
||||
} else if (h < 120) {
|
||||
r = x;
|
||||
g = c;
|
||||
b = 0;
|
||||
} else if (h < 180) {
|
||||
r = 0;
|
||||
g = c;
|
||||
b = x;
|
||||
} else if (h < 240) {
|
||||
r = 0;
|
||||
g = x;
|
||||
b = c;
|
||||
} else if (h < 300) {
|
||||
r = x;
|
||||
g = 0;
|
||||
b = c;
|
||||
} else {
|
||||
r = c;
|
||||
g = 0;
|
||||
b = x;
|
||||
}
|
||||
|
||||
const toHex = (n) => Math.round((n + m) * 255).toString(16).padStart(2, "0");
|
||||
const toHex = (n) =>
|
||||
Math.round((n + m) * 255)
|
||||
.toString(16)
|
||||
.padStart(2, "0");
|
||||
return `#${toHex(r)}${toHex(g)}${toHex(b)}`.toUpperCase();
|
||||
}
|
||||
|
||||
@ -5731,7 +5664,7 @@ function openColorPickerPanel({ mode, entityId, editUrl, type }) {
|
||||
|
||||
let h = 0;
|
||||
if (d !== 0) {
|
||||
if (max === r) h = ((g - b) / d + 6) % 6 * 60;
|
||||
if (max === r) h = (((g - b) / d + 6) % 6) * 60;
|
||||
else if (max === g) h = ((b - r) / d + 2) * 60;
|
||||
else h = ((r - g) / d + 4) * 60;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user