Added — Settings → Automations, pipeline visuel (API steps v7.0) :
- Cartes ordinees (trigger/condition/delay/action) : resume + edition
TYPÉE par kind/type (datalist evenements, 7 ops, 8 types d'action avec
leurs champs reels), ajout/edition/suppression/haut-bas via
POST/PUT/DELETE /workspace/automations[/steps]/...
- ✨ Convertir le JSON en pipeline (legacy → steps ordonnes) ; textareas
JSON masques des qu'un step existe.
- Gate E2E « editeur visuel de steps » : creation → edition → ajout →
carte « Action · webhook » sous CSP reel.
Fixed (trouve par le gate) — 51 expressions Alpine MULTI-INSTRUCTIONS
(`a=1; b()`) = interdites par le parseur CSP (une seule expression par
directive ; ';' = token inattendu) — INVISIBLE pour le scan par tokens :
- Conversion en methodes dans 11 fichiers : nav settings x8 (navTo),
menu section base x7 (closeAndSetCount/Move/...), parts+editeur x13
(setSharePerm, more*, markAndSave...), breadcrumb x5 (hover*/goClose),
library/local x6 (menus popup), board x3 (pickStatus/...), ctx-menu x2
(addTagAndClear), agent/card/gitea/workspaces x6.
- Scanner dedie scan_semi (inventaire ';' hors chaines) ajoute au lot.
Verifs : 39 templates Jinja parse OK · scan expressions = 0 incompatible
(4 faux positifs en chaines) · **E2E 8/8** · suite **1094/1094** ·
ruff OK · docs a jour
194 lines
11 KiB
HTML
194 lines
11 KiB
HTML
{% extends "base.html" %}
|
|
{% block page_title %}{{ workspace_name }}{% endblock %}
|
|
{% block title_prefix %}{{ workspace_name }}{% endblock %}
|
|
{% block page_icon %}{{ fd_icon("link",18) }}{% endblock %}
|
|
|
|
{% block topbar %}
|
|
{% set breadcrumb_items = [{"label": owner ~ "/" ~ repo, "url": None}] %}
|
|
{% set page_icon = "link" %}
|
|
{% set right_actions %}<button class="page-action-btn" @click="showNewFile=!showNewFile" title="New file">{{ fd_icon("file",14) }}+</button><button class="page-action-btn" @click="triggerUpload()" title="Upload file">{{ fd_icon("upload",14) }}</button><input type="file" id="gitea-upload-input" style="display:none" @change="doUpload($event)" multiple><button class="page-action-btn" @click="refreshTree()" title="Refresh">{{ fd_icon("refresh",14) }}</button><a href="/accounts/settings" class="topbar-btn" title="Settings">{{ fd_icon("settings",16) }}</a>{% endset %}
|
|
{% include '_header.html' %}
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<!-- Prism.js syntax highlighting -->
|
|
<link rel="stylesheet" href="/static/css/prism.css">
|
|
<script src="/static/js/prism.min.js"></script>
|
|
<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 pre[class*="language-"]{background:transparent;border:none;padding:0;margin:0;white-space:pre;word-break:normal;}
|
|
.gw-content code[class*="language-"]{font-size:13px;line-height:1.5;tab-size:4;}
|
|
.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);}
|
|
|
|
/* Prism theme tweaks for dark UI */
|
|
.gw-content pre[class*="language-"]{background:var(--bg-tertiary)!important;border:1px solid var(--border)!important;border-radius:8px!important;padding:16px!important;}
|
|
</style>
|
|
|
|
<script data-cfasync="false" nonce="{{ csp_nonce() }}" src="/static/js/gitea_workspace.js?v={{ asset_version }}"></script>
|
|
<div class="gw-main" x-data="giteaWorkspace()">
|
|
<!-- File view -->
|
|
<div x-show="showFile && !showEditor" x-transition>
|
|
<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()">{{ fd_icon("edit",14) }} Edit</button>
|
|
<button class="btn btn-danger" @click="deleteCurrentFile()">{{ fd_icon("trash",14) }} Delete</button>
|
|
</div>
|
|
<div class="gw-content">
|
|
<!-- Skeleton loading -->
|
|
<div x-show="fileLoading" class="skeleton-file">
|
|
<div class="skeleton" style="height:15px;width:60%;margin-bottom:12px;"></div>
|
|
<div class="skeleton" style="height:15px;width:80%;margin-bottom:12px;"></div>
|
|
<div class="skeleton" style="height:15px;width:40%;margin-bottom:12px;"></div>
|
|
<div class="skeleton" style="height:15px;width:75%;margin-bottom:12px;"></div>
|
|
<div class="skeleton" style="height:15px;width:55%;margin-bottom:12px;"></div>
|
|
<div class="skeleton" style="height:15px;width:90%;margin-bottom:12px;"></div>
|
|
<div class="skeleton" style="height:15px;width:35%;margin-bottom:12px;"></div>
|
|
<div class="skeleton" style="height:15px;width:65%;margin-bottom:12px;"></div>
|
|
<div class="skeleton" style="height:15px;width:45%;margin-bottom:12px;"></div>
|
|
<div class="skeleton" style="height:15px;width:70%;margin-bottom:12px;"></div>
|
|
<div class="skeleton" style="height:15px;width:50%;"></div>
|
|
</div>
|
|
<!-- Syntax-highlighted code -->
|
|
<pre x-show="!fileLoading"><code :class="'language-'+fileLanguage" x-ref="codeBlock" x-text="fileContent"></code></pre>
|
|
</div>
|
|
</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>
|
|
</datalist>
|
|
<button class="btn-primary" @click="saveFile()">{{ fd_icon("save",14) }} Commit</button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- File tree browser (like local-workspace) -->
|
|
<template x-if="!showFile && !showEditor && !showPrivate">
|
|
<div class="gw-content">
|
|
<!-- Breadcrumb -->
|
|
<div class="gw-breadcrumb" style="display:flex;align-items:center;gap:4px;margin-bottom:16px;font-size:14px;color:var(--text-secondary);">
|
|
<a href="#" @click.prevent="navigateToRoot()" style="color:var(--text-secondary);text-decoration:none;" x-text="owner + '/' + repo"></a>
|
|
<template x-for="(b, i) in folderStack" :key="i">
|
|
<span style="display:contents;">
|
|
<span style="color:var(--text-tertiary);">/</span>
|
|
<a href="#" @click.prevent="navigateToFolder(i)" style="color:var(--text-secondary);text-decoration:none;" x-text="b"></a>
|
|
</span>
|
|
</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>{{ fd_icon("folder",16) }} Empty</h2>
|
|
<p>No files in this folder</p>
|
|
<button class="btn btn-primary" @click="showNewFile=!showNewFile">{{ fd_icon("file",14) }}+ 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)"
|
|
@contextmenu.prevent="openGwContext($event, item)"
|
|
style="display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:6px;cursor:pointer;">
|
|
<span x-init="bindGwIcon($el, item)" 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>
|
|
|
|
<!-- Context menu -->
|
|
<div class="gw-context-menu"
|
|
x-show="gwCtx.visible"
|
|
:style="'left:' + gwCtx.x + 'px; top:' + gwCtx.y + 'px'"
|
|
@click.outside="gwCtx.visible = false"
|
|
style="display:none;position:fixed;background:var(--bg-modal);border:1px solid var(--border);border-radius:var(--radius-md);box-shadow:var(--shadow-popover);z-index:1100;min-width:160px;padding:4px;">
|
|
<div class="ctx-item" @click="gwRename()">{{ fd_icon("edit",14) }} Rename</div>
|
|
<div class="ctx-item ctx-danger" @click="gwDelete()">{{ fd_icon("trash",14) }} Delete</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()">{{ fd_icon("save",14) }} Create file</button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Private pages view -->
|
|
<template x-if="showPrivate && !editingPrivate">
|
|
<div class="gw-content">
|
|
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;">
|
|
<h3>{{ fd_icon("lock",14) }} Private Pages</h3>
|
|
<button class="btn btn-primary" onclick="window.FlowDeck.createPage()">+ New Page</button>
|
|
</div>
|
|
<template x-if="privatePages.length === 0">
|
|
<div class="empty-state" style="padding:40px 20px;">
|
|
<p>No private pages yet. Create one to store notes linked to this project.</p>
|
|
</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;display:flex;align-items:center;justify-content:space-between;"
|
|
@click="openPrivate(pp.id)">
|
|
<div>
|
|
<div style="font-weight:500;" x-text="pp.title"></div>
|
|
<div style="font-size:12px;color:var(--text-dim);" x-text="fmtGwDate(pp)"></div>
|
|
</div>
|
|
<button class="btn btn-danger" style="padding:4px 8px;font-size:11px;" @click.stop="deletePrivate(pp.id)">{{ fd_icon('trash',14) }}</button>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Private page editor -->
|
|
<template x-if="editingPrivate">
|
|
<div>
|
|
<div class="gw-file-toolbar">
|
|
<strong>Editing: <span x-text="editingPrivateTitle || 'Untitled'"></span></strong>
|
|
<span style="flex:1;"></span>
|
|
<button class="btn" @click="savePrivate()">{{ fd_icon("save",14) }} Save</button>
|
|
<button class="btn" @click="closePrivateEdit()">Cancel</button>
|
|
</div>
|
|
<div class="gw-content">
|
|
<input x-model="editingPrivateTitle" placeholder="Page title" style="width:100%;padding:10px 12px;background:var(--bg-tertiary);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:16px;margin-bottom:12px;outline:none;">
|
|
<textarea x-model="editingPrivateContent" placeholder="Start writing…" style="min-height:60vh;"></textarea>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
|
|
{% endblock %}
|