feat: refactorer le système de palette de couleurs avec popup unifié, simplification du CSS pour les notes colorées et backgrounds, et amélioration du positionnement responsive de la palette

This commit is contained in:
Bruno Charest 2026-02-16 15:50:43 -05:00
parent 8f937c016d
commit 9ca8d345f6
56 changed files with 921 additions and 731 deletions

View File

@ -542,6 +542,100 @@ body.view-notes .content-container {
color: #e8eaed;
}
.bookmark-palette {
display: inline-flex;
align-items: center;
position: relative;
}
.bookmark-palette > button {
background: none;
border: none;
width: 34px;
height: 34px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: var(--text-light, #5f6368);
cursor: pointer;
font-size: 1.1rem;
transition: background-color 0.2s, color 0.2s;
}
[data-theme="dark"] .bookmark-palette > button {
color: #9aa0a6;
}
.palette-popup {
display: none;
position: absolute;
left: 0;
bottom: calc(100% + 8px);
background: var(--background-secondary, #ffffff);
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 12px;
padding: 10px;
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.18);
z-index: 50;
width: max-content;
max-width: min(340px, calc(100vw - 32px));
}
[data-theme="dark"] .palette-popup {
background: #202124;
border-color: rgba(255, 255, 255, 0.16);
}
.palette-popup.open {
display: block;
}
.palette-row {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.palette-row + .palette-row {
margin-top: 10px;
}
.palette-btn {
width: 24px;
height: 24px;
border-radius: 50%;
border: 1px solid rgba(0, 0, 0, 0.18);
cursor: pointer;
padding: 0;
background-position: center bottom;
background-size: cover;
}
[data-theme="dark"] .palette-btn {
border-color: rgba(255, 255, 255, 0.22);
}
.palette-btn.is-active {
outline: 2px solid var(--primary-color, #2563eb);
outline-offset: 2px;
}
.palette-btn-bg-none {
display: flex;
align-items: center;
justify-content: center;
}
.note-modal-color-picker {
position: relative;
}
.palette-popup.note-modal-palette {
bottom: auto;
top: calc(100% + 8px);
}
.spacer {
flex: 1;
}
@ -708,18 +802,12 @@ body.view-notes .content-container {
--note-card-fg: #2a2d31;
}
[data-theme="dark"] .note-card.note-color-grey {
background-color: #e8eaed;
border-color: transparent;
--note-card-fg: #2a2d31;
}
.note-card.note-has-bg,
.note-modal.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;
background-position: center bottom;
}
.note-card .note-title,
@ -734,642 +822,83 @@ body.view-notes .content-container {
color: var(--note-card-fg, #202124);
}
.link-outer[class*="note-color-"] {
color: var(--note-card-fg, #202124);
}
.link-outer.note-color-default {
background-color: #20293a;
border-color: transparent;
--note-card-fg: #dbe7ff;
}
[data-theme="dark"] .link-outer.note-color-default {
background-color: #20293a;
border-color: transparent;
--note-card-fg: #dbe7ff;
}
.link-outer.note-color-red {
background-color: #f28b82;
border-color: transparent;
--note-card-fg: #2f1714;
}
[data-theme="dark"] .link-outer.note-color-red {
background-color: #f28b82;
border-color: transparent;
--note-card-fg: #2f1714;
}
.link-outer.note-color-orange {
background-color: #fbbc04;
border-color: transparent;
--note-card-fg: #3d2a00;
}
[data-theme="dark"] .link-outer.note-color-orange {
background-color: #fbbc04;
border-color: transparent;
--note-card-fg: #3d2a00;
}
.link-outer.note-color-yellow {
background-color: #fff475;
border-color: transparent;
--note-card-fg: #383100;
}
[data-theme="dark"] .link-outer.note-color-yellow {
background-color: #fff475;
border-color: transparent;
--note-card-fg: #383100;
}
.link-outer.note-color-green {
background-color: #ccff90;
border-color: transparent;
--note-card-fg: #203400;
}
[data-theme="dark"] .link-outer.note-color-green {
background-color: #ccff90;
border-color: transparent;
--note-card-fg: #203400;
}
.link-outer.note-color-teal {
background-color: #a7ffeb;
border-color: transparent;
--note-card-fg: #08342d;
}
[data-theme="dark"] .link-outer.note-color-teal {
background-color: #a7ffeb;
border-color: transparent;
--note-card-fg: #08342d;
}
.link-outer.note-color-blue {
background-color: #cbf0f8;
border-color: transparent;
--note-card-fg: #113541;
}
[data-theme="dark"] .link-outer.note-color-blue {
background-color: #cbf0f8;
border-color: transparent;
--note-card-fg: #113541;
}
.link-outer.note-color-darkblue {
background-color: #aecbfa;
border-color: transparent;
--note-card-fg: #102645;
}
[data-theme="dark"] .link-outer.note-color-darkblue {
background-color: #aecbfa;
border-color: transparent;
--note-card-fg: #102645;
}
.link-outer.note-color-purple {
background-color: #d7aefb;
border-color: transparent;
--note-card-fg: #2f1845;
}
[data-theme="dark"] .link-outer.note-color-purple {
background-color: #d7aefb;
border-color: transparent;
--note-card-fg: #2f1845;
}
.link-outer.note-color-pink {
background-color: #fdcfe8;
border-color: transparent;
--note-card-fg: #4a1d34;
}
[data-theme="dark"] .link-outer.note-color-pink {
background-color: #fdcfe8;
border-color: transparent;
--note-card-fg: #4a1d34;
}
.link-outer.note-color-brown {
background-color: #e6c9a8;
border-color: transparent;
--note-card-fg: #3c2714;
}
[data-theme="dark"] .link-outer.note-color-brown {
background-color: #e6c9a8;
border-color: transparent;
--note-card-fg: #3c2714;
}
.link-outer.note-color-grey {
background-color: #e8eaed;
border-color: transparent;
--note-card-fg: #2a2d31;
}
[data-theme="dark"] .link-outer.note-color-grey {
background-color: #e8eaed;
border-color: transparent;
--note-card-fg: #2a2d31;
}
.link-outer[class*="note-color-"] .link-title,
.link-outer[class*="note-color-"] .link-url,
.link-outer[class*="note-color-"] .link-meta,
.link-outer[class*="note-color-"] .link-description,
.link-outer[class*="note-color-"] .link-tag a,
.link-outer[class*="note-color-"] .link-actions a,
.link-outer[class*="note-color-"] .link-actions button,
.link-outer[class*="note-color-"] .bookmark-palette > button {
color: inherit;
}
.link-actions .bookmark-palette {
display: flex;
align-items: center;
}
.link-actions .bookmark-palette > button {
background: none;
border: none;
width: 36px;
height: 36px;
border-radius: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
cursor: pointer;
}
.link-actions .bookmark-palette > button i {
font-size: 1.15rem;
}
.link-actions .bookmark-palette > button:hover {
background: rgba(255, 255, 255, 0.08);
}
.link-actions .bookmark-palette .palette-popup {
left: 0;
right: auto;
}
/* --- PALETTE POPUP --- */
.palette-popup {
position: absolute;
bottom: 100%;
left: 0;
width: min(500px, 92vw);
background: #1f232b;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
border-radius: 12px;
padding: 0;
z-index: 5000;
display: none;
/* JS toggles this */
}
[data-theme="dark"] .palette-popup {
background: #1f232b;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
border: 1px solid #5f6368;
}
.palette-popup.open {
display: block;
}
.palette-popup.open-down {
top: calc(100% + 8px);
bottom: auto;
}
.palette-btn {
width: 32px;
height: 32px;
min-width: 32px;
min-height: 32px;
padding: 0;
border-radius: 50%;
border: 1px solid rgba(255, 255, 255, 0.18);
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
position: relative;
flex: 0 0 auto;
background-position: center;
/* --- NOTES: Vue Masonry/Grid (multi-colonnes, largeur variable) --- */
.notes-masonry .note-card.note-has-bg {
/* Les cartes masonry ont des largeurs variables selon les breakpoints:
- >1200px: ~25% (4 colonnes)
- 800-1200px: ~33% (3 colonnes)
- 500-800px: ~50% (2 colonnes)
- <500px: 100% (1 colonne)
On utilise 'cover' avec un focus sur le centre pour garder l'essentiel visible */
background-size: cover;
background-position: center bottom;
/* Pour les petites cartes en masonry, on s'assure que l'image couvre bien */
min-height: 120px;
}
.palette-btn:hover {
border-color: rgba(255, 255, 255, 0.8);
}
[data-theme="dark"] .palette-btn {
border-color: rgba(255, 255, 255, 0.2);
}
.palette-btn.is-active {
outline: 2px solid #a855f7;
outline-offset: 1px;
}
.palette-row {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 12px;
overflow-x: auto;
flex-wrap: nowrap;
scrollbar-width: thin;
}
.palette-row::-webkit-scrollbar {
height: 6px;
}
.palette-row::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.24);
border-radius: 999px;
}
.palette-row+.palette-row {
border-top: 1px solid rgba(255, 255, 255, 0.14);
}
.palette-btn-default,
.palette-btn-bg-none {
background: #2b313c;
color: #d7dce5;
display: inline-flex;
align-items: center;
justify-content: center;
}
.palette-btn-default i,
.palette-btn-bg-none i {
font-size: 1rem;
}
/* Prevent link action generic button rules from deforming swatches in bookmark palette */
.link-actions .palette-popup .palette-btn {
width: 32px;
height: 32px;
min-width: 32px;
min-height: 32px;
border-radius: 50%;
padding: 0;
background: transparent;
border: 1px solid rgba(255, 255, 255, 0.18);
background-position: center;
/* --- NOTES: Vue Liste (600px max, centré) --- */
.notes-list-view .note-card.note-has-bg {
/* Vue liste: largeur fixe de 600px, cards plus larges qu'en masonry
On peut utiliser cover mais avec une position légèrement ajustée */
background-size: cover;
background-position: center bottom;
/* Les cards en liste sont souvent plus hautes, on adapte */
min-height: 100px;
}
.link-actions .palette-popup .palette-btn:hover {
background: transparent;
border-color: rgba(255, 255, 255, 0.8);
/* --- NOTES: Modal (min(720px, 92vw)) --- */
.note-modal.note-has-bg {
/* Modal plus large (jusqu'à 720px), on privilégie une couverture complète
avec une légère obscurcissement pour la lisibilité du texte */
background-size: cover;
background-position: center bottom;
/* L'overlay gradient est déjà présent dans la règle de base */
}
.link-outer.palette-open,
.view-list .link-outer.palette-open,
.view-compact .link-outer.palette-open {
overflow: visible;
z-index: 4000;
content-visibility: visible;
contain: none;
contain-intrinsic-size: auto;
/* --- BOOKMARKS: Vue Grid (minmax(320px, 1fr)) --- */
.view-grid .link-outer.note-has-bg {
/* Grid bookmarks: cards de minimum 320px, layout en grille
Les cards sont généralement carrées ou rectangulaires selon le contenu */
background-size: cover;
background-position: center bottom;
/* S'assurer que le background couvre bien la card avec le padding existant */
background-origin: padding-box;
}
/* --- MODAL FOR NOTE VIEW --- */
.note-modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
backdrop-filter: blur(2px);
opacity: 0;
pointer-events: none;
transition: opacity 0.2s;
/* --- BOOKMARKS: Vue Liste (1 colonne, thumbnail latéral) --- */
.view-list .link-outer.note-has-bg {
/* List view: cards en pleine largeur avec layout flex et thumbnail de 300px
On ajuste pour que le background couvre toute la hauteur malgré le thumbnail */
background-size: cover;
background-position: center bottom;
/* Le thumbnail a son propre mask gradient, on s'assure que le background
couvre bien toute la card derrière le contenu */
background-origin: padding-box;
}
.note-modal-overlay.open {
opacity: 1;
pointer-events: auto;
/* --- BOOKMARKS: Vue Compact (liste dense, pas de thumbnail) --- */
.view-compact .link-outer.note-has-bg {
/* Compact view: cards très compactes sans thumbnail, hauteur réduite (~60px)
On utilise contain ou une taille spécifique pour éviter que l'image
soit trop étirée sur une si petite hauteur */
background-size: 200% auto;
background-position: center bottom;
/* Réduire l'opacité du gradient pour plus de visibilité sur petites cards */
background-image: linear-gradient(rgba(0, 0, 0, 0.08), rgba(0, 0, 0, 0.08)), var(--note-bg-image);
}
.note-modal {
background: var(--modal-note-bg, #7a4b00);
color: var(--modal-note-fg, #fff2d9);
width: min(720px, 92vw);
max-height: 88vh;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.42);
position: relative;
display: flex;
flex-direction: column;
--note-scrollbar-thumb: rgba(255, 229, 170, 0.75);
--note-separator: rgba(255, 230, 182, 0.22);
--note-footer-bg: rgba(0, 0, 0, 0.14);
}
.note-modal.note-color-default {
--modal-note-bg: #20293a;
--modal-note-fg: #dbe7ff;
--note-scrollbar-thumb: rgba(219, 231, 255, 0.45);
--note-separator: rgba(219, 231, 255, 0.2);
--note-footer-bg: rgba(255, 255, 255, 0.08);
}
.note-modal.note-color-red {
--modal-note-bg: #f28b82;
--modal-note-fg: #2f1714;
--note-scrollbar-thumb: rgba(47, 23, 20, 0.45);
--note-separator: rgba(47, 23, 20, 0.2);
--note-footer-bg: rgba(255, 255, 255, 0.18);
}
.note-modal.note-color-orange {
--modal-note-bg: #fbbc04;
--modal-note-fg: #3d2a00;
--note-scrollbar-thumb: rgba(61, 42, 0, 0.45);
--note-separator: rgba(61, 42, 0, 0.2);
--note-footer-bg: rgba(255, 255, 255, 0.2);
}
.note-modal.note-color-yellow {
--modal-note-bg: #fff475;
--modal-note-fg: #383100;
--note-scrollbar-thumb: rgba(56, 49, 0, 0.45);
--note-separator: rgba(56, 49, 0, 0.2);
--note-footer-bg: rgba(255, 255, 255, 0.24);
}
.note-modal.note-color-green {
--modal-note-bg: #ccff90;
--modal-note-fg: #203400;
--note-scrollbar-thumb: rgba(32, 52, 0, 0.45);
--note-separator: rgba(32, 52, 0, 0.2);
--note-footer-bg: rgba(255, 255, 255, 0.22);
}
.note-modal.note-color-teal {
--modal-note-bg: #a7ffeb;
--modal-note-fg: #08342d;
--note-scrollbar-thumb: rgba(8, 52, 45, 0.45);
--note-separator: rgba(8, 52, 45, 0.2);
--note-footer-bg: rgba(255, 255, 255, 0.22);
}
.note-modal.note-color-blue {
--modal-note-bg: #cbf0f8;
--modal-note-fg: #113541;
--note-scrollbar-thumb: rgba(17, 53, 65, 0.45);
--note-separator: rgba(17, 53, 65, 0.2);
--note-footer-bg: rgba(255, 255, 255, 0.22);
}
.note-modal.note-color-darkblue {
--modal-note-bg: #aecbfa;
--modal-note-fg: #102645;
--note-scrollbar-thumb: rgba(16, 38, 69, 0.45);
--note-separator: rgba(16, 38, 69, 0.2);
--note-footer-bg: rgba(255, 255, 255, 0.2);
}
.note-modal.note-color-purple {
--modal-note-bg: #d7aefb;
--modal-note-fg: #2f1845;
--note-scrollbar-thumb: rgba(47, 24, 69, 0.45);
--note-separator: rgba(47, 24, 69, 0.2);
--note-footer-bg: rgba(255, 255, 255, 0.2);
}
.note-modal.note-color-pink {
--modal-note-bg: #fdcfe8;
--modal-note-fg: #4a1d34;
--note-scrollbar-thumb: rgba(74, 29, 52, 0.45);
--note-separator: rgba(74, 29, 52, 0.2);
--note-footer-bg: rgba(255, 255, 255, 0.2);
}
.note-modal.note-color-brown {
--modal-note-bg: #e6c9a8;
--modal-note-fg: #3c2714;
--note-scrollbar-thumb: rgba(60, 39, 20, 0.45);
--note-separator: rgba(60, 39, 20, 0.2);
--note-footer-bg: rgba(255, 255, 255, 0.2);
}
.note-modal.note-color-grey {
--modal-note-bg: #e8eaed;
--modal-note-fg: #2a2d31;
--note-scrollbar-thumb: rgba(42, 45, 49, 0.45);
--note-separator: rgba(42, 45, 49, 0.2);
--note-footer-bg: rgba(255, 255, 255, 0.2);
}
[data-theme="dark"] .note-modal {
border: 1px solid rgba(255, 255, 255, 0.12);
}
.note-modal-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
padding: 18px 20px 10px;
flex-shrink: 0;
}
.note-modal .note-title {
font-size: 1.6rem;
line-height: 1.35;
margin: 0;
color: inherit;
font-weight: 500;
}
.note-modal-pin-toggle {
background: transparent;
border: none;
color: inherit;
opacity: 0.85;
width: 34px;
height: 34px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
flex-shrink: 0;
transition: background-color 0.2s, opacity 0.2s;
}
.note-modal-pin-toggle i {
font-size: 1.2rem;
}
.note-modal-pin-toggle:hover,
.note-modal-pin-toggle.active {
opacity: 1;
background-color: rgba(255, 255, 255, 0.14);
}
.note-modal-content {
flex: 1;
overflow-y: auto;
padding: 0 20px 18px;
scrollbar-width: thin;
scrollbar-color: var(--note-scrollbar-thumb) transparent;
}
.note-modal-content::-webkit-scrollbar {
width: 10px;
}
.note-modal-content::-webkit-scrollbar-track {
background: transparent;
}
.note-modal-content::-webkit-scrollbar-thumb {
background: var(--note-scrollbar-thumb);
border-radius: 10px;
border: 2px solid transparent;
background-clip: content-box;
}
.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;
color: inherit;
}
.note-modal .note-body * {
color: inherit;
}
.note-modal-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
padding: 10px 20px 12px;
border-top: 1px solid var(--note-separator);
flex-shrink: 0;
}
.note-modal-tags.is-empty {
display: none;
}
.note-modal-tags .note-tag {
background: rgba(255, 255, 255, 0.12);
border: 1px solid rgba(255, 255, 255, 0.22);
color: inherit;
border-radius: 999px;
padding: 4px 10px;
font-size: 0.72rem;
letter-spacing: 0.02em;
}
.note-modal-actions {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 8px 12px;
border-top: 1px solid var(--note-separator);
background: var(--note-footer-bg);
flex-shrink: 0;
}
.note-modal-actions-left {
display: flex;
align-items: center;
gap: 2px;
flex-wrap: wrap;
}
.note-modal-actions-left > button,
.note-modal-actions-left > a,
.note-modal-actions-left > .note-modal-color-picker > 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;
text-decoration: none;
transition: background-color 0.2s, opacity 0.2s;
}
.note-modal-actions-left > button:hover,
.note-modal-actions-left > a:hover,
.note-modal-actions-left > .note-modal-color-picker > button:hover {
opacity: 1;
background: rgba(255, 255, 255, 0.14);
}
.note-modal-color-picker {
position: relative;
display: inline-flex;
}
.note-modal-palette {
left: -8px;
bottom: calc(100% + 8px);
width: min(500px, 92vw);
}
.note-modal-close-btn {
background: transparent;
border: none;
color: inherit;
font-weight: 600;
border-radius: 8px;
padding: 6px 10px;
cursor: pointer;
}
.note-modal-close-btn:hover {
background: rgba(255, 255, 255, 0.14);
}
@media (max-width: 640px) {
.note-modal {
width: 96vw;
max-height: 92vh;
/* Responsive: sur petits écrans, ajuster les backgrounds */
@media (max-width: 768px) {
/* En mobile, les cards sont plus étroites, on ajuste */
.notes-masonry .note-card.note-has-bg,
.view-grid .link-outer.note-has-bg {
background-size: cover;
background-position: center bottom;
}
.note-modal .note-title {
font-size: 1.25rem;
/* Vue liste en mobile: thumbnail passe en haut */
.view-list .link-outer.note-has-bg {
background-size: cover;
background-position: center bottom;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 MiB

View File

@ -1,34 +0,0 @@
# Note backgrounds (Shaarli Pro)
Ce dossier contient les images de fond utilisées par la palette des notes (`notebg-*`).
## Fichiers attendus
Le thème référence actuellement ces fichiers :
- `bg-canyon.png`
- `bg-leaves.png`
- `bg-shore.png`
- `bg-sunset.png`
- `bg-planet.png`
- `bg-crystal.png`
- `bg-orchid.png`
- `bg-lake.png`
- `bg-ladder.png`
- `bg-burst.png`
## Formats recommandés
- **SVG** (idéal pour motifs/illustrations légères)
- **JPG / JPEG** (photos)
- **PNG** (illustrations avec transparence)
- **WEBP** (poids réduit, recommandé)
## Conseils d'image
- Ratio conseillé: **16:9** ou **4:3**
- Taille conseillée: entre **1200x800** et **1920x1080**
- Poids conseillé: **< 500 KB** par image pour garder l'interface fluide
- Privilégier des visuels **peu chargés** pour conserver la lisibilité du texte
Si vous remplacez une image, conservez le même nom de fichier pour qu'elle apparaisse automatiquement dans la palette.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 793 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

View File

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 73 KiB

View File

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 130 KiB

View File

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 116 KiB

View File

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 154 KiB

View File

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 156 KiB

View File

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

View File

Before

Width:  |  Height:  |  Size: 111 KiB

After

Width:  |  Height:  |  Size: 111 KiB

View File

Before

Width:  |  Height:  |  Size: 147 KiB

After

Width:  |  Height:  |  Size: 147 KiB

View File

Before

Width:  |  Height:  |  Size: 185 KiB

After

Width:  |  Height:  |  Size: 185 KiB

View File

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 118 KiB

View File

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 130 KiB

View File

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

View File

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 139 KiB

View File

Before

Width:  |  Height:  |  Size: 137 KiB

After

Width:  |  Height:  |  Size: 137 KiB

View File

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View File

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 121 KiB

View File

Before

Width:  |  Height:  |  Size: 168 KiB

After

Width:  |  Height:  |  Size: 168 KiB

View File

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

Before

Width:  |  Height:  |  Size: 145 KiB

After

Width:  |  Height:  |  Size: 145 KiB

View File

Before

Width:  |  Height:  |  Size: 186 KiB

After

Width:  |  Height:  |  Size: 186 KiB

View File

Before

Width:  |  Height:  |  Size: 125 KiB

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

View File

@ -61,8 +61,8 @@ visibility: '{$visibility}',
untaggedonly: {if="$untaggedonly"}true{else}false{/if}
};
</script>
<script src="{$base_path}/{function="ltrim($asset_path, '/')"}/js/script.js?v=1.0.4" defer></script>
<script src="{$base_path}/{function="ltrim($asset_path, '/')"}/js/custom_views.js?v=1.0.4" defer></script>
<script src="{$base_path}/{function="ltrim($asset_path, '/')"}/js/script.js?v=1.0.5" defer></script>
<script src="{$base_path}/{function="ltrim($asset_path, '/')"}/js/custom_views.js?v=1.0.5" defer></script>
{if="file_exists('tpl/shaarli-pro/extra.html')"}
{include="extra"}

View File

@ -0,0 +1,146 @@
window.SHAARLI_NOTE_BACKGROUNDS_MANIFEST = [
{
key: "cafe",
label: "Café",
files: {
light: "bg-light-cafe.jpg",
dark: "bg-dark-cafe.jpg",
},
},
{
key: "codes",
label: "Codes",
files: {
light: "bg-light-codes.jpg",
dark: "bg-dark-codes.jpg",
},
},
{
key: "dune",
label: "Dune",
files: {
light: "bg-light-dune.jpg",
dark: "bg-dark-dune.jpg",
},
},
{
key: "feuille-ligne",
label: "Feuille Ligne",
files: {
light: "bg-light-feuilleLigne.jpg",
dark: "bg-dark-feuilleLigne.jpg",
},
},
{
key: "feuille-quadrillage",
label: "Feuille Quadrillage",
files: {
light: "bg-light-feuilleQuadrille.jpg",
dark: "bg-dark-feuilleQuadrille.jpg",
},
},
{
key: "fleurs",
label: "Fleurs",
files: {
light: "bg-light-fleurs.jpg",
dark: "bg-dark-fleurs.jpg",
},
},
{
key: "foret",
label: "Forêt",
files: {
light: "bg-light-foret.jpg",
dark: "bg-dark-foret.jpg",
},
},
{
key: "grid",
label: "Grid",
files: {
light: "bg-light-grid.jpg",
dark: "bg-dark-grid.jpg",
},
},
{
key: "journal",
label: "Journal",
files: {
light: "bg-light-journal.jpg",
dark: "bg-dark-journal.jpg",
},
},
{
key: "lecture",
label: "Lecture",
files: {
light: "bg-light-lecture.jpg",
dark: "bg-dark-lecture.jpg",
},
},
{
key: "legumes",
label: "Légumes",
files: {
light: "bg-light-legumes.jpg",
dark: "bg-dark-legumes.jpg",
},
},
{
key: "montagnes",
label: "Montagnes",
files: {
light: "bg-light-montagnes.jpg",
dark: "bg-dark-montagnes.jpg",
},
},
{
key: "ocean",
label: "Océan",
files: {
light: "bg-light-ocean.jpg",
dark: "bg-dark-ocean.jpg",
},
},
{
key: "sports",
label: "Sports",
files: {
light: "bg-light-sports.jpg",
dark: "bg-dark-sports.jpg",
},
},
{
key: "vague1",
label: "Vague 1",
files: {
light: "bg-light-vague1.jpg",
dark: "bg-dark-vague1.jpg",
},
},
{
key: "vague2",
label: "Vague 2",
files: {
light: "bg-light-vague2.jpg",
dark: "bg-dark-vague2.jpg",
},
},
{
key: "ville",
label: "Ville",
files: {
light: "bg-light-ville.jpg",
dark: "bg-dark-ville.jpg",
},
},
{
key: "voyage",
label: "Voyage",
files: {
light: "bg-light-voyage.jpg",
dark: "bg-dark-voyage.jpg",
},
},
];

View File

@ -0,0 +1,146 @@
[
{
"key": "cafe",
"label": "Café",
"files": {
"light": "bg-light-cafe.jpg",
"dark": "bg-dark-cafe.jpg"
}
},
{
"key": "codes",
"label": "Codes",
"files": {
"light": "bg-light-codes.jpg",
"dark": "bg-dark-codes.jpg"
}
},
{
"key": "dune",
"label": "Dune",
"files": {
"light": "bg-light-dune.jpg",
"dark": "bg-dark-dune.jpg"
}
},
{
"key": "feuille-ligne",
"label": "Feuille Ligne",
"files": {
"light": "bg-light-feuilleLigne.jpg",
"dark": "bg-dark-feuilleLigne.jpg"
}
},
{
"key": "feuille-quadrillage",
"label": "Feuille Quadrillage",
"files": {
"light": "bg-light-feuilleQuadrille.jpg",
"dark": "bg-dark-feuilleQuadrille.jpg"
}
},
{
"key": "fleurs",
"label": "Fleurs",
"files": {
"light": "bg-light-fleurs.jpg",
"dark": "bg-dark-fleurs.jpg"
}
},
{
"key": "foret",
"label": "Forêt",
"files": {
"light": "bg-light-foret.jpg",
"dark": "bg-dark-foret.jpg"
}
},
{
"key": "grid",
"label": "Grid",
"files": {
"light": "bg-light-grid.jpg",
"dark": "bg-dark-grid.jpg"
}
},
{
"key": "journal",
"label": "Journal",
"files": {
"light": "bg-light-journal.jpg",
"dark": "bg-dark-journal.jpg"
}
},
{
"key": "lecture",
"label": "Lecture",
"files": {
"light": "bg-light-lecture.jpg",
"dark": "bg-dark-lecture.jpg"
}
},
{
"key": "legumes",
"label": "Légumes",
"files": {
"light": "bg-light-legumes.jpg",
"dark": "bg-dark-legumes.jpg"
}
},
{
"key": "montagnes",
"label": "Montagnes",
"files": {
"light": "bg-light-montagnes.jpg",
"dark": "bg-dark-montagnes.jpg"
}
},
{
"key": "ocean",
"label": "Océan",
"files": {
"light": "bg-light-ocean.jpg",
"dark": "bg-dark-ocean.jpg"
}
},
{
"key": "sports",
"label": "Sports",
"files": {
"light": "bg-light-sports.jpg",
"dark": "bg-dark-sports.jpg"
}
},
{
"key": "vague1",
"label": "Vague 1",
"files": {
"light": "bg-light-vague1.jpg",
"dark": "bg-dark-vague1.jpg"
}
},
{
"key": "vague2",
"label": "Vague 2",
"files": {
"light": "bg-light-vague2.jpg",
"dark": "bg-dark-vague2.jpg"
}
},
{
"key": "ville",
"label": "Ville",
"files": {
"light": "bg-light-ville.jpg",
"dark": "bg-dark-ville.jpg"
}
},
{
"key": "voyage",
"label": "Voyage",
"files": {
"light": "bg-light-voyage.jpg",
"dark": "bg-dark-voyage.jpg"
}
}
]

View File

@ -6,6 +6,7 @@ document.addEventListener("DOMContentLoaded", function () {
const linkList = document.getElementById("links-list");
const container = document.querySelector(".content-container");
const startViewInitialization = function () {
if (typeof initBookmarkPaletteButtons === "function") {
initBookmarkPaletteButtons();
}
@ -23,21 +24,99 @@ document.addEventListener("DOMContentLoaded", function () {
} else if (searchTags === "note") {
initNoteView(linkList, container);
}
};
// Charger les backgrounds dynamiquement, puis initialiser les vues
initThemeModeBackgroundSync();
if (typeof loadBackgroundOptions === "function") {
loadBackgroundOptions()
.then(() => {
startViewInitialization();
})
.catch((err) => {
console.error("Erreur lors du chargement des backgrounds:", err);
// Initialiser quand même même si le chargement échoue
startViewInitialization();
});
} else {
startViewInitialization();
}
});
const NOTE_COLOR_OPTIONS = [
{ key: "default", label: "Par défaut", hex: "#20293A" },
{ key: "red", label: "Rouge", hex: "#f28b82" },
{ key: "orange", label: "Orange", hex: "#fbbc04" },
{ key: "yellow", label: "Jaune", hex: "#fff475" },
{ key: "green", label: "Vert", hex: "#ccff90" },
{ key: "teal", label: "Menthe", hex: "#a7ffeb" },
{ key: "blue", label: "Bleu clair", hex: "#cbf0f8" },
{ key: "darkblue", label: "Bleu", hex: "#aecbfa" },
{ key: "purple", label: "Violet", hex: "#d7aefb" },
{ key: "pink", label: "Rose", hex: "#fdcfe8" },
{ key: "brown", label: "Beige", hex: "#e6c9a8" },
{ key: "grey", label: "Gris", hex: "#e8eaed" },
{
key: "default",
label: "Par défaut",
light: "#ffffff",
dark: "#20293A"
},
{
key: "red",
label: "Rouge",
light: "#f28b82",
dark: "#9c2116"
},
{
key: "orange",
label: "Orange",
light: "#fbbc04",
dark: "#9c7a16"
},
{
key: "yellow",
label: "Jaune",
light: "#fff475",
dark: "#9c9116"
},
{
key: "green",
label: "Vert",
light: "#ccff90",
dark: "#5e9c16"
},
{
key: "teal",
label: "Menthe",
light: "#a7ffeb",
dark: "#169c7d"
},
{
key: "blue",
label: "Bleu clair",
light: "#cbf0f8",
dark: "#16849c"
},
{
key: "darkblue",
label: "Bleu",
light: "#aecbfa",
dark: "#16499c"
},
{
key: "purple",
label: "Violet",
light: "#d7aefb",
dark: "#5d169c"
},
{
key: "pink",
label: "Rose",
light: "#fdcfe8",
dark: "#9c165f"
},
{
key: "brown",
label: "Beige",
light: "#e6c9a8",
dark: "#9c5d16"
},
{
key: "grey",
label: "Gris",
light: "#e8eaed",
dark: "#4e5764"
}
];
const NOTE_BACKGROUND_TAG_PREFIX = "notebg-";
@ -68,25 +147,325 @@ function resolveThemeAssetBasePath() {
return "/tpl/shaarli-pro";
}
const NOTE_BACKGROUND_BASE_PATH = `${resolveThemeAssetBasePath().replace(/\/$/, "")}/img/note-backgrounds`;
const NOTE_BACKGROUND_OPTIONS = [
{ key: "bg-canyon", label: "Canyon", file: "bg-canyon.png" },
{ key: "bg-leaves", label: "Feuilles", file: "bg-leaves.png" },
{ key: "bg-shore", label: "Rivage", file: "bg-shore.png" },
{ key: "bg-sunset", label: "Coucher", file: "bg-sunset.png" },
{ key: "bg-planet", label: "Planète", file: "bg-planet.png" },
{ key: "bg-crystal", label: "Cristal", file: "bg-crystal.png" },
{ key: "bg-orchid", label: "Orchidée", file: "bg-orchid.png" },
{ key: "bg-lake", label: "Lac", file: "bg-lake.png" },
{ key: "bg-ladder", label: "Échelle", file: "bg-ladder.png" },
{ key: "bg-burst", label: "Étoile", file: "bg-burst.png" },
const NOTE_BACKGROUND_ASSET_ROOT = `${resolveThemeAssetBasePath().replace(/\/$/, "")}/img`;
// Liste des backgrounds - chargée dynamiquement depuis le serveur
let NOTE_BACKGROUND_OPTIONS = [];
let isBackgroundOptionsLoaded = false;
let backgroundOptionsLoadPromise = null;
window.__shaarliCustomViewsVersion = "1.0.5-inline-bg-manifest";
if (typeof console !== "undefined" && console && typeof console.info === "function") {
console.info("[custom_views] loaded", window.__shaarliCustomViewsVersion);
}
const NOTE_BACKGROUND_MANIFEST_INLINE = [
{ key: "cafe", label: "Café", files: { light: "bg-light-cafe.jpg", dark: "bg-dark-cafe.jpg" } },
{ key: "codes", label: "Codes", files: { light: "bg-light-codes.jpg", dark: "bg-dark-codes.jpg" } },
{ key: "dune", label: "Dune", files: { light: "bg-light-dune.jpg", dark: "bg-dark-dune.jpg" } },
{ key: "feuille-ligne", label: "Feuille Ligne", files: { light: "bg-light-feuilleLigne.jpg", dark: "bg-dark-feuilleLigne.jpg" } },
{ key: "feuille-quadrillage", label: "Feuille Quadrillage", files: { light: "bg-light-feuilleQuadrille.jpg", dark: "bg-dark-feuilleQuadrille.jpg" } },
{ key: "fleurs", label: "Fleurs", files: { light: "bg-light-fleurs.jpg", dark: "bg-dark-fleurs.jpg" } },
{ key: "foret", label: "Forêt", files: { light: "bg-light-foret.jpg", dark: "bg-dark-foret.jpg" } },
{ key: "grid", label: "Grid", files: { light: "bg-light-grid.jpg", dark: "bg-dark-grid.jpg" } },
{ key: "journal", label: "Journal", files: { light: "bg-light-journal.jpg", dark: "bg-dark-journal.jpg" } },
{ key: "lecture", label: "Lecture", files: { light: "bg-light-lecture.jpg", dark: "bg-dark-lecture.jpg" } },
{ key: "legumes", label: "Légumes", files: { light: "bg-light-legumes.jpg", dark: "bg-dark-legumes.jpg" } },
{ key: "montagnes", label: "Montagnes", files: { light: "bg-light-montagnes.jpg", dark: "bg-dark-montagnes.jpg" } },
{ key: "ocean", label: "Océan", files: { light: "bg-light-ocean.jpg", dark: "bg-dark-ocean.jpg" } },
{ key: "sports", label: "Sports", files: { light: "bg-light-sports.jpg", dark: "bg-dark-sports.jpg" } },
{ key: "vague1", label: "Vague 1", files: { light: "bg-light-vague1.jpg", dark: "bg-dark-vague1.jpg" } },
{ key: "vague2", label: "Vague 2", files: { light: "bg-light-vague2.jpg", dark: "bg-dark-vague2.jpg" } },
{ key: "ville", label: "Ville", files: { light: "bg-light-ville.jpg", dark: "bg-dark-ville.jpg" } },
{ key: "voyage", label: "Voyage", files: { light: "bg-light-voyage.jpg", dark: "bg-dark-voyage.jpg" } },
];
function getNoteBackgroundUrl(backgroundKey) {
const found = NOTE_BACKGROUND_OPTIONS.find((bg) => bg.key === backgroundKey);
if (!found) return "";
/**
* Charge la liste des backgrounds depuis le endpoint PHP
* @returns {Promise<Array>} Liste des backgrounds
*/
function loadBackgroundOptions() {
if (isBackgroundOptionsLoaded) {
return Promise.resolve(NOTE_BACKGROUND_OPTIONS);
}
return `${NOTE_BACKGROUND_BASE_PATH}/${found.file}`;
if (backgroundOptionsLoadPromise) {
return backgroundOptionsLoadPromise;
}
NOTE_BACKGROUND_OPTIONS = normalizeDynamicBackgroundOptions(NOTE_BACKGROUND_MANIFEST_INLINE);
isBackgroundOptionsLoaded = true;
backgroundOptionsLoadPromise = Promise.resolve(NOTE_BACKGROUND_OPTIONS);
return backgroundOptionsLoadPromise;
}
function normalizeBackgroundKey(key) {
if (typeof key !== "string") return "";
return key
.trim()
.toLowerCase()
.replace(/[\s_]+/g, "-")
.replace(/[^a-z0-9-]/g, "-")
.replace(/-+/g, "-")
.replace(/^-|-$/g, "");
}
function formatBackgroundLabel(rawLabel, fallbackKey) {
const base = (typeof rawLabel === "string" && rawLabel.trim() !== "" ? rawLabel : fallbackKey)
.replace(/[_-]+/g, " ")
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
.replace(/\s+/g, " ")
.trim();
return base
.split(" ")
.filter((part) => part !== "")
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join(" ");
}
function getCurrentThemeMode() {
return document.documentElement.getAttribute("data-theme") === "dark" ? "dark" : "light";
}
function getColorOption(colorKey) {
const normalizedKey = typeof colorKey === "string" ? colorKey.trim().toLowerCase() : "";
return NOTE_COLOR_OPTIONS.find((opt) => opt.key === normalizedKey) || NOTE_COLOR_OPTIONS.find((opt) => opt.key === "default") || null;
}
function getThemeColorValue(option, mode = getCurrentThemeMode()) {
if (!option || typeof option !== "object") return "";
const safeMode = mode === "dark" ? "dark" : "light";
const preferred = typeof option[safeMode] === "string" ? option[safeMode].trim() : "";
const fallbackLight = typeof option.light === "string" ? option.light.trim() : "";
const fallbackDark = typeof option.dark === "string" ? option.dark.trim() : "";
return preferred || fallbackLight || fallbackDark;
}
function parseHexColor(colorValue) {
if (typeof colorValue !== "string") return null;
const safeValue = colorValue.trim();
const shortHexMatch = /^#([0-9a-f]{3})$/i.exec(safeValue);
if (shortHexMatch) {
return {
r: parseInt(shortHexMatch[1].charAt(0).repeat(2), 16),
g: parseInt(shortHexMatch[1].charAt(1).repeat(2), 16),
b: parseInt(shortHexMatch[1].charAt(2).repeat(2), 16),
};
}
const longHexMatch = /^#([0-9a-f]{6})$/i.exec(safeValue);
if (longHexMatch) {
return {
r: parseInt(longHexMatch[1].slice(0, 2), 16),
g: parseInt(longHexMatch[1].slice(2, 4), 16),
b: parseInt(longHexMatch[1].slice(4, 6), 16),
};
}
return null;
}
function getReadableForegroundForBackground(colorValue, mode = getCurrentThemeMode()) {
const rgb = parseHexColor(colorValue);
if (!rgb) {
return mode === "dark" ? "#e8eaed" : "#202124";
}
const luminance = (0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b) / 255;
return luminance > 0.56 ? "#202124" : "#f5f7fb";
}
function normalizeDynamicBackgroundOptions(rawOptions) {
const rawList = Array.isArray(rawOptions)
? rawOptions
: rawOptions && Array.isArray(rawOptions.backgrounds)
? rawOptions.backgrounds
: [];
const mergedByKey = {};
rawList.forEach((option) => {
if (!option || typeof option !== "object") return;
const key = normalizeBackgroundKey(typeof option.key === "string" ? option.key : option.label || "");
if (!key) return;
const files = option.files && typeof option.files === "object" ? option.files : {};
const lightFile = typeof files.light === "string" ? files.light.trim() : "";
const darkFile = typeof files.dark === "string" ? files.dark.trim() : "";
if (!lightFile && !darkFile) return;
if (!mergedByKey[key]) {
mergedByKey[key] = {
key,
label: formatBackgroundLabel(option.label || "", key),
paths: {
light: "",
dark: "",
},
};
}
if (lightFile) {
mergedByKey[key].paths.light = `note-bg-light/${lightFile}`;
}
if (darkFile) {
mergedByKey[key].paths.dark = `note-bg-dark/${darkFile}`;
}
});
return Object.values(mergedByKey).sort((a, b) => a.label.localeCompare(b.label, "fr", { sensitivity: "base" }));
}
function getAvailableBackgroundOptionsForMode(mode = getCurrentThemeMode()) {
const safeMode = mode === "dark" ? "dark" : "light";
return NOTE_BACKGROUND_OPTIONS.filter((option) => option.paths && option.paths[safeMode]);
}
function getNoteBackgroundUrl(backgroundKey, mode = getCurrentThemeMode()) {
const normalizedKey = normalizeBackgroundKey(backgroundKey);
if (!normalizedKey) return "";
const found = NOTE_BACKGROUND_OPTIONS.find((bg) => bg.key === normalizedKey);
if (!found || !found.paths) return "";
const safeMode = mode === "dark" ? "dark" : "light";
const selectedPath = found.paths[safeMode] || "";
if (!selectedPath) return "";
return `${NOTE_BACKGROUND_ASSET_ROOT}/${selectedPath}`;
}
function getElementVisualColor(element) {
if (!element) return "default";
const colorClass = Array.from(element.classList).find((cls) => cls.startsWith("note-color-"));
if (colorClass) {
return colorClass.replace("note-color-", "") || "default";
}
return element.dataset.color || "default";
}
function getElementVisualBackground(element) {
if (!element) return "none";
const normalizedBackground = normalizeBackgroundKey(element.dataset.background || "");
return normalizedBackground || "none";
}
function refreshNoteBackgroundVisuals() {
document.querySelectorAll(".note-card, .note-modal, .link-outer").forEach((element) => {
applyNoteVisualState(element, {
color: getElementVisualColor(element),
background: getElementVisualBackground(element),
});
});
}
function refreshBackgroundPalettes() {
document.querySelectorAll(".note-card").forEach((card) => {
const palettePopup = card.querySelector(".note-hover-actions .palette-popup");
if (!palettePopup) return;
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"]',
);
palettePopup.innerHTML = generatePaletteButtons({
id: noteId,
editUrl: editLink && editLink.href ? editLink.href : "#",
color: getElementVisualColor(card),
background: getElementVisualBackground(card),
});
});
document.querySelectorAll(".link-outer").forEach((card) => {
const palettePopup = card.querySelector(".bookmark-palette .palette-popup");
if (!palettePopup) return;
const noteId = card.dataset.id || card.getAttribute("data-id") || card.id;
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;
palettePopup.innerHTML = generateBookmarkPaletteButtons(
noteId,
editLink && editLink.href ? editLink.href : "#",
getElementVisualColor(card),
getElementVisualBackground(card),
);
});
const modal = document.querySelector(".note-modal-overlay");
if (!modal || !modal.currentNote) return;
const modalCard = modal.querySelector(".note-modal");
if (modalCard) {
modal.currentNote.color = getElementVisualColor(modalCard);
modal.currentNote.background = getElementVisualBackground(modalCard);
}
const modalColorPopup = modal.querySelector("#note-modal-color-popup");
if (modalColorPopup) {
modalColorPopup.innerHTML = generateModalPaletteButtons(modal.currentNote);
}
}
function parseBackgroundManifestPayload(rawPayload) {
const payload = typeof rawPayload === "string" ? rawPayload.trim() : "";
if (!payload) return [];
try {
return JSON.parse(payload);
} catch (parseError) {
const firstArrayBracket = payload.indexOf("[");
const lastArrayBracket = payload.lastIndexOf("]");
if (firstArrayBracket === -1 || lastArrayBracket === -1 || lastArrayBracket <= firstArrayBracket) {
throw parseError;
}
const jsonSlice = payload.slice(firstArrayBracket, lastArrayBracket + 1);
return JSON.parse(jsonSlice);
}
}
let isThemeModeBackgroundSyncInitialized = false;
function initThemeModeBackgroundSync() {
if (isThemeModeBackgroundSyncInitialized) return;
const root = document.documentElement;
if (!root) return;
let lastTheme = getCurrentThemeMode();
const observer = new MutationObserver((mutations) => {
const themeChanged = mutations.some((mutation) => mutation.attributeName === "data-theme");
if (!themeChanged) return;
const nextTheme = getCurrentThemeMode();
if (nextTheme === lastTheme) return;
lastTheme = nextTheme;
refreshNoteBackgroundVisuals();
refreshBackgroundPalettes();
});
observer.observe(root, { attributes: true, attributeFilter: ["data-theme"] });
isThemeModeBackgroundSyncInitialized = true;
}
function positionPalettePopup(popup) {
@ -123,14 +502,29 @@ function positionPalettePopup(popup) {
function applyNoteVisualState(element, note) {
if (!element || !note) return;
const color = note.color || "default";
const background = note.background || "none";
const resolvedColorOption = getColorOption(note.color || "default");
const color = resolvedColorOption ? resolvedColorOption.key : "default";
const colorValue = getThemeColorValue(resolvedColorOption);
const foregroundColor = getReadableForegroundForBackground(colorValue);
const normalizedBackground = normalizeBackgroundKey(note.background || "");
const background = normalizedBackground || "none";
element.classList.forEach((cls) => {
if (cls.startsWith("note-color-")) element.classList.remove(cls);
});
element.classList.add(`note-color-${color}`);
if (colorValue) {
element.style.backgroundColor = colorValue;
element.style.borderColor = "transparent";
} else {
element.style.removeProperty("background-color");
}
if (foregroundColor) {
element.style.setProperty("--note-card-fg", foregroundColor);
}
if (background && background !== "none") {
const bgUrl = getNoteBackgroundUrl(background);
if (bgUrl) {
@ -166,8 +560,8 @@ function extractNoteVisualStateFromTags(tags) {
let background = "none";
const foundBgTag = safeTags.find((t) => typeof t === "string" && t.startsWith(NOTE_BACKGROUND_TAG_PREFIX));
if (foundBgTag) {
const candidate = foundBgTag.substring(NOTE_BACKGROUND_TAG_PREFIX.length);
if (candidate && NOTE_BACKGROUND_OPTIONS.some((bg) => bg.key === candidate)) {
const candidate = normalizeBackgroundKey(foundBgTag.substring(NOTE_BACKGROUND_TAG_PREFIX.length));
if (candidate) {
background = candidate;
}
}
@ -246,19 +640,21 @@ function initBookmarkPaletteButtons() {
function generateBookmarkPaletteButtons(bookmarkId, editUrl, currentColor, currentBackground) {
const color = currentColor || "default";
const background = currentBackground || "none";
const background = normalizeBackgroundKey(currentBackground || "") || "none";
const colorButtons = [
`<button class="palette-btn palette-btn-default ${color === "default" ? "is-active" : ""}" title="Par défaut" onclick="setNoteColor('${bookmarkId}', 'default', '${editUrl}')"><i class="mdi mdi-format-color-reset"></i></button>`,
...NOTE_COLOR_OPTIONS.filter((opt) => opt.key !== "default").map(
(opt) =>
`<button class="palette-btn note-color-${opt.key} ${color === opt.key ? "is-active" : ""}" title="${opt.label}" onclick="setNoteColor('${bookmarkId}', '${opt.key}', '${editUrl}')" style="background-color:${opt.hex}"></button>`,
(opt) => {
const swatchColor = getThemeColorValue(opt);
return `<button class="palette-btn note-color-${opt.key} ${color === opt.key ? "is-active" : ""}" title="${opt.label}" onclick="setNoteColor('${bookmarkId}', '${opt.key}', '${editUrl}')" style="background-color:${swatchColor}"></button>`;
},
),
].join("");
const backgroundButtons = [
`<button class="palette-btn palette-btn-bg-none ${background === "none" ? "is-active" : ""}" title="Sans image" onclick="setNoteBackground('${bookmarkId}', 'none', '${editUrl}')"><i class="mdi mdi-image-off-outline"></i></button>`,
...NOTE_BACKGROUND_OPTIONS.map((bg) => {
...getAvailableBackgroundOptionsForMode().map((bg) => {
const bgUrl = getNoteBackgroundUrl(bg.key);
return `<button class="palette-btn palette-btn-bg ${background === bg.key ? "is-active" : ""}" title="${bg.label}" onclick="setNoteBackground('${bookmarkId}', '${bg.key}', '${editUrl}')" style="background-image:url('${bgUrl}')"></button>`;
}),
@ -754,7 +1150,7 @@ function parseNoteFromLink(linkEl) {
tags.push(t);
}
} else if (t.startsWith(NOTE_BACKGROUND_TAG_PREFIX)) {
background = t.substring(NOTE_BACKGROUND_TAG_PREFIX.length) || "none";
background = normalizeBackgroundKey(t.substring(NOTE_BACKGROUND_TAG_PREFIX.length)) || "none";
} else {
tags.push(t);
}
@ -970,19 +1366,21 @@ function openNoteModal(note) {
function generateModalPaletteButtons(note) {
const currentColor = note.color || "default";
const currentBackground = note.background || "none";
const currentBackground = normalizeBackgroundKey(note.background || "") || "none";
const colorButtons = [
`<button class="palette-btn palette-btn-default ${currentColor === "default" ? "is-active" : ""}" title="Par défaut" onclick="setModalNoteColor('default')"><i class="mdi mdi-format-color-reset"></i></button>`,
...NOTE_COLOR_OPTIONS.filter((opt) => opt.key !== "default").map(
(opt) =>
`<button class="palette-btn note-color-${opt.key} ${currentColor === opt.key ? "is-active" : ""}" title="${opt.label}" onclick="setModalNoteColor('${opt.key}')" style="background-color:${opt.hex}"></button>`,
(opt) => {
const swatchColor = getThemeColorValue(opt);
return `<button class="palette-btn note-color-${opt.key} ${currentColor === opt.key ? "is-active" : ""}" title="${opt.label}" onclick="setModalNoteColor('${opt.key}')" style="background-color:${swatchColor}"></button>`;
},
),
].join("");
const backgroundButtons = [
`<button class="palette-btn palette-btn-bg-none ${currentBackground === "none" ? "is-active" : ""}" title="Sans image" onclick="setModalNoteBackground('none')"><i class="mdi mdi-image-off-outline"></i></button>`,
...NOTE_BACKGROUND_OPTIONS.map((bg) => {
...getAvailableBackgroundOptionsForMode().map((bg) => {
const bgUrl = getNoteBackgroundUrl(bg.key);
return `<button class="palette-btn palette-btn-bg ${currentBackground === bg.key ? "is-active" : ""}" title="${bg.label}" onclick="setModalNoteBackground('${bg.key}')" style="background-image:url('${bgUrl}')"></button>`;
}),
@ -1020,9 +1418,10 @@ window.setModalNoteBackground = function (backgroundKey) {
if (!modal || !modal.currentNote) return;
const currentNote = modal.currentNote;
setNoteBackground(currentNote.id, backgroundKey, currentNote.editUrl);
const normalizedBackgroundKey = backgroundKey === "none" ? "none" : normalizeBackgroundKey(backgroundKey) || "none";
setNoteBackground(currentNote.id, normalizedBackgroundKey, currentNote.editUrl);
currentNote.background = backgroundKey;
currentNote.background = normalizedBackgroundKey;
const modalCard = modal.querySelector(".note-modal");
if (modalCard) {
applyNoteVisualState(modalCard, currentNote);
@ -1038,19 +1437,21 @@ window.setModalNoteBackground = function (backgroundKey) {
function generatePaletteButtons(note) {
const currentColor = note.color || "default";
const currentBackground = note.background || "none";
const currentBackground = normalizeBackgroundKey(note.background || "") || "none";
const colorButtons = [
`<button class="palette-btn palette-btn-default ${currentColor === "default" ? "is-active" : ""}" title="Par défaut" onclick="setNoteColor('${note.id}', 'default', '${note.editUrl}')"><i class="mdi mdi-format-color-reset"></i></button>`,
...NOTE_COLOR_OPTIONS.filter((opt) => opt.key !== "default").map(
(opt) =>
`<button class="palette-btn note-color-${opt.key} ${currentColor === opt.key ? "is-active" : ""}" title="${opt.label}" onclick="setNoteColor('${note.id}', '${opt.key}', '${note.editUrl}')" style="background-color:${opt.hex}"></button>`,
(opt) => {
const swatchColor = getThemeColorValue(opt);
return `<button class="palette-btn note-color-${opt.key} ${currentColor === opt.key ? "is-active" : ""}" title="${opt.label}" onclick="setNoteColor('${note.id}', '${opt.key}', '${note.editUrl}')" style="background-color:${swatchColor}"></button>`;
},
),
].join("");
const backgroundButtons = [
`<button class="palette-btn palette-btn-bg-none ${currentBackground === "none" ? "is-active" : ""}" title="Sans image" onclick="setNoteBackground('${note.id}', 'none', '${note.editUrl}')"><i class="mdi mdi-image-off-outline"></i></button>`,
...NOTE_BACKGROUND_OPTIONS.map((bg) => {
...getAvailableBackgroundOptionsForMode().map((bg) => {
const bgUrl = getNoteBackgroundUrl(bg.key);
return `<button class="palette-btn palette-btn-bg ${currentBackground === bg.key ? "is-active" : ""}" title="${bg.label}" onclick="setNoteBackground('${note.id}', '${bg.key}', '${note.editUrl}')" style="background-image:url('${bgUrl}')"></button>`;
}),
@ -1157,27 +1558,29 @@ window.setNoteColor = function (noteId, color, editUrl) {
};
window.setNoteBackground = function (noteId, backgroundKey, editUrl) {
const normalizedBackgroundKey = backgroundKey === "none" ? "none" : normalizeBackgroundKey(backgroundKey) || "none";
const card = document.querySelector(`.note-card[data-id="${noteId}"]`);
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, background: backgroundKey });
applyNoteVisualState(card, { color, background: normalizedBackgroundKey });
}
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, background: backgroundKey });
applyNoteVisualState(bookmarkCard, { color, background: normalizedBackgroundKey });
const palettePopup = bookmarkCard.querySelector(".bookmark-palette .palette-popup");
if (palettePopup) {
palettePopup.innerHTML = generateBookmarkPaletteButtons(noteId, editUrl, color, backgroundKey);
palettePopup.innerHTML = generateBookmarkPaletteButtons(noteId, editUrl, color, normalizedBackgroundKey);
}
}
const modal = document.querySelector(".note-modal-overlay");
if (modal && modal.currentNote && String(modal.currentNote.id) === String(noteId)) {
modal.currentNote.background = backgroundKey;
modal.currentNote.background = normalizedBackgroundKey;
const modalCard = modal.querySelector(".note-modal");
if (modalCard) {
applyNoteVisualState(modalCard, modal.currentNote);
@ -1212,8 +1615,8 @@ window.setNoteBackground = function (noteId, backgroundKey, editUrl) {
let tagsArray = currentTags.split(/[\s,]+/).filter((t) => t.trim() !== "");
tagsArray = tagsArray.filter((t) => !t.startsWith(NOTE_BACKGROUND_TAG_PREFIX));
if (backgroundKey && backgroundKey !== "none") {
tagsArray.push(`${NOTE_BACKGROUND_TAG_PREFIX}${backgroundKey}`);
if (normalizedBackgroundKey && normalizedBackgroundKey !== "none") {
tagsArray.push(`${NOTE_BACKGROUND_TAG_PREFIX}${normalizedBackgroundKey}`);
}
formData.set("lf_tags", tagsArray.join(" "));