fix: /local-workspace — chemin du header + clic sur un dossier du sidebar (v7.69.9)
FlowDeck CI / lint (push) Successful in 1m37s
FlowDeck CI / test (push) Failing after 24m19s
FlowDeck CI / docker (push) Skipped

Corrige deux régressions de /local-workspace :

- le chemin du header restait bloqué sur « Home / <workspace> » quel que
  soit le dossier affiché : la route rend désormais breadcrumb_items
  (Home / <workspace> / <dossier> / <sous-dossier>, niveaux cliquables,
  collapse « … » au-delà de 4) et la navigation sans rechargement recalcule
  le chemin via l'event flowdeck:breadcrumb-changed ;
- le clic sur un dossier du sidebar affichait TOUS les composants à la
  fois : Alpine.data('wsInitData') retournait le même objet singleton, le
  2e montage (navigation partielle) levait « Cannot redefine property:
  \ » et initTree abandonnait, laissant tout le contenu au state
  brut. La factory retourne désormais une enveloppe fraîche par montage
  qui délègue à l'état réactif partagé. #lw-config est aussi relu à chaque
  exécution (le 2e montage gardait le folder_id du 1er chargement).

Inclus également le travail en cours de l'arbre : Library (colonnes Last
visited/Source, ordre d'en-tête, favoris à icônes Workspace), Meeting
Notes (bloc, CSS, routes, docs), coloration de code hljs, badges
favori/publié dans l'arbre local-workspace, docs (DATA_MODEL,
architectures) et tests associés.
This commit is contained in:
2026-10-09 16:58:04 -04:00
parent 7468e497c6
commit 0218d8f5e5
44 changed files with 12060 additions and 1729 deletions
+67 -9
View File
@@ -35,6 +35,9 @@ function libraryPage() {
shareDir: 'all',
showAuthor: false,
showSource: false,
showEditedBy: true,
showEditedTime: true,
showVisited: true,
// Tags
workspaceTags: [],
@@ -90,6 +93,12 @@ function libraryPage() {
for (var i = 0; i < this.flatItems.length; i++) {
var item = this.flatItems[i];
// ── En-tête de section de date (onglet AI Meeting Notes) ──
if (item._isSection) {
html += '<div class="lib-section-header">' + self._escHtml(item.title) + '</div>';
continue;
}
// ── Synthetic "+ Add new" placeholder row for an expanded parent ──
if (item._isAddNew) {
var pad = 30 + (item.depth || 0) * 20;
@@ -154,10 +163,19 @@ function libraryPage() {
html += '<div class="row-cell lib-col-author"><div class="row-author">';
html += '<div class="author-avatar">' + (item.author_initial || '?') + '</div>';
html += '<span>' + (item.author || '\u2014') + '</span></div></div>';
// Source
html += '<div class="row-cell lib-col-source"><span class="row-source">' + (item.source_label || 'Private') + '</span></div>';
// Last edited by
html += '<div class="row-cell lib-col-editedby"><div class="row-author">';
html += '<div class="author-avatar">' + (item.edited_by_initial || '?') + '</div>';
html += '<span>' + (item.edited_by || '\u2014') + '</span></div></div>';
// Edited
html += '<div class="row-cell lib-col-edited"><span class="row-time">' + self.formatTime(item.updated_at) + '</span></div>';
// Last visited
html += '<div class="row-cell lib-col-visited"><span class="row-time">' +
(item.visited_at ? self.formatTime(item.visited_at) : '\u2014') + '</span></div>';
// Source (icône de source + libellé) — en dernier, comme l'en-tête
html += '<div class="row-cell lib-col-source"><span class="row-source">' +
getSvgIcon(item.source_icon || 'file', 13) +
'<span>' + (item.source_label || 'Private') + '</span></span></div>';
html += '</div>';
}
body.innerHTML = html;
@@ -200,8 +218,9 @@ bindHtmlIcon(el) {
bindHtmlItem(el, item) {
Alpine.effect(() => { el.innerHTML = this._renderIcon(item); });
},
_renderIcon(item) {
_renderIcon(item) {
if (!item) return getSvgIcon('file', 14);
if (item.is_meeting) return getSvgIcon('calendar', 14);
// Custom emoji (image URL), icon name, or unicode emoji
if (item.page_icon) return '<span style="font-size:14px;line-height:1;display:inline-flex;align-items:center;">' + (window.fdIconHtml ? window.fdIconHtml(item.page_icon, 14) : this._escHtml(item.page_icon)) + '</span>';
if (item.is_folder || item.has_children) return getSvgIcon('folder', 14);
@@ -226,13 +245,47 @@ _renderIcon(item) {
return (r.tags || []).some(function(t) { return t.name === tf; });
});
}
if (this.tab === 'meetings') result = this._groupMeetings(result);
// Use splice for Alpine 3.14.9 reactivity (array reference reassign fails)
this.flatItems.splice(0, this.flatItems.length, ...result);
this.selectedCount = Object.keys(this.selected).length;
this.allSelected = this.flatItems.length > 0 && this.selectedCount === this.flatItems.length;
var selectable = result.filter(function(r) { return !r._isSection; });
this.allSelected = selectable.length > 0 && this.selectedCount === selectable.length;
this._renderTable();
},
// Regroupement par date pour l'onglet AI Meeting Notes : sections
// Today / Yesterday / This week / Last week / This month / Older.
_meetingBucket(updatedAt) {
var d = new Date(String(updatedAt || '').replace(' ', 'T'));
if (isNaN(d.getTime())) return 5;
var now = new Date();
var start = new Date(now.getFullYear(), now.getMonth(), now.getDate());
var diff = Math.floor((start.getTime() - new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime()) / 86400000);
if (diff <= 0) return 0;
if (diff === 1) return 1;
if (diff <= 7) return 2;
if (diff <= 14) return 3;
if (diff <= 30) return 4;
return 5;
},
_groupMeetings(items) {
var labels = ['Today', 'Yesterday', 'This week', 'Last week', 'This month', 'Older'];
var buckets = [[], [], [], [], [], []];
for (var i = 0; i < items.length; i++) {
if (items[i]._isSection) continue;
buckets[this._meetingBucket(items[i].updated_at)].push(items[i]);
}
var out = [];
for (var b = 0; b < buckets.length; b++) {
if (!buckets[b].length) continue;
out.push({ _isSection: true, id: 'sec-' + b, title: labels[b] });
for (var j = 0; j < buckets[b].length; j++) out.push(buckets[b][j]);
}
return out;
},
// ── Global dispatch (bypasses Alpine x-for reactivity issues) ──
toggleItemById(id) {
var item = this._findItem(id);
@@ -399,7 +452,7 @@ _renderIcon(item) {
},
_canAddChild() {
return this.tab !== 'favorites' && this.tab !== 'shared' && this.tab !== 'published' && this.tab !== 'repository';
return this.tab !== 'favorites' && this.tab !== 'shared' && this.tab !== 'published' && this.tab !== 'repository' && this.tab !== 'meetings';
},
_getCsrf() {
@@ -443,13 +496,14 @@ _renderIcon(item) {
emptyText: '',
_recomputeEmpty() {
var icons = {recents:'clock',favorites:'star',shared:'users',published:'globe',private:'lock',workspace:'folder',repository:'folder'};
var icons = {recents:'clock',meetings:'calendar',favorites:'star',shared:'users',published:'globe',private:'lock',workspace:'folder',repository:'folder'};
this.emptyIcon = icons[this.tab] || 'file';
var titles = {recents:'No recent pages',favorites:'No favorites',shared:'No shared pages',
var titles = {recents:'No recent pages',meetings:'No meeting notes',favorites:'No favorites',shared:'No shared pages',
published:'No published pages',private:'No private pages',workspace:'No workspace pages',
repository:'Empty repository'};
this.emptyTitle = titles[this.tab] || 'No items';
var texts = {recents:'Pages you\'ve recently viewed or edited will appear here.',
meetings:'Create a meeting note from the sidebar (+) — it will appear here, grouped by date.',
favorites:'Right-click a page in the sidebar and select "Add to Favorites".',
shared:'Shared pages from your team will appear here.',
published:'Pages published to the web will appear here.',
@@ -478,7 +532,7 @@ _renderIcon(item) {
console.log('[Library] workspaceId=' + this.workspaceId + ' isGitea=' + this.isGiteaActive);
var params = new URLSearchParams(window.location.search);
var t = params.get('tab');
if (t && ['recents','favorites','shared','published','private','workspace','repository'].indexOf(t) !== -1) {
if (t && ['recents','meetings','favorites','shared','published','private','workspace','repository'].indexOf(t) !== -1) {
this.tab = t;
}
this._recomputeEmpty();
@@ -524,7 +578,7 @@ _renderIcon(item) {
if (this.tab === 'repository' && this.isGiteaActive) {
url += sep + 'gitea_owner=' + encodeURIComponent(this.giteaOwner) + '&gitea_repo=' + encodeURIComponent(this.giteaRepo);
sep = '&';
} else if (this.tab !== 'repository' && this.tab !== 'recents' && this.tab !== 'favorites') {
} else if (this.tab !== 'repository' && this.tab !== 'recents' && this.tab !== 'favorites' && this.tab !== 'meetings') {
// Hierarchical tree view: show only root-level pages at the top
url += sep + 'tree=1';
sep = '&';
@@ -573,6 +627,9 @@ _renderIcon(item) {
toggleColumn(col) {
if (col === 'author') this.showAuthor = !this.showAuthor;
else if (col === 'source') this.showSource = !this.showSource;
else if (col === 'editedby') this.showEditedBy = !this.showEditedBy;
else if (col === 'editedtime') this.showEditedTime = !this.showEditedTime;
else if (col === 'visited') this.showVisited = !this.showVisited;
},
doSort() {
@@ -650,6 +707,7 @@ _renderIcon(item) {
} else {
var s = {};
for (var i = 0; i < this.flatItems.length; i++) {
if (this.flatItems[i]._isSection || this.flatItems[i]._isAddNew) continue;
s[this.flatItems[i].id] = true;
}
this.selected = s;