local-workspace: replace Alpine preview panel with vanilla JS DOM manipulation
Alpine 3.14.9 x-if and x-show are fundamentally broken for reactive
visibility toggling of the preview panel. Complete rewrite:
- Replace all Alpine directives in preview panel with static HTML + IDs
- selectForPreview(): vanilla JS to populate fields and show panel
- closePreviewPanel(): vanilla JS to hide panel
- _loadPreviewContentVanilla(): async content loader using innerHTML
- Open button wired via onclick to navigate to /pages/{id}
- No Alpine reactivity dependencies - panel just works
This commit is contained in:
@@ -796,40 +796,72 @@ window._wsInitData = (function() {
|
||||
return null;
|
||||
},
|
||||
|
||||
// ── Preview panel ──
|
||||
// ── Preview panel (vanilla JS — Alpine x-if/x-show broken in 3.14.9) ──
|
||||
selectForPreview(node) {
|
||||
this.previewItem = node;
|
||||
this.previewPanelContent = '<div style="text-align:center;padding:40px;color:var(--text-tertiary)">Loading...</div>';
|
||||
this._loadPreviewContent(node);
|
||||
var panel = document.getElementById('ws-preview-panel');
|
||||
if (!panel) return;
|
||||
// Populate panel
|
||||
document.getElementById('ws-preview-icon').innerHTML = this._fileIcon(node.name, node.is_folder, node.content_format);
|
||||
document.getElementById('ws-preview-title').textContent = node.name;
|
||||
document.getElementById('ws-preview-type').textContent = node.is_folder ? 'Folder' : this._fileTypeLabel(node.name, node.content_format);
|
||||
document.getElementById('ws-preview-size').textContent = node.size_display || '-';
|
||||
document.getElementById('ws-preview-date').textContent = this._formatDate(node.updated_at);
|
||||
// Tags
|
||||
var tagDiv = document.getElementById('ws-preview-tags');
|
||||
tagDiv.innerHTML = '';
|
||||
if (node.tags && node.tags.length > 0) {
|
||||
tagDiv.style.display = 'flex';
|
||||
for (var i = 0; i < node.tags.length; i++) {
|
||||
var t = node.tags[i];
|
||||
tagDiv.innerHTML += '<span class=\"tag-chip\" style=\"background:' + this._escHtml(t.color) + '\">' + this._escHtml(t.name) + '</span>';
|
||||
}
|
||||
} else {
|
||||
tagDiv.style.display = 'none';
|
||||
}
|
||||
// Open button
|
||||
var openBtn = document.getElementById('ws-preview-open-btn');
|
||||
openBtn.onclick = function() { window.location = '/pages/' + node.id; };
|
||||
// Body
|
||||
document.getElementById('ws-preview-body').innerHTML = '<div style=\"text-align:center;padding:40px;color:var(--text-tertiary)\">Loading...</div>';
|
||||
// Show panel
|
||||
panel.style.display = 'flex';
|
||||
// Load content async
|
||||
this._loadPreviewContentVanilla(node);
|
||||
},
|
||||
|
||||
async _loadPreviewContent(node) {
|
||||
closePreviewPanel() {
|
||||
var panel = document.getElementById('ws-preview-panel');
|
||||
if (panel) panel.style.display = 'none';
|
||||
this.previewItem = null;
|
||||
},
|
||||
|
||||
async _loadPreviewContentVanilla(node) {
|
||||
var body = document.getElementById('ws-preview-body');
|
||||
if (!body) return;
|
||||
if (node.is_folder) {
|
||||
this.previewPanelContent = '<div style="text-align:center;padding:40px;color:var(--text-secondary)">'
|
||||
+ '<span class="empty-state-icon">'+getSvgIcon('folder',32)+'</span>'
|
||||
+ '<div style="font-size:14px">' + (node.child_count||0) + ' item(s)</div>'
|
||||
+ '<a href="/local-workspace?folder=' + node.id + '" class="btn btn-secondary" style="display:inline-block;text-decoration:none;margin-top:16px">Open folder</a>'
|
||||
body.innerHTML = '<div style=\"text-align:center;padding:40px;color:var(--text-secondary)\">'
|
||||
+ '<span>' + getSvgIcon('folder',32) + '</span>'
|
||||
+ '<div style=\"font-size:14px\">' + (node.child_count||0) + ' item(s)</div>'
|
||||
+ '<a href=\"/local-workspace?folder=' + node.id + '\" class=\"btn btn-secondary\" style=\"display:inline-block;text-decoration:none;margin-top:16px\">Open folder</a>'
|
||||
+ '</div>';
|
||||
return;
|
||||
}
|
||||
try {
|
||||
var name = (node.name || '').toLowerCase();
|
||||
// Image preview
|
||||
if (/\.(png|jpe?g|gif|webp|svg|bmp|ico)$/.test(name)) {
|
||||
var wsId = 1;
|
||||
var imgUrl = '/api/files/' + wsId + '/' + encodeURIComponent(node.name);
|
||||
this.previewPanelContent = '<div style="text-align:center;padding:12px;">'
|
||||
+ '<img src="' + imgUrl + '" style="max-width:100%;max-height:50vh;border-radius:8px;" onerror="this.style.display=\'none\'">'
|
||||
body.innerHTML = '<div style=\"text-align:center;padding:12px;\">'
|
||||
+ '<img src=\"' + imgUrl + '\" style=\"max-width:100%;max-height:50vh;border-radius:8px;\">'
|
||||
+ '</div>';
|
||||
return;
|
||||
}
|
||||
// PDF preview
|
||||
if (/\.pdf$/.test(name)) {
|
||||
this.previewPanelContent = '<div style="text-align:center;padding:24px;color:var(--text-secondary);">'
|
||||
+ '<span style="font-size:48px;display:block;margin-bottom:8px;">'+getSvgIcon('file',32)+'</span>'
|
||||
+ '<div style="font-size:14px;margin-bottom:4px;">' + this._escHtml(node.name) + '</div>'
|
||||
+ '<div style="font-size:12px;color:var(--text-tertiary);margin-bottom:16px;">' + (node.size_display || '') + '</div>'
|
||||
+ '<a href="/pages/' + node.id + '" class="btn btn-secondary" style="display:inline-block;text-decoration:none;">Open in viewer</a>'
|
||||
body.innerHTML = '<div style=\"text-align:center;padding:24px;color:var(--text-secondary);\">'
|
||||
+ '<span style=\"font-size:48px;display:block;margin-bottom:8px;\">' + getSvgIcon('file',32) + '</span>'
|
||||
+ '<div style=\"font-size:14px;margin-bottom:4px;\">' + this._escHtml(node.name) + '</div>'
|
||||
+ '<div style=\"font-size:12px;color:var(--text-tertiary);margin-bottom:16px;\">' + (node.size_display || '') + '</div>'
|
||||
+ '<a href=\"/pages/' + node.id + '\" class=\"btn btn-secondary\" style=\"display:inline-block;text-decoration:none;\">Open in viewer</a>'
|
||||
+ '</div>';
|
||||
return;
|
||||
}
|
||||
@@ -841,14 +873,13 @@ window._wsInitData = (function() {
|
||||
try {
|
||||
var meta = JSON.parse(content);
|
||||
if (meta.file_path && /\.(png|jpe?g|gif|webp|svg|bmp|ico)$/i.test(meta.file_path)) {
|
||||
var wsId2 = 1;
|
||||
var imgUrl2 = '/api/files/' + wsId2 + '/' + encodeURIComponent(meta.file_path);
|
||||
this.previewPanelContent = '<img src="' + imgUrl2 + '" style="max-width:100%;max-height:50vh;border-radius:8px;">';
|
||||
var imgUrl2 = '/api/files/' + 1 + '/' + encodeURIComponent(meta.file_path);
|
||||
body.innerHTML = '<img src=\"' + imgUrl2 + '\" style=\"max-width:100%;max-height:50vh;border-radius:8px;\">';
|
||||
return;
|
||||
}
|
||||
} catch(e) {}
|
||||
preview = '<div style="text-align:center;padding:20px;color:var(--text-secondary)">'
|
||||
+ '<span class="empty-state-icon">'+getSvgIcon('file',32)+'</span>'
|
||||
preview = '<div style=\"text-align:center;padding:20px;color:var(--text-secondary)\">'
|
||||
+ '<span>' + getSvgIcon('file',32) + '</span>'
|
||||
+ (node.size_display || '') + ' — open to view</div>';
|
||||
} else if (format === 'blocks') {
|
||||
try {
|
||||
@@ -856,16 +887,16 @@ window._wsInitData = (function() {
|
||||
if (blocks && blocks.blocks) {
|
||||
preview = blocks.blocks.slice(0, 30).map(function(b) {
|
||||
var txt = b.content || b.text || (typeof b === 'string' ? b : '');
|
||||
return '<div style="margin-bottom:6px;line-height:1.5">' + this._escHtml(txt) + '</div>';
|
||||
return '<div style=\"margin-bottom:6px;line-height:1.5\">' + this._escHtml(txt) + '</div>';
|
||||
}, this).join('');
|
||||
}
|
||||
} catch(e) { preview = '<pre>' + this._escHtml(String(content).substring(0, 2000)) + '</pre>'; }
|
||||
} else {
|
||||
preview = '<pre>' + this._escHtml(String(content).substring(0, 2000)) + '</pre>';
|
||||
}
|
||||
this.previewPanelContent = preview || '<div style="color:var(--text-tertiary);text-align:center;padding:20px">(empty)</div>';
|
||||
body.innerHTML = preview || '<div style=\"color:var(--text-tertiary);text-align:center;padding:20px\">(empty)</div>';
|
||||
} catch(e) {
|
||||
this.previewPanelContent = '<div style="text-align:center;padding:20px;color:var(--text-tertiary)">Preview not available</div>';
|
||||
body.innerHTML = '<div style=\"text-align:center;padding:20px;color:var(--text-tertiary)\">Preview not available</div>';
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2800,33 +2831,27 @@ console.log('FLOWDECK v2.4.6 — IIFE done, _wsInitData keys:', Object.keys(wind
|
||||
<div class="file-preview-body" x-html="previewContent"></div>
|
||||
</div>
|
||||
|
||||
<!-- Preview Panel — right side panel for file preview -->
|
||||
<div class="preview-panel" x-show="previewItem" x-transition:enter="preview-enter" x-transition:leave="preview-leave" x-cloak>
|
||||
<template x-if="previewItem">
|
||||
<div style="display:flex;flex-direction:column;height:100%;">
|
||||
<!-- Preview Panel — right side panel (vanilla JS, no Alpine) -->
|
||||
<div id="ws-preview-panel" class="preview-panel" style="display:none;">
|
||||
<div class="preview-panel-header">
|
||||
<span x-html="_fileIcon(previewItem.name, previewItem.is_folder, previewItem.content_format)"></span>
|
||||
<span class="preview-panel-title" x-text="previewItem.name"></span>
|
||||
<button class="preview-close" @click="previewItem=null" title="Close preview">×</button>
|
||||
<span id="ws-preview-icon"></span>
|
||||
<span class="preview-panel-title" id="ws-preview-title"></span>
|
||||
<button class="preview-close" onclick="window._wsData.closePreviewPanel()" title="Close preview">×</button>
|
||||
</div>
|
||||
<div class="preview-panel-meta">
|
||||
<span x-text="previewItem.is_folder ? 'Folder' : _fileTypeLabel(previewItem.name, previewItem.content_format)"></span>
|
||||
<span x-text="previewItem.size_display || '—'"></span>
|
||||
<span x-text="_formatDate(previewItem.updated_at)"></span>
|
||||
<span id="ws-preview-type"></span>
|
||||
<span id="ws-preview-size"></span>
|
||||
<span id="ws-preview-date"></span>
|
||||
</div>
|
||||
<div class="preview-panel-tags" x-show="(previewItem.tags||[]).length > 0">
|
||||
<template x-for="t in (previewItem.tags||[])" :key="'ptag'+t.id">
|
||||
<span class="tag-chip" :style="{background: t.color}"><span x-text="t.name"></span></span>
|
||||
</template>
|
||||
<div class="preview-panel-tags" id="ws-preview-tags" style="display:none;">
|
||||
</div>
|
||||
<div class="preview-panel-body" id="ws-preview-body">
|
||||
<div style="text-align:center;padding:40px;color:var(--text-tertiary)">Select a file to preview</div>
|
||||
</div>
|
||||
<div class="preview-panel-body" x-html="previewPanelContent || '<div style=text-align:center;padding:40px;color:var(--text-tertiary)>Loading preview...</div>'"></div>
|
||||
<div class="preview-panel-actions">
|
||||
<button class="btn btn-secondary" @click="openPage(previewItem.id)">{{ fd_icon("file",14) }} Open</button>
|
||||
<button class="btn btn-secondary" @click="startRename(previewItem)">{{ fd_icon("edit",14) }} Rename</button>
|
||||
<button class="btn btn-danger" @click="deleteWithUndo(previewItem)">{{ fd_icon("trash",14) }} Delete</button>
|
||||
<button class="btn btn-secondary" id="ws-preview-open-btn">{{ fd_icon("file",14) }} Open</button>
|
||||
<button class="btn btn-secondary" onclick="window._wsData.closePreviewPanel()">{{ fd_icon("edit",14) }} Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div><!-- /ws-split -->
|
||||
|
||||
|
||||
Reference in New Issue
Block a user