feat: gitea-workspace now has file tree browser like local-workspace
- Main content area now shows file/folder tree with breadcrumb navigation - Click folders to drill down, click files to open - Breadcrumb shows current path with clickable segments - Sorted tree: folders first, then files alphabetically - refreshTree() now reloads without full page reload - Empty state with 'New file' button when folder is empty
This commit is contained in:
@@ -88,20 +88,51 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- Empty state -->
|
<!-- File tree browser (like local-workspace) -->
|
||||||
<template x-if="!showFile && !showEditor && !showPrivate">
|
<template x-if="!showFile && !showEditor && !showPrivate">
|
||||||
<div class="gw-content">
|
<div class="gw-content">
|
||||||
<div class="empty-state">
|
<!-- Breadcrumb -->
|
||||||
<h2>📁 <span x-text="owner + '/' + repo"></span></h2>
|
<div class="gw-breadcrumb" style="display:flex;align-items:center;gap:4px;margin-bottom:16px;font-size:14px;color:var(--text-secondary);">
|
||||||
<p x-show="!showNewFile">Select a file from the sidebar or create a new one</p>
|
<a href="#" @click.prevent="navigateToRoot()" style="color:var(--text-secondary);text-decoration:none;" x-text="owner + '/' + repo"></a>
|
||||||
<!-- New file form -->
|
<template x-for="(b, i) in folderStack" :key="i">
|
||||||
<div x-show="showNewFile" style="margin-top:16px;text-align:left;max-width:400px;margin-left:auto;margin-right:auto;">
|
<span style="display:contents;">
|
||||||
<input type="text" x-model="newFilePath" placeholder="path/to/file.md" class="settings-input" style="margin-bottom:8px;">
|
<span style="color:var(--text-tertiary);">/</span>
|
||||||
<textarea x-model="newFileContent" placeholder="File content..." class="settings-input" style="height:120px;margin-bottom:8px;"></textarea>
|
<a href="#" @click.prevent="navigateToFolder(i)" style="color:var(--text-secondary);text-decoration:none;" x-text="b"></a>
|
||||||
<input type="text" x-model="newFileMsg" placeholder="Commit message" class="settings-input" style="margin-bottom:8px;">
|
</span>
|
||||||
<button class="btn btn-primary" @click="createNewFile()">💾 Create file</button>
|
</template>
|
||||||
|
<span x-show="treeLoading" style="color:var(--text-tertiary);">Loading...</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tree items -->
|
||||||
|
<div x-show="!treeLoading">
|
||||||
|
<template x-if="treeItems.length === 0">
|
||||||
|
<div class="empty-state">
|
||||||
|
<h2>📁 Empty</h2>
|
||||||
|
<p>No files in this folder</p>
|
||||||
|
<button class="btn btn-primary" @click="showNewFile=!showNewFile">📄+ New file</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="ws-tree">
|
||||||
|
<template x-for="item in sortedTreeItems" :key="item.path">
|
||||||
|
<div class="ws-tree-item"
|
||||||
|
@click="item.type==='folder' ? drillDown(item.path) : openFile(item.path, item.sha)"
|
||||||
|
style="display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:6px;cursor:pointer;">
|
||||||
|
<span x-text="item.type==='folder' ? '📁' : '📄'" style="font-size:16px;"></span>
|
||||||
|
<span x-text="item.name" style="flex:1;font-size:14px;" :style="{ fontWeight: item.type==='folder' ? '500' : '400' }"></span>
|
||||||
|
<span x-text="item.type==='folder' ? '' : formatSize(item.size)" style="font-size:12px;color:var(--text-tertiary);"></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- New file form -->
|
||||||
|
<div x-show="showNewFile" style="margin-top:16px;max-width:400px;">
|
||||||
|
<input type="text" x-model="newFilePath" placeholder="path/to/file.md" class="settings-input" style="margin-bottom:8px;">
|
||||||
|
<textarea x-model="newFileContent" placeholder="File content..." class="settings-input" style="height:120px;margin-bottom:8px;"></textarea>
|
||||||
|
<input type="text" x-model="newFileMsg" placeholder="Commit message" class="settings-input" style="margin-bottom:8px;">
|
||||||
|
<button class="btn btn-primary" @click="createNewFile()">💾 Create file</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -171,16 +202,61 @@ function giteaWorkspace() {
|
|||||||
editContent: '',
|
editContent: '',
|
||||||
commitMessage: 'Update via FlowDeck',
|
commitMessage: 'Update via FlowDeck',
|
||||||
commitHistory: JSON.parse(localStorage.getItem('fd_commit_msgs') || '[]'),
|
commitHistory: JSON.parse(localStorage.getItem('fd_commit_msgs') || '[]'),
|
||||||
|
// File tree browser
|
||||||
|
treeItems: [],
|
||||||
|
treeLoading: false,
|
||||||
|
folderStack: [],
|
||||||
|
currentPath: '',
|
||||||
|
|
||||||
|
get sortedTreeItems() {
|
||||||
|
return [...this.treeItems].sort(function(a, b) {
|
||||||
|
if (a.type === b.type) return a.name.localeCompare(b.name);
|
||||||
|
return a.type === 'folder' ? -1 : 1;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
// Set cookie for sidebar
|
// Set cookie for sidebar
|
||||||
document.cookie = 'flowdeck_workspace=gitea:' + this.owner + ':' + this.repo + ';path=/;SameSite=Lax';
|
document.cookie = 'flowdeck_workspace=gitea:' + this.owner + ':' + this.repo + ';path=/;SameSite=Lax';
|
||||||
// Load root tree into sidebar
|
// Load sidebar tree
|
||||||
this.loadSidebarTree();
|
this.loadSidebarTree();
|
||||||
|
// Load main content tree
|
||||||
|
this.loadMainTree('');
|
||||||
// Listen for sidebar clicks on Gitea items
|
// Listen for sidebar clicks on Gitea items
|
||||||
this.setupSidebarClicks();
|
this.setupSidebarClicks();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async loadMainTree(path) {
|
||||||
|
this.treeLoading = true;
|
||||||
|
this.currentPath = path;
|
||||||
|
try {
|
||||||
|
var url = '/api/gitea/projects/' + this.owner + '/' + this.repo + '/tree';
|
||||||
|
if (path) url += '?path=' + encodeURIComponent(path);
|
||||||
|
var r = await fetch(url);
|
||||||
|
if (!r.ok) { this.treeItems = []; return; }
|
||||||
|
var d = await r.json();
|
||||||
|
this.treeItems = d.tree || [];
|
||||||
|
} catch(e) { this.treeItems = []; }
|
||||||
|
finally { this.treeLoading = false; }
|
||||||
|
},
|
||||||
|
|
||||||
|
drillDown(path) {
|
||||||
|
this.folderStack.push(path.split('/').pop());
|
||||||
|
this.loadMainTree(path);
|
||||||
|
},
|
||||||
|
|
||||||
|
navigateToFolder(idx) {
|
||||||
|
// Truncate stack and rebuild path
|
||||||
|
this.folderStack = this.folderStack.slice(0, idx + 1);
|
||||||
|
var path = this.folderStack.join('/');
|
||||||
|
this.loadMainTree(path);
|
||||||
|
},
|
||||||
|
|
||||||
|
navigateToRoot() {
|
||||||
|
this.folderStack = [];
|
||||||
|
this.loadMainTree('');
|
||||||
|
},
|
||||||
|
|
||||||
async loadSidebarTree() {
|
async loadSidebarTree() {
|
||||||
try {
|
try {
|
||||||
var r = await fetch('/api/gitea/projects/' + this.owner + '/' + this.repo + '/tree');
|
var r = await fetch('/api/gitea/projects/' + this.owner + '/' + this.repo + '/tree');
|
||||||
@@ -444,8 +520,9 @@ function giteaWorkspace() {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async refreshTree() {
|
async refreshTree() {
|
||||||
// Reload page to refresh sidebar tree
|
// Reload main tree and sidebar tree without full page reload
|
||||||
window.location.reload();
|
this.loadMainTree(this.currentPath);
|
||||||
|
this.loadSidebarTree();
|
||||||
},
|
},
|
||||||
|
|
||||||
formatSize(bytes) {
|
formatSize(bytes) {
|
||||||
|
|||||||
Reference in New Issue
Block a user