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:
+111
-12
@@ -1,6 +1,12 @@
|
||||
// Config de la page courante — relue à CHAQUE exécution du script : sur une
|
||||
// navigation partielle htmx le <script> est ré-exécuté (avec le nouveau
|
||||
// #lw-config) alors que le corps du fichier est verrouillé par le garde
|
||||
// ci-dessous. Avec un `const` capturé à la première exécution, la page
|
||||
// gardait le folder/workspace du premier chargement.
|
||||
var LW = (function(){try{var el=document.getElementById('lw-config');return el?JSON.parse(el.textContent):{}}catch{return {}}})();
|
||||
|
||||
if (!window.__fdLocalWorkspaceScriptsLoaded) {
|
||||
window.__fdLocalWorkspaceScriptsLoaded = true;
|
||||
const LW=(()=>{try{const el=document.getElementById('lw-config');return el?JSON.parse(el.textContent):{}}catch{return {}}})();
|
||||
|
||||
console.log('FLOWDECK v2.4.6 — IIFE running, about to set _wsInitData');
|
||||
const _wsInitData = (function() { // lexical : NON propriété globalThis →
|
||||
@@ -150,6 +156,19 @@ const _wsInitData = (function() { // lexical : NON propriété globalThis →
|
||||
try {
|
||||
// Expose globally for event handlers (BEFORE await)
|
||||
window._wsData = this;
|
||||
// Navigation partielle : l'objet est partagé entre les montages →
|
||||
// on recale l'état sur la page courante (#lw-config fraîchement lu)
|
||||
// et on remet à zéro les panneaux/menus de la vue précédente.
|
||||
if (LW && LW.current_folder_id != null) this.currentFolder = LW.current_folder_id;
|
||||
this.searchQuery = '';
|
||||
this.filterType = '';
|
||||
this.editingPath = false;
|
||||
this.previewItem = null;
|
||||
this.uploading = false;
|
||||
this.undoVisible = false;
|
||||
this.moveModalOpen = false;
|
||||
this.isDragging = false;
|
||||
this.clearSelection();
|
||||
var url = '/api/local-workspace/tree';
|
||||
if (this.currentFolder && this.currentFolder > 0) {
|
||||
url += '?folder=' + this.currentFolder;
|
||||
@@ -197,6 +216,32 @@ const _wsInitData = (function() { // lexical : NON propriété globalThis →
|
||||
} catch(e) { console.error('Tree load error:', e); this.loaded = true; }
|
||||
this._recompute();
|
||||
this._initResizeHandle();
|
||||
this._syncHeaderPath();
|
||||
},
|
||||
|
||||
/** Recalcule le chemin du header (Home / <workspace> / <dossier> / …) à
|
||||
partir de la pile de dossiers courante — même mécanique que le rendu
|
||||
serveur, mais sans rechargement. */
|
||||
_syncHeaderPath() {
|
||||
var stack = this.folderStack || [];
|
||||
var crumbs = [
|
||||
{ label: 'Home', url: '/workspaces', id: null, icon: 'home', menu: false, home: true },
|
||||
{ label: (LW && LW.workspace_name) || 'Workspace', url: '/local-workspace',
|
||||
id: null, icon: 'folder', menu: false }
|
||||
];
|
||||
for (var i = 0; i < stack.length; i++) {
|
||||
var last = (i === stack.length - 1);
|
||||
crumbs.push({
|
||||
label: stack[i].name,
|
||||
url: last ? null : '/local-workspace?folder=' + stack[i].id,
|
||||
id: stack[i].id,
|
||||
icon: 'folder',
|
||||
menu: true
|
||||
});
|
||||
}
|
||||
window.dispatchEvent(new CustomEvent('flowdeck:breadcrumb-changed', {
|
||||
detail: { workspace_id: (LW && LW.workspace_id) || 0, crumbs: crumbs }
|
||||
}));
|
||||
},
|
||||
|
||||
_initResizeHandle() {
|
||||
@@ -486,6 +531,12 @@ const _wsInitData = (function() { // lexical : NON propriété globalThis →
|
||||
if (b.type === 'code' || b.type === 'code_block') {
|
||||
tag = 'pre';
|
||||
style += 'background:var(--bg-tertiary);padding:10px 14px;border-radius:6px;font-family:monospace;font-size:12px;white-space:pre-wrap;';
|
||||
var cLang = (window.FDHighlight && b.language) ? FDHighlight.normalizeLang(b.language) : '';
|
||||
var cAttr = (cLang && cLang !== 'plaintext') ? ' data-hljs data-hljs-lang="' + cLang + '" class="hljs language-' + cLang + '"' : '';
|
||||
var cHtml = (window.FDHighlight && cLang && cLang !== 'plaintext')
|
||||
? (function(){ try { return FDHighlight.highlightText(txt, cLang); } catch (e) { return ''; } })() || this._escHtml(txt)
|
||||
: this._escHtml(txt);
|
||||
return '<' + tag + ' style="' + style + '"><code' + cAttr + '>' + (txt ? cHtml : '<br>') + '</code></' + tag + '>';
|
||||
}
|
||||
return '<' + tag + ' style="' + style + '">' + (txt ? this._escHtml(txt) : '<br>') + '</' + tag + '>';
|
||||
}, this).join('');
|
||||
@@ -493,7 +544,7 @@ const _wsInitData = (function() { // lexical : NON propriété globalThis →
|
||||
preview = '<div style="text-align:center;padding:30px;color:var(--text-tertiary)">Empty document</div>';
|
||||
}
|
||||
} catch {
|
||||
preview = '<pre style="background:var(--bg-tertiary);padding:14px;border-radius:6px;overflow:auto;font-size:12px;font-family:monospace;white-space:pre-wrap;">' + this._escHtml(String(content).substring(0, 3000)) + '</pre>';
|
||||
preview = this._codeHtml(String(content).substring(0, 3000), node.name, 3000);
|
||||
}
|
||||
|
||||
} else if (format === 'markdown') {
|
||||
@@ -501,15 +552,16 @@ const _wsInitData = (function() { // lexical : NON propriété globalThis →
|
||||
preview = '<div style="white-space:pre-wrap;line-height:1.6;font-size:13px;">' + this._escHtml(String(content).substring(0, 5000)) + '</div>';
|
||||
|
||||
} else {
|
||||
// Unknown format — show as text
|
||||
// Unknown format — show as (highlighted) text
|
||||
var display = String(content || '').trim();
|
||||
if (display) {
|
||||
preview = '<pre style="background:var(--bg-tertiary);padding:14px;border-radius:6px;overflow:auto;font-size:12px;font-family:monospace;white-space:pre-wrap;">' + this._escHtml(display.substring(0, 5000)) + '</pre>';
|
||||
preview = this._codeHtml(display, node.name, 5000);
|
||||
} else {
|
||||
preview = '<div style="text-align:center;padding:30px;color:var(--text-tertiary)">No content</div>';
|
||||
}
|
||||
}
|
||||
body.innerHTML = preview || '<div style="color:var(--text-tertiary);text-align:center;padding:20px">(empty)</div>';
|
||||
try { if (window.FDHighlight) FDHighlight.highlightAllIn(body); } catch (e) {}
|
||||
} catch {
|
||||
body.innerHTML = '<div style="text-align:center;padding:20px;color:var(--text-tertiary)">Preview not available</div>';
|
||||
}
|
||||
@@ -519,6 +571,23 @@ const _wsInitData = (function() { // lexical : NON propriété globalThis →
|
||||
return String(s||'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
||||
},
|
||||
|
||||
// ── hljs: code-aware <pre> for previews (FDHighlight, ~230 file types).
|
||||
// Falls back to escaped plain text when the helper is unavailable.
|
||||
_codeHtml(text, name, maxLen) {
|
||||
var t = String(text == null ? '' : text).substring(0, maxLen || 5000);
|
||||
if (window.FDHighlight) {
|
||||
try {
|
||||
var lang = FDHighlight.langForFile(name || '');
|
||||
var inner = FDHighlight.highlightText(t, lang);
|
||||
var cls = lang !== 'plaintext' ? ' class="hljs language-' + lang + '"' : '';
|
||||
return '<pre style="background:var(--bg-tertiary);padding:14px;border-radius:6px;overflow:auto;font-size:12px;font-family:monospace;white-space:pre-wrap;">'
|
||||
+ '<code' + cls + '>' + inner + '</code></pre>';
|
||||
} catch (e) { /* fallback below */ }
|
||||
}
|
||||
return '<pre style="background:var(--bg-tertiary);padding:14px;border-radius:6px;overflow:auto;font-size:12px;font-family:monospace;white-space:pre-wrap;">'
|
||||
+ this._escHtml(t) + '</pre>';
|
||||
},
|
||||
|
||||
// ── Content snippet for content view ──
|
||||
contentSnippetMap: {},
|
||||
|
||||
@@ -765,7 +834,10 @@ const _wsInitData = (function() { // lexical : NON propriété globalThis →
|
||||
html += '<span class="icon">'+icon+'</span>';
|
||||
if (c.is_folder) {
|
||||
var countBadge = (c.child_count > 0) ? ' <span class="child-count">('+c.child_count+')</span>' : '';
|
||||
html += '<span class="name folder" onclick="window._wsData.navigateToFolder('+escId+')">'+c.name+countBadge+'</span>';
|
||||
var statusBadges = (c.is_shared ? '<span class="ws-shared-badge" title="Partag\u00e9" onclick="event.stopPropagation()">👥</span>' : '')
|
||||
+ (c.favorited ? '<span class="ws-fav-badge" title="Favorite">★</span>' : '')
|
||||
+ (c.published ? '<span class="ws-pub-badge" title="Published to web">🌐</span>' : '');
|
||||
html += '<span class="name folder" onclick="window._wsData.navigateToFolder('+escId+')">'+statusBadges+c.name+countBadge+'</span>';
|
||||
} else {
|
||||
// Click to rename, double-click to open
|
||||
html += '<span class="name file" onclick="event.stopPropagation();window._wsData.startRenderChildRename('+escId+',\''+escName+'\')"'
|
||||
@@ -773,8 +845,10 @@ const _wsInitData = (function() { // lexical : NON propriété globalThis →
|
||||
+ ' title="Click to rename, double-click to open"'
|
||||
+ ' onmouseenter="var d=window._wsData;if(d)d.showPreview(event,{id:'+escId+',name:\''+escName+'\',is_folder:false,content_format:\''+(c.content_format||'')+'\'})"'
|
||||
+ ' onmouseleave="var d=window._wsData;if(d)d.hidePreview()">'
|
||||
+ (c.is_shared ? '<span class="ws-shared-badge" title="Partag\u00e9" onclick="event.stopPropagation()">👥</span>' : '')
|
||||
+ '<span id="rc-name-'+escId+'">'+c.name+'</span>'
|
||||
+ (c.is_shared ? '<span class="ws-shared-badge" title="Partag\u00e9" onclick="event.stopPropagation()">👥</span>' : '')
|
||||
+ (c.favorited ? '<span class="ws-fav-badge" title="Favorite">★</span>' : '')
|
||||
+ (c.published ? '<span class="ws-pub-badge" title="Published to web">🌐</span>' : '')
|
||||
+ '<span id="rc-name-'+escId+'">'+c.name+'</span>'
|
||||
+ '<input id="rc-input-'+escId+'" class="inline-rename-input" style="display:none"'
|
||||
+ ' onkeydown="if(event.key===\'Enter\')window._wsData.commitRenderChildRename('+escId+')"'
|
||||
+ ' onblur="window._wsData.commitRenderChildRename('+escId+')"'
|
||||
@@ -956,6 +1030,7 @@ const _wsInitData = (function() { // lexical : NON propriété globalThis →
|
||||
this._saveExpandedState();
|
||||
this.previewItem = null;
|
||||
this._recompute();
|
||||
this._syncHeaderPath();
|
||||
var self = this;
|
||||
this.$nextTick(function() { self._syncCheckboxes(); });
|
||||
} catch(e) { console.error('Load folder error:', e); }
|
||||
@@ -1230,8 +1305,9 @@ const _wsInitData = (function() { // lexical : NON propriété globalThis →
|
||||
} else {
|
||||
this.previewContent = 'Loading...';
|
||||
this._fetchRetry(url).then(function(t) {
|
||||
var content = t.substring(0, 500);
|
||||
self.previewContent = content; self.previewCache[cacheKey] = content;
|
||||
var html = self._codeHtml(t, node.name, 500);
|
||||
// Tooltip body is already monospace/pre-wrap: keep it lightweight.
|
||||
self.previewContent = html; self.previewCache[cacheKey] = html;
|
||||
}).catch(function() { self.previewContent = '(preview not available)'; });
|
||||
}
|
||||
} else {
|
||||
@@ -2043,9 +2119,32 @@ console.log('FLOWDECK v2.4.6 — IIFE done, _wsInitData keys:', Object.keys(_wsI
|
||||
// A20 ph3 : registre Alpine.data (le build CSP ne résout que le registre ;
|
||||
// x-data="wsInitData()" au lieu de l'identifiant global _wsInitData).
|
||||
var _regWs = function () {
|
||||
// objet partagé JS↔Alpine via closure LEXICALE (const hors window →
|
||||
// non snapshoté par ji ; probe4 : factory retournant l'objet = accepté).
|
||||
Alpine.data('wsInitData', function () { return _wsInitData; });
|
||||
// Objet partagé JS↔Alpine via closure LEXICALE (const hors window →
|
||||
// non snapshoté par ji). L'état reste unique, MAIS la factory Alpine
|
||||
// doit retourner une enveloppe FRAÎCHE à chaque montage : Alpine pose ses
|
||||
// magics ($nextTick…) avec Object.defineProperty non-configurable. En
|
||||
// retournant le même objet, le 2e montage (navigation partielle
|
||||
// /local-workspace → /local-workspace?folder=) levait
|
||||
// « Cannot redefine property: $nextTick », initTree échouait et TOUT le
|
||||
// contenu restait au state brut (tous les x-show visibles à la fois).
|
||||
// L'enveloppe délègue lecture/écriture au proxy réactif partagé.
|
||||
Alpine.data('wsInitData', function () {
|
||||
var R = Alpine.reactive(_wsInitData);
|
||||
var t = {};
|
||||
Object.keys(_wsInitData).forEach(function (k) {
|
||||
if (typeof _wsInitData[k] === 'function') {
|
||||
t[k] = _wsInitData[k];
|
||||
} else {
|
||||
Object.defineProperty(t, k, {
|
||||
get: function () { return R[k]; },
|
||||
set: function (v) { R[k] = v; },
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
return t;
|
||||
});
|
||||
// Bloc preview : le parseur le place HORS de la div racine (structure
|
||||
// pré-existante) → montage distinct DÉLÉGUANT vers l'objet partagé.
|
||||
// Wrapper = objet unique (les magics $nextTick… ne sont redéfinissables
|
||||
|
||||
Reference in New Issue
Block a user