peek panels: reusable editor module — shared _page_editor_content.html + embed template
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped

Architecture for zero code duplication:
- Extract page editor HTML into _page_editor_content.html (shared include)
- page_editor.html includes it (same behavior as before)
- page_editor_embed.html includes it with empty topbar (no header in peek)
- board.py: ?embed=1 renders page_editor_embed.html

Peek panels now load /pages/{id}?embed=1 in iframe:
- Editor renders without sidebar or header breadcrumb/actions
- Full editing capability (blocks, markdown, slash commands, save)
- Same code — one change to _page_editor_content.html updates everywhere
This commit is contained in:
2026-07-22 10:50:02 -04:00
parent e32abfbfa6
commit afe670bdd3
6 changed files with 936 additions and 577 deletions
+6 -36
View File
@@ -446,13 +446,8 @@
</div>
</div>
</div>
<div class="peek-body">
<div x-show="peekLoading" class="peek-loading">Loading…</div>
<div x-show="!peekLoading && peekContent" x-html="peekContent"></div>
<div x-show="!peekLoading && !peekContent" class="peek-empty">
<div style="margin-bottom:8px;">{{ fd_icon("file",32) }}</div>
<div style="font-size:14px;color:var(--text-secondary);">No preview available</div>
</div>
<div class="peek-body" style="flex:1;display:flex;flex-direction:column;">
<iframe id="lib-peek-iframe" src="" style="width:100%;height:100%;border:none;flex:1;"></iframe>
</div>
</div>
</template>
@@ -1203,40 +1198,15 @@ function libraryPage() {
// ── Side peek ──
async openPeek(item) {
this.peekItem = item;
this.peekContent = '';
this.peekLoading = true;
this.peekFavorited = item.favorited || false;
try {
var r = await fetch('/board/api/pages/' + item.id);
var d = await r.json();
var content = d.content || '';
// Simple preview
if (d.content_format === 'blocks') {
try {
var blocks = typeof content === 'string' ? JSON.parse(content) : content;
var preview = '';
var lines = Array.isArray(blocks) ? blocks.slice(0, 30) : ((blocks && blocks.blocks) ? blocks.blocks.slice(0, 30) : []);
for (var i = 0; i < lines.length; i++) {
var b = lines[i];
var txt = b.content || b.text || (typeof b === 'string' ? b : '');
if (txt && txt.trim()) preview += '<div style="margin-bottom:6px;font-size:14px;line-height:1.5;color:var(--text-primary);">' + this._escHtml(String(txt)) + '</div>';
}
this.peekContent = preview || '<div style="color:var(--text-dim);">(empty page)</div>';
} catch(e) {
this.peekContent = '<pre style="font-size:13px;color:var(--text-primary);white-space:pre-wrap;">' + this._escHtml(String(content).substring(0, 2000)) + '</pre>';
}
} else {
this.peekContent = content ? '<div style="font-size:14px;line-height:1.6;color:var(--text-primary);white-space:pre-wrap;">' + this._escHtml(String(content).substring(0, 2000)) + '</div>' : '';
}
} catch(e) {
this.peekContent = '';
}
this.peekLoading = false;
var iframe = document.getElementById('lib-peek-iframe');
if (iframe) iframe.src = '/pages/' + item.id + '?embed=1';
},
closePeek() {
this.peekItem = null;
this.peekContent = '';
var iframe = document.getElementById('lib-peek-iframe');
if (iframe) iframe.src = '';
},
async toggleFavoritePeek() {