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:
parent
2015d3c2b6
commit
20e6682826
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue