feat: star favoris dynamique + sidebar refresh sans reload
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped

- page_editor: étoile ⭐/☆ dynamique selon favorited (x-text)
- toggleFavorite() appelle refreshFavorites() du sidebar
- base.html: refreshFavorites() fetch /api/library/favorites + rebuild DOM
- Context menu 'Add to Favorites' → refreshFavorites() au lieu de location.reload()
- Les items dynamiques ont onclick/oncontextmenu pour fonctionner hors Alpine
This commit is contained in:
2026-07-17 23:50:37 -04:00
parent 25386bc79f
commit fe64617677
2 changed files with 39 additions and 4 deletions
+35 -2
View File
@@ -601,6 +601,40 @@
// ── Favorites tracking ──
favoriteIds: new Set([{% for p in favorite_pages %}'{{ p.id }}'{% if not loop.last %},{% endif %}{% endfor %}]),
refreshFavorites() {
const self = this;
fetch('/api/library/favorites', {credentials:'same-origin'})
.then(r => r.json())
.then(data => {
const items = data.items || [];
self.favoriteIds = new Set(items.map(i => i.id));
const favList = document.querySelector('.sidebar-items[data-section="favorites"]');
if (!favList) return;
if (items.length === 0) {
favList.innerHTML = '<li class="sidebar-item empty-hint"><span class="page-icon">⭐</span><span class="page-name text-dim">No favorites yet</span></li>';
} else {
favList.innerHTML = items.map(function(item) {
const icon = item.icon || '📄';
const name = (item.title || 'Untitled');
const safeName = name.replace(/'/g, "\\'").replace(/"/g, '&quot;');
const url = item.url || ('/pages/' + item.id);
return '<li class="sidebar-item" style="padding-left:8px;cursor:pointer" ' +
'data-page-id="' + item.id + '" data-page-depth="0" data-has-children="false" ' +
'onclick="window.appState.navigateTo(\'' + url + '\')" ' +
'oncontextmenu="event.preventDefault();window.appState.showContextMenu(event,\'' + item.id + '\',\'' + safeName + '\')">' +
'<span class="tree-toggle-placeholder"></span>' +
'<span class="page-icon">' + icon + '</span>' +
'<span class="page-name">' + safeName + '</span>' +
'<span class="item-actions">' +
'<button class="item-action-btn" title="View in Library" onclick="event.stopPropagation();window.appState.navigateTo(\'/library\')">📚</button>' +
'<button class="item-action-btn" title="New sub-page" onclick="event.stopPropagation();window.appState.newSubPage(\'' + item.id + '\')">+</button>' +
'</span>' +
'</li>';
}).join('');
}
});
},
// ── Sections collapsible ──
sectionsOpen: (function() {
@@ -727,8 +761,7 @@
if (isFav) self.favoriteIds.delete(pageId);
else self.favoriteIds.add(pageId);
self.contextMenu.visible = false;
// Reload to update sidebar
location.reload();
self.refreshFavorites();
})
.catch(e => this.toast('Favorite action failed', 'error'));
break;
+4 -2
View File
@@ -2,7 +2,7 @@
page_title %}{{ page.title }}{% endblock %} {% block topbar %}
{% set page_icon = "📄" if page.content_format != 'file' else "📎" %}
{% set page_title = page.title %}
{% set right_actions = '<span class="topbar-edited" style="cursor:pointer;" @click="window.E && window.E.toggleActivityOpen()">Edited <span x-text="window.E && window.E.timeAgo || \'\'"></span> ▾</span><button class="topbar-btn share-btn" @click="window.E && window.E.toggleShareOpen()">🔒 Share ▾</button><button class="topbar-btn" @click="window.E && window.E.copyPageLink()" title="Copy link">🔗</button><button class="topbar-btn star-btn" @click="window.E && window.E.toggleFavorite()">⭐</button><button class="topbar-btn relative" @click="window.E && window.E.toggleMoreOpen()">⋯</button>' %}
{% set right_actions = '<span class="topbar-edited" style="cursor:pointer;" @click="window.E && window.E.toggleActivityOpen()">Edited <span x-text="window.E && window.E.timeAgo || \'\'"></span> ▾</span><button class="topbar-btn share-btn" @click="window.E && window.E.toggleShareOpen()">🔒 Share ▾</button><button class="topbar-btn" @click="window.E && window.E.copyPageLink()" title="Copy link">🔗</button><button class="topbar-btn star-btn" @click="window.E && window.E.toggleFavorite()" x-text="(window.E && window.E.favorited) ? \'⭐\' : \'☆\'"></button><button class="topbar-btn relative" @click="window.E && window.E.toggleMoreOpen()">⋯</button>' %}
{% include '_header.html' %}
{% endblock %} {% block content %}
<div class="page-editor-wrapper" x-data="editorState()">
@@ -686,7 +686,9 @@ page_title %}{{ page.title }}{% endblock %} {% block topbar %}
const csrf=document.cookie.match(/csrf_token=([^;]+)/);
const m=this.favorited?'DELETE':'POST';
fetch(`/board/api/favorites/${this.pid}`,{method:m,headers:{'X-CSRF-Token':csrf?csrf[1]:''}})
.then(r=>r.json()).then(()=>{this.favorited=!this.favorited;this.showToast(this.favorited?'Added to favorites':'Removed from favorites');}).catch(()=>{this.showToast('Failed to toggle favorite');});
.then(r=>r.json()).then(()=>{this.favorited=!this.favorited;this.showToast(this.favorited?'Added to favorites':'Removed from favorites');
if(window.appState&&window.appState.refreshFavorites)window.appState.refreshFavorites();
}).catch(()=>{this.showToast('Failed to toggle favorite');});
},
toggleActivityOpen(){
if(!this.activityOpen){this.activityOpen=true;this._justOpened=true;setTimeout(()=>this._justOpened=false,200);}