feat: sidebar Repository identique au Workspace-local, pages forge unifiees, GC miroirs vides
FlowDeck CI / lint (push) Successful in 1m37s
FlowDeck CI / test (push) Successful in 25m11s
FlowDeck CI / docker (push) Successful in 1m32s

This commit is contained in:
2026-10-09 22:33:34 -04:00
parent 9ea0cddf3c
commit d95178156f
13 changed files with 1110 additions and 175 deletions
+52 -15
View File
@@ -329,16 +329,18 @@
</div>
</div>
<!-- Repository (Gitea) — only visible for Gitea workspaces -->
<!-- Repository (forge) — same navigation structure as Workspace-local -->
{% if gitea_workspace %}
<div class="sidebar-section" x-show="isSectionVisible('gitea')" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100">
<div class="sidebar-section-header" @click="toggleSection('gitea')" @contextmenu.prevent="openSectionMenu($event, 'gitea')">
<div class="sidebar-section-title">
<span class="chevron" :class="{ open: sectionsOpen.gitea }">▶</span>
<span class="section-icon">{{ fd_icon("link",14) }}</span>
<span class="section-label">Repository (Gitea)</span>
<span class="section-label">Repository{% if forge %} ({{ forge|capitalize }}){% elif gitea_owner and gitea_repo %} (Gitea){% endif %}</span>
</div>
<div class="sidebar-section-actions">
<button class="section-action-btn" title="New file in local mirror" @click.stop="fdGwNewFile()">{{ fd_icon("file",16) }}</button>
<button class="section-action-btn" title="New folder in local mirror" @click.stop="fdGwNewFolder()">{{ fd_icon("folder",16) }}</button>
<button class="section-action-btn" title="Refresh" @click.stop="fdGwRefresh()">{{ fd_icon("refresh",16) }}</button>
<button class="section-action-btn section-menu-btn" title="Section options" @click.stop="openSectionMenu($event, 'gitea')">⋮</button>
</div>
@@ -1441,6 +1443,7 @@
giteaWorkspace: {{ 'true' if gitea_workspace else 'false' }},
giteaOwner: '{{ gitea_owner|default("") }}',
giteaRepo: '{{ gitea_repo|default("") }}',
forge: '{{ forge|default("gitea") }}',
giteaTree: [], // loaded client-side for sidebar
currentView: 'home',
@@ -2123,7 +2126,16 @@
.catch(function(err) { self.toast('Meeting note creation failed: ' + err.message, 'error'); });
},
fdCreateFolder() { window.FlowDeck.showCreateFolderModal(); },
fdGwRefresh() { if (window._gwData) window._gwData.refreshTree(); },
fdGwRefresh() { if (window._gwData) window._gwData.refreshTree(); else this.loadGiteaTree(); },
forgeTreeBase() {
// Gitea and GitHub share the same sidebar tree shape; only the API
// prefix differs. Defaults to Gitea for existing cookies/pages.
var f = (this.forge || '').toLowerCase();
if (f === 'github') return '/api/github/projects/' + this.giteaOwner + '/' + this.giteaRepo;
return '/api/gitea/projects/' + this.giteaOwner + '/' + this.giteaRepo;
},
fdGwNewFile() { if (window._gwData && window._gwData.showNewFile !== undefined) { window._gwData.showNewFile = true; } else this.newPageInWorkspace(); },
fdGwNewFolder() { if (window._gwData && window._gwData.createFolderAtRoot) window._gwData.createFolderAtRoot(); else this.newFolderInWorkspace(); },
sidebarSections() {
if (Object.keys(this.sidebarConfig).length) return this.sidebarConfig;
return {
@@ -2350,19 +2362,44 @@
.then(data => { window.location.href = `/pages/${data.id}`; })
.catch(err => { this.toast('Failed to create sub-page: ' + err.message, 'error'); });
},
fdRepoTreeHtml(item, depth) {
// Shared Repository row — same DOM contract as _workspace_tree_macro:
// .sidebar-item.ws-tree-item + data-is-folder/depth/name + chevron +
// .page-icon/.page-name. Depth indent matches local (4 + depth*12px).
var isFolder = item.type === 'folder' || item.type === 'dir';
var safe = (item.name || '').replace(/'/g, "\\'");
var pad = 4 + (depth || 0) * 12;
var chev = isFolder
? '<button class="tree-chevron" data-repo-toggle="' + item.path + '" title="Toggle folder">▶</button>'
: '<span style="width:12px;flex-shrink:0;"></span>';
var icon = isFolder ? getSvgIcon('folder', 14) : getSvgIcon('file', 14);
var label = isFolder
? '<span class="page-name tree-folder-link" title="Open folder">' + item.name + '</span>'
: '<span class="page-name">' + item.name + '</span>';
return '<li class="sidebar-item ws-tree-item" data-gitea-path="' + item.path + '"'
+ ' data-gitea-type="' + (isFolder ? 'folder' : 'file') + '"'
+ ' data-gitea-sha="' + (item.sha || '') + '"'
+ ' data-is-folder="' + (isFolder ? 'true' : 'false') + '"'
+ ' data-depth="' + (depth || 0) + '" data-name="' + safe + '"'
+ ' style="padding-left:' + pad + 'px;">'
+ chev + '<span class="page-icon">' + icon + '</span>' + label + '</li>';
},
loadGiteaTree() {
if (!this.giteaWorkspace || !this.giteaOwner || !this.giteaRepo) {
console.log('loadGiteaTree: skipped (giteaWorkspace=' + this.giteaWorkspace + ', owner=' + this.giteaOwner + ', repo=' + this.giteaRepo + ')');
return;
}
// Skip if giteaWorkspace component already loaded the tree (has data-gitea-path items)
var container = document.getElementById('sidebar-workspace-items');
// Fallback loader for the Repository section on pages where the
// forge-workspace bundle is not mounted. MUST target
// #sidebar-gitea-items — never #sidebar-workspace-items (local mirror).
var container = document.getElementById('sidebar-gitea-items');
if (container && container.querySelector('[data-gitea-path]')) {
console.log('loadGiteaTree: skipped (tree already loaded by giteaWorkspace component)');
console.log('loadGiteaTree: skipped (tree already loaded by workspace component)');
return;
}
console.log('loadGiteaTree: fetching /api/gitea/projects/' + this.giteaOwner + '/' + this.giteaRepo + '/tree');
fetch('/api/gitea/projects/' + this.giteaOwner + '/' + this.giteaRepo + '/tree')
var base = this.forgeTreeBase();
console.log('loadGiteaTree: fetching ' + base + '/tree');
fetch(base + '/tree')
.then(function(r) {
if (!r.ok) throw new Error('HTTP ' + r.status + ': ' + r.statusText);
return r.json();
@@ -2370,7 +2407,7 @@
.then(function(data) {
this.giteaTree = data.tree || [];
if (!container) {
console.log('loadGiteaTree: #sidebar-workspace-items not found');
console.log('loadGiteaTree: #sidebar-gitea-items not found');
return;
}
container.innerHTML = '';
@@ -2378,13 +2415,13 @@
container.innerHTML = '<li class="sidebar-item empty-hint"><span class="page-icon page-icon-svg">'+getSvgIcon('file',16)+'</span><span class="page-name text-dim">No files yet</span></li>';
return;
}
this.giteaTree.forEach(function(item) {
var li = document.createElement('li');
li.className = 'sidebar-item';
var icon = item.type === 'dir' ? '<span class="page-icon-svg">'+getSvgIcon('folder',14)+'</span>' : '<span class="page-icon-svg">'+getSvgIcon('file',14)+'</span>';
li.innerHTML = '<span class="page-icon">' + icon + '</span><span class="page-name">' + item.name + '</span>';
container.appendChild(li);
var self = this;
var html = '';
self.giteaTree.forEach(function(item) {
var norm = { name: item.name, path: item.path, sha: item.sha, type: (item.type === 'dir' ? 'folder' : item.type) };
html += self.fdRepoTreeHtml(norm, 0);
});
container.innerHTML = html;
console.log('loadGiteaTree: rendered ' + this.giteaTree.length + ' items');
}.bind(this))
.catch(function(err) {