feat: implémenter système Todo complet avec bouton segmenté sidebar (bookmark/note/todo), toggle Todo dans formulaire édition avec gestion émoji ✅ automatique, masquage champ URL pour notes/todos, tag shaarli-todo unifié (remplacement todo), déduplication tags case-insensitive, correction tag archive (shaarli-archiver→shaarli-archive), ajout shaarli-todo aux tags cachés par défaut, styles bouton segmenté avec gradient bleu et hover states, et support responsive mobile avec masquage tex

This commit is contained in:
2026-02-20 16:33:40 -05:00
parent adb2564153
commit fb5254445f
7 changed files with 167 additions and 15 deletions
+85 -4
View File
@@ -91,11 +91,12 @@ document.addEventListener('DOMContentLoaded', () => {
const DEFAULT_HIDDEN_TAGS = [
'note',
'shaarli-pin',
'shaarli-todo',
'note-color-*',
'notebg-*',
'notefilter-*',
'readitlater',
'shaarli-archiver'
'shaarli-archive'
];
// Get hidden tags from localStorage
@@ -1602,6 +1603,7 @@ document.addEventListener('DOMContentLoaded', () => {
const tagsTextInput = form.querySelector('.bookmark-tags-text-input');
const readLaterCheckbox = form.querySelector('.bookmark-toggle-readlater');
const noteCheckbox = form.querySelector('.bookmark-toggle-note');
const todoCheckbox = form.querySelector('.bookmark-toggle-todo');
const titleInput = form.querySelector('input[name="lf_title"]');
const isDarkTheme = document.documentElement.getAttribute('data-theme') === 'dark';
@@ -1677,6 +1679,11 @@ document.addEventListener('DOMContentLoaded', () => {
.map((tag) => normalizeTag(tag))
.filter(Boolean);
// Remove duplicate tags (case-insensitive)
tags = tags.filter((tag, index, self) =>
index === self.findIndex((t) => t.toLowerCase() === tag.toLowerCase())
);
// Update return URL based on bookmark type (note vs regular)
const returnUrlInput = form.querySelector('input[name="returnurl"]');
if (returnUrlInput) {
@@ -1702,19 +1709,35 @@ document.addEventListener('DOMContentLoaded', () => {
noteCheckbox.checked = tags.some((tag) => /^note$/i.test(tag));
};
const syncTodoCheckbox = () => {
if (!todoCheckbox) return;
todoCheckbox.checked = tags.some((tag) => /^shaarli-todo$/i.test(tag));
};
// Helper functions to manage note emoji in title
const NOTE_EMOJI = '📝';
const TODO_EMOJI = '✅';
const hasNoteEmoji = (title) => {
return title && title.startsWith(NOTE_EMOJI);
};
const hasTodoEmoji = (title) => {
return title && title.startsWith(TODO_EMOJI);
};
const addNoteEmoji = (title) => {
if (!title) return NOTE_EMOJI + ' ';
if (hasNoteEmoji(title)) return title;
return NOTE_EMOJI + ' ' + title;
};
const addTodoEmoji = (title) => {
if (!title) return TODO_EMOJI + ' ';
if (hasTodoEmoji(title)) return title;
return TODO_EMOJI + ' ' + title;
};
const removeNoteEmoji = (title) => {
if (!title) return '';
if (title.startsWith(NOTE_EMOJI + ' ')) {
@@ -1726,6 +1749,17 @@ document.addEventListener('DOMContentLoaded', () => {
return title;
};
const removeTodoEmoji = (title) => {
if (!title) return '';
if (title.startsWith(TODO_EMOJI + ' ')) {
return title.substring(TODO_EMOJI.length + 1);
}
if (title.startsWith(TODO_EMOJI)) {
return title.substring(TODO_EMOJI.length);
}
return title;
};
const updateNoteTitle = (isNote) => {
if (!titleInput) return;
const currentTitle = titleInput.value || '';
@@ -1739,6 +1773,20 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
};
const updateTodoTitle = (isTodo) => {
if (!titleInput) return;
const currentTitle = titleInput.value || '';
if (isTodo) {
if (!hasTodoEmoji(currentTitle)) {
titleInput.value = addTodoEmoji(currentTitle);
}
} else {
if (hasTodoEmoji(currentTitle)) {
titleInput.value = removeTodoEmoji(currentTitle);
}
}
};
const addTag = (rawTag) => {
const tag = normalizeTag(rawTag);
@@ -1750,6 +1798,7 @@ document.addEventListener('DOMContentLoaded', () => {
updateHiddenTags();
syncReadLaterCheckbox();
syncNoteCheckbox();
syncTodoCheckbox();
renderTags();
refreshAutocomplete();
}
@@ -1760,6 +1809,7 @@ document.addEventListener('DOMContentLoaded', () => {
updateHiddenTags();
syncReadLaterCheckbox();
syncNoteCheckbox();
syncTodoCheckbox();
renderTags();
refreshAutocomplete();
};
@@ -1861,6 +1911,26 @@ document.addEventListener('DOMContentLoaded', () => {
updateHiddenTags();
syncReadLaterCheckbox();
syncTodoCheckbox();
renderTags();
refreshAutocomplete();
});
}
if (todoCheckbox) {
todoCheckbox.addEventListener('change', () => {
tags = tags.filter((tag) => !/^shaarli-todo$/i.test(tag));
if (todoCheckbox.checked) {
tags.push('shaarli-todo');
updateTodoTitle(true);
} else {
updateTodoTitle(false);
}
updateHiddenTags();
syncReadLaterCheckbox();
syncNoteCheckbox();
renderTags();
refreshAutocomplete();
});
@@ -1889,13 +1959,24 @@ document.addEventListener('DOMContentLoaded', () => {
updateHiddenTags();
syncReadLaterCheckbox();
syncNoteCheckbox();
syncTodoCheckbox();
renderTags();
refreshAutocomplete();
// Initial title update if note tag is present
if (noteCheckbox && noteCheckbox.checked && titleInput) {
// Initial title update if note or todo tag is present
if (todoCheckbox && todoCheckbox.checked && titleInput) {
const currentTitle = titleInput.value || '';
if (!hasNoteEmoji(currentTitle) && !currentTitle.trim()) {
if (!hasTodoEmoji(currentTitle) && !currentTitle.trim()) {
titleInput.value = TODO_EMOJI + ' ';
} else if (!hasTodoEmoji(currentTitle)) {
titleInput.value = addTodoEmoji(currentTitle);
}
} else if (noteCheckbox && noteCheckbox.checked && titleInput) {
const currentTitle = titleInput.value || '';
// Replace default "Note:" title with just the emoji
if (currentTitle === 'Note:' || currentTitle === 'Note') {
titleInput.value = NOTE_EMOJI + ' ';
} else if (!hasNoteEmoji(currentTitle) && !currentTitle.trim()) {
titleInput.value = NOTE_EMOJI + ' ';
} else if (!hasNoteEmoji(currentTitle)) {
titleInput.value = addNoteEmoji(currentTitle);