feat: sidebar arborescence Gitea + tab user repos avec vrai username
1. Tab user repos: fetch username via /auth/user → affiche 'bruno' au lieu de 'Personal' 2. Sidebar: ws_cookie='gitea:owner:repo' → _sidebar_data() fetch le tree depuis Gitea API 3. base.html: data-gitea-path/type/sha sur les items workspace pour les projets Gitea 4. gitea_workspace.html: refonte sans tree panel → navigation via sidebar - Clic fichier → ouvre dans le contenu (lecture + edit + commit) - Clic dossier → lazy-load les enfants dans le sidebar 5. openGitea(): set cookie flowdeck_workspace avant navigation
This commit is contained in:
@@ -69,7 +69,41 @@ def _sidebar_data(request: Request, repos: list[dict]) -> dict:
|
||||
ws_cookie = request.cookies.get("flowdeck_workspace", "")
|
||||
active_ws_name = "Workspace"
|
||||
workspace_pages = []
|
||||
if ws_cookie:
|
||||
gitea_workspace = False
|
||||
|
||||
if ws_cookie and ws_cookie.startswith("gitea:"):
|
||||
# Gitea workspace: fetch repo tree for sidebar
|
||||
parts = ws_cookie.split(":", 2)
|
||||
if len(parts) >= 3:
|
||||
gitea_owner = parts[1]
|
||||
gitea_repo = parts[2]
|
||||
active_ws_name = f"{gitea_owner}/{gitea_repo}"
|
||||
gitea_workspace = True
|
||||
# Fetch root tree from Gitea API (use admin token as fallback)
|
||||
try:
|
||||
from app.services.gitea_client import get_user_gitea_client, GiteaClient
|
||||
gitea = get_user_gitea_client(request) or GiteaClient()
|
||||
import asyncio
|
||||
tree_items = asyncio.run(gitea.get_repo_contents(gitea_owner, gitea_repo))
|
||||
for item in tree_items:
|
||||
is_folder = item.get("type") == "dir"
|
||||
workspace_pages.append({
|
||||
"id": item.get("path", ""),
|
||||
"name": item.get("name", ""),
|
||||
"icon": "📁" if is_folder else "📄",
|
||||
"url": f"javascript:void(0)",
|
||||
"active": False,
|
||||
"indent": 0,
|
||||
"depth": 0,
|
||||
"has_children": is_folder,
|
||||
"children": [],
|
||||
"_gitea_path": item.get("path", ""),
|
||||
"_gitea_type": "folder" if is_folder else "file",
|
||||
"_gitea_sha": item.get("sha", ""),
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
elif ws_cookie:
|
||||
workspace_pages = _load_workspace_pages(ws_cookie)
|
||||
try:
|
||||
with get_conn() as conn:
|
||||
@@ -83,6 +117,7 @@ def _sidebar_data(request: Request, repos: list[dict]) -> dict:
|
||||
"workspace_name": ws, "workspace_initial": initial,
|
||||
"active_ws_name": active_ws_name,
|
||||
"workspace_pages": workspace_pages,
|
||||
"gitea_workspace": gitea_workspace,
|
||||
"current_page": "Dashboard", "last_edited": "now",
|
||||
"recent_pages": recent_pages,
|
||||
"private_pages": [],
|
||||
|
||||
@@ -28,7 +28,10 @@
|
||||
@contextmenu.prevent="showContextMenu($event, '{{ page.id }}', '{{ page.name }}')"
|
||||
data-page-id="{{ page.id }}"
|
||||
data-page-depth="{{ depth }}"
|
||||
data-has-children="{{ 'true' if page.has_children else 'false' }}">
|
||||
data-has-children="{{ 'true' if page.has_children else 'false' }}"
|
||||
{% if page._gitea_path %}data-gitea-path="{{ page._gitea_path }}"{% endif %}
|
||||
{% if page._gitea_type %}data-gitea-type="{{ page._gitea_type }}"{% endif %}
|
||||
{% if page._gitea_sha %}data-gitea-sha="{{ page._gitea_sha }}"{% endif %}>
|
||||
{# Toggle chevron — only visible for items with children #}
|
||||
{% if page.has_children %}
|
||||
<button class="tree-toggle"
|
||||
|
||||
+118
-212
@@ -12,7 +12,7 @@
|
||||
<span class="page-title-topbar" x-text="owner + '/' + repo"></span>
|
||||
</div>
|
||||
<div class="topbar-right">
|
||||
<button class="page-action-btn" @click="refresh()" title="Refresh">🔄</button>
|
||||
<button class="page-action-btn" @click="refreshTree()" title="Refresh">🔄</button>
|
||||
<button class="page-action-btn" onclick="window.location='/accounts/settings'" title="Settings">⚙</button>
|
||||
</div>
|
||||
</header>
|
||||
@@ -20,16 +20,7 @@
|
||||
|
||||
{% block content %}
|
||||
<style>
|
||||
.gw-layout{display:flex;height:calc(100vh - 48px);}
|
||||
.gw-tree{width:280px;min-width:280px;background:var(--bg-sidebar);border-right:1px solid var(--border);overflow-y:auto;padding:12px 0;}
|
||||
.gw-tree-item{display:flex;align-items:center;gap:6px;padding:4px 16px;font-size:13px;color:var(--text-secondary);cursor:pointer;transition:background 100ms;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
||||
.gw-tree-item:hover{background:var(--bg-tertiary);color:var(--text);}
|
||||
.gw-tree-item.selected{background:var(--accent);color:#fff;}
|
||||
.gw-tree-icon{font-size:14px;flex-shrink:0;}
|
||||
.gw-tree-indent{padding-left:32px !important;}
|
||||
.gw-tree-indent2{padding-left:48px !important;}
|
||||
.gw-tree-section-title{font-size:11px;font-weight:600;color:var(--text-dim);text-transform:uppercase;letter-spacing:.5px;padding:8px 16px 4px;margin-top:8px;border-top:1px solid var(--border);}
|
||||
.gw-main{flex:1;display:flex;flex-direction:column;overflow:hidden;}
|
||||
.gw-main{display:flex;flex-direction:column;height:calc(100vh - 48px);overflow:hidden;}
|
||||
.gw-content{padding:24px;overflow-y:auto;flex:1;}
|
||||
.gw-content h3{font-size:16px;margin-bottom:12px;color:var(--text-dim);}
|
||||
.gw-content pre{background:var(--bg-tertiary);border:1px solid var(--border);border-radius:8px;padding:16px;overflow-x:auto;font-size:13px;line-height:1.5;white-space:pre-wrap;word-break:break-word;max-height:70vh;overflow-y:auto;}
|
||||
@@ -37,136 +28,68 @@
|
||||
.gw-content .empty-state h2{font-size:18px;margin-bottom:8px;color:var(--text);}
|
||||
.gw-content textarea{width:100%;min-height:50vh;background:var(--bg-tertiary);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:13px;padding:12px;font-family:monospace;resize:vertical;outline:none;}
|
||||
.gw-content textarea:focus{border-color:var(--accent);}
|
||||
.gw-file-toolbar{display:flex;align-items:center;gap:8px;padding:8px 24px;background:var(--bg-secondary);border-bottom:1px solid var(--border);font-size:13px;color:var(--text-dim);}
|
||||
.gw-file-toolbar strong{color:var(--text);margin-right:12px;}
|
||||
.gw-commit-bar{display:flex;align-items:center;gap:8px;padding:12px 24px;background:var(--bg-secondary);border-top:1px solid var(--border);flex-wrap:wrap;}
|
||||
.gw-commit-bar input{flex:1;min-width:180px;padding:8px 12px;background:var(--bg-tertiary);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:13px;outline:none;}
|
||||
.gw-commit-bar input:focus{border-color:var(--accent);}
|
||||
.gw-file-toolbar{display:flex;align-items:center;gap:8px;padding:8px 24px;background:var(--bg-secondary);border-bottom:1px solid var(--border);font-size:13px;color:var(--text-dim);}
|
||||
.gw-file-toolbar strong{color:var(--text);margin-right:12px;}
|
||||
.btn-primary{padding:8px 16px;border-radius:8px;font-size:13px;cursor:pointer;border:none;font-weight:500;background:var(--accent);color:#fff;}
|
||||
.btn-primary:hover{background:var(--accent-hover);}
|
||||
.btn-danger{background:rgba(255,80,80,.15);color:var(--danger);}
|
||||
.btn-danger{background:rgba(255,80,80,.15);color:var(--danger);border:none;padding:8px 16px;border-radius:8px;font-size:13px;cursor:pointer;font-weight:500;}
|
||||
.btn-danger:hover{background:rgba(255,80,80,.25);}
|
||||
.btn{border:none;padding:8px 16px;border-radius:8px;font-size:13px;cursor:pointer;font-weight:500;border:1px solid var(--border);background:transparent;color:var(--text-dim);}
|
||||
.btn:hover{background:var(--bg-tertiary);color:var(--text);}
|
||||
.loading-spinner{text-align:center;padding:20px;color:var(--text-dim);}
|
||||
</style>
|
||||
|
||||
<div class="gw-layout" x-data="giteaWorkspace()">
|
||||
<!-- Tree sidebar -->
|
||||
<div class="gw-tree">
|
||||
<div class="gw-tree-section-title">📁 GITEA REPO</div>
|
||||
<div class="gw-main" x-data="giteaWorkspace()">
|
||||
<!-- File view -->
|
||||
<template x-if="showFile && !showEditor">
|
||||
<div>
|
||||
<div class="gw-file-toolbar">
|
||||
<strong x-text="filePath || ''"></strong>
|
||||
<span x-text="formatSize(fileSize)"></span>
|
||||
<span style="flex:1;"></span>
|
||||
<button class="btn" @click="openEditor()">✏️ Edit</button>
|
||||
<button class="btn btn-danger" @click="deleteCurrentFile()">🗑 Delete</button>
|
||||
</div>
|
||||
<div class="gw-content">
|
||||
<pre x-text="fileContent"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Loading -->
|
||||
<div class="loading-spinner" x-show="loading">Loading…</div>
|
||||
|
||||
<!-- Tree items (recursive rendering) -->
|
||||
<template x-for="item in treeItems" :key="item.path">
|
||||
<div>
|
||||
<div class="gw-tree-item"
|
||||
:class="{selected: item.path === selectedPath}"
|
||||
:style="'padding-left:' + (16 + item.depth * 16) + 'px'"
|
||||
@click="item.type==='folder' ? toggleFolder(item) : openFile(item)">
|
||||
<span class="gw-tree-icon" x-text="item.type==='folder' ? (item.expanded ? '📂' : '📁') : '📄'"></span>
|
||||
<span x-text="item.name" style="overflow:hidden;text-overflow:ellipsis;"></span>
|
||||
</div>
|
||||
<!-- children -->
|
||||
<template x-if="item.expanded">
|
||||
<template x-for="child in item.children" :key="child.path">
|
||||
<div class="gw-tree-item"
|
||||
:class="{selected: child.path === selectedPath}"
|
||||
:style="'padding-left:' + (16 + child.depth * 16) + 'px'"
|
||||
@click="child.type==='folder' ? toggleFolder(child) : openFile(child)">
|
||||
<span class="gw-tree-icon" x-text="child.type==='folder' ? (child.expanded ? '📂' : '📁') : '📄'"></span>
|
||||
<span x-text="child.name" style="overflow:hidden;text-overflow:ellipsis;"></span>
|
||||
</div>
|
||||
<!-- Editor view -->
|
||||
<template x-if="showEditor">
|
||||
<div>
|
||||
<div class="gw-file-toolbar">
|
||||
<strong>Editing: <span x-text="filePath || 'new file'"></span></strong>
|
||||
<span style="flex:1;"></span>
|
||||
<button class="btn" @click="showEditor=false">Cancel</button>
|
||||
</div>
|
||||
<div class="gw-content">
|
||||
<textarea x-model="editContent"></textarea>
|
||||
</div>
|
||||
<div class="gw-commit-bar">
|
||||
<input type="text" x-model="commitMessage" placeholder="Commit message" list="commit-history">
|
||||
<datalist id="commit-history">
|
||||
<template x-for="msg in commitHistory" :key="msg">
|
||||
<option :value="msg">
|
||||
</template>
|
||||
</template>
|
||||
</datalist>
|
||||
<button class="btn-primary" @click="saveFile()">💾 Commit</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Private section -->
|
||||
<div class="gw-tree-section-title">🔒 PRIVATE PAGES</div>
|
||||
<div class="gw-tree-item" @click="selectedPath='--private--'; showEditor=false; showFile=false;">
|
||||
<span class="gw-tree-icon">📝</span>
|
||||
<span>My notes</span>
|
||||
</div>
|
||||
<div class="gw-tree-item" @click="newPrivatePage()" style="color:var(--accent);">
|
||||
<span class="gw-tree-icon">➕</span>
|
||||
<span>New page</span>
|
||||
</template>
|
||||
|
||||
<!-- Empty state -->
|
||||
<template x-if="!showFile && !showEditor">
|
||||
<div class="gw-content">
|
||||
<div class="empty-state">
|
||||
<h2>📁 <span x-text="owner + '/' + repo"></span></h2>
|
||||
<p>Select a file from the sidebar to view it.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main content -->
|
||||
<div class="gw-main">
|
||||
<!-- Private pages view -->
|
||||
<template x-if="selectedPath==='--private--'">
|
||||
<div class="gw-content">
|
||||
<template x-if="privatePages.length === 0">
|
||||
<div class="empty-state">
|
||||
<h2>🔒 Private Pages</h2>
|
||||
<p>Create FlowDeck pages attached to this Gitea project. These are stored locally, not in the repo.</p>
|
||||
<button class="btn btn-primary" @click="newPrivatePage()" style="margin-top:12px;">+ New Page</button>
|
||||
</div>
|
||||
</template>
|
||||
<template x-for="pp in privatePages" :key="pp.id">
|
||||
<div style="background:var(--bg-secondary);border:1px solid var(--border);border-radius:8px;padding:16px;margin-bottom:8px;cursor:pointer;"
|
||||
@click="openPrivatePage(pp)">
|
||||
<div style="font-weight:500;margin-bottom:4px;" x-text="pp.title"></div>
|
||||
<div style="font-size:12px;color:var(--text-dim);" x-text="pp.updated_at ? 'Updated ' + new Date(pp.updated_at).toLocaleDateString() : ''"></div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- File view -->
|
||||
<template x-if="showFile && !showEditor">
|
||||
<div>
|
||||
<div class="gw-file-toolbar">
|
||||
<strong x-text="currentFile?.name || ''"></strong>
|
||||
<span x-text="formatSize(currentFile?.size || 0)"></span>
|
||||
<span style="flex:1;"></span>
|
||||
<button class="btn" @click="openEditor()">✏️ Edit</button>
|
||||
<button class="btn btn-danger" @click="deleteCurrentFile()">🗑 Delete</button>
|
||||
</div>
|
||||
<div class="gw-content">
|
||||
<pre x-text="fileContent"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Editor view -->
|
||||
<template x-if="showEditor">
|
||||
<div>
|
||||
<div class="gw-file-toolbar">
|
||||
<strong>Editing: <span x-text="currentFile?.name || 'new file'"></span></strong>
|
||||
<span style="flex:1;"></span>
|
||||
<button class="btn" @click="showEditor=false">Cancel</button>
|
||||
</div>
|
||||
<div class="gw-content">
|
||||
<textarea x-model="editContent"></textarea>
|
||||
</div>
|
||||
<div class="gw-commit-bar">
|
||||
<input type="text" x-model="commitMessage" placeholder="Commit message" list="commit-history">
|
||||
<datalist id="commit-history">
|
||||
<template x-for="msg in commitHistory" :key="msg">
|
||||
<option :value="msg">
|
||||
</template>
|
||||
</datalist>
|
||||
<button class="btn-primary" @click="saveFile()">💾 Commit</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Empty state -->
|
||||
<template x-if="!showFile && !showEditor && selectedPath!=='--private--'">
|
||||
<div class="gw-content">
|
||||
<div class="empty-state">
|
||||
<h2>📁 <span x-text="owner + '/' + repo"></span></h2>
|
||||
<p>Select a file from the tree to view it, or create a private page.</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
@@ -177,81 +100,81 @@ function giteaWorkspace() {
|
||||
|
||||
return {
|
||||
owner, repo,
|
||||
treeItems: [],
|
||||
selectedPath: '',
|
||||
currentFile: null,
|
||||
fileContent: '',
|
||||
showFile: false,
|
||||
showEditor: false,
|
||||
filePath: '',
|
||||
fileContent: '',
|
||||
fileSize: 0,
|
||||
fileSha: '',
|
||||
editContent: '',
|
||||
commitMessage: 'Update via FlowDeck',
|
||||
commitHistory: JSON.parse(localStorage.getItem('fd_commit_msgs') || '[]'),
|
||||
loading: true,
|
||||
privatePages: [],
|
||||
|
||||
async init() {
|
||||
await this.loadRoot();
|
||||
await this.loadPrivatePages();
|
||||
init() {
|
||||
// Set cookie for sidebar
|
||||
document.cookie = 'flowdeck_workspace=gitea:' + this.owner + ':' + this.repo + ';path=/;SameSite=Lax';
|
||||
// Listen for sidebar clicks on Gitea items
|
||||
this.setupSidebarClicks();
|
||||
},
|
||||
|
||||
async refresh() {
|
||||
this.treeItems = [];
|
||||
this.loading = true;
|
||||
await this.loadRoot();
|
||||
},
|
||||
|
||||
async loadRoot() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const r = await fetch(`/api/gitea/projects/${this.owner}/${this.repo}/tree`);
|
||||
if (r.ok) {
|
||||
const d = await r.json();
|
||||
this.treeItems = (d.tree || []).map(item => ({
|
||||
...item,
|
||||
depth: 0,
|
||||
expanded: false,
|
||||
children: [],
|
||||
}));
|
||||
setupSidebarClicks() {
|
||||
var self = this;
|
||||
document.addEventListener('click', function(e) {
|
||||
var el = e.target.closest('.sidebar-item[data-gitea-path]');
|
||||
if (!el) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
var path = el.getAttribute('data-gitea-path');
|
||||
var type = el.getAttribute('data-gitea-type');
|
||||
if (type === 'folder') {
|
||||
self.loadSubdir(path, el);
|
||||
} else {
|
||||
self.openFile(path, el.getAttribute('data-gitea-sha'));
|
||||
}
|
||||
} catch(e) {}
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
|
||||
async toggleFolder(item) {
|
||||
if (item.expanded) {
|
||||
item.expanded = false;
|
||||
async loadSubdir(path, el) {
|
||||
// Check if already loaded
|
||||
var ul = el.querySelector('ul');
|
||||
if (ul) {
|
||||
ul.style.display = ul.style.display === 'none' ? '' : 'none';
|
||||
return;
|
||||
}
|
||||
if (item.children.length > 0) {
|
||||
item.expanded = true;
|
||||
return;
|
||||
}
|
||||
// Lazy load
|
||||
try {
|
||||
const r = await fetch(`/api/gitea/projects/${this.owner}/${this.repo}/tree?path=${encodeURIComponent(item.path)}`);
|
||||
if (r.ok) {
|
||||
const d = await r.json();
|
||||
item.children = (d.tree || []).map(child => ({
|
||||
...child,
|
||||
depth: item.depth + 1,
|
||||
expanded: false,
|
||||
children: [],
|
||||
}));
|
||||
}
|
||||
item.expanded = true;
|
||||
var r = await fetch('/api/gitea/projects/' + this.owner + '/' + this.repo + '/tree?path=' + encodeURIComponent(path));
|
||||
if (!r.ok) return;
|
||||
var d = await r.json();
|
||||
var children = d.tree || [];
|
||||
ul = document.createElement('ul');
|
||||
ul.className = 'sidebar-items';
|
||||
ul.style.paddingLeft = '20px';
|
||||
children.forEach(function(child) {
|
||||
var icon = child.type === 'folder' ? '📁' : '📄';
|
||||
var li = document.createElement('li');
|
||||
li.className = 'sidebar-item';
|
||||
li.setAttribute('data-gitea-path', child.path);
|
||||
li.setAttribute('data-gitea-type', child.type === 'folder' ? 'folder' : 'file');
|
||||
li.setAttribute('data-gitea-sha', child.sha || '');
|
||||
li.style.cursor = 'pointer';
|
||||
li.innerHTML = '<span class="sidebar-icon">' + icon + '</span> ' + child.name;
|
||||
ul.appendChild(li);
|
||||
});
|
||||
el.appendChild(ul);
|
||||
} catch(e) {}
|
||||
},
|
||||
|
||||
async openFile(item) {
|
||||
this.selectedPath = item.path;
|
||||
this.currentFile = item;
|
||||
async openFile(path, sha) {
|
||||
this.filePath = path;
|
||||
this.fileSha = sha || '';
|
||||
this.showEditor = false;
|
||||
this.showFile = true;
|
||||
try {
|
||||
const r = await fetch(`/api/gitea/projects/${this.owner}/${this.repo}/file?path=${encodeURIComponent(item.path)}`);
|
||||
var r = await fetch('/api/gitea/projects/' + this.owner + '/' + this.repo + '/file?path=' + encodeURIComponent(path));
|
||||
if (r.ok) {
|
||||
const d = await r.json();
|
||||
var d = await r.json();
|
||||
this.fileContent = d.content || '';
|
||||
this.fileSize = d.content ? d.content.length : 0;
|
||||
} else {
|
||||
this.fileContent = '[Error loading file]';
|
||||
}
|
||||
@@ -266,74 +189,57 @@ function giteaWorkspace() {
|
||||
},
|
||||
|
||||
async saveFile() {
|
||||
if (!this.currentFile) return;
|
||||
const path = this.currentFile.path;
|
||||
if (!this.filePath) return;
|
||||
try {
|
||||
const r = await fetch(`/api/gitea/projects/${this.owner}/${this.repo}/file`, {
|
||||
var r = await fetch('/api/gitea/projects/' + this.owner + '/' + this.repo + '/file', {
|
||||
method: 'PUT',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
path, content: this.editContent,
|
||||
message: this.commitMessage,
|
||||
sha: this.currentFile.sha,
|
||||
path: this.filePath, content: this.editContent,
|
||||
message: this.commitMessage, sha: this.fileSha,
|
||||
})
|
||||
});
|
||||
if (r.ok) {
|
||||
this.fileContent = this.editContent;
|
||||
this.showEditor = false;
|
||||
// Save commit message history
|
||||
if (!this.commitHistory.includes(this.commitMessage)) {
|
||||
this.commitHistory.unshift(this.commitMessage);
|
||||
if (this.commitHistory.length > 10) this.commitHistory.pop();
|
||||
localStorage.setItem('fd_commit_msgs', JSON.stringify(this.commitHistory));
|
||||
}
|
||||
} else {
|
||||
const d = await r.json();
|
||||
var d = await r.json();
|
||||
alert('Failed to save: ' + (d.error || 'Unknown error'));
|
||||
}
|
||||
} catch(e) {
|
||||
alert('Network error');
|
||||
}
|
||||
} catch(e) { alert('Network error'); }
|
||||
},
|
||||
|
||||
async deleteCurrentFile() {
|
||||
if (!this.currentFile) return;
|
||||
if (!confirm(`Delete ${this.currentFile.path} from ${this.owner}/${this.repo}?`)) return;
|
||||
if (!this.filePath) return;
|
||||
if (!confirm('Delete ' + this.filePath + ' from ' + this.owner + '/' + this.repo + '?')) return;
|
||||
try {
|
||||
const r = await fetch(`/api/gitea/projects/${this.owner}/${this.repo}/file?path=${encodeURIComponent(this.currentFile.path)}&sha=${encodeURIComponent(this.currentFile.sha)}&message=${encodeURIComponent('Delete via FlowDeck')}`, {
|
||||
var r = await fetch('/api/gitea/projects/' + this.owner + '/' + this.repo + '/file?path=' + encodeURIComponent(this.filePath) + '&sha=' + encodeURIComponent(this.fileSha) + '&message=' + encodeURIComponent('Delete via FlowDeck'), {
|
||||
method: 'DELETE',
|
||||
});
|
||||
if (r.ok) {
|
||||
this.showFile = false;
|
||||
this.selectedPath = '';
|
||||
await this.refresh();
|
||||
this.filePath = '';
|
||||
await this.refreshTree();
|
||||
}
|
||||
} catch(e) {}
|
||||
},
|
||||
|
||||
async refreshTree() {
|
||||
// Reload page to refresh sidebar tree
|
||||
window.location.reload();
|
||||
},
|
||||
|
||||
formatSize(bytes) {
|
||||
if (!bytes) return '';
|
||||
if (bytes < 1024) return bytes + ' B';
|
||||
if (bytes < 1048576) return (bytes/1024).toFixed(1) + ' KB';
|
||||
return (bytes/1048576).toFixed(1) + ' MB';
|
||||
},
|
||||
|
||||
// ── Private Pages ──
|
||||
|
||||
async loadPrivatePages() {
|
||||
// Pages stored in FlowDeck DB, linked to this Gitea project
|
||||
// For now, store them as local workspace pages with gitea_owner/gitea_repo
|
||||
// TODO: Create a dedicated API endpoint for gitea private pages
|
||||
},
|
||||
|
||||
newPrivatePage() {
|
||||
// TODO: Create a new FlowDeck page linked to this Gitea project
|
||||
alert('Private pages will be available in a future update. Stay tuned!');
|
||||
},
|
||||
|
||||
openPrivatePage(pp) {
|
||||
// TODO: Open FlowDeck page editor
|
||||
},
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -285,6 +285,17 @@ function workspacesPage() {
|
||||
// Fetch repos: user repos + org repos
|
||||
const groups = [];
|
||||
|
||||
// Get username for the "personal" tab
|
||||
let myLogin = 'Me';
|
||||
try {
|
||||
const meRes = await fetch('/auth/user');
|
||||
if (meRes.ok) {
|
||||
const meData = await meRes.json();
|
||||
myLogin = meData.user?.login || 'Me';
|
||||
if (myLogin.includes('_')) myLogin = myLogin.split('_').pop(); // strip gitea_ prefix if present
|
||||
}
|
||||
} catch(e) {}
|
||||
|
||||
// User repos (no org filter)
|
||||
try {
|
||||
const userRes = await fetch('/api/gitea/projects');
|
||||
@@ -292,7 +303,7 @@ function workspacesPage() {
|
||||
const d = await userRes.json();
|
||||
const repos = d.projects || [];
|
||||
if (repos.length) {
|
||||
groups.push({org: 'Personal', avatar: '', projects: repos.map(r => ({...r, owner: r.owner || {login: '~'}}))});
|
||||
groups.push({org: myLogin, avatar: '', projects: repos.map(r => ({...r, owner: r.owner || {login: myLogin}}))});
|
||||
}
|
||||
}
|
||||
} catch(e) {}
|
||||
@@ -322,6 +333,8 @@ function workspacesPage() {
|
||||
const owner = proj.owner?.login || proj.owner?.username || proj.full_name?.split('/')[0] || '';
|
||||
const repo = proj.name;
|
||||
if (owner && repo) {
|
||||
// Set workspace cookie so sidebar shows tree
|
||||
document.cookie = 'flowdeck_workspace=gitea:' + owner + ':' + repo + ';path=/;SameSite=Lax';
|
||||
window.location = `/gitea-workspace?owner=${encodeURIComponent(owner)}&repo=${encodeURIComponent(repo)}`;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user