fix: /local-workspace — chemin du header + clic sur un dossier du sidebar (v7.69.9)
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:
+67
-9
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user