Files
flowdeck/app/templates/gitea_workspace.html
T
bruno e78ca4b775
FlowDeck CI / test (push) Failing after 3s
FlowDeck CI / docker (push) Has been skipped
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
2026-07-13 16:42:11 -04:00

247 lines
9.8 KiB
HTML

{% extends "base.html" %}
{% block page_title %}Gitea Workspace{% endblock %}
{% block page_icon %}🔗{% endblock %}
{% block topbar %}
<header class="topbar">
<button class="hamburger-btn" @click="mobileSidebarOpen = true; sidebarCollapsed = false;" title="Menu">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
</button>
<div class="topbar-left">
<a href="/workspaces" class="breadcrumb" style="color:rgba(255,255,255,.5);text-decoration:none;">Home</a>
<span class="page-title-topbar" x-text="owner + '/' + repo"></span>
</div>
<div class="topbar-right">
<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>
{% endblock %}
{% block content %}
<style>
.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;}
.gw-content .empty-state{text-align:center;padding:60px 20px;color:var(--text-dim);}
.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);}
.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);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);}
</style>
<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>
<!-- 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>
</datalist>
<button class="btn-primary" @click="saveFile()">💾 Commit</button>
</div>
</div>
</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>
</template>
</div>
<script>
function giteaWorkspace() {
const params = new URLSearchParams(window.location.search);
const owner = params.get('owner') || '';
const repo = params.get('repo') || '';
return {
owner, repo,
showFile: false,
showEditor: false,
filePath: '',
fileContent: '',
fileSize: 0,
fileSha: '',
editContent: '',
commitMessage: 'Update via FlowDeck',
commitHistory: JSON.parse(localStorage.getItem('fd_commit_msgs') || '[]'),
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();
},
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'));
}
});
},
async loadSubdir(path, el) {
// Check if already loaded
var ul = el.querySelector('ul');
if (ul) {
ul.style.display = ul.style.display === 'none' ? '' : 'none';
return;
}
try {
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(path, sha) {
this.filePath = path;
this.fileSha = sha || '';
this.showEditor = false;
this.showFile = true;
try {
var r = await fetch('/api/gitea/projects/' + this.owner + '/' + this.repo + '/file?path=' + encodeURIComponent(path));
if (r.ok) {
var d = await r.json();
this.fileContent = d.content || '';
this.fileSize = d.content ? d.content.length : 0;
} else {
this.fileContent = '[Error loading file]';
}
} catch(e) {
this.fileContent = '[Error loading file]';
}
},
openEditor() {
this.editContent = this.fileContent;
this.showEditor = true;
},
async saveFile() {
if (!this.filePath) return;
try {
var r = await fetch('/api/gitea/projects/' + this.owner + '/' + this.repo + '/file', {
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
path: this.filePath, content: this.editContent,
message: this.commitMessage, sha: this.fileSha,
})
});
if (r.ok) {
this.fileContent = this.editContent;
this.showEditor = false;
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 {
var d = await r.json();
alert('Failed to save: ' + (d.error || 'Unknown error'));
}
} catch(e) { alert('Network error'); }
},
async deleteCurrentFile() {
if (!this.filePath) return;
if (!confirm('Delete ' + this.filePath + ' from ' + this.owner + '/' + this.repo + '?')) return;
try {
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.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';
},
};
}
</script>
{% endblock %}