feat: barre d'apps commune et références croisées des notes (#215)
This commit is contained in:
@@ -3424,6 +3424,13 @@
|
||||
|
||||
// Initialize outline/TOC for this document
|
||||
OutlineManager.init();
|
||||
|
||||
// Fusion #215 (ACC-1.2/1.3) — point d'extension : le module
|
||||
// draco-liens.js rend la section « Liens » de la note
|
||||
// (références croisées + « ouvrir dans TikTak » si projetée).
|
||||
document.dispatchEvent(new CustomEvent("obsigate:file-rendered", {
|
||||
detail: { vault: data.vault, path: data.path },
|
||||
}));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
/* Barre d'apps commune dracodev — v0.1.0 (bloc 0, ACC-0.2)
|
||||
Jetons repris du prototype validé Console v7.2.
|
||||
Thème : clair par défaut, sombre via [data-theme="dark"] sur <html>
|
||||
ou prefers-color-scheme si l'app intégratrice ne force rien. */
|
||||
|
||||
.draco-bar {
|
||||
--bg: #f2f2f0; --surface: #ffffff; --surface-2: #e9eae6; --surface-3: #f6f6f3;
|
||||
--ink: #1b1d1a; --ink-2: #5c615a; --ink-3: #787d75; --line: #dedfd9;
|
||||
--agent: #c2437e; --danger: #cf4444;
|
||||
height: 60px; box-sizing: border-box;
|
||||
display: grid; grid-template-columns: auto minmax(200px, 520px) auto;
|
||||
align-items: center; gap: 16px; padding: 0 12px;
|
||||
background: var(--surface); border-bottom: 1px solid var(--line);
|
||||
font: 500 14px/1.3 system-ui, "Segoe UI", sans-serif; color: var(--ink);
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.draco-bar { --bg:#101211; --surface:#1a1c1a; --surface-2:#242624; --surface-3:#20221f;
|
||||
--ink:#eceee9; --ink-2:#a6aba2; --ink-3:#8b9089; --line:#2e302c; }
|
||||
}
|
||||
[data-theme="dark"] .draco-bar { --bg:#101211; --surface:#1a1c1a; --surface-2:#242624; --surface-3:#20221f;
|
||||
--ink:#eceee9; --ink-2:#a6aba2; --ink-3:#8b9089; --line:#2e302c; }
|
||||
|
||||
.draco-bar-zone { display: flex; align-items: center; gap: 8px; min-width: 0; }
|
||||
.draco-bar-centre { justify-content: center; }
|
||||
.draco-bar-droite { justify-content: flex-end; }
|
||||
|
||||
/* Marque */
|
||||
.draco-brand { display: flex; align-items: center; gap: 7px; color: var(--ink); text-decoration: none; font-weight: 800; }
|
||||
.draco-brand-mark { color: var(--agent); }
|
||||
|
||||
/* Onglets d'apps — accordéon : seule l'app courante est déployée */
|
||||
.draco-apptabs { display: flex; gap: 6px; }
|
||||
.draco-apptab {
|
||||
position: relative; display: flex; align-items: center; gap: 8px;
|
||||
height: 44px; padding: 0 9px; border-radius: 12px;
|
||||
color: var(--ink); text-decoration: none; background: transparent;
|
||||
transition: background .18s ease;
|
||||
}
|
||||
.draco-apptab:hover { background: var(--surface-2); }
|
||||
.draco-apptab-ico {
|
||||
width: 30px; height: 30px; flex: none; display: grid; place-items: center;
|
||||
border-radius: 9px; background: var(--app); color: #fff; font-size: 15px;
|
||||
}
|
||||
.draco-apptab-txt { display: none; flex-direction: column; line-height: 1.15; }
|
||||
.draco-apptab-txt strong { font-size: 14px; font-weight: 700; }
|
||||
.draco-apptab-txt small { font-size: 11.5px; color: var(--ink-2); }
|
||||
.draco-apptab[data-active="true"] { background: var(--surface-3); box-shadow: inset 0 -2px 0 var(--app, var(--agent)); }
|
||||
.draco-apptab[data-active="true"] .draco-apptab-txt { display: flex; }
|
||||
|
||||
/* Pastilles compteurs — petites, rouges, coin bas-droit (standard v7.1) */
|
||||
.draco-badge {
|
||||
position: absolute; right: -4px; bottom: -4px; min-width: 17px; height: 17px;
|
||||
padding: 0 4px; border-radius: 9px; box-sizing: border-box;
|
||||
background: #e5484d; color: #fff; font-size: 10.5px; font-weight: 700;
|
||||
display: grid; place-items: center; border: 2px solid var(--surface);
|
||||
}
|
||||
.draco-badge[hidden] { display: none; }
|
||||
|
||||
/* Recherche */
|
||||
.draco-search {
|
||||
display: flex; align-items: center; gap: 8px; width: 100%; height: 38px;
|
||||
padding: 0 12px; border-radius: 10px; border: 1px solid var(--line);
|
||||
background: var(--surface-3); color: var(--ink-3); cursor: pointer; font: inherit;
|
||||
}
|
||||
.draco-search kbd {
|
||||
margin-left: auto; font: 600 11px system-ui; color: var(--ink-2);
|
||||
border: 1px solid var(--line); border-radius: 5px; padding: 1px 5px;
|
||||
}
|
||||
|
||||
/* Domaines — contrôle segmenté */
|
||||
.draco-domaines { display: flex; align-items: center; gap: 2px; background: var(--surface-3); border-radius: 10px; padding: 3px; }
|
||||
.draco-domaines-lbl { font-size: 10.5px; letter-spacing: .06em; color: var(--ink-3); padding: 0 6px; text-transform: uppercase; }
|
||||
.draco-domaines button {
|
||||
border: 0; background: transparent; color: var(--ink-2); font: inherit; font-size: 13px;
|
||||
height: 30px; padding: 0 10px; border-radius: 8px; cursor: pointer;
|
||||
}
|
||||
.draco-domaines button[data-on="true"] { background: var(--surface); color: var(--ink); font-weight: 700; box-shadow: 0 1px 3px rgb(0 0 0 / .18); }
|
||||
|
||||
/* Alertes — la cloche ne disparaît JAMAIS (correctif v7.2) */
|
||||
.draco-alertes {
|
||||
position: relative; display: flex; align-items: center; gap: 7px; height: 38px;
|
||||
padding: 0 11px; border-radius: 10px; border: 1px solid var(--line);
|
||||
background: transparent; color: var(--ink); font: inherit; cursor: pointer;
|
||||
}
|
||||
|
||||
/* Spark */
|
||||
.draco-spark {
|
||||
position: relative; display: flex; align-items: center; gap: 7px; height: 38px;
|
||||
padding: 0 14px; border: 0; border-radius: 10px; cursor: pointer;
|
||||
background: var(--agent); color: #fff; font: inherit; font-weight: 700;
|
||||
}
|
||||
.draco-spark .draco-badge { border-color: var(--agent); }
|
||||
|
||||
.draco-icon-btn {
|
||||
width: 38px; height: 38px; border-radius: 10px; border: 1px solid transparent;
|
||||
background: transparent; color: var(--ink-2); font: inherit; cursor: pointer;
|
||||
}
|
||||
.draco-icon-btn:hover { background: var(--surface-2); }
|
||||
.draco-avatar {
|
||||
width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center;
|
||||
background: #2f6fed; color: #fff; font-weight: 700;
|
||||
}
|
||||
|
||||
/* Rappel de l'en-tête masqué — onglet ancré au bord supérieur (v6) */
|
||||
.draco-bar-rappel {
|
||||
position: fixed; top: 0; left: 16px; z-index: 60; cursor: pointer;
|
||||
background: var(--surface, #1a1c1a); border: 1px solid var(--line, #2e302c); border-top: 0;
|
||||
border-radius: 0 0 10px 10px; padding: 5px 12px; font: 600 12px system-ui; color: var(--ink-2, #a6aba2);
|
||||
}
|
||||
|
||||
/* Paliers — les zones latérales cèdent, la barre ne casse jamais */
|
||||
@media (max-width: 1440px) {
|
||||
.draco-brand-name, .draco-domaines-lbl { display: none; }
|
||||
.draco-bar { grid-template-columns: auto minmax(200px, 440px) auto; }
|
||||
}
|
||||
@media (max-width: 1200px) {
|
||||
.draco-search-txt, .draco-search kbd, .draco-alertes-txt, .draco-spark-txt { display: none; }
|
||||
.draco-search { width: 38px; padding: 0; justify-content: center; }
|
||||
.draco-bar { grid-template-columns: auto auto 1fr; }
|
||||
.draco-bar-droite { justify-content: flex-end; }
|
||||
}
|
||||
@media (max-width: 700px) {
|
||||
/* Mobile : la barre du haut se condense ; la navigation d'apps passe
|
||||
en barre basse propre à chaque app (patron du prototype v5+). */
|
||||
.draco-apptabs, .draco-domaines-lbl { display: none; }
|
||||
.draco-bar { height: 52px; }
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/* Barre d'apps commune dracodev — comportement (v0.1.0, ACC-0.2)
|
||||
*
|
||||
* Aucun gestionnaire inline : un seul écouteur délégué sur le document
|
||||
* (patron validé par le prototype — les onclick du HTML généré sont
|
||||
* proscrits dans tout le programme).
|
||||
*
|
||||
* Configuration attendue avant le chargement :
|
||||
* window.DRACO_BAR_CONFIG = {
|
||||
* current: "tiktak", // app courante
|
||||
* domain: "pro", // domaine actif
|
||||
* counts: { tiktak: 3, kanbante: 3, alertes: 3, spark: 0 },
|
||||
* onAction: function (act, el) { … } // l'app relaie les actions
|
||||
* } // qu'elle ne connaît pas
|
||||
* Actions émises (data-act) : rail, palette, domaine (data-dom),
|
||||
* alertes, spark, menu, rappel-entete. L'app intégratrice décide de
|
||||
* chacune (ouvrir la palette, le panneau Spark, changer de domaine…).
|
||||
*/
|
||||
(function () {
|
||||
"use strict";
|
||||
var cfg = window.DRACO_BAR_CONFIG || {};
|
||||
|
||||
function setCounts(counts) {
|
||||
Object.keys(counts || {}).forEach(function (k) {
|
||||
var b = document.querySelector('.draco-badge[data-badge="' + k + '"]');
|
||||
if (!b) return;
|
||||
b.textContent = counts[k];
|
||||
b.hidden = !(counts[k] > 0);
|
||||
});
|
||||
}
|
||||
function setCurrent(app) {
|
||||
document.querySelectorAll(".draco-apptab").forEach(function (t) {
|
||||
t.dataset.active = t.dataset.app === app ? "true" : "false";
|
||||
});
|
||||
}
|
||||
function setDomain(dom) {
|
||||
document.querySelectorAll('.draco-domaines [data-dom]').forEach(function (b) {
|
||||
b.dataset.on = b.dataset.dom === dom ? "true" : "false";
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("click", function (ev) {
|
||||
var el = ev.target.closest("[data-act]");
|
||||
if (!el) return;
|
||||
var act = el.dataset.act;
|
||||
if (act === "domaine") setDomain(el.dataset.dom);
|
||||
if (typeof cfg.onAction === "function") cfg.onAction(act, el, ev);
|
||||
else document.dispatchEvent(new CustomEvent("draco:bar", { detail: { act: act, el: el } }));
|
||||
});
|
||||
|
||||
/* API publique : l'app met les compteurs à jour sans recharger. */
|
||||
window.DRACO_BAR = { setCounts: setCounts, setCurrent: setCurrent, setDomain: setDomain };
|
||||
|
||||
if (cfg.current) setCurrent(cfg.current);
|
||||
if (cfg.domain) setDomain(cfg.domain);
|
||||
if (cfg.counts) setCounts(cfg.counts);
|
||||
})();
|
||||
@@ -0,0 +1,187 @@
|
||||
/* Section « Liens » des notes — Fusion #215 (ACC-1.2 + ACC-1.3).
|
||||
*
|
||||
* Module autonome (vanilla, zéro dépendance, zéro build) : il écoute
|
||||
* l'événement « obsigate:file-rendered » émis par renderFile
|
||||
* (frontend/app.js) et rend sa section à la fin de #content-area :
|
||||
*
|
||||
* - ACC-1.2 : si la note est une projection TikTak (frontmatter
|
||||
* « tiktak_id » écrit par la synchro TikTak ↔ ObsiGate), un bandeau
|
||||
* « Tâche TikTak projetée » avec le bouton « Ouvrir dans TikTak »
|
||||
* (lien profond https://tiktak.dracodev.net/?task=<id>) ;
|
||||
* - ACC-1.3 : les références croisées de la note (GET /api/refs),
|
||||
* ajout par collage d'une URL profonde, retrait en un clic.
|
||||
*
|
||||
* Textes via window.t (i18n FR/EN — clés « draco.links.* » des locales).
|
||||
*/
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var COLORS = {
|
||||
obsigate: "#7a5af5",
|
||||
flowdeck: "#2f6fed",
|
||||
tiktak: "#e8762c",
|
||||
kanbante: "#0d9488",
|
||||
};
|
||||
|
||||
function t(key, fallback) {
|
||||
try {
|
||||
var v = window.t && window.t(key);
|
||||
return v && v !== key ? v : fallback;
|
||||
} catch (e) {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
function authHeaders() {
|
||||
var tok = null;
|
||||
try {
|
||||
tok = sessionStorage.getItem("obsigate_access_token");
|
||||
} catch (e) { /* stockage indisponible */ }
|
||||
return tok ? { Authorization: "Bearer " + tok } : {};
|
||||
}
|
||||
|
||||
async function api(path, opts) {
|
||||
var options = opts || {};
|
||||
options.headers = Object.assign(
|
||||
{ "Content-Type": "application/json" },
|
||||
authHeaders(),
|
||||
options.headers || {}
|
||||
);
|
||||
options.credentials = "same-origin";
|
||||
var resp = await fetch(path, options);
|
||||
if (!resp.ok) {
|
||||
var detail = "";
|
||||
try {
|
||||
detail = (await resp.json()).detail || "";
|
||||
} catch (e) { /* corps non JSON */ }
|
||||
throw new Error(detail || "HTTP " + resp.status);
|
||||
}
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
function el(tag, cls, text) {
|
||||
var node = document.createElement(tag);
|
||||
if (cls) node.className = cls;
|
||||
if (text !== undefined) node.textContent = text;
|
||||
return node;
|
||||
}
|
||||
|
||||
async function render(vault, path) {
|
||||
var area = document.getElementById("content-area");
|
||||
if (!area || !vault || !path) return;
|
||||
var old = document.getElementById("draco-liens");
|
||||
if (old) old.remove();
|
||||
|
||||
var section = el("section", "draco-liens");
|
||||
section.id = "draco-liens";
|
||||
var title = el("h3", "draco-liens-title", t("draco.links.title", "Liens"));
|
||||
section.appendChild(title);
|
||||
area.appendChild(section);
|
||||
|
||||
// ACC-1.2 — note projetée depuis TikTak ?
|
||||
if (/\.md$/i.test(path)) {
|
||||
try {
|
||||
var rawUrl = "/api/file/" + encodeURIComponent(vault) + "/raw?path=" +
|
||||
encodeURIComponent(path);
|
||||
var rawData = await api(rawUrl);
|
||||
var m = /^---\r?\n([\s\S]*?)\r?\n---/.exec(rawData.raw || "");
|
||||
var tid = m && /^tiktak_id:\s*(\d+)\s*$/m.exec(m[1]);
|
||||
if (tid) {
|
||||
var bandeau = el("div", "draco-liens-projete");
|
||||
bandeau.appendChild(el("span", "draco-liens-badge",
|
||||
t("draco.links.projected", "Tâche TikTak projetée")));
|
||||
var ouvrir = el("a", "btn-action draco-liens-ouvrir",
|
||||
t("draco.links.openInTikTak", "Ouvrir dans TikTak"));
|
||||
ouvrir.href = "https://tiktak.dracodev.net/?task=" + tid[1];
|
||||
ouvrir.target = "_blank";
|
||||
ouvrir.rel = "noopener";
|
||||
bandeau.appendChild(ouvrir);
|
||||
section.appendChild(bandeau);
|
||||
}
|
||||
} catch (e) { /* la détection est opportuniste : silence */ }
|
||||
}
|
||||
|
||||
// ACC-1.3 — références croisées de la note
|
||||
var list = el("ul", "draco-liens-list");
|
||||
section.appendChild(list);
|
||||
var erreur = el("p", "draco-liens-erreur");
|
||||
erreur.hidden = true;
|
||||
section.appendChild(erreur);
|
||||
|
||||
async function refresh() {
|
||||
list.innerHTML = "";
|
||||
try {
|
||||
var data = await api("/api/refs?vault=" + encodeURIComponent(vault) +
|
||||
"&path=" + encodeURIComponent(path));
|
||||
(data.refs || []).forEach(function (r) {
|
||||
var li = el("li", "draco-liens-item");
|
||||
var dot = el("span", "draco-ref-dot");
|
||||
dot.style.background = COLORS[r.target_app] || "#888";
|
||||
li.appendChild(dot);
|
||||
var a = el("a", "", r.label);
|
||||
a.href = r.target_url;
|
||||
a.target = "_blank";
|
||||
a.rel = "noopener";
|
||||
li.appendChild(a);
|
||||
var del = el("button", "draco-liens-del", "✕");
|
||||
del.type = "button";
|
||||
del.title = t("draco.links.remove", "Retirer le lien");
|
||||
del.addEventListener("click", async function () {
|
||||
try {
|
||||
await api("/api/refs/" + encodeURIComponent(r.id), { method: "DELETE" });
|
||||
refresh();
|
||||
} catch (e) {
|
||||
erreur.textContent = e.message;
|
||||
erreur.hidden = false;
|
||||
}
|
||||
});
|
||||
li.appendChild(del);
|
||||
list.appendChild(li);
|
||||
});
|
||||
} catch (e) { /* section facultative : ne jamais casser la note */ }
|
||||
}
|
||||
|
||||
var form = el("form", "draco-liens-form");
|
||||
var urlInput = el("input");
|
||||
urlInput.type = "url";
|
||||
urlInput.required = true;
|
||||
urlInput.placeholder = t("draco.links.urlPlaceholder",
|
||||
"Coller une URL profonde (Flowdeck, TikTak, Gitea, ObsiGate)…");
|
||||
var labelInput = el("input");
|
||||
labelInput.type = "text";
|
||||
labelInput.placeholder = t("draco.links.labelPlaceholder", "Libellé (optionnel)");
|
||||
var submit = el("button", "btn-action", t("draco.links.add", "Lier"));
|
||||
submit.type = "submit";
|
||||
form.appendChild(urlInput);
|
||||
form.appendChild(labelInput);
|
||||
form.appendChild(submit);
|
||||
form.addEventListener("submit", async function (ev) {
|
||||
ev.preventDefault();
|
||||
erreur.hidden = true;
|
||||
try {
|
||||
await api("/api/refs", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
vault: vault,
|
||||
path: path,
|
||||
url: urlInput.value.trim(),
|
||||
label: labelInput.value.trim() || null,
|
||||
}),
|
||||
});
|
||||
urlInput.value = "";
|
||||
labelInput.value = "";
|
||||
refresh();
|
||||
} catch (e) {
|
||||
erreur.textContent = e.message;
|
||||
erreur.hidden = false;
|
||||
}
|
||||
});
|
||||
section.appendChild(form);
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
document.addEventListener("obsigate:file-rendered", function (ev) {
|
||||
if (ev.detail) render(ev.detail.vault, ev.detail.path);
|
||||
});
|
||||
})();
|
||||
+112
-1
@@ -75,6 +75,7 @@
|
||||
@media (prefers-reduced-motion: reduce) { .bs-spinner { animation-duration: 1.8s; } }
|
||||
</style>
|
||||
<link rel="stylesheet" href="/static/style.css" />
|
||||
<link rel="stylesheet" href="/static/draco-barre.css" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css"
|
||||
@@ -222,7 +223,7 @@
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<body class="has-draco-bar">
|
||||
<!-- Boot splash : visible tant que l'app n'a pas booté avec son CSS.
|
||||
Sur desktop, la page embarquée (tauri://) n'a pas /static/style.css →
|
||||
sans ce splash, le squelette HTML brut serait visible pendant le
|
||||
@@ -329,6 +330,114 @@
|
||||
</div>
|
||||
|
||||
<!-- Main Application (wrapped for show/hide) -->
|
||||
<!-- Barre d'apps commune dracodev — Fusion #215 (ACC-1.1).
|
||||
Snippet FIGÉ vendangé depuis Projets/dracodev-commons v0.1.0
|
||||
(ui/barre-apps/) : ne pas modifier la structure ici ; toute
|
||||
évolution passe par une nouvelle version de commons.
|
||||
CSS/JS : /static/draco-barre.css, /static/draco-barre.js. -->
|
||||
<header class="draco-bar" data-current="obsigate">
|
||||
<div class="draco-bar-zone draco-bar-gauche">
|
||||
<button type="button" class="draco-icon-btn" data-act="rail" aria-label="Barre latérale">☰</button>
|
||||
<a class="draco-brand" href="/" aria-label="dracodev">
|
||||
<span class="draco-brand-mark">✦</span><span class="draco-brand-name">dracodev</span>
|
||||
</a>
|
||||
<nav class="draco-apptabs" aria-label="Applications">
|
||||
<a class="draco-apptab" data-app="obsigate" href="https://og.dracodev.net/">
|
||||
<span class="draco-apptab-ico" style="--app:#7a5af5">▤</span>
|
||||
<span class="draco-apptab-txt"><strong>ObsiGate</strong><small>Notes</small></span>
|
||||
</a>
|
||||
<a class="draco-apptab" data-app="flowdeck" href="https://flowdeck.dracodev.net/">
|
||||
<span class="draco-apptab-ico" style="--app:#2f6fed">▦</span>
|
||||
<span class="draco-apptab-txt"><strong>Flowdeck</strong><small>Projets</small></span>
|
||||
</a>
|
||||
<a class="draco-apptab" data-app="tiktak" href="https://tiktak.dracodev.net/">
|
||||
<span class="draco-apptab-ico" style="--app:#e8762c">✓</span>
|
||||
<span class="draco-apptab-txt"><strong>TikTak</strong><small>Tâches</small></span>
|
||||
<span class="draco-badge" data-badge="tiktak" hidden>0</span>
|
||||
</a>
|
||||
<a class="draco-apptab" data-app="kanbante" href="https://kanbante.dracodev.net/">
|
||||
<span class="draco-apptab-ico" style="--app:#0d9488">▥</span>
|
||||
<span class="draco-apptab-txt"><strong>Kanbante</strong><small>Kanban</small></span>
|
||||
<span class="draco-badge" data-badge="kanbante" hidden>0</span>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="draco-bar-zone draco-bar-centre">
|
||||
<button type="button" class="draco-search" data-act="palette" aria-label="Rechercher partout (Ctrl+K)">
|
||||
<span class="draco-search-ico">⌕</span>
|
||||
<span class="draco-search-txt">Rechercher partout…</span>
|
||||
<kbd>⌘K</kbd>
|
||||
</button>
|
||||
</div>
|
||||
<div class="draco-bar-zone draco-bar-droite">
|
||||
<div class="draco-domaines" role="group" aria-label="Domaine">
|
||||
<span class="draco-domaines-lbl">Domaine</span>
|
||||
<button type="button" data-act="domaine" data-dom="pro">Pro</button>
|
||||
<button type="button" data-act="domaine" data-dom="famille">Famille</button>
|
||||
<button type="button" data-act="domaine" data-dom="perso">Perso</button>
|
||||
</div>
|
||||
<button type="button" class="draco-alertes" data-act="alertes" aria-label="Alertes unifiées">
|
||||
<svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></svg>
|
||||
<span class="draco-alertes-txt">Alertes</span>
|
||||
<span class="draco-badge" data-badge="alertes" hidden>0</span>
|
||||
</button>
|
||||
<button type="button" class="draco-spark" data-act="spark" aria-label="Ouvrir Spark, l'agent commun">
|
||||
<span class="draco-spark-ico">✦</span><span class="draco-spark-txt">Spark</span>
|
||||
<span class="draco-badge" data-badge="spark" hidden>0</span>
|
||||
</button>
|
||||
<button type="button" class="draco-icon-btn" data-act="menu" aria-label="Plus d'actions" aria-haspopup="menu">•••</button>
|
||||
<span class="draco-avatar" id="draco-avatar" title="">✦</span>
|
||||
</div>
|
||||
</header>
|
||||
<div class="draco-bar-rappel" data-act="rappel-entete" hidden>✦ dracodev — afficher l'en-tête</div>
|
||||
<script>
|
||||
/* Configuration ObsiGate de la barre commune (Fusion #215).
|
||||
Phase 1 : navigation entre apps et domaine mémorisé
|
||||
localement ; la recherche focus la recherche native
|
||||
d'ObsiGate ; Alertes unifiées et Spark arrivent avec la
|
||||
fédération / l'agent commun (phases suivantes). */
|
||||
window.DRACO_BAR_CONFIG = {
|
||||
current: "obsigate",
|
||||
domain: (function () { try { return localStorage.getItem("draco_domain") || "pro"; } catch (e) { return "pro"; } })(),
|
||||
counts: {},
|
||||
onAction: function (act, el) {
|
||||
var dire = function (m) {
|
||||
var d = document.createElement("div");
|
||||
d.textContent = m;
|
||||
d.style.cssText = "position:fixed;left:50%;bottom:22px;transform:translateX(-50%);z-index:20000;background:#161b22;color:#e6edf3;padding:10px 16px;border-radius:10px;font:500 13.5px system-ui;box-shadow:0 6px 24px rgb(0 0 0/.35)";
|
||||
document.body.appendChild(d);
|
||||
setTimeout(function () { d.remove(); }, 3400);
|
||||
};
|
||||
if (act === "domaine") { try { localStorage.setItem("draco_domain", el.dataset.dom); } catch (e) { } return; }
|
||||
if (act === "rail") { var b = document.getElementById("sidebar-toggle-btn"); if (b) b.click(); return; }
|
||||
if (act === "palette") { var s = document.getElementById("search-input"); if (s) { s.focus(); s.scrollIntoView({ block: "center" }); } return; }
|
||||
if (act === "menu") { var m = document.getElementById("header-menu-btn"); if (m) m.click(); return; }
|
||||
if (act === "alertes") { dire("Alertes unifiées : arrive avec la fédération (programme Fusion)."); return; }
|
||||
if (act === "spark") { dire("Spark, l'agent commun : arrive en phase 4 du programme Fusion."); return; }
|
||||
if (act === "rappel-entete") {
|
||||
var bar = document.querySelector(".draco-bar");
|
||||
if (bar) bar.style.display = "";
|
||||
el.hidden = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
/* Avatar : initiale de l'utilisateur courant (best effort). */
|
||||
window.addEventListener("DOMContentLoaded", function () {
|
||||
var tok = null;
|
||||
try { tok = sessionStorage.getItem("obsigate_access_token"); } catch (e) { }
|
||||
fetch("/api/auth/me", {
|
||||
credentials: "same-origin",
|
||||
headers: tok ? { Authorization: "Bearer " + tok } : {}
|
||||
}).then(function (r) { return r.ok ? r.json() : null; })
|
||||
.then(function (u) {
|
||||
var nom = u && (u.display_name || u.username);
|
||||
if (nom) {
|
||||
var av = document.getElementById("draco-avatar");
|
||||
if (av) { av.textContent = nom.charAt(0).toUpperCase(); av.title = nom; }
|
||||
}
|
||||
}).catch(function () { /* avatar générique conservé */ });
|
||||
});
|
||||
</script>
|
||||
<div id="app" class="app-container">
|
||||
<!-- Header -->
|
||||
<header class="header">
|
||||
@@ -5931,5 +6040,7 @@ curl -X POST https://votre-serveur.com/webhook \
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script defer src="/static/draco-barre.js"></script>
|
||||
<script defer src="/static/draco-liens.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -752,6 +752,13 @@
|
||||
"upload.custom_folder": "Or enter a new subfolder path...",
|
||||
"upload.import_summary": "Items to import: {files} file(s), {dirs} folder(s)",
|
||||
"upload.import_button": "Import",
|
||||
"draco.links.add": "Link",
|
||||
"draco.links.labelPlaceholder": "Label (optional)",
|
||||
"draco.links.openInTikTak": "Open in TikTak",
|
||||
"draco.links.projected": "Projected TikTak task",
|
||||
"draco.links.remove": "Remove link",
|
||||
"draco.links.title": "Links",
|
||||
"draco.links.urlPlaceholder": "Paste a deep link (Flowdeck, TikTak, Gitea, ObsiGate)…",
|
||||
"editor.cancel": "Cancel",
|
||||
"editor.close": "Close",
|
||||
"editor.delete_error": "Delete error",
|
||||
|
||||
@@ -752,6 +752,13 @@
|
||||
"upload.custom_folder": "Ou saisir un nouveau sous-dossier...",
|
||||
"upload.import_summary": "Éléments à importer : {files} fichier(s), {dirs} dossier(s)",
|
||||
"upload.import_button": "Importer",
|
||||
"draco.links.add": "Lier",
|
||||
"draco.links.labelPlaceholder": "Libellé (optionnel)",
|
||||
"draco.links.openInTikTak": "Ouvrir dans TikTak",
|
||||
"draco.links.projected": "Tâche TikTak projetée",
|
||||
"draco.links.remove": "Retirer le lien",
|
||||
"draco.links.title": "Liens",
|
||||
"draco.links.urlPlaceholder": "Coller une URL profonde (Flowdeck, TikTak, Gitea, ObsiGate)…",
|
||||
"editor.cancel": "Annuler",
|
||||
"editor.close": "Fermer",
|
||||
"editor.delete_error": "Erreur de suppression",
|
||||
|
||||
@@ -14292,3 +14292,42 @@ textarea.file-chat-input {
|
||||
|
||||
|
||||
|
||||
|
||||
/* — Barre d'apps commune dracodev + section « Liens » des notes
|
||||
(Fusion #215, snippet figé dracodev-commons v0.1.0) — */
|
||||
body.has-draco-bar .app-container { height: calc(100vh - 60px); }
|
||||
@media (max-width: 700px) {
|
||||
body.has-draco-bar .app-container { height: calc(100vh - 52px); }
|
||||
}
|
||||
.draco-liens {
|
||||
margin: 28px 0 8px; padding: 16px 18px;
|
||||
border: 1px solid var(--border); border-radius: 12px;
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
.draco-liens-title { margin: 0 0 10px; font-size: 15px; color: var(--text-primary); }
|
||||
.draco-liens-projete {
|
||||
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
|
||||
margin-bottom: 12px; padding: 10px 12px; border-radius: 10px;
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
.draco-liens-badge { font-weight: 700; color: #e8762c; font-size: 13px; }
|
||||
.draco-liens-ouvrir { text-decoration: none; }
|
||||
.draco-liens-list { list-style: none; margin: 0 0 12px; padding: 0; }
|
||||
.draco-liens-item { display: flex; align-items: center; gap: 4px; padding: 4px 0; }
|
||||
.draco-ref-dot {
|
||||
display: inline-block; width: 9px; height: 9px; border-radius: 50%;
|
||||
margin-right: 7px; flex: none;
|
||||
}
|
||||
.draco-liens-item a { color: var(--text-primary); }
|
||||
.draco-liens-del {
|
||||
margin-left: 8px; border: 0; background: transparent; cursor: pointer;
|
||||
color: var(--text-secondary); font-size: 12px;
|
||||
}
|
||||
.draco-liens-del:hover { color: var(--err); }
|
||||
.draco-liens-form { display: flex; gap: 6px; flex-wrap: wrap; }
|
||||
.draco-liens-form input {
|
||||
flex: 1 1 200px; min-width: 0; padding: 7px 10px; border-radius: 8px;
|
||||
border: 1px solid var(--border); background: var(--bg-primary);
|
||||
color: var(--text-primary); font: inherit; font-size: 13.5px;
|
||||
}
|
||||
.draco-liens-erreur { color: var(--err); font-size: 13px; margin: 8px 0 0; }
|
||||
|
||||
Reference in New Issue
Block a user