diff --git a/shaarli-pro/css/custom_views.css b/shaarli-pro/css/custom_views.css
index 31b9f6d..0c05dc4 100644
--- a/shaarli-pro/css/custom_views.css
+++ b/shaarli-pro/css/custom_views.css
@@ -19,7 +19,7 @@ body.view-todo .content-container {
}
[data-theme="dark"] body.view-todo .content-container {
- background-color: var(--bg-body);
+ background-color: #20293a;
}
body.view-todo #linklist {
@@ -105,7 +105,7 @@ body.view-todo #linklist {
}
[data-theme="dark"] .todo-main {
- background-color: #0f172a;
+ background-color: #20293a;
}
.todo-main-header {
@@ -212,14 +212,16 @@ body.view-todo #linklist {
/* --- NOTES VIEW --- */
/* Wrapper */
-body.view-notes .content-container {
+body.view-notes .content-container,
+body.view-archive .content-container {
padding: 2rem;
background-color: var(--bg-body);
min-height: 100vh;
}
-[data-theme="dark"] body.view-notes .content-container {
- background-color: var(--bg-body);
+[data-theme="dark"] body.view-notes .content-container,
+[data-theme="dark"] body.view-archive .content-container {
+ background-color: #20293a;
}
/* Tool Bar / Input Area */
@@ -233,7 +235,7 @@ body.view-notes .content-container {
}
.notes-top-bar-inner {
- width: 600px;
+ width: 800px;
max-width: 100%;
display: flex;
align-items: flex-start;
@@ -241,10 +243,11 @@ body.view-notes .content-container {
}
.note-input-container {
- width: 600px;
+ width: 800px;
max-width: 100%;
background-color: var(--background-secondary, #ffffff);
border-radius: 8px;
+ border: 1px solid #000;
box-shadow: 0 1px 2px 0 rgba(60, 64, 67, 0.3), 0 2px 6px 2px rgba(60, 64, 67, 0.15);
transition: box-shadow 0.2s;
overflow: hidden;
@@ -255,9 +258,9 @@ body.view-notes .content-container {
}
[data-theme="dark"] .note-input-container {
- background-color: #202124;
- border: none;
- box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.6), 0 2px 6px 2px rgba(0, 0, 0, 0.4);
+ background-color: #20293a;
+ border: 1px solid var(--border);
+ box-shadow: none;
}
.note-input-collapsed {
@@ -268,6 +271,16 @@ body.view-notes .content-container {
color: var(--note-card-fg, var(--text-light, #80868b));
font-weight: 500;
font-size: 1rem;
+ padding: 12px 16px;
+ cursor: text;
+ color: var(--note-card-fg, var(--text-light, #80868b));
+ font-weight: 500;
+ font-size: 1rem;
+ padding: 12px 16px;
+ cursor: text;
+ color: var(--note-card-fg, var(--text-light, #80868b));
+ font-weight: 500;
+ font-size: 1rem;
}
.note-input-container.is-editing {
@@ -327,6 +340,14 @@ body.view-notes .content-container {
.todo-draft-row .todo-item-text {
padding: 5px 4px;
font-size: 0.95rem;
+ background-color: transparent;
+ border: none !important;
+ outline: none !important;
+ box-shadow: none !important;
+}
+
+[data-theme="dark"] .todo-draft-row .todo-item-text {
+ background-color: transparent;
}
.todo-draft-add-btn {
@@ -355,26 +376,32 @@ body.view-notes .content-container {
.note-input-title {
width: 100%;
- border: 0;
- outline: none;
- background: transparent;
+ border: none !important;
+ outline: none !important;
+ box-shadow: none !important;
+ background-color: transparent;
color: inherit;
font-size: 1.05rem;
font-weight: 500;
padding: 0;
}
+[data-theme="dark"] .note-input-title {
+ background-color: transparent;
+}
+
.note-input-description-source {
width: 100%;
- border: none;
- background: transparent;
+ border: none !important;
+ outline: none !important;
+ box-shadow: none !important;
+ background-color: transparent;
color: inherit;
padding: 0;
resize: none;
min-height: 120px;
max-height: 44vh;
overflow: auto;
- outline: none;
display: block;
font-size: 0.95rem;
line-height: 1.5;
@@ -385,7 +412,8 @@ body.view-notes .content-container {
}
[data-theme="dark"] .note-input-description-source {
- border: none;
+ border: none !important;
+ background-color: transparent;
}
.note-input-container.is-enhanced .note-input-description-source {
@@ -419,8 +447,15 @@ body.view-notes .content-container {
}
@keyframes fadeIn {
- from { opacity: 0; transform: translateY(-4px); }
- to { opacity: 1; transform: translateY(0); }
+ from {
+ opacity: 0;
+ transform: translateY(-4px);
+ }
+
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
}
.note-format-btn {
@@ -476,7 +511,7 @@ body.view-notes .content-container {
flex-wrap: wrap;
}
-.note-input-actions-left > button {
+.note-input-actions-left>button {
background: transparent;
border: none;
color: inherit;
@@ -491,17 +526,17 @@ body.view-notes .content-container {
padding: 0;
}
-.note-input-actions-left > button:disabled {
+.note-input-actions-left>button:disabled {
cursor: default;
opacity: 0.35;
}
-.note-input-actions-left > button:hover:not(:disabled) {
+.note-input-actions-left>button:hover:not(:disabled) {
opacity: 1;
background: rgba(0, 0, 0, 0.08);
}
-[data-theme="dark"] .note-input-actions-left > button:hover:not(:disabled) {
+[data-theme="dark"] .note-input-actions-left>button:hover:not(:disabled) {
background: rgba(255, 255, 255, 0.14);
}
@@ -619,15 +654,63 @@ body.view-notes .content-container {
column-span: none;
border-radius: 12px;
overflow: hidden;
+ border: 1px solid #000;
}
.note-card {
border-radius: 12px;
overflow: hidden;
+ border: 1px solid #000;
+}
+
+[data-theme="dark"] .note-card {
+ border-color: #dedfe2;
}
.note-card .note-inner {
+ position: relative;
padding: 14px 16px 12px;
+ padding-right: 58px;
+}
+
+.note-pin-corner {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ width: auto;
+ height: auto;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ text-decoration: none;
+ color: var(--note-card-fg, currentColor);
+ background: transparent;
+ border: none;
+ z-index: 4;
+ opacity: 0.92;
+ transition: transform 0.15s ease, opacity 0.15s ease, color 0.15s ease;
+}
+
+.note-pin-corner i {
+ font-size: 1.35rem;
+}
+
+.note-pin-corner:hover {
+ transform: scale(1.06);
+ opacity: 1;
+}
+
+.note-pin-corner.active {
+ color: var(--note-card-fg, currentColor);
+}
+
+[data-theme="dark"] .note-pin-corner {
+ background: transparent;
+ border: none;
+}
+
+[data-theme="dark"] .note-pin-corner.active {
+ color: var(--note-card-fg, currentColor);
}
@media (max-width: 1200px) {
@@ -683,7 +766,7 @@ body.view-notes .content-container {
overflow: hidden;
border-radius: 8px;
background: var(--background-secondary, #ffffff);
- border: 1px solid rgba(0, 0, 0, 0.12);
+ border: 1px solid #000;
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.22);
color: inherit;
display: flex;
@@ -696,8 +779,8 @@ body.note-modal-open {
[data-theme="dark"] .note-modal {
background: #202124;
- border-color: transparent;
- box-shadow: 0 14px 28px rgba(0,0,0,0.5), 0 10px 10px rgba(0,0,0,0.4);
+ border-color: #dedfe2;
+ box-shadow: 0 14px 28px rgba(0, 0, 0, 0.5), 0 10px 10px rgba(0, 0, 0, 0.4);
}
.note-modal-header {
@@ -859,9 +942,9 @@ body.note-modal-open {
flex-wrap: wrap;
}
-.note-modal-actions-left > button,
-.note-modal-actions-left > a,
-.note-modal-actions-left > .note-modal-color-picker > button {
+.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;
@@ -876,16 +959,16 @@ body.note-modal-open {
text-decoration: none;
}
-.note-modal-actions-left > button:hover,
-.note-modal-actions-left > a:hover,
-.note-modal-actions-left > .note-modal-color-picker > button:hover {
+.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(0, 0, 0, 0.08);
}
-[data-theme="dark"] .note-modal-actions-left > button:hover,
-[data-theme="dark"] .note-modal-actions-left > a:hover,
-[data-theme="dark"] .note-modal-actions-left > .note-modal-color-picker > button:hover {
+[data-theme="dark"] .note-modal-actions-left>button:hover,
+[data-theme="dark"] .note-modal-actions-left>a:hover,
+[data-theme="dark"] .note-modal-actions-left>.note-modal-color-picker>button:hover {
background: rgba(255, 255, 255, 0.14);
}
@@ -1163,9 +1246,9 @@ body.note-modal-open {
}
}
-.note-hover-actions > button,
-.note-hover-actions > a,
-.note-hover-actions > div > button {
+.note-hover-actions>button,
+.note-hover-actions>a,
+.note-hover-actions>div>button {
background: transparent;
border: none;
width: 32px;
@@ -1182,17 +1265,17 @@ body.note-modal-open {
opacity: 0.7;
}
-.note-hover-actions > button:hover,
-.note-hover-actions > a:hover,
-.note-hover-actions > div > button:hover {
+.note-hover-actions>button:hover,
+.note-hover-actions>a:hover,
+.note-hover-actions>div>button:hover {
background-color: rgba(0, 0, 0, 0.08);
color: inherit;
opacity: 1;
}
-[data-theme="dark"] .note-hover-actions > button:hover,
-[data-theme="dark"] .note-hover-actions > a:hover,
-[data-theme="dark"] .note-hover-actions > div > button:hover {
+[data-theme="dark"] .note-hover-actions>button:hover,
+[data-theme="dark"] .note-hover-actions>a:hover,
+[data-theme="dark"] .note-hover-actions>div>button:hover {
background-color: rgba(255, 255, 255, 0.14);
color: inherit;
}
@@ -1204,7 +1287,7 @@ body.note-modal-open {
color: var(--text-muted);
}
-.bookmark-palette > button {
+.bookmark-palette>button {
background: none;
border: none;
width: 36px;
@@ -1219,7 +1302,7 @@ body.note-modal-open {
transition: background-color 0.2s, color 0.2s;
}
-.bookmark-palette > button:hover {
+.bookmark-palette>button:hover {
background: var(--primary-light);
color: var(--primary);
}
@@ -1316,7 +1399,7 @@ body.note-modal-open {
justify-content: start;
}
-.palette-row + .palette-row {
+.palette-row+.palette-row {
margin-top: 10px;
}
@@ -1499,7 +1582,7 @@ body.note-modal-open {
}
.bg-studio-thumb-solid {
- background: linear-gradient(135deg, rgba(255,255,255,0.15), rgba(255,255,255,0.02));
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0.02));
}
.bg-studio-tile {
@@ -1740,14 +1823,14 @@ body.note-modal-open {
/* Reference: Keep Colors */
/* Default */
.note-card.note-color-default {
- background-color: #20293a;
- border-color: transparent;
- --note-card-fg: #dbe7ff;
+ background-color: var(--bg-body);
+ border-color: #000;
+ --note-card-fg: var(--text-main, #111827);
}
[data-theme="dark"] .note-card.note-color-default {
background-color: #20293a;
- border-color: transparent;
+ border-color: #dedfe2;
--note-card-fg: #dbe7ff;
}
@@ -1883,25 +1966,25 @@ body.note-modal-open {
/* Grey */
.note-card.note-color-grey {
-background-color: #e8eaed;
-border-color: transparent;
---note-card-fg: #2a2d31;
+ background-color: #e8eaed;
+ border-color: transparent;
+ --note-card-fg: #2a2d31;
}
.note-card.note-has-bg,
.note-modal.note-has-bg,
.note-input-container.note-has-bg,
.link-outer.note-has-bg {
-background-image: linear-gradient(rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0.16)), var(--note-bg-image);
-background-size: cover;
-background-position: center bottom;
+ background-image: linear-gradient(rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0.16)), var(--note-bg-image);
+ background-size: cover;
+ background-position: center bottom;
}
.note-card.todo-card.note-has-bg[data-font-color="auto"] {
---note-card-fg: rgba(255, 255, 255, 0.92);
-color: var(--note-card-fg);
-background-image: linear-gradient(rgba(0, 0, 0, 0.42), rgba(0, 0, 0, 0.42)), var(--note-bg-image);
-text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55);
+ --note-card-fg: rgba(255, 255, 255, 0.92);
+ color: var(--note-card-fg);
+ background-image: linear-gradient(rgba(0, 0, 0, 0.42), rgba(0, 0, 0, 0.42)), var(--note-bg-image);
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55);
background-image: linear-gradient(rgba(0, 0, 0, 0.42), rgba(0, 0, 0, 0.42)), var(--note-bg-image);
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55);
}
@@ -1916,6 +1999,11 @@ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55);
.note-card[class*="note-color-"] {
color: var(--note-card-fg, #202124);
+ border: 1px solid #000;
+}
+
+[data-theme="dark"] .note-card[class*="note-color-"] {
+ border: 1px solid #dedfe2;
}
.link-outer {
@@ -1994,13 +2082,14 @@ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55);
/* 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;
}
-
+
/* Vue liste en mobile: thumbnail passe en haut */
.view-list .link-outer.note-has-bg {
background-size: cover;
@@ -2052,21 +2141,17 @@ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55);
.note-card.note-filter-lined,
.note-modal.note-filter-lined,
.link-outer.note-filter-lined {
- background-image: repeating-linear-gradient(
- transparent,
- transparent 29px,
- rgba(0, 0, 0, 0.1) 30px
- ) !important;
+ background-image: repeating-linear-gradient(transparent,
+ transparent 29px,
+ rgba(0, 0, 0, 0.1) 30px) !important;
}
[data-theme="dark"] .note-card.note-filter-lined,
[data-theme="dark"] .note-modal.note-filter-lined,
[data-theme="dark"] .link-outer.note-filter-lined {
- background-image: repeating-linear-gradient(
- transparent,
- transparent 29px,
- rgba(255, 255, 255, 0.1) 30px
- ) !important;
+ background-image: repeating-linear-gradient(transparent,
+ transparent 29px,
+ rgba(255, 255, 255, 0.1) 30px) !important;
}
/* 4. Quadrillé - Grid */
@@ -2126,25 +2211,21 @@ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55);
.note-card.note-filter-stripes,
.note-modal.note-filter-stripes,
.link-outer.note-filter-stripes {
- background-image: repeating-linear-gradient(
- 45deg,
- transparent,
- transparent 10px,
- rgba(0, 0, 0, 0.05) 10px,
- rgba(0, 0, 0, 0.05) 20px
- ) !important;
+ background-image: repeating-linear-gradient(45deg,
+ transparent,
+ transparent 10px,
+ rgba(0, 0, 0, 0.05) 10px,
+ rgba(0, 0, 0, 0.05) 20px) !important;
}
[data-theme="dark"] .note-card.note-filter-stripes,
[data-theme="dark"] .note-modal.note-filter-stripes,
[data-theme="dark"] .link-outer.note-filter-stripes {
- background-image: repeating-linear-gradient(
- 45deg,
- transparent,
- transparent 10px,
- rgba(255, 255, 255, 0.05) 10px,
- rgba(255, 255, 255, 0.05) 20px
- ) !important;
+ background-image: repeating-linear-gradient(45deg,
+ transparent,
+ transparent 10px,
+ rgba(255, 255, 255, 0.05) 10px,
+ rgba(255, 255, 255, 0.05) 20px) !important;
}
/* =========================================
@@ -2284,7 +2365,7 @@ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55);
height: 16px;
border: 2px solid white;
border-radius: 50%;
- box-shadow: 0 2px 6px rgba(0,0,0,0.4), inset 0 0 0 1px rgba(0,0,0,0.2);
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4), inset 0 0 0 1px rgba(0, 0, 0, 0.2);
transform: translate(-50%, -50%);
pointer-events: none;
}
@@ -2312,7 +2393,7 @@ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55);
border-radius: 50%;
background: white;
border: 2px solid rgba(15, 23, 42, 0.3);
- box-shadow: 0 2px 6px rgba(0,0,0,0.3);
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
cursor: pointer;
}
@@ -2322,7 +2403,7 @@ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55);
border-radius: 50%;
background: white;
border: 2px solid rgba(15, 23, 42, 0.3);
- box-shadow: 0 2px 6px rgba(0,0,0,0.3);
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
cursor: pointer;
}
@@ -2478,12 +2559,12 @@ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55);
.bg-studio-swatch:hover {
transform: scale(1.12);
- box-shadow: 0 6px 16px rgba(0,0,0,0.3);
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.3);
}
.bg-studio-swatch.is-active {
border: 2px solid rgba(255, 255, 255, 0.95);
- box-shadow: 0 4px 12px rgba(0,0,0,0.3);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.bg-studio-swatch-custom {
@@ -2521,7 +2602,12 @@ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55);
/* Custom note color style */
.note-card.note-color-custom,
.note-modal.note-color-custom {
- border-color: transparent;
+ border-color: #000;
+}
+
+[data-theme="dark"] .note-card.note-color-custom,
+[data-theme="dark"] .note-modal.note-color-custom {
+ border-color: #dedfe2;
}
/* Ensure Font section is properly sized */
@@ -2533,14 +2619,13 @@ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55);
}
/* --- ARCHIVE VIEW --- */
+/* Ensure archive view uses same background as notes view */
body.view-archive .content-container {
- padding: 2rem;
- background-color: var(--bg-body);
- min-height: 100vh;
+ background-color: var(--bg-body) !important;
}
[data-theme="dark"] body.view-archive .content-container {
- background-color: var(--bg-body);
+ background-color: #20293a !important;
}
/* Archive Title */
@@ -2579,25 +2664,20 @@ body.view-archive .content-container {
color: #9aa0a6;
}
-/* Archive wrapper */
-.archive-wrapper {
- max-width: 1200px;
- margin: 0 auto;
-}
-
/* Archive top bar adjustments */
.archive-top-bar {
flex-direction: column;
align-items: center;
padding-right: 0;
gap: 1rem;
+ position: relative;
}
.archive-top-bar .notes-tools {
- position: relative;
- right: auto;
- top: auto;
- transform: none;
+ position: absolute;
+ right: 1rem;
+ top: 50%;
+ transform: translateY(-50%);
}
body.view-todo .note-card.todo-card .note-body {
@@ -2747,12 +2827,12 @@ body.view-todo .note-card.todo-card .todo-checklist-preview-wrap {
opacity: 0.75;
}
-.todo-item-checkbox:checked + .todo-checklist-box-ui {
+.todo-item-checkbox:checked+.todo-checklist-box-ui {
background: currentColor;
box-shadow: inset 0 0 0 3px rgba(0, 0, 0, 0.22);
}
-[data-theme="dark"] .todo-item-checkbox:checked + .todo-checklist-box-ui {
+[data-theme="dark"] .todo-item-checkbox:checked+.todo-checklist-box-ui {
box-shadow: inset 0 0 0 3px rgba(0, 0, 0, 0.35);
}
diff --git a/shaarli-pro/css/style.css b/shaarli-pro/css/style.css
index 93743fa..d03db30 100644
--- a/shaarli-pro/css/style.css
+++ b/shaarli-pro/css/style.css
@@ -325,6 +325,7 @@ a:focus:not(:focus-visible) {
.sidebar-add-segment span {
display: none;
}
+
.sidebar-add-segment {
padding: 0.75rem;
}
@@ -3246,6 +3247,7 @@ select:focus {
.page-edit .toastui-editor-mode-switch {
background: var(--bookmark-input-bg);
}
+
.page-edit .toastui-editor-defaultUI {
border: 0;
}
@@ -3266,6 +3268,10 @@ select:focus {
background-color: rgba(126, 168, 255, 0.16);
}
+.page-edit .toastui-editor-toolbar-margin {
+ margin-bottom: 0.5rem;
+}
+
.page-edit .toastui-editor-toolbar-icons {
opacity: 0.96;
}
@@ -3283,6 +3289,148 @@ select:focus {
border-top: 1px solid var(--bookmark-input-border);
}
+[data-theme="dark"] .page-add .toastui-editor-defaultUI,
+[data-theme="dark"] .page-add .toastui-editor-md-container,
+[data-theme="dark"] .page-add .toastui-editor-ww-container,
+[data-theme="dark"] .page-edit .toastui-editor-defaultUI,
+[data-theme="dark"] .page-edit .toastui-editor-md-container,
+[data-theme="dark"] .page-edit .toastui-editor-ww-container {
+ background: var(--toastui-panel-surface, #0a1429) !important;
+ color: var(--bookmark-text-main, var(--text-main)) !important;
+ border-color: var(--toastui-border, #1f3560) !important;
+}
+
+[data-theme="dark"] .page-add .toastui-editor-toolbar,
+[data-theme="dark"] .page-add .toastui-editor-mode-switch,
+[data-theme="dark"] .page-edit .toastui-editor-toolbar,
+[data-theme="dark"] .page-edit .toastui-editor-mode-switch {
+ background: var(--toastui-toolbar-bg, linear-gradient(180deg, rgba(28, 49, 86, 0.95) 0%, rgba(14, 26, 47, 0.96) 100%)) !important;
+ border-color: var(--toastui-border, #1f3560) !important;
+}
+
+[data-theme="dark"] .page-add .toastui-editor-toolbar,
+[data-theme="dark"] .page-edit .toastui-editor-toolbar {
+ border-bottom: 1px solid var(--toastui-border, #1f3560) !important;
+ box-shadow: inset 0 -1px 0 rgba(9, 12, 23, 0.6) !important;
+}
+
+[data-theme="dark"] .page-add .toastui-editor-md-tab-container,
+[data-theme="dark"] .page-add .toastui-editor-mode-switch,
+[data-theme="dark"] .page-edit .toastui-editor-md-tab-container,
+[data-theme="dark"] .page-edit .toastui-editor-mode-switch {
+ border-top: 1px solid var(--toastui-border, #1f3560) !important;
+}
+
+[data-theme="dark"] .page-add .toastui-editor-mode-switch .tab-item,
+[data-theme="dark"] .page-edit .toastui-editor-mode-switch .tab-item {
+ background: transparent !important;
+ color: var(--bookmark-text-muted, var(--text-secondary)) !important;
+ border-radius: 6px;
+ padding: 0.45rem 0.8rem;
+ transition: background-color 0.2s ease, color 0.2s ease;
+}
+
+[data-theme="dark"] .page-add .toastui-editor-mode-switch .tab-item.active,
+[data-theme="dark"] .page-add .toastui-editor-mode-switch .tab-item.selected,
+[data-theme="dark"] .page-edit .toastui-editor-mode-switch .tab-item.active,
+[data-theme="dark"] .page-edit .toastui-editor-mode-switch .tab-item.selected {
+ background: var(--toastui-tab-hover, rgba(112, 160, 255, 0.24)) !important;
+ color: var(--bookmark-text-main, var(--text-main)) !important;
+ box-shadow: 0 4px 18px rgba(23, 40, 72, 0.55);
+}
+
+[data-theme="dark"] .page-add .toastui-editor-toolbar button,
+[data-theme="dark"] .page-edit .toastui-editor-toolbar button {
+ color: #e2e9ff !important;
+}
+
+[data-theme="dark"] .page-add .toastui-editor-toolbar button:hover,
+[data-theme="dark"] .page-add .toastui-editor-toolbar button.active,
+[data-theme="dark"] .page-add .toastui-editor-toolbar button:focus-visible,
+[data-theme="dark"] .page-edit .toastui-editor-toolbar button:hover,
+[data-theme="dark"] .page-edit .toastui-editor-toolbar button.active,
+[data-theme="dark"] .page-edit .toastui-editor-toolbar button:focus-visible {
+ background-color: rgba(118, 164, 255, 0.26) !important;
+}
+
+[data-theme="dark"] .page-add .toastui-editor-toolbar button:hover .toastui-editor-toolbar-icons,
+[data-theme="dark"] .page-add .toastui-editor-toolbar button.active .toastui-editor-toolbar-icons,
+[data-theme="dark"] .page-add .toastui-editor-toolbar button:focus-visible .toastui-editor-toolbar-icons,
+[data-theme="dark"] .page-edit .toastui-editor-toolbar button:hover .toastui-editor-toolbar-icons,
+[data-theme="dark"] .page-edit .toastui-editor-toolbar button.active .toastui-editor-toolbar-icons,
+[data-theme="dark"] .page-edit .toastui-editor-toolbar button:focus-visible .toastui-editor-toolbar-icons {
+ opacity: 1 !important;
+}
+
+[data-theme="dark"] .page-add .toastui-editor-toolbar-icons,
+[data-theme="dark"] .page-edit .toastui-editor-toolbar-icons {
+ opacity: 0.85 !important;
+ filter: none !important;
+}
+
+[data-theme="dark"] .page-add .toastui-editor-toolbar-icons::before,
+[data-theme="dark"] .page-add .toastui-editor-toolbar-icons::after,
+[data-theme="dark"] .page-edit .toastui-editor-toolbar-icons::before,
+[data-theme="dark"] .page-edit .toastui-editor-toolbar-icons::after {
+ filter: none !important;
+}
+
+[data-theme="dark"] .page-add .toastui-editor-toolbar button.toastui-editor-toolbar-icons,
+[data-theme="dark"] .page-edit .toastui-editor-toolbar button.toastui-editor-toolbar-icons {
+ filter: none !important;
+}
+
+[data-theme="dark"] .page-add .toastui-editor-toolbar button svg,
+[data-theme="dark"] .page-add .toastui-editor-toolbar button svg *,
+[data-theme="dark"] .page-edit .toastui-editor-toolbar button svg,
+[data-theme="dark"] .page-edit .toastui-editor-toolbar button svg * {
+ fill: rgba(255, 255, 255, 0.85) !important;
+ stroke: rgba(255, 255, 255, 0.85) !important;
+ opacity: 1 !important;
+}
+
+[data-theme="dark"] .page-add .toastui-editor-toolbar-divider,
+[data-theme="dark"] .page-edit .toastui-editor-toolbar-divider {
+ background-color: rgba(255, 255, 255, 0.12) !important;
+}
+
+[data-theme="dark"] .page-add .toastui-editor-toolbar *,
+[data-theme="dark"] .page-edit .toastui-editor-toolbar * {
+ background-color: transparent;
+}
+
+[data-theme="dark"] .page-add .toastui-editor-toolbar-group,
+[data-theme="dark"] .page-edit .toastui-editor-toolbar-group {
+ background: transparent !important;
+ border-color: rgba(255, 255, 255, 0.12) !important;
+ box-shadow: none !important;
+}
+
+[data-theme="dark"] .page-add .toastui-editor-toolbar-group:not(:last-child),
+[data-theme="dark"] .page-edit .toastui-editor-toolbar-group:not(:last-child) {
+ border-right-color: rgba(255, 255, 255, 0.12) !important;
+}
+
+[data-theme="dark"] .page-add .toastui-editor-toolbar-item,
+[data-theme="dark"] .page-edit .toastui-editor-toolbar-item {
+ background-color: transparent !important;
+ border-color: transparent !important;
+ box-shadow: none !important;
+}
+
+[data-theme="dark"] .page-add .toastui-editor-toolbar button,
+[data-theme="dark"] .page-edit .toastui-editor-toolbar button {
+ background-color: transparent !important;
+ border-color: transparent !important;
+}
+
+[data-theme="dark"] .page-add .toastui-editor-contents,
+[data-theme="dark"] .page-add .toastui-editor-contents *,
+[data-theme="dark"] .page-edit .toastui-editor-contents,
+[data-theme="dark"] .page-edit .toastui-editor-contents * {
+ color: var(--bookmark-text-main, var(--text-main)) !important;
+}
+
.page-edit .bookmark-tags-input {
min-height: 48px;
display: flex;
@@ -3534,6 +3682,11 @@ select:focus {
--bookmark-tag-remove-bg: rgba(255, 255, 255, 0.16);
--bookmark-tag-remove-bg-hover: rgba(255, 255, 255, 0.26);
--bookmark-save-bg: linear-gradient(135deg, #6fa8ff 0%, #4d82f0 100%);
+ --toastui-panel-bg: #101d38;
+ --toastui-panel-surface: #0a1429;
+ --toastui-toolbar-bg: linear-gradient(180deg, rgba(28, 49, 86, 0.95) 0%, rgba(14, 26, 47, 0.96) 100%);
+ --toastui-border: #1f3560;
+ --toastui-tab-hover: rgba(112, 160, 255, 0.24);
}
.page-edit .bookmark-editor-card {
@@ -4018,7 +4171,7 @@ select:focus {
margin-right: 0;
}
- .row > [class*="col-"] {
+ .row>[class*="col-"] {
width: 100%;
max-width: 100%;
float: none;
@@ -4577,4 +4730,4 @@ table {
.search-results {
max-height: calc(90vh - 200px);
}
-}
+}
\ No newline at end of file
diff --git a/shaarli-pro/editlink.html b/shaarli-pro/editlink.html
index 68bc8fe..04fb19d 100644
--- a/shaarli-pro/editlink.html
+++ b/shaarli-pro/editlink.html
@@ -17,10 +17,10 @@
{/if}
{$batchModeValue=empty($batch_mode) ? '0' : '1'}
{function="($readLaterChecked = strpos(' ' . $link.tags . ' ', ' readitlater ') != false || strpos(' ' . $link.tags . ' ', ' readlater ') != false || strpos(' ' . $link.tags . ' ', ' toread ') != false) ? '' : ''"}
-{function="($isNoteOrTodo = strpos(' ' . $link.tags . ' ', ' note ') != false || strpos(' ' . $link.tags . ' ', ' shaarli-todo ') != false || strpos(' ' . $link.tags . ' ', ' shaarli-todo ') != false) ? '' : ''"}
+{function="($isNoteOrTodo = strpos(' ' . $link.tags . ' ', ' note ') != false || strpos(' ' . $link.tags . ' ', ' shaarli-note ') != false || strpos(' ' . $link.tags . ' ', ' todo ') != false || strpos(' ' . $link.tags . ' ', ' shaarli-todo ') != false) ? '' : ''"}
{function="($noteDefaultChecked = $link_is_new && empty($link.url) && !$isNoteOrTodo) ? '' : ''"}
-{function="($noteChecked = strpos(' ' . $link.tags . ' ', ' note ') != false || $noteDefaultChecked) ? '' : ''"}
-{function="($todoChecked = strpos(' ' . $link.tags . ' ', ' shaarli-todo ') != false) ? '' : ''"}
+{function="($noteChecked = strpos(' ' . $link.tags . ' ', ' note ') != false || strpos(' ' . $link.tags . ' ', ' shaarli-note ') != false || $noteDefaultChecked) ? '' : ''"}
+{function="($todoChecked = strpos(' ' . $link.tags . ' ', ' todo ') != false || strpos(' ' . $link.tags . ' ', ' shaarli-todo ') != false) ? '' : ''"}
{$effectiveTags=$link.tags}
{if="$noteDefaultChecked && strpos(' ' . $effectiveTags . ' ', ' note ') == false"}
{$effectiveTags=trim($effectiveTags . ' note')}
diff --git a/shaarli-pro/includes.html b/shaarli-pro/includes.html
index 3d6e235..25cbf98 100644
--- a/shaarli-pro/includes.html
+++ b/shaarli-pro/includes.html
@@ -67,6 +67,7 @@ visibility: '{$visibility}',
untaggedonly: (function(){/*{if="$untaggedonly"}*/return true;/*{else}*/return false;/*{/if}*/})()
};
+
diff --git a/shaarli-pro/js/custom_views.js b/shaarli-pro/js/custom_views.js
index 9f0b0eb..175a9a6 100644
--- a/shaarli-pro/js/custom_views.js
+++ b/shaarli-pro/js/custom_views.js
@@ -2,7 +2,7 @@ document.addEventListener("DOMContentLoaded", function () {
// Check URL parameters for custom views
const urlParams = new URLSearchParams(window.location.search);
const searchTagsRaw = urlParams.get("searchtags") || urlParams.get("searchTags") || "";
-
+
// Also check URL path for tag format (e.g., /tag/note)
const urlPath = window.location.pathname;
const pathMatch = urlPath.match(/\/tag\/(.+)$/);
@@ -10,17 +10,21 @@ document.addEventListener("DOMContentLoaded", function () {
// Parse all active tags to safely detect the view
const activeTags = (searchTagsRaw + " " + pathTagRaw).toLowerCase().split(/[\s,]+/).filter(t => t);
-
+
// Foolproof detection using sidebar active state and DOM rendered tags
const hasNoteActiveMenu = !!document.querySelector('.sidebar-link[aria-label="Notes"].active, .header-nav-link[aria-label="Notes"].active, .sidebar-link[href*="searchtags=note"].active, .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');
- const hasArchiveActiveMenu = !!document.querySelector('.sidebar-link[aria-label="Archive"].active, .header-nav-link[aria-label="Archive"].active, .sidebar-link[href*="searchtags=shaarli-archive"].active');
-
+ const 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 isNoteView = activeTags.includes("note") || activeTags.includes("shaarli-note") || hasNoteActiveMenu || domChipTags.includes("note") || domChipTags.includes("shaarli-note");
- const isTodoView = activeTags.includes("shaarli-todo") || hasTodoActiveMenu || domChipTags.includes("shaarli-todo");
- const isArchiveView = activeTags.includes("shaarli-archive") || hasArchiveActiveMenu || domChipTags.includes("shaarli-archive");
+ const isArchiveTag = (tag) => tag === "shaarli-archive" || tag === "shaarli-archiver";
+ const isNoteTag = (tag) => tag === "note" || tag === "shaarli-note" || tag === "#note";
+ const isTodoTag = (tag) => tag === "todo" || tag === "shaarli-todo" || tag === "#todo";
+
+ const isNoteView = activeTags.some(isNoteTag) || hasNoteActiveMenu || domChipTags.some(isNoteTag);
+ const isTodoView = activeTags.some(isTodoTag) || hasTodoActiveMenu || domChipTags.some(isTodoTag);
+ const isArchiveView = activeTags.some(isArchiveTag) || hasArchiveActiveMenu || domChipTags.some(isArchiveTag);
const linkList = document.getElementById("links-list");
const container = document.querySelector(".content-container");
@@ -194,9 +198,9 @@ document.addEventListener("DOMContentLoaded", function () {
const checked = String(todoMatch[1]).toLowerCase() === "x";
html.push(
`
`
- + ``
- + `${renderInline(todoMatch[2])}`
- + "",
+ + ``
+ + `${renderInline(todoMatch[2])}`
+ + "",
);
return;
}
@@ -409,9 +413,9 @@ document.addEventListener("DOMContentLoaded", function () {
initBookmarkPaletteButtons();
}
- if (typeof organizePinnedBookmarks === "function") {
- window.requestAnimationFrame(organizePinnedBookmarks);
- }
+ if (typeof organizePinnedBookmarks === "function") {
+ window.requestAnimationFrame(organizePinnedBookmarks);
+ }
// Always init Pinned Items logic (sorting and listeners)
// This function is defined at the end of the file
@@ -466,7 +470,7 @@ document.addEventListener("DOMContentLoaded", function () {
// Charger les backgrounds dynamiquement, puis initialiser les vues
initThemeModeBackgroundSync();
-
+
if (typeof loadBackgroundOptions === "function") {
loadBackgroundOptions()
.then(() => {
@@ -500,7 +504,7 @@ const NOTE_COLOR_OPTIONS = [
{
key: "default",
label: "Par défaut",
- light: "#ffffff",
+ light: "#f8fafc",
dark: "#20293A"
},
{
@@ -605,13 +609,15 @@ const NOTE_BACKGROUND_TAG_PREFIX = "notebg-";
function isTechnicalTag(tag) {
if (typeof tag !== "string") return false;
- const t = tag.trim();
+ const t = tag.trim().toLowerCase();
if (!t) return false;
- if (t === "note") return true;
+ if (t === "note" || t === "#note") return true;
if (t === "shaarli-note") return true;
+ if (t === "todo" || t === "#todo") return true;
if (t === "shaarli-todo") return true;
if (t === "shaarli-pin") return true;
+ if (t === "brain-dump") return true;
if (t.startsWith(NOTE_FONT_COLOR_TAG_PREFIX)) return true;
if (t.startsWith(NOTE_COLOR_TAG_PREFIX)) return true;
if (t.startsWith(NOTE_FILTER_TAG_PREFIX)) return true;
@@ -694,6 +700,16 @@ 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;
+ });
+}
+
let tagDisplayRemovalInitialized = false;
function initTagDisplayAndRemoval() {
if (tagDisplayRemovalInitialized) return;
@@ -1136,7 +1152,7 @@ function getElementVisualFontColor(element) {
}
function refreshNoteFilterVisuals() {
- document.querySelectorAll(".note-card, .note-modal, .link-outer").forEach((element) => {
+ document.querySelectorAll(".note-card, .note-modal, .link-outer, .note-input-container").forEach((element) => {
applyNoteVisualState(element, {
color: getElementVisualColor(element),
filter: getElementVisualFilter(element),
@@ -1238,7 +1254,7 @@ function initThemeModeBackgroundSync() {
if (nextTheme === lastTheme) return;
lastTheme = nextTheme;
- refreshNoteBackgroundVisuals();
+ refreshNoteFilterVisuals();
refreshBackgroundPalettes();
});
@@ -1388,17 +1404,12 @@ function ensureBackgroundStudioPanel() {
if (action === "set-defaults") {
if (mode === "draft") {
panelEl.dataset.color = "default";
- panelEl.dataset.filter = "none";
- panelEl.dataset.background = "none";
- panelEl.dataset.fontColor = "auto";
- applyDraft({ color: "default", filter: "none", background: "none", fontColor: "auto" });
+ applyDraft({ color: "default" });
renderBackgroundStudioPanel(panelEl);
} else if (mode === "modal") {
setModalNoteColor("default");
- setModalNoteFilter("none");
} else {
setNoteColor(entityId, "default", editUrl);
- setNoteFilter(entityId, "none", editUrl);
}
return;
}
@@ -1814,7 +1825,7 @@ function applyNoteVisualState(element, note) {
if (colorValue) {
element.style.backgroundColor = colorValue;
- element.style.borderColor = "transparent";
+ element.style.removeProperty("border-color");
} else {
element.style.removeProperty("background-color");
}
@@ -2323,7 +2334,7 @@ function initTodoView(linkList, container) {
-
+
@@ -2403,10 +2414,19 @@ function initTodoView(linkList, container) {
});
modalOverlay.querySelector("#todo-modal-delete").addEventListener("click", () => {
+ if (!confirm("Supprimer cette tâche ?")) return;
const modalCard = modalOverlay.querySelector(".note-modal");
const deleteUrl = modalCard.dataset.deleteUrl;
+ const todoId = modalCard.dataset.todoId;
if (deleteUrl && deleteUrl !== "#") {
- window.location.href = deleteUrl;
+ deleteEntitySilently(deleteUrl)
+ .then(() => {
+ window.location.href = "/?searchtags=shaarli-todo";
+ })
+ .catch((err) => {
+ console.error("Delete failed:", err);
+ alert("Erreur lors de la suppression.");
+ });
}
});
@@ -2884,6 +2904,13 @@ function renderTodos(container, todos, viewMode) {
const actions = document.createElement("div");
actions.className = "note-hover-actions";
+ const pinCorner = document.createElement("a");
+ pinCorner.href = todo.pinUrl;
+ pinCorner.title = todo.isPinned ? "Unpin" : "Pin";
+ pinCorner.className = `note-pin-corner ${todo.isPinned ? "active" : ""}`;
+ pinCorner.innerHTML = ``;
+ inner.appendChild(pinCorner);
+
const paletteBtnId = `palette-${todo.id}`;
actions.innerHTML = `
@@ -2891,11 +2918,25 @@ function renderTodos(container, todos, viewMode) {
-
-
-
+
+
`;
+ const deleteBtn = actions.querySelector(".todo-delete-btn");
+ deleteBtn?.addEventListener("click", (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ if (!confirm("Supprimer cette tâche ?")) return;
+ deleteEntitySilently(todo.deleteUrl)
+ .then(() => {
+ window.location.href = "/?searchtags=shaarli-todo";
+ })
+ .catch((err) => {
+ console.error("Delete failed:", err);
+ alert("Erreur lors de la suppression.");
+ });
+ });
+
const paletteBtn = actions.querySelector(`#${paletteBtnId}`);
paletteBtn.addEventListener("click", (e) => {
e.preventDefault();
@@ -3841,7 +3882,7 @@ function initNoteView(linkList, container) {
-
+
@@ -3945,10 +3986,19 @@ function initNoteView(linkList, container) {
});
modalOverlay.querySelector("#note-modal-delete").addEventListener("click", () => {
+ if (!confirm("Supprimer cette note ?")) return;
const modalCard = modalOverlay.querySelector(".note-modal");
const deleteUrl = modalCard.dataset.deleteUrl;
+ const noteId = modalCard.dataset.noteId;
if (deleteUrl && deleteUrl !== "#") {
- window.location.href = deleteUrl;
+ deleteEntitySilently(deleteUrl)
+ .then(() => {
+ window.location.href = "/?searchtags=note";
+ })
+ .catch((err) => {
+ console.error("Delete failed:", err);
+ alert("Erreur lors de la suppression.");
+ });
}
});
@@ -4046,7 +4096,7 @@ function initArchiveView(linkList, container) {
const notes = links.map((link) => parseNoteFromLink(link));
// Filter only archived notes
- const archivedNotes = notes.filter((note) => (note.tags || []).includes("shaarli-archive"));
+ const archivedNotes = notes.filter((note) => (note.tags || []).some((t) => t === "shaarli-archive" || t === "shaarli-archiver"));
// Initial Render (Grid)
renderNotes(contentArea, archivedNotes, "grid", true); // true = archive mode
@@ -4085,7 +4135,7 @@ function initArchiveView(linkList, container) {
-
+
@@ -4171,10 +4221,19 @@ function initArchiveView(linkList, container) {
});
modalOverlay.querySelector("#note-modal-delete").addEventListener("click", () => {
+ if (!confirm("Supprimer cette note ?")) return;
const modalCard = modalOverlay.querySelector(".note-modal");
const deleteUrl = modalCard.dataset.deleteUrl;
+ const noteId = modalCard.dataset.noteId;
if (deleteUrl && deleteUrl !== "#") {
- window.location.href = deleteUrl;
+ deleteEntitySilently(deleteUrl)
+ .then(() => {
+ window.location.href = "/?searchtags=shaarli-archive";
+ })
+ .catch((err) => {
+ console.error("Delete failed:", err);
+ alert("Erreur lors de la suppression.");
+ });
}
});
@@ -4293,10 +4352,10 @@ function renderNotes(container, notes, viewMode, isArchiveMode = false) {
let visibleNotes;
if (isArchiveMode) {
// In archive mode: show only notes with shaarli-archive tag
- visibleNotes = notes.filter((note) => (note.tags || []).includes("shaarli-archive"));
+ visibleNotes = notes.filter((note) => (note.tags || []).some((t) => t === "shaarli-archive" || t === "shaarli-archiver"));
} else {
// In normal notes mode: hide archived notes
- visibleNotes = notes.filter((note) => !(note.tags || []).includes("shaarli-archive"));
+ visibleNotes = notes.filter((note) => !(note.tags || []).some((t) => t === "shaarli-archive" || t === "shaarli-archiver"));
}
// Sort: Pinned items first
@@ -4348,7 +4407,7 @@ function renderNotes(container, notes, viewMode, isArchiveMode = false) {
if (note.descHtml || note.descText || note._noteMarkdown) {
const body = document.createElement("div");
body.className = "note-body";
-
+
if (note._noteMarkdown !== undefined) {
body.innerHTML = renderMarkdown(note._noteMarkdown);
} else {
@@ -4359,7 +4418,7 @@ function renderNotes(container, notes, viewMode, isArchiveMode = false) {
body.innerHTML = renderMarkdown(textToRender);
}
}
-
+
inner.appendChild(body);
}
@@ -4379,6 +4438,13 @@ function renderNotes(container, notes, viewMode, isArchiveMode = false) {
const actions = document.createElement("div");
actions.className = "note-hover-actions";
+ const pinCorner = document.createElement("a");
+ pinCorner.href = note.pinUrl;
+ pinCorner.title = note.isPinned ? "Unpin" : "Pin";
+ pinCorner.className = `note-pin-corner ${note.isPinned ? "active" : ""}`;
+ pinCorner.innerHTML = ``;
+ inner.appendChild(pinCorner);
+
// Palette Button Logic
const paletteBtnId = `palette-${note.id}`;
const archiveBtnId = `archive-${note.id}`;
@@ -4393,11 +4459,25 @@ function renderNotes(container, notes, viewMode, isArchiveMode = false) {
-
-
+
`;
+ const deleteBtn = actions.querySelector(".note-delete-btn");
+ deleteBtn?.addEventListener("click", (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ if (!confirm("Supprimer cette note ?")) return;
+ deleteEntitySilently(note.deleteUrl)
+ .then(() => {
+ window.location.href = isArchiveMode ? "/?searchtags=shaarli-archive" : "/?searchtags=note";
+ })
+ .catch((err) => {
+ console.error("Delete failed:", err);
+ alert("Erreur lors de la suppression.");
+ });
+ });
+
const openEditorBtn = actions.querySelector(".note-open-editor-btn");
openEditorBtn?.addEventListener("click", (e) => {
e.preventDefault();
@@ -5211,7 +5291,7 @@ function initPinnedItems() {
document.addEventListener(
"click",
function (e) {
- const btn = e.target.closest('a[href*="do=pin"], .note-hover-actions a[href*="pin"], .link-actions a[href*="pin"]');
+ const btn = e.target.closest('a[href*="do=pin"], .note-hover-actions a[href*="pin"], .note-pin-corner[href*="pin"], .link-actions a[href*="pin"]');
if (btn) {
e.preventDefault();
e.stopPropagation();
@@ -5496,15 +5576,15 @@ function openColorPickerPanel({ mode, entityId, editUrl, type }) {
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");
- }
+ 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 = "";
@@ -5518,24 +5598,24 @@ function openColorPickerPanel({ mode, entityId, editUrl, type }) {
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);
+ 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 {
- 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 = "";
@@ -5732,10 +5812,11 @@ function openColorPickerPanel({ mode, entityId, editUrl, type }) {
setFromHex(defaultColor);
}
+ panel.style.display = "block";
+ panel.style.visibility = "hidden";
// Position the color picker panel
positionColorPickerPanel(panel, mode);
-
- panel.style.display = "block";
+ panel.style.visibility = "visible";
panel.classList.add("open");
panel.setAttribute("aria-hidden", "false");
}
@@ -5751,20 +5832,26 @@ function positionColorPickerPanel(panel, mode) {
if (bgPanel && bgPanel.classList.contains("open")) {
const bgRect = bgPanel.getBoundingClientRect();
+ const panelRect = panel.getBoundingClientRect();
+ const panelWidth = Math.max(1, panelRect.width);
+ const panelHeight = Math.max(1, panelRect.height);
// Position to the right of the bg-studio panel
let left = bgRect.right + 10;
let top = bgRect.top;
// Check if it fits on the right
- if (left + panel.offsetWidth > window.innerWidth - viewportPadding) {
+ if (left + panelWidth > window.innerWidth - viewportPadding) {
// Position to the left instead
- left = bgRect.left - panel.offsetWidth - 10;
+ left = bgRect.left - panelWidth - 10;
}
// Ensure it stays within viewport
if (left < viewportPadding) left = viewportPadding;
- if (top + panel.offsetHeight > window.innerHeight - viewportPadding) {
- top = window.innerHeight - viewportPadding - panel.offsetHeight;
+ if (left + panelWidth > window.innerWidth - viewportPadding) {
+ left = window.innerWidth - viewportPadding - panelWidth;
+ }
+ if (top + panelHeight > window.innerHeight - viewportPadding) {
+ top = window.innerHeight - viewportPadding - panelHeight;
}
if (top < viewportPadding) top = viewportPadding;
@@ -5775,8 +5862,10 @@ function positionColorPickerPanel(panel, mode) {
} else {
// Center in viewport if bg panel not available
const panelRect = panel.getBoundingClientRect();
- const left = Math.max(viewportPadding, (window.innerWidth - panelRect.width) / 2);
- const top = Math.max(viewportPadding, (window.innerHeight - panelRect.height) / 2);
+ const clampedWidth = Math.max(1, panelRect.width);
+ const clampedHeight = Math.max(1, panelRect.height);
+ const left = Math.max(viewportPadding, Math.min((window.innerWidth - clampedWidth) / 2, window.innerWidth - viewportPadding - clampedWidth));
+ const top = Math.max(viewportPadding, Math.min((window.innerHeight - clampedHeight) / 2, window.innerHeight - viewportPadding - clampedHeight));
panel.style.left = `${Math.round(left)}px`;
panel.style.top = `${Math.round(top)}px`;
}
@@ -5899,7 +5988,7 @@ function setNoteColorVisual(noteId, colorKey) {
});
element.classList.add("note-color-custom");
element.style.backgroundColor = hex;
- element.style.borderColor = "transparent";
+ element.style.removeProperty("border-color");
element.dataset.color = "custom";
element.dataset.customColor = hex;
const fg = getReadableForegroundForBackground(hex);
@@ -6038,7 +6127,7 @@ function setModalCustomNoteColor(color) {
const modalCard = modal.querySelector(".note-modal");
if (modalCard) {
modalCard.style.backgroundColor = color;
- modalCard.style.borderColor = "transparent";
+ modalCard.style.removeProperty("border-color");
modalCard.dataset.color = "custom";
modalCard.dataset.customColor = color;
}
diff --git a/shaarli-pro/js/script.js b/shaarli-pro/js/script.js
index be12a85..2427c04 100644
--- a/shaarli-pro/js/script.js
+++ b/shaarli-pro/js/script.js
@@ -1,4 +1,20 @@
document.addEventListener('DOMContentLoaded', () => {
+ // ===== Add Todo Button Handler (Android convention) =====
+ const addTodoBtn = document.querySelector('.sidebar-add-todo');
+ if (addTodoBtn) {
+ addTodoBtn.addEventListener('click', (e) => {
+ e.preventDefault();
+ if (!window.ShaarItRules) {
+ console.warn('[shaarit] ShaarItRules not available, cannot generate todo URL');
+ return;
+ }
+ const basePath = addTodoBtn.getAttribute('data-base-path') || '';
+ const todoUrl = window.ShaarItRules.generateTodoUrl();
+ const redirectUrl = `${basePath}/admin/shaare?post=${encodeURIComponent(todoUrl)}&tags=todo&title=%E2%9C%85%20`;
+ window.location.href = redirectUrl;
+ });
+ }
+
// ===== Theme Toggle =====
const themeCheckbox = document.getElementById('theme-toggle-checkbox');
const themeIconLight = document.getElementById('theme-icon-light');
@@ -88,14 +104,19 @@ document.addEventListener('DOMContentLoaded', () => {
const HIDDEN_TAGS_STORAGE_KEY = 'shaarli_hidden_tags';
// Default system tags that are hidden by default
+ // Harmonisé avec ShaarIt Android (PRESET_SYSTEM_TAGS).
const DEFAULT_HIDDEN_TAGS = [
'note',
- 'shaarli-pin',
+ 'shaarli-note',
+ 'todo',
'shaarli-todo',
+ 'shaarli-pin',
'note-color-*',
'notebg-*',
'notefilter-*',
+ 'font-*',
'readitlater',
+ 'brain-dump',
'shaarli-archive'
];
@@ -1706,12 +1727,14 @@ document.addEventListener('DOMContentLoaded', () => {
const syncNoteCheckbox = () => {
if (!noteCheckbox) return;
- noteCheckbox.checked = tags.some((tag) => /^note$/i.test(tag));
+ // Reconnaît les conventions Android (`note`, `#note`) et legacy (`shaarli-note`).
+ noteCheckbox.checked = tags.some((tag) => /^(note|#note|shaarli-note)$/i.test(tag));
};
const syncTodoCheckbox = () => {
if (!todoCheckbox) return;
- todoCheckbox.checked = tags.some((tag) => /^shaarli-todo$/i.test(tag));
+ // Reconnaît les conventions Android (`todo`, `#todo`) et legacy (`shaarli-todo`).
+ todoCheckbox.checked = tags.some((tag) => /^(todo|#todo|shaarli-todo)$/i.test(tag));
};
// Helper functions to manage note emoji in title
@@ -1956,6 +1979,66 @@ document.addEventListener('DOMContentLoaded', () => {
form.addEventListener('submit', commitInputValue);
+ // === Todo migration: shaarli-todo → todo + Android URL ===
+ // Lors de la sauvegarde d'un todo, remplacer le tag legacy par la norme Android
+ // et générer une URL Android si vide ou legacy.
+ form.addEventListener('submit', () => {
+ try {
+ if (!window.ShaarItRules) return;
+ const urlField = form.querySelector('input[name="lf_url"]');
+ const url = urlField ? (urlField.value || '').trim() : '';
+
+ // Vérifier si c'est un todo (tag shaarli-todo ou todo)
+ const hasTodoTag = tags.some((t) => /^(todo|shaarli-todo)$/i.test(t));
+ if (!hasTodoTag) return;
+
+ // 1. Remplacer shaarli-todo par todo
+ const hadLegacyTag = tags.some((t) => /^shaarli-todo$/i.test(t));
+ if (hadLegacyTag) {
+ tags = tags.filter((t) => !/^shaarli-todo$/i.test(t));
+ if (!tags.some((t) => /^todo$/i.test(t))) {
+ tags.push('todo');
+ }
+ updateHiddenTags();
+ }
+
+ // 2. Générer URL Android si vide ou legacy
+ if (!url || /^https?:\/\/shaarli-todo/.test(url) || url === 'http://shaarli-todo') {
+ const newUrl = window.ShaarItRules.generateTodoUrl();
+ urlField.value = newUrl;
+ }
+ } catch (e) {
+ console.warn('[shaarit] todo migration failed:', e);
+ }
+ });
+
+ // === Content-type auto-tagging (harmonisation ShaarIt Android) ===
+ // À la soumission, détecte le type de contenu depuis l'URL et injecte
+ // les tags automatiques (video, podcast, radio, music, article, news,
+ // social, repository+dev, shopping, image, pdf...).
+ // Les tags déjà présents ne sont jamais dupliqués ; aucun tag n'est
+ // supprimé. Désactivé pour les notes/todos (URLs internes).
+ form.addEventListener('submit', () => {
+ try {
+ if (!window.ShaarItRules) return;
+ const urlField = form.querySelector('input[name="lf_url"]');
+ const url = urlField ? (urlField.value || '').trim() : '';
+ if (!url) return;
+
+ // Ne pas auto-tagger les notes/todos (URLs internes reconnues par ShaarItRules).
+ const fakeLink = { url: url, tags: tags };
+ if (window.ShaarItRules.isNote(fakeLink) || window.ShaarItRules.isTodo(fakeLink)) return;
+
+ const detection = window.ShaarItRules.detectContentType(url);
+ if (!detection || !detection.tags || detection.tags.length === 0) return;
+
+ tags = window.ShaarItRules.mergeAutoTags(tags, detection.tags);
+ updateHiddenTags();
+ } catch (e) {
+ console.warn('[shaarit] content-type auto-tagging failed:', e);
+ }
+ });
+
updateHiddenTags();
syncReadLaterCheckbox();
syncNoteCheckbox();
diff --git a/shaarli-pro/js/shaarit-rules.js b/shaarli-pro/js/shaarit-rules.js
new file mode 100644
index 0000000..a14731a
--- /dev/null
+++ b/shaarli-pro/js/shaarit-rules.js
@@ -0,0 +1,377 @@
+/**
+ * shaarit-rules.js
+ *
+ * Règles métier partagées avec l'application Android ShaarIt.
+ * Source de vérité unique pour :
+ * - la détection des notes / todos / épinglés ;
+ * - les tags techniques cachés par défaut ;
+ * - la détection de type de contenu (video, podcast, radio, music, article,
+ * news, social, repository/dev, shopping, image, pdf, document) ;
+ * - la génération d'URLs internes (notes / todos).
+ *
+ * Non destructif : toutes les conventions existantes du thème web continuent
+ * d'être reconnues en lecture. Les règles Android viennent s'y ajouter.
+ *
+ * Exposé sur `window.ShaarItRules`.
+ */
+(function (global) {
+ "use strict";
+
+ // ---------------------------------------------------------------------------
+ // Tags système (cachés par défaut côté UI)
+ // ---------------------------------------------------------------------------
+
+ var PRESET_SYSTEM_TAGS = [
+ { name: "note", desc: "Notes - Identifiant interne", hidden: true },
+ { name: "shaarli-note", desc: "Notes - Alias legacy", hidden: true },
+ { name: "todo", desc: "Tâches - Identifiant interne", hidden: true },
+ { name: "shaarli-todo", desc: "Tâches - Alias legacy", hidden: true },
+ { name: "shaarli-pin", desc: "Épinglé - Favoris en haut", hidden: true },
+ { name: "note-color-*", desc: "Couleurs des notes (wildcard)", hidden: true },
+ { name: "notebg-*", desc: "Fonds des notes (wildcard)", hidden: true },
+ { name: "notefilter-*", desc: "Filtres des notes (wildcard)", hidden: true },
+ { name: "font-*", desc: "Couleur de police (wildcard)", hidden: true },
+ { name: "readitlater", desc: "À lire plus tard", hidden: true },
+ { name: "brain-dump", desc: "Capture rapide d'idées", hidden: true },
+ { name: "shaarli-archive", desc: "Archivé", hidden: false },
+ { name: "shaarli-archiver",desc: "Archivé - Alias legacy", hidden: false }
+ ];
+
+ // ---------------------------------------------------------------------------
+ // Détection d'entités (note / todo / pin) - compatibilité Android + legacy
+ // ---------------------------------------------------------------------------
+
+ function toLower(x) { return String(x || "").toLowerCase(); }
+
+ function asTagArray(tags) {
+ if (Array.isArray(tags)) return tags.map(toLower);
+ if (typeof tags === "string") {
+ return tags.split(/[\s,|]+/).map(toLower).filter(Boolean);
+ }
+ return [];
+ }
+
+ /**
+ * Détection d'une note.
+ * Compatible avec les règles Android :
+ * - URL `note://` (mais pas `note://todo-`)
+ * - URL `http://shaare` / `/shaare`
+ * - URL `https://shaarit.app/note/...`
+ * - Tag `note`, `#note`, `shaarli-note`
+ */
+ function isNote(link) {
+ if (!link) return false;
+ var url = String(link.url || "").trim();
+ var tags = asTagArray(link.tags);
+
+ if (url) {
+ var u = url.toLowerCase();
+ if (u.indexOf("note://") === 0 && u.indexOf("note://todo-") !== 0) return true;
+ if (u.indexOf("http://shaare") === 0) return true;
+ if (u.indexOf("/shaare") === 0) return true;
+ if (u.indexOf("https://shaarit.app/note/") === 0) return true;
+ if (u.indexOf("http://shaarit.app/note/") === 0) return true;
+ }
+
+ for (var i = 0; i < tags.length; i++) {
+ if (tags[i] === "note" || tags[i] === "#note" || tags[i] === "shaarli-note") return true;
+ }
+ return false;
+ }
+
+ /**
+ * Détection d'une tâche.
+ * Compatible avec les règles Android :
+ * - URL `note://todo-...`
+ * - URL `https://shaarit.app/todo/...`
+ * - URL `http://shaarli-todo` (legacy web)
+ * - Tag `todo`, `#todo`, `shaarli-todo`
+ */
+ function isTodo(link) {
+ if (!link) return false;
+ var url = String(link.url || "").trim();
+ var tags = asTagArray(link.tags);
+
+ if (url) {
+ var u = url.toLowerCase();
+ if (u.indexOf("note://todo-") === 0) return true;
+ if (u.indexOf("https://shaarit.app/todo/") === 0) return true;
+ if (u.indexOf("http://shaarit.app/todo/") === 0) return true;
+ if (u.indexOf("http://shaarli-todo") === 0) return true;
+ if (u.indexOf("https://shaarli-todo") === 0) return true;
+ }
+
+ for (var i = 0; i < tags.length; i++) {
+ if (tags[i] === "todo" || tags[i] === "#todo" || tags[i] === "shaarli-todo") return true;
+ }
+ return false;
+ }
+
+ /** Détection épinglé via tag `shaarli-pin`. */
+ function isPinned(link) {
+ if (!link) return false;
+ var tags = asTagArray(link.tags);
+ return tags.indexOf("shaarli-pin") !== -1;
+ }
+
+ /** Détection archive (tag `shaarli-archive` ou legacy `shaarli-archiver`). */
+ function isArchived(link) {
+ if (!link) return false;
+ var tags = asTagArray(link.tags);
+ return tags.indexOf("shaarli-archive") !== -1 || tags.indexOf("shaarli-archiver") !== -1;
+ }
+
+ // ---------------------------------------------------------------------------
+ // Génération d'URLs internes (style Android)
+ // ---------------------------------------------------------------------------
+
+ function randomId() {
+ // Identifiant court type base36, 12 caractères.
+ if (global.crypto && global.crypto.getRandomValues) {
+ var arr = new Uint8Array(9);
+ global.crypto.getRandomValues(arr);
+ var out = "";
+ for (var i = 0; i < arr.length; i++) {
+ out += ("0" + arr[i].toString(36)).slice(-2);
+ }
+ return out.substring(0, 12);
+ }
+ return (Date.now().toString(36) + Math.random().toString(36).slice(2, 10)).substring(0, 12);
+ }
+
+ function generateNoteUrl() {
+ return "https://shaarit.app/note/" + randomId();
+ }
+
+ function generateTodoUrl() {
+ return "https://shaarit.app/todo/" + randomId();
+ }
+
+ // ---------------------------------------------------------------------------
+ // Détection de type de contenu (ContentType, règles Android)
+ // ---------------------------------------------------------------------------
+
+ var CONTENT_TYPES = {
+ UNKNOWN: "unknown",
+ ARTICLE: "article",
+ VIDEO: "video",
+ PODCAST: "podcast",
+ IMAGE: "image",
+ PDF: "pdf",
+ REPOSITORY: "repository",
+ DOCUMENT: "document",
+ SOCIAL: "social",
+ SHOPPING: "shopping",
+ NEWSLETTER: "newsletter",
+ MUSIC: "music",
+ RADIO: "radio",
+ NEWS: "news"
+ };
+
+ // Tags auto-ajoutés par type de contenu (conformes règles Android UI).
+ var CONTENT_TYPE_TAGS = {
+ video: ["video"],
+ podcast: ["podcast"],
+ radio: ["radio"],
+ music: ["music"],
+ article: ["article"],
+ news: ["news"],
+ social: ["social"],
+ repository: ["repository", "dev"],
+ shopping: ["shopping"],
+ newsletter: ["newsletter"],
+ image: ["image"],
+ pdf: ["pdf"],
+ document: [],
+ unknown: []
+ };
+
+ function parseHost(url) {
+ try {
+ var u = new URL(url);
+ return (u.hostname || "").toLowerCase();
+ } catch (e) {
+ // Fallback très simple si URL invalide
+ var m = String(url || "").match(/^[a-z][a-z0-9+.-]*:\/\/([^/?#]+)/i);
+ return m ? m[1].toLowerCase() : "";
+ }
+ }
+
+ function hostContainsAny(host, list) {
+ for (var i = 0; i < list.length; i++) {
+ if (host.indexOf(list[i]) !== -1) return true;
+ }
+ return false;
+ }
+
+ // --- Audio : RADIO > PODCAST > MUSIC ---
+
+ var RADIO_HOSTS = [
+ "playerservices.streamtheworld.com", "icecast", "shoutcast",
+ "fluxradios.com", "tunein.com", "radio.garden", "mytuner-radio.com",
+ "iheart.com", "onlineradiobox.com", "radio.net"
+ ];
+
+ function isRadio(url, host) {
+ if (/\.(m3u|m3u8|pls)(\?|$)/i.test(url)) return true;
+ if (hostContainsAny(host, RADIO_HOSTS)) return true;
+ if (host.indexOf("stream.") === 0 || host.indexOf("live.") === 0) return true;
+ if (host.indexOf("ici.radio-canada.ca") !== -1 || host.indexOf("radio-canada.ca") !== -1) {
+ if (/\/balados(\/|$)/i.test(url)) return false;
+ if (/\/(direct|premiere|audio-fil)(\/|$)/i.test(url)) return true;
+ }
+ return false;
+ }
+
+ var PODCAST_HOSTS = [
+ "podcasts.apple.com", "overcast.fm", "pocketcasts.com", "castbox.fm",
+ "stitcher.com", "acast.com", "anchor.fm", "libsyn.com",
+ "simplecast.com", "buzzsprout.com"
+ ];
+
+ function isPodcast(url, host) {
+ if (hostContainsAny(host, PODCAST_HOSTS)) return true;
+ if (/open\.spotify\.com\/(show|episode)\//i.test(url)) return true;
+ if (/\/balados(\/|$)|\/ohdio\/balados(\/|$)/i.test(url)) return true;
+ if (/\.(xml|rss)(\?|$)/i.test(url)) return true;
+ return false;
+ }
+
+ var MUSIC_HOSTS = [
+ "music.apple.com", "deezer.com", "tidal.com", "music.youtube.com",
+ "bandcamp.com", "soundcloud.com", "mixcloud.com", "beatport.com"
+ ];
+
+ function isMusic(url, host) {
+ if (hostContainsAny(host, MUSIC_HOSTS)) return true;
+ if (/open\.spotify\.com\/(track|album|artist|playlist)\//i.test(url)) return true;
+ return false;
+ }
+
+ // --- Catégories web ---
+
+ var VIDEO_HOSTS = ["youtube.com", "youtu.be", "vimeo.com", "dailymotion.com", "twitch.tv", "netflix.com"];
+ function isVideo(host) { return hostContainsAny(host, VIDEO_HOSTS); }
+
+ var SOCIAL_HOSTS = [
+ "facebook.com", "instagram.com", "tiktok.com", "twitter.com", "x.com",
+ "linkedin.com", "reddit.com", "snapchat.com", "pinterest.com", "mastodon"
+ ];
+ function isSocial(host) { return hostContainsAny(host, SOCIAL_HOSTS); }
+
+ var REPO_HOSTS = ["github.com", "gitlab.com", "bitbucket.org", "stackoverflow.com"];
+ function isRepository(host) { return hostContainsAny(host, REPO_HOSTS); }
+
+ var SHOPPING_HOSTS = ["amazon", "ebay", "etsy.com", "aliexpress.com", "shopify"];
+ function isShopping(host) { return hostContainsAny(host, SHOPPING_HOSTS); }
+
+ var DOCUMENT_HOSTS = [
+ "docs.google.com", "drive.google.com", "notion.so", "trello.com",
+ "jira", "confluence"
+ ];
+ function isDocument(host) { return hostContainsAny(host, DOCUMENT_HOSTS); }
+
+ var NEWS_HOSTS = [
+ "news", "nytimes", "lemonde", "bbc", "cnn", "reuters",
+ "theguardian", "lefigaro"
+ ];
+ function isNews(host) { return hostContainsAny(host, NEWS_HOSTS); }
+
+ var NEWSLETTER_HOSTS = ["substack", "revue", "mailchimp"];
+ function isNewsletter(host) { return hostContainsAny(host, NEWSLETTER_HOSTS); }
+
+ var IMAGE_HOSTS = ["imgur.com", "flickr.com"];
+ function isImageUrl(url, host) {
+ if (/\.(jpe?g|png|gif|webp|bmp|svg)(\?|$)/i.test(url)) return true;
+ return hostContainsAny(host, IMAGE_HOSTS);
+ }
+
+ function isPdfUrl(url) {
+ return /\.pdf(\?|$)/i.test(url);
+ }
+
+ /**
+ * Détecte le type de contenu d'une URL (règles Android).
+ * @returns {{type: string, tags: string[]}}
+ */
+ function detectContentType(url) {
+ var raw = String(url || "").trim();
+ if (!raw) return { type: CONTENT_TYPES.UNKNOWN, tags: [] };
+
+ // URLs internes : pas de détection
+ var low = raw.toLowerCase();
+ if (low.indexOf("note://") === 0 ||
+ low.indexOf("https://shaarit.app/note/") === 0 ||
+ low.indexOf("https://shaarit.app/todo/") === 0 ||
+ low.indexOf("http://shaare") === 0 ||
+ low.indexOf("/shaare") === 0 ||
+ low.indexOf("http://shaarli-todo") === 0) {
+ return { type: CONTENT_TYPES.UNKNOWN, tags: [] };
+ }
+
+ var host = parseHost(raw);
+
+ // 1. Fichiers (priorité haute sur l'extension)
+ if (isPdfUrl(raw)) return { type: CONTENT_TYPES.PDF, tags: CONTENT_TYPE_TAGS.pdf };
+ if (isImageUrl(raw, host))return { type: CONTENT_TYPES.IMAGE, tags: CONTENT_TYPE_TAGS.image };
+
+ // 2. Audio : RADIO > PODCAST > MUSIC
+ if (isRadio(raw, host)) return { type: CONTENT_TYPES.RADIO, tags: CONTENT_TYPE_TAGS.radio };
+ if (isPodcast(raw, host)) return { type: CONTENT_TYPES.PODCAST, tags: CONTENT_TYPE_TAGS.podcast };
+ if (isMusic(raw, host)) return { type: CONTENT_TYPES.MUSIC, tags: CONTENT_TYPE_TAGS.music };
+
+ // 3. Vidéo
+ if (isVideo(host)) return { type: CONTENT_TYPES.VIDEO, tags: CONTENT_TYPE_TAGS.video };
+
+ // 4. Plateformes spécifiques
+ if (isRepository(host)) return { type: CONTENT_TYPES.REPOSITORY, tags: CONTENT_TYPE_TAGS.repository };
+ if (isDocument(host)) return { type: CONTENT_TYPES.DOCUMENT, tags: CONTENT_TYPE_TAGS.document };
+ if (isSocial(host)) return { type: CONTENT_TYPES.SOCIAL, tags: CONTENT_TYPE_TAGS.social };
+ if (isShopping(host)) return { type: CONTENT_TYPES.SHOPPING, tags: CONTENT_TYPE_TAGS.shopping };
+ if (isNewsletter(host)) return { type: CONTENT_TYPES.NEWSLETTER, tags: CONTENT_TYPE_TAGS.newsletter };
+ if (isNews(host)) return { type: CONTENT_TYPES.NEWS, tags: CONTENT_TYPE_TAGS.news };
+
+ return { type: CONTENT_TYPES.UNKNOWN, tags: [] };
+ }
+
+ /**
+ * Fusion d'un tableau de tags existants avec les tags auto-détectés.
+ * Conserve l'ordre existant, ajoute uniquement les tags manquants.
+ */
+ function mergeAutoTags(existingTags, autoTags) {
+ var list = Array.isArray(existingTags) ? existingTags.slice() : [];
+ var lower = list.map(toLower);
+ (autoTags || []).forEach(function (t) {
+ var clean = String(t || "").trim();
+ if (!clean) return;
+ if (lower.indexOf(clean.toLowerCase()) === -1) {
+ list.push(clean);
+ lower.push(clean.toLowerCase());
+ }
+ });
+ return list;
+ }
+
+ // ---------------------------------------------------------------------------
+ // Export
+ // ---------------------------------------------------------------------------
+
+ var api = {
+ PRESET_SYSTEM_TAGS: PRESET_SYSTEM_TAGS,
+ CONTENT_TYPES: CONTENT_TYPES,
+ CONTENT_TYPE_TAGS: CONTENT_TYPE_TAGS,
+
+ isNote: isNote,
+ isTodo: isTodo,
+ isPinned: isPinned,
+ isArchived: isArchived,
+
+ generateNoteUrl: generateNoteUrl,
+ generateTodoUrl: generateTodoUrl,
+
+ detectContentType: detectContentType,
+ mergeAutoTags: mergeAutoTags
+ };
+
+ global.ShaarItRules = api;
+})(typeof window !== "undefined" ? window : this);
diff --git a/shaarli-pro/linklist.html b/shaarli-pro/linklist.html
index 33dfb31..500e1f1 100644
--- a/shaarli-pro/linklist.html
+++ b/shaarli-pro/linklist.html
@@ -47,11 +47,16 @@
{if="count($links)==0"}
+ {if="in_array('note', $active_search_tags) || in_array('shaarli-note', $active_search_tags) || in_array('todo', $active_search_tags) || in_array('shaarli-todo', $active_search_tags) || in_array('shaarli-archive', $active_search_tags) || in_array('shaarli-archiver', $active_search_tags)"}
+
+ {include="linklist.paging"}
+ {else}
Aucun bookmark trouvé
{if="!empty($search_term)"}Aucun résultat pour : {$search_term}{else}Commencez à ajouter des bookmarks pour les voir apparaître ici.{/if}
+ {/if}
{else}
diff --git a/shaarli-pro/migrate-todos.php b/shaarli-pro/migrate-todos.php
new file mode 100644
index 0000000..59a269d
--- /dev/null
+++ b/shaarli-pro/migrate-todos.php
@@ -0,0 +1,104 @@
+getContainer()->get('db');
+if (!$linkDb) {
+ die("Erreur : base de données non accessible.\n");
+}
+
+// Récupérer tous les liens avec le tag shaarli-todo
+$allLinks = $linkDb->getLinks();
+$todosToMigrate = [];
+
+foreach ($allLinks as $link) {
+ $tags = explode(' ', $link->getTags());
+ $hasTodoTag = in_array('shaarli-todo', $tags, true);
+ if ($hasTodoTag) {
+ $todosToMigrate[] = $link;
+ }
+}
+
+if (empty($todosToMigrate)) {
+ echo "Aucun todo à migrer.\n";
+ exit(0);
+}
+
+echo "Trouvé " . count($todosToMigrate) . " todo(s) à migrer.\n";
+
+// Fonction pour générer un UUID court (style Android)
+function generateTodoUuid() {
+ if (function_exists('random_bytes')) {
+ $bytes = random_bytes(9);
+ $out = '';
+ for ($i = 0; $i < 9; $i++) {
+ $out .= str_pad(base_convert(ord($bytes[$i]), 10, 36), 2, '0', STR_PAD_LEFT);
+ }
+ return substr($out, 0, 12);
+ }
+ return substr(base_convert(time(), 10, 36) . base_convert(mt_rand(), 10, 36), 0, 12);
+}
+
+// Migrer chaque todo
+$migrated = 0;
+$errors = 0;
+
+foreach ($todosToMigrate as $link) {
+ try {
+ $tags = explode(' ', $link->getTags());
+
+ // 1. Remplacer shaarli-todo par todo
+ $tags = array_filter($tags, function ($t) {
+ return $t !== 'shaarli-todo';
+ });
+ $tags = array_values($tags);
+ if (!in_array('todo', $tags, true)) {
+ $tags[] = 'todo';
+ }
+ $link->setTags($tags);
+
+ // 2. Générer URL Android si vide ou legacy
+ $url = $link->getUrl();
+ if (empty($url) || strpos($url, 'http://shaarli-todo') === 0 || strpos($url, 'https://shaarli-todo') === 0) {
+ $uuid = generateTodoUuid();
+ $link->setUrl('https://shaarit.app/todo/' . $uuid);
+ }
+
+ // Sauvegarder
+ $linkDb->save($link);
+ $migrated++;
+ echo "✓ Migré : {$link->getTitle()} ({$link->getId()})\n";
+ } catch (Exception $e) {
+ $errors++;
+ echo "✗ Erreur : {$link->getTitle()} - {$e->getMessage()}\n";
+ }
+}
+
+echo "\n=== Résumé ===\n";
+echo "Migrés : $migrated\n";
+echo "Erreurs : $errors\n";
+echo "Total : " . count($todosToMigrate) . "\n";
+
+if ($errors === 0) {
+ echo "\n✓ Migration terminée avec succès !\n";
+ exit(0);
+} else {
+ echo "\n⚠ Migration terminée avec $errors erreur(s).\n";
+ exit(1);
+}
diff --git a/shaarli-pro/page.header.html b/shaarli-pro/page.header.html
index f22fdb7..114844d 100644
--- a/shaarli-pro/page.header.html
+++ b/shaarli-pro/page.header.html
@@ -49,7 +49,7 @@ Bookmarklet detection logic
Quotidien
-
@@ -93,7 +93,7 @@ Bookmarklet detection logic
-
@@ -154,7 +154,7 @@ Bookmarklet detection logic
QUOTIDIEN
-
diff --git a/shaarli-pro/tools.html b/shaarli-pro/tools.html
index 8581a7a..f9d8af8 100644
--- a/shaarli-pro/tools.html
+++ b/shaarli-pro/tools.html
@@ -430,13 +430,19 @@
(function() {
const STORAGE_KEY = 'shaarli_hidden_tags';
+ // Harmonisé avec ShaarIt Android (PRESET_SYSTEM_TAGS).
const PRESET_TAGS = [
{ name: 'note', desc: 'Notes - Internal note identifier' },
+ { name: 'shaarli-note', desc: 'Notes - Legacy alias' },
+ { name: 'todo', desc: 'Tasks - Internal todo identifier' },
+ { name: 'shaarli-todo', desc: 'Tasks - Legacy alias' },
{ name: 'shaarli-pin', desc: 'Pinned - Keeps bookmarks at top' },
{ name: 'note-color-*', desc: 'Note Colors - Wildcard for color tags' },
{ name: 'notebg-*', desc: 'Note Backgrounds - Wildcard for background tags' },
{ name: 'notefilter-*', desc: 'Note Filters - Wildcard for filter tags' },
+ { name: 'font-*', desc: 'Note Font Colors - Wildcard' },
{ name: 'readitlater', desc: 'Read Later - Temporary reading list' },
+ { name: 'brain-dump', desc: 'Brain dump - Quick idea capture' },
{ name: 'shaarli-archive', desc: 'Archived - Archived notes' }
];