fix(content): mobile — status file di tab Files, rail tools full width

- tab Files menampilkan '✓/● nama-file' (🖼️ untuk asset) sebagai ganti
  baris pinned nama file + status tersimpan yang memakan ruang horizontal
- baris pinned dihapus; rail template kini memakai lebar penuh
- aksi simpan/publish/edit tetap di panel-actions; asset dapat tombol
  salin path di header
- bottom-sheet mobile minimal h50 saat buka tab/pilih file (tidak lagi
  tertahan di peek 84px)
- label tab dibatasi 180px + ellipsis untuk nama file panjang
This commit is contained in:
a2nr 2026-10-26 16:34:29 +07:00
parent 2015d3c2b6
commit 20e6682826
1 changed files with 67 additions and 108 deletions

View File

@ -132,7 +132,16 @@
].filter((t) => t.show !== false)
);
let allTabs = $derived([{ id: 'files', label: 'Files' }, ...contentTabs]);
// Label tab "Files": saat ada file aktif, tampilkan status + nama file
// (menggantikan baris pinned di rail yang memakan ruang horizontal).
let filesTabLabel = $derived.by(() => {
if (!mgr.activePath) return 'Files';
const name = mgr.activePath.split('/').pop() ?? mgr.activePath;
if (mgr.treeRoot === 'assets') return `🖼️ ${name}`;
return `${mgr.dirty ? '●' : '✓'} ${name}`;
});
let allTabs = $derived([{ id: 'files', label: filesTabLabel }, ...contentTabs]);
// Auto-select: setelah preview file aktif selesai dimuat, pindah ke tab
// konten pertama file itu (skip 'files'). Tanpa tab konten → tetap Files.
@ -208,6 +217,7 @@
if (mgr.dirty && !window.confirm('Ada perubahan yang belum disimpan. Lanjut buka file lain?')) {
return;
}
ensureMobileWorkspace();
if (mgr.treeRoot === 'assets') {
// Asset file: load for preview only, don't go through draft system
mgr.activePath = path;
@ -231,9 +241,6 @@
mgr.activeTab = 'files';
await mgr.loadDraft(path);
}
if (mgr.isMobile) {
mgr.mobileMode = mgr.treeRoot === 'assets' ? 'h50' : 'peek';
}
}
function handleEditorChange(value: string) {
@ -351,6 +358,18 @@
await mgr.handlePublish();
}
/**
* Mobile: pastikan bottom-sheet cukup tinggi untuk menampilkan tab, rail
* tools, dan tree. Mode `hidden`/`peek`/`h30` terlalu sempit — naikkan ke
* h50 minimal saat user membuka tab / memilih file.
*/
function ensureMobileWorkspace() {
if (!mgr.isMobile) return;
if (mgr.mobileMode === 'hidden' || mgr.mobileMode === 'peek' || mgr.mobileMode === 'h30') {
mgr.mobileMode = 'h50';
}
}
function handleTabClick(tab: string) {
if (mgr.activeTab === 'velxio' && tab !== 'velxio') {
syncVelxioToMarkdown();
@ -358,9 +377,7 @@
// Pilihan manual user membatalkan auto-select yang masih menunggu preview.
autoSelectPath = null;
mgr.activeTab = tab;
if (mgr.isMobile && mgr.mobileMode === 'hidden') {
mgr.mobileMode = 'h50';
}
ensureMobileWorkspace();
}
// Step the mobile workspace height up/down through the available modes
@ -1035,6 +1052,8 @@
<button class="panel-btn publish-btn" use:preserveFocus onclick={() => handlePublishAction()} disabled={mgr.publishing || mgr.saving} title="Publish">
{mgr.publishing ? '⏳' : '🚀'}
</button>
{:else if mgr.activePath && isAssetFile}
<button class="panel-btn" use:preserveFocus onclick={handleCopyAssetPath} title="Salin path asset">📋</button>
{/if}
{/snippet}
@ -1072,68 +1091,47 @@
<!-- Quick tools rail — dipakai desktop & mobile (satu cara tampil, C3) -->
{#snippet toolsRail()}
{#if mgr.activePath}
{#if mgr.activePath && !isAssetFile}
<!-- Templates saja: nama file + status pindah ke label tab Files,
aksi simpan/publish/edit sudah ada di panel-actions header. -->
<div class="quick-tools-rail" data-preserve-editor-focus>
<!-- Pinned: status file aktif + aksi utama (tidak ikut scroll) -->
<div class="qt-pinned">
<span class="qt-file-name" title={mgr.activePath}>{isAssetFile ? '🖼️' : '📄'} {activeFileName}</span>
{#if isAssetFile}
<span class="qt-path" title={mgr.activePath}>/assets/{mgr.activePath}</span>
<button type="button" class="qt-chip" use:preserveFocus onclick={handleCopyAssetPath} title="Salin path asset">📋 Salin Path</button>
{:else}
<span class="qt-dirty-badge" class:dirty={mgr.dirty}>{mgr.dirty ? '● Belum disimpan' : '✓ Tersimpan'}</span>
<button type="button" class="qt-chip qt-action-edit" use:preserveFocus onclick={() => { contentViewMode = contentViewMode === 'edit' ? 'preview' : 'edit'; }} title="Toggle editor teks (Ctrl+E)">
{contentViewMode === 'edit' ? '👁️' : '✏️'}
</button>
<button type="button" class="qt-chip" use:preserveFocus onclick={() => handleSaveAction()} disabled={mgr.saving || (!mgr.dirty && mgr.activeTab !== 'velxio')} title="Simpan">
{mgr.saving ? '⏳' : '💾'}
</button>
<button type="button" class="qt-chip" use:preserveFocus onclick={() => handlePublishAction()} disabled={mgr.publishing || mgr.saving} title="Publish">
{mgr.publishing ? '⏳' : '🚀'}
</button>
{/if}
</div>
<div class="qt-scroll">
<div class="qt-divider" aria-hidden="true"></div>
{#if !isAssetFile}
<!-- Scrollable templates -->
<div class="qt-scroll">
<div class="qt-divider" aria-hidden="true"></div>
<!-- Layout templates -->
<div class="qt-group">
<span class="qt-label">📐 Layout</span>
{#each FEATURE_TEMPLATES.filter(t => t.group === 'layout') as tpl}
<button type="button" class="qt-chip" use:preserveFocus onclick={() => insertTemplate(tpl)} title={getTemplateDesc(tpl.id)}>
{tpl.label}
</button>
{/each}
</div>
<div class="qt-divider" aria-hidden="true"></div>
<!-- Set soal templates -->
<div class="qt-group">
<span class="qt-label">🎯 Set Soal</span>
{#each FEATURE_TEMPLATES.filter(t => t.group === 'set_soal') as tpl}
<button type="button" class="qt-chip" use:preserveFocus onclick={() => insertTemplate(tpl)} title={getTemplateDesc(tpl.id)}>
{tpl.label}
</button>
{/each}
</div>
<div class="qt-divider" aria-hidden="true"></div>
<!-- Inline templates -->
<div class="qt-group">
<span class="qt-label">⚡ Inline</span>
{#each FEATURE_TEMPLATES.filter(t => t.group === 'inline') as tpl}
<button type="button" class="qt-chip" use:preserveFocus onclick={() => insertTemplate(tpl)} title={getTemplateDesc(tpl.id)}>
{tpl.label}
</button>
{/each}
</div>
<!-- Layout templates -->
<div class="qt-group">
<span class="qt-label">📐 Layout</span>
{#each FEATURE_TEMPLATES.filter(t => t.group === 'layout') as tpl}
<button type="button" class="qt-chip" use:preserveFocus onclick={() => insertTemplate(tpl)} title={getTemplateDesc(tpl.id)}>
{tpl.label}
</button>
{/each}
</div>
{/if}
<div class="qt-divider" aria-hidden="true"></div>
<!-- Set soal templates -->
<div class="qt-group">
<span class="qt-label">🎯 Set Soal</span>
{#each FEATURE_TEMPLATES.filter(t => t.group === 'set_soal') as tpl}
<button type="button" class="qt-chip" use:preserveFocus onclick={() => insertTemplate(tpl)} title={getTemplateDesc(tpl.id)}>
{tpl.label}
</button>
{/each}
</div>
<div class="qt-divider" aria-hidden="true"></div>
<!-- Inline templates -->
<div class="qt-group">
<span class="qt-label">⚡ Inline</span>
{#each FEATURE_TEMPLATES.filter(t => t.group === 'inline') as tpl}
<button type="button" class="qt-chip" use:preserveFocus onclick={() => insertTemplate(tpl)} title={getTemplateDesc(tpl.id)}>
{tpl.label}
</button>
{/each}
</div>
</div>
</div>
{/if}
{/snippet}
@ -1526,6 +1524,9 @@
cursor: pointer;
white-space: nowrap;
flex-shrink: 0;
max-width: 180px;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: -1px;
z-index: 0;
transition: background 0.15s, color 0.15s;
@ -1811,44 +1812,6 @@
border-bottom: 1px solid var(--color-border);
flex-shrink: 0;
}
.qt-pinned {
display: flex;
align-items: center;
gap: 0.35rem;
padding: 0.4rem 0.6rem;
background: var(--color-bg-secondary);
border-right: 1px solid var(--color-border);
flex-shrink: 0;
}
.qt-file-name {
font-size: 0.78rem;
font-weight: 600;
color: var(--color-text);
max-width: 180px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.qt-path {
font-size: 0.7rem;
color: var(--color-text-muted);
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.qt-dirty-badge {
font-size: 0.68rem;
padding: 0.15rem 0.45rem;
border-radius: 4px;
background: #ebfbee;
color: #2b8a3e;
white-space: nowrap;
}
.qt-dirty-badge.dirty {
background: #fff3bf;
color: #e67700;
}
.qt-scroll {
display: flex;
align-items: center;
@ -1909,10 +1872,6 @@
opacity: 0.45;
cursor: not-allowed;
}
.qt-action-edit {
color: var(--color-primary, #339af0);
font-weight: 600;
}
/* Floating mode */
.editor-area.floating {
position: fixed;