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 → // invisible au snapshot ji du build CSP (valeur bannie sinon) return { tree:[], displayTree:[], breadcrumb: [], loaded:false, showCreate:false, showRename:false, showDelete:false, showAll:false, createType:'file', newName:'', parentFolder:null, target:null, expanded: (function() { try { return JSON.parse(localStorage.getItem('fd_ws_expanded') || '{}'); } catch { return {}; } })(), currentFolder: LW.current_folder_id, searchQuery: '', filterType: '', filterOpen: false, editingPath: false, folderStack: [], // Inline rename renamingId: null, renamingName: '', renameInputRef: null, // Preview cache previewCache: {}, // Clipboard for copy/paste clipboard: [], // Sort sortBy: '', viewMode: localStorage.getItem('fd_default_view') || 'tree', viewMenuOpen: false, sortOpen: false, searchOpen: false, viewIcons: {tree:'',list:'',details:'',title:'',content:''}, viewLabels: {tree:'Tree',list:'List',details:'Details',title:'Cards',content:'Content'}, // Context menu — state lives in the shared fdCtx Alpine store (_ctx_menu.html) // Tags workspaceTags: [], tagFilter: '', tagInputId: null, tagsVersion: 0, // Preview panel previewItem: null, previewPanelContent: '', // Multi-select selectedIds: {}, selectedIdsCount: 0, lastSelectedId: null, allSelected: false, moreOpen: false, // Move-to modal moveModalOpen: false, moveIds: [], moveSearchQuery: '', moveCandidates: [], _moveAllCandidates: [], // Undo undoVisible: false, undoMessage: '', undoItemId: null, undoTimeout: null, // Upload tracking uploadSpeed: '', uploadStartTime: 0, // Path bar pathValue: '/workspace/', async navigateToRoot() { this.folderStack = []; this.navIndex = 0; this._saveExpandedState(); await this._loadFolder(0); }, async navigateToBreadcrumb(idx) { this.folderStack = this.folderStack.slice(0, idx + 1); this.navIndex = idx; this._saveExpandedState(); var entry = this.folderStack[idx]; await this._loadFolder(entry ? entry.id : 0); }, startPathEdit() { this.editingPath = true; var self = this; this.$nextTick(function() { var inp = self.$refs.pathEdit; if (inp) { inp.focus(); inp.select(); } }); }, // Drag indicators dragCount: 0, dragOverParentId: null, // Online/offline online: navigator.onLine, pendingSyncCount: 0, pinnedIds: (function() { try { return JSON.parse(localStorage.getItem('fd_ws_pinned') || '[]'); } catch { return []; } })(), // Navigation history navHistory: [], navIndex: -1, canGoBack: false, canGoForward: false, // Preview popup previewVisible: false, previewContent: '', previewName: '', previewX: 0, previewY: 0, // Drag & drop state isDragging: false, dropTarget: null, dropTargetName: '', uploading: false, uploadStatus: '', uploadProgress: 0, draggedItemId: null, async init() { try { // Expose globally for event handlers (BEFORE await) window._wsData = this; var url = '/api/local-workspace/tree'; if (this.currentFolder && this.currentFolder > 0) { url += '?folder=' + this.currentFolder; } var r = await fetch(url); var d = await r.json(); // Keep tree hierarchical, only roots in displayTree var roots = d.tree || []; this._markTree(roots, 0); this.tree = roots; this.displayTree = roots; this.breadcrumb = d.breadcrumb || []; this.loaded = true; // Initialize folder stack from breadcrumb this.folderStack = (this.breadcrumb || []).map(function(b) { return {id: b.id, name: b.name}; }); this.navIndex = this.folderStack.length > 0 ? this.folderStack.length - 1 : 0; // Expose globally for renderChildren onclick handlers window._wsData = this; // Listen for online/offline var self = this; window.addEventListener('online', function() { self.online = true; }); window.addEventListener('offline', function() { self.online = false; }); // v6.0.0 PWA: reflect offline queue state from the client module if (window.FlowOffline && window.FlowOffline.onChange) { window.FlowOffline.onChange(function(s) { self.online = s.online; self.pendingSyncCount = s.pendingSyncCount; }); } // Side-peek editor posts renames here so the tree/peek title stay in sync. window.addEventListener('message', function(ev) { if (ev.origin !== window.location.origin) return; var m = ev.data; if (!m || m.type !== 'fd-page-renamed' || m.id == null) return; var node = self._findNodeById(self.tree, m.id); if (node) node.name = m.title || 'Untitled'; self.displayTree = self.displayTree.slice(); var t = document.getElementById('ws-preview-title'); if (t && self.previewItem && self.previewItem.id === m.id) t.textContent = m.title || 'Untitled'; var side = document.querySelector('.sidebar-item.ws-tree-item[data-id="' + m.id + '"] .page-name'); if (side) side.textContent = m.title || 'Untitled'; }); // Load workspace tags this.loadWorkspaceTags(); } catch(e) { console.error('Tree load error:', e); this.loaded = true; } this._recompute(); this._initResizeHandle(); }, _initResizeHandle() { var panel = document.getElementById('ws-preview-panel'); var handle = document.getElementById('ws-preview-resize-handle'); if (!panel || !handle) return; var startX, startWidth, dragging = false; handle.addEventListener('pointerdown', function(e) { e.preventDefault(); handle.setPointerCapture(e.pointerId); startX = e.clientX; startWidth = panel.offsetWidth; dragging = true; document.body.style.userSelect = 'none'; }); handle.addEventListener('pointermove', function(e) { if (!dragging) return; var deltaX = startX - e.clientX; var newWidth = Math.max(300, Math.min(window.innerWidth * 0.9, startWidth + deltaX)); panel.style.width = newWidth + 'px'; }); handle.addEventListener('pointerup', function(e) { if (!dragging) return; document.body.style.userSelect = ''; var moved = Math.abs(e.clientX - startX); if (moved < 3) { window._wsData.closePreviewPanel(); } dragging = false; try { localStorage.setItem('fd_peek_width', panel.style.width); } catch { /* volontaire */ } handle.releasePointerCapture(e.pointerId); }); try { var saved = localStorage.getItem('fd_peek_width'); if (saved) panel.style.width = saved; } catch { /* volontaire */ } }, _recompute() { // Recompute all former getters as plain properties this.canGoBack = this.navIndex > 0; this.canGoForward = this.navIndex < this.folderStack.length - 1; this.pathValue = '/workspace/' + this.folderStack.filter(function(b){return b.id > 0}).map(function(b){return b.name}).join('/'); // flatTree var ft = []; this._flattenTree(this.tree, ft); this.flatTree = ft; // filteredTableItems this._computeFilteredTableItems(); this._syncSelection(); }, // ── Pin/Favorite items ── togglePin(id) { var idx = this.pinnedIds.indexOf(id); if (idx === -1) this.pinnedIds.push(id); else this.pinnedIds.splice(idx, 1); try { localStorage.setItem('fd_ws_pinned', JSON.stringify(this.pinnedIds)); } catch { /* volontaire */ } this._haptic(); }, isPinned(id) { return this.pinnedIds.indexOf(id) !== -1; }, // ── Multi-select ── toggleSelect(node, ev) { var e = ev || {}; if (e.shiftKey && this.lastSelectedId) { // Range select var allIds = this._collectVisibleIds(this.displayTree); var start = allIds.indexOf(this.lastSelectedId); var end = allIds.indexOf(node.id); if (start !== -1 && end !== -1) { var from = Math.min(start, end), to = Math.max(start, end); for (var i = from; i <= to; i++) this.selectedIds[allIds[i]] = true; } } else if (e.ctrlKey || e.metaKey) { // Toggle single, keep others if (!!this.selectedIds[node.id]) delete this.selectedIds[node.id]; else this.selectedIds[node.id] = true; } else { // Notion-style: toggle this item, keep the rest of the selection if (!!this.selectedIds[node.id]) delete this.selectedIds[node.id]; else this.selectedIds[node.id] = true; } this.lastSelectedId = node.id; // Force reactivity this.selectedIds = Object.assign({}, this.selectedIds); this._syncSelection(); }, _collectVisibleIds(nodes) { var ids = []; for (var i=0; i 0) { var r = this._findInTree(n.children, targetId, currentPath.concat([n.name])); if (r) return r; } } return null; }, // ── Preview panel (loads editor in iframe via /pages/{id}?embed=1) ── selectForPreview(node) { var panel = document.getElementById('ws-preview-panel'); if (!panel) return; this.previewItem = node; document.getElementById('ws-preview-icon').innerHTML = this._fileIcon(node.name, node.is_folder, node.content_format, node.page_icon); document.getElementById('ws-preview-title').textContent = node.name; var openBtn = document.getElementById('ws-preview-open-btn'); openBtn.onclick = function() { if (window.fdNavigate) window.fdNavigate('/pages/' + node.id); else window.location = '/pages/' + node.id; }; var favBtn = document.getElementById('ws-preview-fav-btn'); favBtn.onclick = function() { window.showToast('Favorite toggled', 'success'); }; var copyBtn = document.getElementById('ws-preview-copy-btn'); copyBtn.onclick = function() { var url = window.location.origin + '/pages/' + node.id; navigator.clipboard.writeText(url).then(function() { window.showToast('Link copied', 'success'); }).catch(function() { window.showToast('Failed to copy', 'error'); }); }; // Load editor in iframe var iframe = document.getElementById('ws-preview-iframe'); if (iframe) iframe.src = '/pages/' + node.id + '?embed=1'; // Show panel panel.style.display = 'flex'; requestAnimationFrame(function() { requestAnimationFrame(function() { panel.style.transform = 'translateX(0)'; }); }); }, closePreviewPanel() { var panel = document.getElementById('ws-preview-panel'); if (!panel) return; panel.style.transform = 'translateX(100%)'; var self = this; setTimeout(function() { panel.style.display = 'none'; self.previewItem = null; }, 200); }, async _loadPreviewContentVanilla(node) { var body = document.getElementById('ws-preview-body'); if (!body) return; if (node.is_folder) { body.innerHTML = '
' + '' + getSvgIcon('folder',32) + '' + '
' + (node.child_count||0) + ' item(s)
' + 'Open folder' + '
'; return; } try { var name = (node.name || '').toLowerCase(); // Images: try direct file URL if (/\.(png|jpe?g|gif|webp|svg|bmp|ico)$/.test(name)) { body.innerHTML = '
' + '' + '
'; return; } // PDF: show icon + open link if (/\.pdf$/.test(name)) { body.innerHTML = '
' + '' + getSvgIcon('file',32) + '' + '
' + this._escHtml(node.name) + '
' + '
' + (node.size_display || '') + '
' + 'Open in viewer' + '
'; return; } // Fetch content from API var r = await this._fetchRetry('/api/local-workspace/page-content/' + node.id, true); var content = r.content || ''; var format = r.format || ''; var preview = ''; if (format === 'file') { // Uploaded file — show metadata preview = '
' + '
' + getSvgIcon('file',32) + '
' + '
' + this._escHtml(node.name) + '
' + '
' + (node.size_display || '') + '
' + 'Open file' + '
'; } else if (format === 'blocks') { // FlowDeck internal page — blocks can be array or {blocks:[...]} try { var blocks = typeof content === 'string' ? JSON.parse(content) : content; var blockList = Array.isArray(blocks) ? blocks : (blocks && blocks.blocks ? blocks.blocks : []); if (blockList.length > 0) { preview = blockList.slice(0, 50).map(function(b) { var txt = b.content || b.text || (typeof b === 'string' ? b : ''); var tag = 'div'; var style = 'margin-bottom:6px;line-height:1.5;'; if (b.type && b.type.indexOf('heading') === 0) { var lvl = parseInt(b.type.split('_')[1]) || 1; tag = 'h' + Math.min(lvl + 2, 6); style += 'font-weight:600;'; } 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;'; } return '<' + tag + ' style="' + style + '">' + (txt ? this._escHtml(txt) : '
') + ''; }, this).join(''); } else { preview = '
Empty document
'; } } catch { preview = '
' + this._escHtml(String(content).substring(0, 3000)) + '
'; } } else if (format === 'markdown') { // Markdown — render as preformatted for now preview = '
' + this._escHtml(String(content).substring(0, 5000)) + '
'; } else { // Unknown format — show as text var display = String(content || '').trim(); if (display) { preview = '
' + this._escHtml(display.substring(0, 5000)) + '
'; } else { preview = '
No content
'; } } body.innerHTML = preview || '
(empty)
'; } catch { body.innerHTML = '
Preview not available
'; } }, _escHtml(s) { return String(s||'').replace(/&/g,'&').replace(//g,'>'); }, // ── Content snippet for content view ── contentSnippetMap: {}, // ── Context menu (menu unifié partagé via le store Alpine fdCtx) ── onContextMenu(ev) { var el = ev.target.closest('[data-ws-id]'); if (!el) return; var id = parseInt(el.getAttribute('data-ws-id')); if (!id) return; var node = this._findNodeById(this.displayTree, id) || this._findNodeById(this.tree, id); if (!node) return; var store = window.Alpine && Alpine.store('fdCtx'); if (!store) return; var self = this; if (!this.workspaceTags || this.workspaceTags.length === 0) this.loadWorkspaceTags(); store.setAvail(this._ctxAvailTags(node)); var handlers = { open: function (n) { self.openPage(n.id); }, openTab: function (n) { window.open('/pages/' + n.id, '_blank'); }, peek: function (n) { self.openSidePeek(n.id); }, folder: function (n) { if (n.is_folder) self.navigateToFolder(n.id); }, rename: function (n) { self.startRename(n); }, setIcon: function (icon) { self.setItemIcon(node, icon); }, duplicate: function (n) { self.duplicateItem(n); }, link: function (n) { self.copyItemLink(n); }, download: function (n) { self.downloadItem(n); }, copyContent: function (n) { self.copyItemContent(n); }, move: function (n) { self._ctxMove(n); }, fav: function (n) { self.toggleFavorite(n); }, delete: function (n) { self.deleteWithUndo(n); }, tagExisting: function (t) { self.ctxAddExistingTag(t); }, tagAdd: function (name, color) { self.ctxAddNewTag(name, color); }, tagRemove: function (tagId) { self.ctxRemoveTag(tagId); } }; store.openMenu(ev, node, 'workspace', handlers); }, downloadItem(node) { if (!node || node.is_folder) return; var url = '/api/pages/' + node.id + '/download'; var a = document.createElement('a'); a.href = url; a.style.display = 'none'; document.body.appendChild(a); a.click(); document.body.removeChild(a); if (window.showToast) window.showToast('Download started', 'success'); }, async copyItemContent(node) { if (!node || node.is_folder) return; try { var r = await fetch('/api/pages/' + node.id + '/file-content'); var d = await r.json(); if (d.ok && d.content !== undefined) { await navigator.clipboard.writeText(d.content); if (window.showToast) window.showToast('Content copied to clipboard', 'success'); } else { if (window.showToast) window.showToast('Not a text file', 'error'); } } catch { if (window.showToast) window.showToast('Copy failed', 'error'); } }, _ctxAvailTags(node) { return (this.workspaceTags || []).filter(function(t) { return !((node && node.tags) || []).some(function(nt) { return nt.id === t.id; }); }); }, _ctxRefreshAvail() { var store = window.Alpine && Alpine.store('fdCtx'); if (store && store.node) store.setAvail(this._ctxAvailTags(store.node)); }, copyItemLink(node) { if (!node) return; navigator.clipboard.writeText(window.location.origin + '/pages/' + node.id); if (window.showToast) window.showToast('Link copied', 'success'); }, async toggleFavorite(node) { if (!node) return; try { var method = node.favorited ? 'DELETE' : 'POST'; var r = await fetch('/board/api/favorites/' + node.id, { method: method, headers: {'X-CSRF-Token': getCsrf()} }); if (r.ok) { node.favorited = !node.favorited; if (window.appState && window.appState.refreshFavorites) window.appState.refreshFavorites(); if (window.showToast) window.showToast(node.favorited ? 'Added to Favorites' : 'Removed from Favorites', 'success'); } } catch { /* volontaire */ } }, async setItemIcon(node, icon) { if (!node || !node.id) return; icon = (icon || '').trim(); try { var r = await fetch('/board/api/pages/' + node.id + '/icon', { method: 'POST', headers: {'Content-Type': 'application/json', 'X-CSRF-Token': getCsrf()}, body: JSON.stringify({icon: icon}) }); if (!r.ok) throw new Error('icon update failed'); node.page_icon = icon; this.displayTree = [...this.tree]; this._reloadAfterAction(); if (window.showToast) window.showToast('Icon updated', 'success'); } catch { if (window.showToast) window.showToast('Failed to update icon', 'error'); } }, _ctxMove(node) { if (!node) return; this.selectedIds = {}; this.selectedIds[node.id] = true; this.openMovePicker(); }, // ── Long-press (mobile context menu) ── _touchStartX: 0, _touchStartY: 0, _touchStartTime: 0, _touchMoved: false, _touchTarget: null, onTouchStart(ev) { if (!ev.touches || ev.touches.length !== 1) return; this._touchStartX = ev.touches[0].clientX; this._touchStartY = ev.touches[0].clientY; this._touchStartTime = Date.now(); this._touchMoved = false; this._touchTarget = ev.target; }, onTouchMove(ev) { if (!ev.touches || ev.touches.length !== 1) return; var dx = Math.abs(ev.touches[0].clientX - this._touchStartX); var dy = Math.abs(ev.touches[0].clientY - this._touchStartY); if (dx > 10 || dy > 10) this._touchMoved = true; }, onTouchEnd(ev) { if (this._touchMoved) return; var elapsed = Date.now() - this._touchStartTime; if (elapsed < 600) return; // Long press detected — find target element var el = this._touchTarget; if (!el) return; var idEl = el.closest('[data-ws-id]'); if (!idEl) return; // Prevent default context menu if it hasn't fired yet ev.preventDefault(); var fakeEv = { clientX: this._touchStartX, clientY: this._touchStartY, target: idEl, preventDefault: function(){} }; this.onContextMenu(fakeEv); }, ctxAddExistingTag(tag) { this.ctxAddNewTag(tag.name, tag.color); }, ctxRemoveTag(tagId) { var store = window.Alpine && Alpine.store('fdCtx'); var node = store && store.node; if (!node) return; this.removeTag(node.id, tagId); node.tags = (node.tags || []).filter(function(t) { return t.id !== tagId; }); this._ctxRefreshAvail(); }, async ctxAddNewTag(tagName, color) { tagName = (tagName || '').trim(); if (!tagName) return; var store = window.Alpine && Alpine.store('fdCtx'); var node = store && store.node; if (!node) return; color = color || (store && store.newTagColor) || '#787774'; try { var r = await fetch('/api/local-workspace/items/' + node.id + '/tags', { method: 'POST', headers: {'X-CSRF-Token': getCsrf(), 'Content-Type':'application/json'}, body: JSON.stringify({name: tagName, color: color}) }); if (r.ok) { var d = await r.json(); var cur = node.tags || []; if (!cur.some(function(t) { return t.id === d.tag.id; })) node.tags = cur.concat([d.tag]); this._updateNodeTags(this.tree, node.id, d.tag, 'add'); this.displayTree = [...this.tree]; this.tagsVersion++; this._bumpTagCount(d.tag, 1); await this.loadWorkspaceTags(); this._computeFilteredTableItems(); this._ctxRefreshAvail(); } } catch { /* volontaire */ } }, _markTree(nodes, depth) { for (var i=0; i 0) { this._markTree(n.children, depth + 1); } } }, renderChildren(children, depth) { if (!children || children.length === 0) return ''; var html = ''; for (var i=0; i 0; var icon = this._fileIcon(c.name, c.is_folder, c.content_format, c.page_icon); var escName = this.esc(c.name); var escId = c.id; html += '
  • '; // Checkbox html += ''; // Drag handle html += '
    ' + '' + '' + '
    '; if (c.is_folder && hasKids) { html += ''; } else { html += ''; } html += ''+icon+''; if (c.is_folder) { var countBadge = (c.child_count > 0) ? ' ('+c.child_count+')' : ''; html += ''+c.name+countBadge+''; } else { // Click to rename, double-click to open html += '' + (c.is_shared ? '👥' : '') + ''+c.name+'' + '' + '↢ Open' + ''; } // Open button for files — to the right of title (like Library) html += '
    '; if (c.is_folder) { html += ''; html += ''; html += ''; html += ''+getSvgIcon('external-link',14)+''; html += ''; } html += ''; html += ''; // Tags if (c.tags && c.tags.length > 0) { html += ''; for (var ti=0; ti'+tn+''; } html += ''; } html += '
    '; if (hasKids) { html += ''; } html += '
  • '; } return html; }, // Inline rename for renderChildren (static HTML) startRenderChildRename(id, currentName) { var nameSpan = document.getElementById('rc-name-' + id); var input = document.getElementById('rc-input-' + id); if (!nameSpan || !input) return; nameSpan.style.display = 'none'; input.style.display = ''; input.value = currentName; var w = this._fitNameWidth(currentName); var avail = input.parentElement ? input.parentElement.clientWidth : 0; if (avail > 60) input.style.width = Math.min(w, avail) + 'px'; else input.style.width = w + 'px'; input.focus(); input.select(); }, async commitRenderChildRename(id) { var nameSpan = document.getElementById('rc-name-' + id); var input = document.getElementById('rc-input-' + id); if (!input) return; var newName = (input.value || '').trim(); input.style.display = 'none'; if (nameSpan) nameSpan.style.display = ''; if (!newName) return; try { var r = await fetch('/api/local-workspace/items/' + id, { method: 'PUT', headers: {'X-CSRF-Token': getCsrf(), 'Content-Type':'application/json'}, body: JSON.stringify({name: newName}) }); if (r.ok) { if (nameSpan) nameSpan.textContent = newName; // Update tree node var node = this._findNodeById(this.tree, id); if (node) { node.name = newName; } this._reloadAfterAction(); } } catch { /* volontaire */ } }, esc(s) { if (!s) return ''; return s.replace(/\\/g,'\\\\').replace(/'/g,"\\'").replace(/"/g,'"'); }, _fitNameWidth(text) { var off = document.createElement('span'); off.style.cssText = 'position:absolute;visibility:hidden;white-space:pre;font-size:13px;'; off.textContent = (text || '').toString(); document.body.appendChild(off); var w = off.offsetWidth; document.body.removeChild(off); return Math.max(w + 16, 60); }, // ── Event delegation for renderChildren action buttons ── handleTreeAction(ev) { var btn = (ev.target.closest && ev.target.closest('[data-ws-action]')) || (ev.target.parentElement && ev.target.parentElement.closest('[data-ws-action]')); if (!btn) return; ev.stopPropagation(); var action = btn.getAttribute('data-ws-action'); var id = parseInt(btn.getAttribute('data-ws-id')); var name = btn.getAttribute('data-ws-name') || ''; if (!id) return; switch(action) { case 'new-file': window.FlowDeck.createPage(id); break; case 'new-folder': window.FlowDeck.showCreateFolderModal(id); break; case 'rename': this.startRename({db_id: id, name: name}); break; case 'delete': this.deleteWithUndo({db_id: id, name: name}); break; } }, toggleExpand(id, btn) { this.expanded[id] = !(this.expanded[id] || false); this.expanded = Object.assign({}, this.expanded); this._saveExpandedState(); // For renderChildren static HTML: toggle chevron + sibling
      if (btn) { btn.classList.toggle('open', this.expanded[id]); // Navigate: btn → .ws-tree-item →
    • → var itemDiv = btn.closest('.ws-tree-item'); if (itemDiv && itemDiv.parentElement) { var children = itemDiv.parentElement.children; for (var i = 0; i < children.length; i++) { if (children[i].tagName === 'UL' && children[i].classList.contains('ws-tree')) { children[i].style.display = this.expanded[id] ? '' : 'none'; break; } } } } var self = this; this.$nextTick(function() { self._syncCheckboxes(); }); }, async navigateToFolder(id) { // Find the folder name for the stack var folderNode = this._findNodeById(this.tree, id); var folderName = folderNode ? folderNode.name : 'Folder'; // Push to folder stack for breadcrumb if (this.navIndex < this.folderStack.length - 1) { this.folderStack = this.folderStack.slice(0, this.navIndex + 1); } this.folderStack.push({id: id, name: folderName}); this.navIndex = this.folderStack.length - 1; // Fetch children of this folder await this._loadFolder(id); }, async goToParent() { if (this.folderStack.length <= 1) { await this._loadFolder(0); return; } this.folderStack.pop(); this.navIndex = this.folderStack.length - 1; var parentId = this.folderStack.length > 0 ? this.folderStack[this.folderStack.length - 1].id : 0; await this._loadFolder(parentId); }, async _loadFolder(folderId) { var url = '/api/local-workspace/tree'; if (folderId && folderId > 0) url += '?folder=' + folderId; try { var r = await fetch(url); var d = await r.json(); var roots = d.tree || []; this._markTree(roots, 0); this.tree = roots; this.displayTree = [...roots]; this.showAll = false; this.breadcrumb = d.breadcrumb || []; this.currentFolder = folderId || 0; this._saveExpandedState(); this.previewItem = null; this._recompute(); var self = this; this.$nextTick(function() { self._syncCheckboxes(); }); } catch(e) { console.error('Load folder error:', e); } }, _findNodeById(nodes, id) { for (var i=0; i= this.folderStack.length - 1) return; this.navIndex++; var entry = this.folderStack[this.navIndex]; this._saveExpandedState(); await this._loadFolder(entry ? entry.id : 0); }, _saveExpandedState() { try { localStorage.setItem('fd_ws_expanded', JSON.stringify(this.expanded)); } catch { /* volontaire */ } }, _reloadAfterAction() { this._saveExpandedState(); this._loadFolder(this.currentFolder); // Directly refresh sidebar tree if (window.appState && window.appState.refreshSidebarTree) { window.appState.refreshSidebarTree(); } }, // ── Inline Rename ── startInlineRename(node, ev) { this.renamingId = node.id; this.renamingName = node.name; var self = this; this.$nextTick(function() { var inp = self.$el.querySelector('.inline-rename-input:not([style*="display: none"])'); if (inp) { var w = self._fitNameWidth(self.renamingName); var avail = inp.parentElement ? inp.parentElement.clientWidth : 0; if (avail > 60) inp.style.width = Math.min(w, avail) + 'px'; else inp.style.width = w + 'px'; inp.focus(); inp.select(); } }); }, async commitRename(node) { var n = this.renamingName.trim(); this.renamingId = null; if (!n || n === node.name) return; var r = await fetch('/api/local-workspace/items/' + node.id, { method: 'PUT', headers: {'X-CSRF-Token': getCsrf(), 'Content-Type':'application/json'}, body: JSON.stringify({name: n}) }); if (r.ok) { node.name = n; node.db_name = n; this._saveExpandedState(); if (window.appState && window.appState.refreshSidebarTree) window.appState.refreshSidebarTree(); if (window.FlowDeck && window.FlowDeck.syncPageTitle) window.FlowDeck.syncPageTitle(node.id, n); if (this.folderStack && this.folderStack.length) { this.folderStack.forEach(function(f) { if (String(f.id) === String(node.id)) f.name = n; }); } if (this.breadcrumb && this.breadcrumb.length) { this.breadcrumb.forEach(function(b) { if (String(b.id) === String(node.id)) b.name = n; }); } if (this.previewItem && String(this.previewItem.id) === String(node.id)) { this.previewItem.name = n; var pt = document.getElementById('ws-preview-title'); if (pt) pt.textContent = n; } } }, cancelRename() { this.renamingId = null; }, // ── Keyboard shortcuts ── onKeydown(ev) { var tag = ev.target && ev.target.tagName; if (tag === 'INPUT' || tag === 'TEXTAREA') return; // Don't hijack typing if (this.renamingId) return; // Don't intercept during rename if (ev.key === 'Delete' && !ev.ctrlKey && !ev.metaKey) { ev.preventDefault(); // Delete selected item — implement when multi-select exists } if (ev.key === 'F2' && !ev.ctrlKey) { ev.preventDefault(); // Find hovered or first file item and rename var hovered = document.querySelector('.ws-tree-item:hover'); if (hovered) { var id = parseInt(hovered.getAttribute('data-ws-id') || hovered.getAttribute('data-folder-id')); var name = (hovered.querySelector('.name') || {}).textContent || ''; if (id) this.startInlineRename({id: id, name: name}, ev); } } if (ev.ctrlKey && ev.key === 'c') { // Copy selected items to clipboard ev.preventDefault(); this.clipboard = []; // Will implement with multi-select } if (ev.ctrlKey && ev.key === 'v' && this.clipboard.length > 0) { ev.preventDefault(); // Paste items — move them to current folder var self = this; var targetId = this.currentFolder > 0 ? this.currentFolder : null; this.clipboard.forEach(function(id) { fetch('/api/local-workspace/items/' + id + '/move', { method: 'PUT', headers: {'X-CSRF-Token': getCsrf(), 'Content-Type':'application/json'}, body: JSON.stringify({parent_id: targetId}) }).then(function(r) { if (r.ok) self._reloadAfterAction(); }); }); this.clipboard = []; } }, // ── Duplicate ── async duplicateItem(node) { var r = await fetch('/api/local-workspace/items', { method: 'POST', headers: {'X-CSRF-Token': getCsrf(), 'Content-Type':'application/json'}, body: JSON.stringify({name: node.name + ' (copy)', type: node.type || 'page', parent_id: this.currentFolder > 0 ? this.currentFolder : null}) }); if (r.ok) this._reloadAfterAction(); }, // ── Sort ── doSort() { var s = this.sortBy; if (!s) { this.displayTree = [...this.tree]; this.showAll = false; return; } var sorted = this.tree.slice(); if (s === 'name-asc') sorted.sort(function(a,b) { return (a.name||'').localeCompare(b.name||''); }); else if (s === 'name-desc') sorted.sort(function(a,b) { return (b.name||'').localeCompare(a.name||''); }); else if (s === 'date-asc') sorted.sort(function(a,b) { return (a.updated_at||'').localeCompare(b.updated_at||''); }); else if (s === 'date-desc') sorted.sort(function(a,b) { return (b.updated_at||'').localeCompare(a.updated_at||''); }); else if (s === 'type') sorted.sort(function(a,b) { return (a.is_folder===b.is_folder) ? 0 : a.is_folder ? -1 : 1; }); this.displayTree = sorted; this._recompute(); }, // ── Undo delete ── async deleteWithUndo(node) { var self = this; this.undoItemId = node.db_id || node.id; this.undoMessage = 'Deleted "' + (node.name || 'item') + '"'; this.undoVisible = true; // Delete via API var r = await fetch('/api/local-workspace/items/' + this.undoItemId, {headers: {'X-CSRF-Token': getCsrf()}, method:'DELETE' }); if (!r.ok) { this.undoVisible = false; return; } // Reload from API for reliability (sidebar refresh handled by _reloadAfterAction) this._reloadAfterAction(); // Auto-dismiss undo banner after 5s this.undoTimeout = setTimeout(function() { self.undoVisible = false; self.undoItemId = null; }, 5000); }, undoDelete() { if (this.undoTimeout) clearTimeout(this.undoTimeout); this.undoVisible = false; var ids = (this.undoItemId || '').toString().split(','); this.undoItemId = null; if (!ids.length || !ids[0]) return; // Restore via API (soft-delete undo) — single or bulk var self = this; var total = ids.length; function restoreOne(i) { if (i >= total) { self._reloadAfterAction(); return; } fetch('/api/local-workspace/items/' + ids[i] + '/restore', {headers: {'X-CSRF-Token': getCsrf()}, method: 'POST' }) .finally(function() { restoreOne(i + 1); }); } restoreOne(0); }, _removeFromTree(nodes, id) { for (var i=0; i 0; return true; } } return false; }, // ── Upload speed tracking ── _trackUploadSpeed(totalSize) { var self = this; this.uploadStartTime = Date.now(); this.uploadSpeed = ''; var checkSpeed = setInterval(function() { if (!self.uploading) { clearInterval(checkSpeed); return; } var elapsed = (Date.now() - self.uploadStartTime) / 1000; if (elapsed > 0) { self.uploadSpeed = (totalSize / elapsed / 1024).toFixed(0) + ' KB/s'; } }, 500); }, // ── Path bar navigation ── navigateToPath() { var path = (this.$el.querySelector('.path-input') || {}).value || ''; var parts = path.replace(/^\/workspace\/?/, '').split('/').filter(Boolean); if (parts.length === 0) { if (window.fdNavigate) window.fdNavigate('/local-workspace'); else window.location = '/local-workspace'; return; } // Walk tree to find matching folder var targetId = this._findFolderByPath(this.tree, parts, 0); if (targetId) { if (window.fdNavigate) window.fdNavigate('/local-workspace?folder=' + targetId); else window.location = '/local-workspace?folder=' + targetId; } else { window.showToast('Path not found: ' + parts.join('/'), 'error'); } }, _findFolderByPath(nodes, parts, idx) { if (idx >= parts.length) return null; for (var i=0; i'; this.previewContent = html; this.previewCache[cacheKey] = html; } else if (isPdf) { var html = '
      '+getSvgIcon('file',48)+'
      PDF Document
      Open viewer →
      '; this.previewContent = html; this.previewCache[cacheKey] = html; } else { this.previewContent = 'Loading...'; this._fetchRetry(url).then(function(t) { var content = t.substring(0, 500); self.previewContent = content; self.previewCache[cacheKey] = content; }).catch(function() { self.previewContent = '(preview not available)'; }); } } else { this.previewContent = 'Loading...'; this._fetchRetry('/api/local-workspace/page-content/' + node.id, true) .then(function(d) { var content = self._extractPreview(d.content || '', d.format); self.previewContent = content; self.previewCache[cacheKey] = content; }).catch(function() { self.previewContent = '(preview not available)'; }); } }, // ── Fetch with retry ── _fetchRetry(url, json) { var maxRetries = 2; var delay = 300; return new Promise(function(resolve, reject) { function attempt(n) { fetch(url).then(function(r) { if (!r.ok) throw new Error('HTTP ' + r.status); return json ? r.json() : r.text(); }).then(resolve).catch(function(e) { if (n < maxRetries) { setTimeout(function() { attempt(n+1); }, delay * (n+1)); } else { reject(e); } }); } attempt(0); }); }, // ── Touch haptic feedback ── _haptic() { if (navigator.vibrate) navigator.vibrate(10); }, // ── Expand / Collapse all ── expandAll() { var self = this; this._walkTree(this.tree, function(n) { if (n.is_folder && n.children && n.children.length > 0) { self.expanded[n.id] = true; } }); this.expanded = Object.assign({}, this.expanded); // Toggle static HTML for renderChildren this._toggleAllUl(this.$el, true); }, collapseAll() { this.expanded = {}; this._toggleAllUl(this.$el, false); }, _walkTree(nodes, fn) { for (var i=0; i'+html+''; } if (isFolder) return ''+getSvgIcon('folder',14)+''; // FlowDeck internal pages (no extension, blocks/markdown format) if (contentFormat && contentFormat !== 'file') return ''+getSvgIcon('edit',14)+''; var n = (name || '').toLowerCase(); if (/\.(png|jpe?g|gif|webp|svg|bmp|ico)$/.test(n)) return ''+getSvgIcon('image',14)+''; if (/\.pdf$/.test(n)) return ''+getSvgIcon('file',14)+''; if (/\.(md|markdown)$/.test(n)) return ''+getSvgIcon('edit',14)+''; if (/\.(py)$/.test(n)) return ''+getSvgIcon('file',14)+''; if (/\.(js|jsx|ts|tsx)$/.test(n)) return ''+getSvgIcon('file',14)+''; if (/\.(html?|xml)$/.test(n)) return ''+getSvgIcon('file',14)+''; if (/\.css$/.test(n)) return ''+getSvgIcon('file',14)+''; if (/\.json$/.test(n)) return ''+getSvgIcon('file',14)+''; if (/\.sql$/.test(n)) return ''+getSvgIcon('file',14)+''; if (/\.(sh|bash|zsh)$/.test(n)) return ''+getSvgIcon('file',14)+''; if (/\.ps1$/.test(n)) return ''+getSvgIcon('file',14)+''; if (/\.(rs|go|java|rb|php|c|cpp|h|swift|kt|scala|r)$/.test(n)) return ''+getSvgIcon('file',14)+''; if (/\.(txt|log)$/.test(n)) return ''+getSvgIcon('file',14)+''; if (/\.(zip|tar|gz|rar|7z)$/.test(n)) return ''+getSvgIcon('file',14)+''; return ''+getSvgIcon('file',14)+''; }, // ── Search / Filter ── doFilter() { var q = (this.searchQuery || '').toLowerCase().trim(); var ft = this.filterType || ''; if (!q && !ft && !this.tagFilter) { this.displayTree = [...this.tree]; this.showAll = false; this._computeFilteredTableItems(); return; } // Apply all filters (search + type + tag) to tree view this.displayTree = this._filterTree([...this.tree], q, ft); if (this.tagFilter) { this.displayTree = this._filterTreeByTag(this.displayTree, this.tagFilter); } this._computeFilteredTableItems(); }, _filterTreeByTag(nodes, tagName) { var result = []; for (var i=0; i 0) { var clone = Object.assign({}, n); clone.children = filteredChildren; result.push(clone); } } return result; }, filterByType(type) { this.filterType = (this.filterType === type) ? '' : type; this.doFilter(); }, _filterTree(nodes, query, fileType) { var result = []; for (var i=0; i 0) && typeMatch) { var clone = Object.assign({}, n); clone.children = filteredChildren; result.push(clone); } } return result; }, // ── Preview on hover ── _extractPreview(content, format) { if (!content) return '(empty)'; // Try to extract text from Notion block JSON if (format === 'blocks' || format === 'markdown') { try { var blocks = typeof content === 'string' ? JSON.parse(content) : content; if (blocks && blocks.blocks && Array.isArray(blocks.blocks)) { var lines = []; for (var i=0; i 0 ? this.currentFolder : null; if (folderEl) { var folderLink = folderEl.querySelector('.name.folder'); if (folderLink) { var fid = folderEl.getAttribute('data-folder-id'); if (fid) targetParentId = parseInt(fid); } } // Don't drop on self or same parent if (draggedId === targetParentId) return; this._moveItem(draggedId, targetParentId); return; } // ── External drop (files from computer) ── var folderEl = ev.target.closest('.ws-tree-item'); var parentId = this.currentFolder > 0 ? this.currentFolder : null; var parentName = 'root'; if (folderEl) { var folderLink = folderEl.querySelector('.name.folder'); if (folderLink) { parentName = folderLink.textContent.trim(); // Use data-folder-id attribute (works for both x-for and renderChildren) var fid = folderEl.getAttribute('data-folder-id'); if (fid) parentId = parseInt(fid); } } var items = ev.dataTransfer.items; if (!items) return; var dtFiles = ev.dataTransfer.files; // Check if any item is a directory (WebKit only) var hasDirectory = false; for (var i = 0; i < items.length; i++) { var entry = items[i].webkitGetAsEntry ? items[i].webkitGetAsEntry() : null; if (entry && entry.isDirectory) { hasDirectory = true; break; } } if (hasDirectory) { // Walk directory tree and upload structure this._processDirectoryDrop(items, parentId, parentName); } else if (dtFiles.length > 0) { // Simple file upload this._uploadFiles(dtFiles, parentId, parentName); } }, _processDirectoryDrop(items, parentId, parentName) { var self = this; var allItems = []; // {path: string, type: 'file'|'folder', entry: FileSystemEntry} var pending = items.length; function walkEntry(entry, basePath) { var path = basePath ? basePath + '/' + entry.name : entry.name; if (entry.isFile) { allItems.push({path: path, type: 'file', entry: entry}); pending--; if (pending === 0) doUpload(); } else if (entry.isDirectory) { allItems.push({path: path, type: 'folder', entry: entry}); var reader = entry.createReader(); reader.readEntries(function(entries) { pending += entries.length; for (var i = 0; i < entries.length; i++) { walkEntry(entries[i], path); } pending--; if (pending === 0) doUpload(); }); } } for (var i = 0; i < items.length; i++) { var entry = items[i].webkitGetAsEntry ? items[i].webkitGetAsEntry() : null; if (entry) walkEntry(entry, ''); else pending--; } if (pending === 0) doUpload(); function doUpload() { // Build structure var structure = []; for (var i = 0; i < allItems.length; i++) { structure.push({path: allItems[i].path, type: allItems[i].type}); } // Resolve file entries to actual File objects var fileEntries = []; for (var i = 0; i < allItems.length; i++) { if (allItems[i].type === 'file') fileEntries.push(allItems[i]); } var formData = new FormData(); formData.append('structure', JSON.stringify(structure)); if (parentId) formData.append('parent_id', parentId); function addFile(idx) { if (idx >= fileEntries.length) { // All resolved, upload self._doUpload(formData, parentId, parentName); return; } var fe = fileEntries[idx]; fe.entry.file(function(file) { formData.append('files', file, fe.path); addFile(idx + 1); }, function() { addFile(idx + 1); }); } addFile(0); } }, _uploadFiles(fileList, parentId, parentName) { var formData = new FormData(); for (var i = 0; i < fileList.length; i++) { formData.append('files', fileList[i]); } if (parentId) formData.append('parent_id', parentId); this._doUpload(formData, parentId, parentName); }, async _moveItem(itemId, newParentId) { var body = { parent_id: newParentId || null }; try { var r = await fetch('/api/local-workspace/items/' + itemId + '/move', { method: 'PUT', headers: {'X-CSRF-Token': getCsrf(), 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); if (r.ok) { this._reloadAfterAction(); } else { window.showToast('Move failed', 'error'); } } catch(e) { window.showToast('Move failed: ' + e.message, 'error'); } }, async _doUpload(formData, parentId, parentName) { var self = this; this.uploading = true; this.uploadStatus = 'Uploading to ' + parentName + '...'; this.uploadProgress = 10; var folderHandled = formData.has('structure'); var url = folderHandled ? '/api/local-workspace/upload-folder' : '/api/local-workspace/upload'; try { var r = await fetch(url, { method: 'POST', headers: {'X-CSRF-Token': getCsrf()}, body: formData }); this.uploadProgress = 80; var d = await r.json(); if (r.ok) { this.uploadStatus = '✓ ' + d.items.length + ' item(s) uploaded'; this.uploadProgress = 100; setTimeout(function() { self.uploading = false; self._reloadAfterAction(); }, 800); } else { this.uploadStatus = '✗ ' + (d.error || 'Upload failed'); this.uploadProgress = 0; setTimeout(function() { self.uploading = false; }, 2000); } } catch { this.uploadStatus = '✗ Network error'; this.uploadProgress = 0; setTimeout(function() { self.uploading = false; }, 2000); } }, // ── Tag management ── async loadWorkspaceTags() { try { var r = await fetch('/api/local-workspace/tags', { cache: 'no-store' }); var d = await r.json(); this.workspaceTags = d.tags || []; } catch { this.workspaceTags = []; } }, // Mise à jour OPTIMISTE du compteur d'un tag dans la barre de tags : // rafraîchit immédiatement la pastille (delta +1/-1) sans attendre le // re-fetch (qui viendra ensuite confirmer/surécrire avec les valeurs serveur). _bumpTagCount(tag, delta) { if (!tag) return; var tags = (this.workspaceTags || []).slice(); var i = -1; for (var k = 0; k < tags.length; k++) { if (String(tags[k].id) === String(tag.id)) { i = k; break; } } if (i >= 0) { tags[i] = Object.assign({}, tags[i], { count: Math.max(0, (tags[i].count || 0) + delta) }); } else if (delta > 0) { tags.push({ id: tag.id, name: tag.name || '', color: tag.color || '#787774', count: delta }); } // Réassigne un nouveau tableau → réactivité Alpine this.workspaceTags = tags; }, toggleTagFilter(tagName) { this.tagFilter = (this.tagFilter === tagName) ? '' : tagName; this.filterType = ''; this.searchQuery = ''; this.doFilter(); }, startTagInput(node) { this.tagInputId = node.id; var self = this; this.$nextTick(function() { var inp = self.$el.querySelector('.tag-input:not([style*="display: none"])'); if (inp) inp.focus(); }); }, async addTag(itemId, tagName) { tagName = (tagName || '').trim(); if (!tagName) { this.tagInputId = null; return; } try { var r = await fetch('/api/local-workspace/items/' + itemId + '/tags', { method: 'POST', headers: {'X-CSRF-Token': getCsrf(), 'Content-Type': 'application/json'}, body: JSON.stringify({name: tagName}) }); if (r.ok) { var d = await r.json(); // Update node tags in tree this._updateNodeTags(this.tree, itemId, d.tag, 'add'); this.displayTree = [...this.tree]; this.tagsVersion++; this._bumpTagCount(d.tag, 1); await this.loadWorkspaceTags(); this._computeFilteredTableItems(); } } catch { /* volontaire */ } this.tagInputId = null; }, async removeTag(itemId, tagId) { try { var r = await fetch('/api/local-workspace/items/' + itemId + '/tags/' + tagId, {headers: {'X-CSRF-Token': getCsrf()}, method: 'DELETE' }); if (r.ok) { this._updateNodeTags(this.tree, itemId, null, 'remove', tagId); this.displayTree = [...this.tree]; this.tagsVersion++; this._bumpTagCount({ id: tagId }, -1); await this.loadWorkspaceTags(); this._computeFilteredTableItems(); } } catch { /* volontaire */ } }, _updateNodeTags(nodes, itemId, tag, action, removeTagId) { for (var i=0; i 0 && this.selectedIdsCount >= total; this._syncCheckboxes(); }, _syncCheckboxes() { var boxes = document.querySelectorAll('input.item-checkbox[data-ws-id]'); for (var i = 0; i < boxes.length; i++) { boxes[i].checked = !!this.selectedIds[parseInt(boxes[i].getAttribute('data-ws-id'))]; } }, // ── Multi-select actions (Library-style select bar) ── _selectedIds() { return Object.keys(this.selectedIds).map(Number); }, copySelectedLinks() { this.moreOpen = false; var ids = Object.keys(this.selectedIds); if (!ids.length) return; var links = ids.map(function(id) { return window.location.origin + '/pages/' + id; }); navigator.clipboard.writeText(links.join('\n')); if (window.showToast) window.showToast('Links copied', 'success'); }, async openMovePicker() { var ids = this._selectedIds(); if (!ids.length) return; this.moreOpen = false; this.moveIds = ids; this.moveSearchQuery = ''; this.moveModalOpen = true; try { // Fetch the full workspace tree so every folder is a valid destination var r = await fetch('/api/local-workspace/tree'); var d = await r.json(); var folders = []; this._collectFolders(d.tree || [], folders, 0); this._moveAllCandidates = folders; this.moveCandidates = folders.slice(0, 50); } catch { this._moveAllCandidates = []; this.moveCandidates = []; } }, _collectFolders(nodes, out, depth) { for (var i = 0; i < nodes.length; i++) { var n = nodes[i]; if (n.is_folder) out.push({ id: n.id, name: n.name, depth: depth }); if (n.children) this._collectFolders(n.children, out, depth + 1); } }, doMoveSearch() { var q = (this.moveSearchQuery || '').toLowerCase().trim(); if (!q) { this.moveCandidates = this._moveAllCandidates.slice(0, 50); return; } this.moveCandidates = this._moveAllCandidates.filter(function(it) { return (it.name || '').toLowerCase().indexOf(q) !== -1; }).slice(0, 50); }, async confirmBulkMove(parentId) { this.moveModalOpen = false; var ids = this.moveIds; this.moveIds = []; if (!ids || !ids.length) return; for (var i = 0; i < ids.length; i++) { try { await fetch('/api/local-workspace/items/' + ids[i] + '/move', { method: 'PUT', headers: {'X-CSRF-Token': getCsrf(), 'Content-Type':'application/json'}, body: JSON.stringify({ parent_id: parentId || null }) }); } catch { /* volontaire */ } } this.clearSelection(); this._reloadAfterAction(); if (window.showToast) window.showToast('Item' + (ids.length > 1 ? 's' : '') + ' moved', 'success'); }, // ── A20 ph3 : x-html interdit par le build CSP → x-init + Alpine.effect // (réactivité conservée : lecture des données réactives dans l'effect) ── toggleViewMenu() { this.viewMenuOpen = !this.viewMenuOpen; this.sortOpen = false; this.searchOpen = false; }, toggleSortMenu() { this.sortOpen = !this.sortOpen; this.viewMenuOpen = false; }, toggleFilterMenu() { this.filterOpen = !this.filterOpen; this.viewMenuOpen = false; this.sortOpen = false; }, toggleSearch() { this.searchOpen = !this.searchOpen; if (this.searchOpen) Alpine.nextTick(() => { const el = document.querySelector('[x-ref="searchInput"]'); if (el) el.focus(); }); }, createPageAt(node) { window.FlowDeck.createPage(node.id); }, createFolderAt(node) { window.FlowDeck.showCreateFolderModal(node.id); }, bindSvg(el, name, size) { el.innerHTML = getSvgIcon(name, size); }, bindFileIcon(el, node) { Alpine.effect(() => { el.innerHTML = this._fileIcon(node.name, node.is_folder, node.content_format, node.page_icon); }); }, bindNodeIcon(el, node) { Alpine.effect(() => { el.innerHTML = node.is_folder ? getSvgIcon('folder', 24) : this._fileIcon(node.name, node.is_folder, node.content_format, node.page_icon); }); }, bindChildren(el, node) { Alpine.effect(() => { el.innerHTML = this.renderChildren(node.children, (node.depth || 0) + 1, this.tagsVersion); }); }, bindPreview(el) { Alpine.effect(() => { el.innerHTML = this.previewContent; }); }, }; })(); // Injecter toutes les props comme globales pour Alpine (résout les 36 erreurs de scope) var _wsKeys = Object.keys(_wsInitData); for (var _i = 0; _i < _wsKeys.length; _i++) { try { window[_wsKeys[_i]] = _wsInitData[_wsKeys[_i]]; } catch { /* volontaire */ } } console.log('FLOWDECK v2.4.6 — IIFE done, _wsInitData keys:', Object.keys(_wsInitData).length); // 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; }); // 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 // qu'une fois) + lecture/écriture via Alpine.reactive(_wsInitData) pour // garder la réactivité JS↔Alpine. Alpine.data('wsPreview', function () { const R = Alpine.reactive(_wsInitData); const t = {}; ['previewVisible', 'previewX', 'previewY', 'previewName'].forEach(function (k) { Object.defineProperty(t, k, { get: function () { return R[k]; }, set: function (v) { R[k] = v; }, configurable: true, }); }); t.bindPreview = function (el) { Alpine.effect(function () { el.innerHTML = R.previewContent; }); }; return t; }); }; if (window.Alpine) { _regWs(); fdRefreshXData('wsInitData'); fdRefreshXData('wsPreview'); } else { document.addEventListener('alpine:init', _regWs); } }