diff --git a/shaarli-pro/css/custom_views.css b/shaarli-pro/css/custom_views.css
index 9cb648f..86c3be9 100644
--- a/shaarli-pro/css/custom_views.css
+++ b/shaarli-pro/css/custom_views.css
@@ -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));
}
\ No newline at end of file
diff --git a/shaarli-pro/js/custom_views.js b/shaarli-pro/js/custom_views.js
index 462dfe4..92dfbc0 100644
--- a/shaarli-pro/js/custom_views.js
+++ b/shaarli-pro/js/custom_views.js
@@ -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 = `Erreur Custom Views: ${msg}
${err ? err.stack || err : ''}`;
+ banner.innerHTML = `Erreur Custom Views: ${msg}
${err ? err.stack || err : ""}`;
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, ">");
+ const escape = (value) => String(value).replace(/&/g, "&").replace(//g, ">");
- const escapeAttr = (value) =>
- String(value)
- .replace(/&/g, "&")
- .replace(/"/g, """)
- .replace(//g, ">");
+ const escapeAttr = (value) => String(value).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(
- ``
- + ``
- + `${renderInline(todoMatch[2])}`
- + "",
- );
+ html.push(`` + `` + `${renderInline(todoMatch[2])}` + "");
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, "
");
+ return String(markdown || "").replace(/\n/g, "
");
}
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 ``;
- })
- .join("") +
+ const colorsRowHtml =
+ colors
+ .filter((c) => c.key !== "default" && c.key !== "custom")
+ .map((c) => {
+ const isActive = color === c.key;
+ const hex = c.color || "";
+ return ``;
+ })
+ .join("") +
// Add custom color button (opens color picker)
`