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:
parent
adb2564153
commit
fb5254445f
@ -278,6 +278,58 @@ a:focus:not(:focus-visible) {
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Sidebar Segmented Add Button */
|
||||
.sidebar-add-segmented {
|
||||
display: flex;
|
||||
background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%);
|
||||
border-radius: 0.75rem;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 6px -1px rgba(59, 130, 246, 0.3), 0 2px 4px -1px rgba(59, 130, 246, 0.2);
|
||||
}
|
||||
|
||||
.sidebar-add-segment {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
flex: 1;
|
||||
padding: 0.875rem 0.5rem;
|
||||
background: transparent;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s ease;
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
.sidebar-add-segment:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.sidebar-add-segment:hover {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.sidebar-add-segment i {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.sidebar-add-segment span {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* Responsive: hide text on small screens */
|
||||
@media (max-width: 240px) {
|
||||
.sidebar-add-segment span {
|
||||
display: none;
|
||||
}
|
||||
.sidebar-add-segment {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Theme toggle in sidebar */
|
||||
.theme-toggle-wrapper {
|
||||
display: flex;
|
||||
|
||||
@ -17,12 +17,20 @@
|
||||
{/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="($noteDefaultChecked = $link_is_new && empty($link.url)) ? '' : ''"}
|
||||
{function="($isNoteOrTodo = strpos(' ' . $link.tags . ' ', ' note ') != false || strpos(' ' . $link.tags . ' ', ' shaarli-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) ? '' : ''"}
|
||||
{$effectiveTags=$link.tags}
|
||||
{if="$noteDefaultChecked && strpos(' ' . $effectiveTags . ' ', ' note ') == false"}
|
||||
{$effectiveTags=trim($effectiveTags . ' note')}
|
||||
{/if}
|
||||
{if="$todoChecked && strpos(' ' . $effectiveTags . ' ', ' note ') != false"}
|
||||
{$effectiveTags=trim(str_replace(' note ', ' ', ' ' . $effectiveTags . ' '))}
|
||||
{/if}
|
||||
{if="$todoChecked && strpos(' ' . $effectiveTags . ' ', ' shaarli-todo ') == false"}
|
||||
{$effectiveTags=trim($effectiveTags . ' shaarli-todo')}
|
||||
{/if}
|
||||
{function="($privateChecked = $link.private == true || $link_is_new) ? '' : ''"}
|
||||
<div id="editlinkform{$index}" class="editlinkform container page-edit">
|
||||
<div class="row editlinkform-row">
|
||||
@ -46,7 +54,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group bookmark-field-group">
|
||||
<div class="form-group bookmark-field-group {if="$isNoteOrTodo"}hidden{/if}">
|
||||
<label class="form-label" for="lf_url{$index}">{'URL'|t}</label>
|
||||
<input type="text" class="form-control lf_input" name="lf_url" id="lf_url{$index}" value="{$link.url}" placeholder="https://...">
|
||||
</div>
|
||||
@ -101,6 +109,10 @@
|
||||
<input type="checkbox" id="lf_note{$index}" class="bookmark-toggle-note" {if="$noteChecked"}checked="checked"{/if}>
|
||||
<span>Note</span>
|
||||
</label>
|
||||
<label class="bookmark-toggle-item" for="lf_todo{$index}">
|
||||
<input type="checkbox" id="lf_todo{$index}" class="bookmark-toggle-todo" {if="$todoChecked"}checked="checked"{/if}>
|
||||
<span>Todo</span>
|
||||
</label>
|
||||
<label class="bookmark-toggle-item" for="lf_private{$index}">
|
||||
<input type="checkbox" {if="$privateChecked"}checked="checked"{/if} name="lf_private" id="lf_private{$index}" />
|
||||
<span>{'Private'|t}</span>
|
||||
|
||||
@ -207,7 +207,7 @@
|
||||
{ tag: 'notebg-*', description: 'Note background images' },
|
||||
{ tag: 'notefilter-*', description: 'Note filter categories' },
|
||||
{ tag: 'readitlater', description: 'Read It Later items' },
|
||||
{ tag: 'shaarli-archiver', description: 'Archived notes' }
|
||||
{ tag: 'shaarli-archive', description: 'Archived notes' }
|
||||
];
|
||||
|
||||
const STORAGE_KEY = 'shaarli_hidden_tags';
|
||||
|
||||
@ -23,7 +23,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
|
||||
if (!linkList || !container) return;
|
||||
|
||||
if (searchTags === "todo") {
|
||||
if (searchTags === "shaarli-todo") {
|
||||
initTodoView(linkList, container);
|
||||
} else if (searchTags === "note") {
|
||||
// Pour la vue notes, parser les notes AVANT de supprimer les tags techniques
|
||||
|
||||
@ -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 || '';
|
||||
@ -1740,6 +1774,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);
|
||||
if (!tag) return;
|
||||
@ -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);
|
||||
|
||||
@ -49,7 +49,7 @@ Bookmarklet detection logic
|
||||
<i class="mdi mdi-calendar-today" aria-hidden="true"></i>
|
||||
<span>Quotidien</span>
|
||||
</a>
|
||||
<a href="{$base_path}/?searchtags=todo" class="sidebar-link{if="isset($search_tags) && preg_match('/(^|[\s,])todo([\s,]|$)/i', (string) $search_tags)"} active{/if}" aria-label="Mes tâches">
|
||||
<a href="{$base_path}/?searchtags=shaarli-todo" class="sidebar-link{if="isset($search_tags) && preg_match('/(^|[\s,])shaarli-todo([\s,]|$)/i', (string) $search_tags)"} active{/if}" aria-label="Mes tâches">
|
||||
<i class="mdi mdi-check-circle-outline" aria-hidden="true"></i>
|
||||
<span>Mes tâches</span>
|
||||
</a>
|
||||
@ -86,10 +86,17 @@ Bookmarklet detection logic
|
||||
|
||||
<div class="sidebar-footer">
|
||||
{if="$is_logged_in"}
|
||||
<a href="{$base_path}/admin/add-shaare" class="sidebar-add-btn" aria-label="Nouveau bookmark">
|
||||
<i class="mdi mdi-plus" aria-hidden="true"></i>
|
||||
<span>Nouveau bookmark</span>
|
||||
<div class="sidebar-add-segmented">
|
||||
<a href="{$base_path}/admin/add-shaare" class="sidebar-add-segment" title="Bookmark">
|
||||
<i class="mdi mdi-bookmark-outline"></i>
|
||||
</a>
|
||||
<a href="{$base_path}/admin/shaare?tags=note" class="sidebar-add-segment" title="Note">
|
||||
<i class="mdi mdi-note-text-outline"></i>
|
||||
</a>
|
||||
<a href="{$base_path}/admin/shaare?post=http%3A%2F%2Fshaarli-todo&tags=shaarli-todo&title=%E2%9C%85%20" class="sidebar-add-segment" title="Todo">
|
||||
<i class="mdi mdi-check-circle-outline"></i>
|
||||
</a>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="theme-toggle-wrapper">
|
||||
@ -147,7 +154,7 @@ Bookmarklet detection logic
|
||||
<i class="mdi mdi-calendar" aria-hidden="true"></i>
|
||||
<span>QUOTIDIEN</span>
|
||||
</a>
|
||||
<a href="{$base_path}/?searchtags=todo" class="header-nav-link{if="isset($search_tags) && preg_match('/(^|[\s,])todo([\s,]|$)/i', (string) $search_tags)"} active{/if}" aria-label="Mes tâches">
|
||||
<a href="{$base_path}/?searchtags=shaarli-todo" class="header-nav-link{if="isset($search_tags) && preg_match('/(^|[\s,])shaarli-todo([\s,]|$)/i', (string) $search_tags)"} active{/if}" aria-label="Mes tâches">
|
||||
<i class="mdi mdi-check-circle-outline" aria-hidden="true"></i>
|
||||
<span>TÂCHES</span>
|
||||
</a>
|
||||
|
||||
@ -437,7 +437,7 @@
|
||||
{ name: 'notebg-*', desc: 'Note Backgrounds - Wildcard for background tags' },
|
||||
{ name: 'notefilter-*', desc: 'Note Filters - Wildcard for filter tags' },
|
||||
{ name: 'readitlater', desc: 'Read Later - Temporary reading list' },
|
||||
{ name: 'shaarli-archiver', desc: 'Archived - Archived notes' }
|
||||
{ name: 'shaarli-archive', desc: 'Archived - Archived notes' }
|
||||
];
|
||||
|
||||
const DEFAULT_HIDDEN_TAGS = PRESET_TAGS.map(t => t.name);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user