Compare commits

...
Author SHA1 Message Date
bruno da6de52198 barre-apps v0.1.6 : CSS vendange (menus, avatar bouton)
CI / lint (pull_request) Failing after 1s
CI / test (pull_request) Skipped
CI / build (pull_request) Skipped
CI / e2e (pull_request) Skipped
CI / security (pull_request) Failing after 0s
2026-10-28 10:32:06 -04:00
bruno 87016e5581 barre-apps v0.1.6 : JS vendange (derivation -dev, menus) 2026-10-28 10:32:01 -04:00
bruno e0e45ac048 barre-apps v0.1.6 : fragment inline (avatar bouton compte, aria menu) - menu natif et DRACO_URLS inchanges 2026-10-28 10:31:55 -04:00
bruno 1b3ba04b5b chore(release): v2.81.2 — CHANGELOG
CI / lint (push) Failing after 0s
CI / security (push) Failing after 0s
CI / test (push) Skipped
CI / build (push) Skipped
CI / e2e (push) Skipped
2026-10-28 09:30:42 -04:00
bruno 5c5180336b chore(release): v2.81.2 — synchroniser docs/ROADMAP.md
CI / lint (push) Failing after 1s
CI / security (push) Failing after 0s
CI / test (push) Skipped
CI / build (push) Skipped
CI / e2e (push) Skipped
2026-10-28 09:30:40 -04:00
bruno 73bf863e40 chore(release): v2.81.2 — synchroniser desktop/Cargo.lock
CI / lint (push) Failing after 1s
CI / security (push) Failing after 0s
CI / test (push) Skipped
CI / build (push) Skipped
CI / e2e (push) Skipped
2026-10-28 09:30:37 -04:00
bruno 06d0e354fe chore(release): v2.81.2 — synchroniser desktop/Cargo.toml
CI / lint (push) Failing after 0s
CI / security (push) Failing after 0s
CI / test (push) Skipped
CI / build (push) Skipped
CI / e2e (push) Skipped
2026-10-28 09:30:32 -04:00
bruno a01fba6602 chore(release): v2.81.2 — synchroniser desktop/tauri.conf.json
CI / lint (push) Failing after 1s
CI / security (push) Failing after 0s
CI / test (push) Skipped
CI / build (push) Skipped
CI / e2e (push) Skipped
2026-10-28 09:30:29 -04:00
bruno 54ebf2365c chore(release): v2.81.2 — synchroniser package.json
CI / lint (push) Failing after 0s
CI / security (push) Failing after 0s
CI / test (push) Skipped
CI / build (push) Skipped
CI / e2e (push) Skipped
2026-10-28 09:30:26 -04:00
bruno 24eaa7f9df chore(release): v2.81.2 (panneau Spark / console, PR #35)
CI / security (push) Successful in 2m0s
CI / lint (push) Successful in 3m12s
CI / test (push) Failing after 0s
CI / build (push) Skipped
CI / e2e (push) Skipped
2026-10-28 09:30:24 -04:00
bruno 8e3eb3a9d7 Merge pull request 'fix(sidebar): style console, statut dans le titre, composer console, synchro domaine' (#35) from fix/sidebar-console-style-domaine into main
CI / security (push) Failing after 0s
CI / lint (push) Successful in 3m11s
CI / test (push) Failing after 0s
CI / build (push) Skipped
CI / e2e (push) Skipped
2026-10-28 09:28:50 -04:00
bruno 01ce8b8ec3 Merge pull request 'chore(release): version 2.81.1 (correctifs CI + icone Secrets)' (#36) from fix/version-2.81.1 into main
CI / lint (push) Failing after 2s
CI / test (push) Skipped
CI / build (push) Skipped
CI / e2e (push) Skipped
CI / security (push) Failing after 2m7s
2026-10-28 09:19:57 -04:00
bruno 5dd6809ef0 chore(release): bump version 2.81.1 (correctifs CI + icone Secrets)
CI / security (pull_request) Failing after 1s
CI / lint (pull_request) Successful in 3m17s
CI / test (pull_request) Successful in 5m26s
CI / build (pull_request) Failing after 1s
CI / e2e (pull_request) Skipped
2026-10-28 09:19:32 -04:00
bruno 7b8bc52650 chore(release): bump version 2.81.1 (correctifs CI + icone Secrets) 2026-10-28 09:19:28 -04:00
bruno 1758c3b6d2 chore(release): bump version 2.81.1 (correctifs CI + icone Secrets) 2026-10-28 09:19:24 -04:00
bruno 6669633902 chore(release): bump version 2.81.1 (correctifs CI + icone Secrets) 2026-10-28 09:19:20 -04:00
bruno a65d282c0b chore(release): bump version 2.81.1 (correctifs CI + icone Secrets) 2026-10-28 09:19:17 -04:00
bruno 524b43cc40 chore(release): bump version 2.81.1 (correctifs CI + icone Secrets) 2026-10-28 09:19:13 -04:00
bruno 4e0b27c4d7 chore(release): bump version 2.81.1 (correctifs CI + icone Secrets) 2026-10-28 09:19:10 -04:00
bruno c6d7acfd99 chore(release): bump version 2.81.1 (correctifs CI + icone Secrets) 2026-10-28 09:19:07 -04:00
bruno bf941c71ec chore(release): bump version 2.81.1 (correctifs CI + icone Secrets) 2026-10-28 09:19:05 -04:00
12 changed files with 461 additions and 59 deletions
+13 -1
View File
@@ -6,7 +6,7 @@ Format basé sur [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/),
et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
> **En cours de développement** : les changements à venir sont listés dans la section
> [Unreleased](#unreleased). La dernière version livrée est **2.81.0**.
> [Unreleased](#unreleased). La dernière version livrée est **2.81.2**.
---
@@ -14,6 +14,18 @@ et [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
---
## [2.81.2] — 2026-10-28
### Modifié
- **Panneau Spark / console (PR #35)** — style console du panneau, statut « Connecté » intégré au sous-titre de l'en-tête (barre séparée supprimée), texte sous la zone de saisie supprimé, composer identique à la console (pilule, pièces jointes 4 max, textarea auto-extensible, micro dictée, envoi rond bleu), resynchronisation du domaine à chaque ouverture / changement de stockage / retour console, URL console avec `?domain=` ; la console honore `?domain=` valide (persisté avant chargement des discussions, PR agent-commun #7).
---
## [2.81.1] — 2026-10-28
---
## [2.81.0] — 2026-10-28
---
+3 -3
View File
@@ -4,7 +4,7 @@
**Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive.
[![Version](https://img.shields.io/badge/Version-2.81.0-blue.svg)]()
[![Version](https://img.shields.io/badge/Version-2.81.1-blue.svg)]()
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/)
[![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/)
@@ -977,8 +977,8 @@ Ce projet est sous licence **MIT** — voir le fichier [LICENSE](LICENSE) pour l
## 📝 Changelog
Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.81.0).
Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.81.1).
---
*Projet : ObsiGate | Version : 2.81.0 | Dernière mise à jour : Septembre 2026*
*Projet : ObsiGate | Version : 2.81.1 | Dernière mise à jour : Septembre 2026*
+3 -3
View File
@@ -2,7 +2,7 @@
**Ultra-light web gateway for your Obsidian vaults** — Access, browse, and search all your Obsidian notes from any device via a modern, responsive web interface.
[![Version](https://img.shields.io/badge/Version-2.81.0-blue.svg)]()
[![Version](https://img.shields.io/badge/Version-2.81.1-blue.svg)]()
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/)
[![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/)
@@ -1152,8 +1152,8 @@ This project is licensed under the **MIT License** - see the [LICENSE](LICENSE)
## 📝 Changelog
See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.81.0).
See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.81.1).
---
*Project: ObsiGate | Version: 2.81.0 | Last updated: September 2026*
*Project: ObsiGate | Version: 2.81.1 | Last updated: September 2026*
+1 -1
View File
@@ -1 +1 @@
2.81.0
2.81.2
+1 -1
View File
@@ -2626,7 +2626,7 @@ dependencies = [
[[package]]
name = "obsigate-desktop"
version = "2.81.0"
version = "2.81.2"
dependencies = [
"chrono",
"env_logger",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "obsigate-desktop"
version = "2.81.0"
version = "2.81.2"
description = "ObsiGate Desktop — Porte d'entrée native pour vos vaults Obsidian"
authors = ["Bruno Charest"]
edition = "2021"
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/nicedoc/obsigate/main/desktop/tauri.conf.schema.json",
"productName": "ObsiGate",
"version": "2.81.0",
"version": "2.81.2",
"identifier": "com.obsigate.desktop",
"build": {
"frontendDist": "../frontend",
+1 -1
View File
@@ -1,6 +1,6 @@
# ObsiGate — Roadmap
> **Version :** 2.81.0 | **Dernière mise à jour :** 2026-10-28
> **Version :** 2.81.2 | **Dernière mise à jour :** 2026-10-28
> **Ce fichier ne contient que le travail à venir** (🔵 En cours + ⚪ Backlog) et un index compact
> vers les fonctionnalités livrées.
> - **Méthode de livraison à appliquer pour toute tâche : [DELIVERY_WORKFLOW.md](./DELIVERY_WORKFLOW.md)**
+30 -3
View File
@@ -2,7 +2,9 @@
v0.1.2 : palier mobile resserré selon la spec (voir @media 700px) ;
v0.1.3 : la marque ✦ (plus grande) ouvre le menu des applications —
c'est LE changement d'app en mobile (choix de Bruno, 2026-10-21) ;
v0.1.4 : palier mobile élargi de 700 à 900 px (zone morte comblée).
v0.1.4 : palier mobile élargi de 700 à 900 px (zone morte comblée) ;
v0.1.6 : menus « ••• » et compte (construits par le JS, mêmes jetons
que le menu des applications) ; l'avatar est un vrai <button>.
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. */
@@ -42,6 +44,29 @@
box-shadow: 0 16px 44px rgb(0 0 0 / .28);
}
.draco-apps-menu[hidden] { display: none; }
/* Menus « ••• » et compte (v0.1.6) — même boîte que le menu des apps,
* ancrés à droite ; leurs items réutilisent .draco-apps-item. */
.draco-more-menu, .draco-compte-menu {
position: absolute; top: calc(100% + 6px); right: 10px; z-index: 400;
min-width: 248px; padding: 6px;
background: var(--surface); border: 1px solid var(--line); border-radius: 14px;
box-shadow: 0 16px 44px rgb(0 0 0 / .28);
}
.draco-more-menu[hidden], .draco-compte-menu[hidden] { display: none; }
.draco-menu-item {
display: flex; align-items: center; gap: 10px; width: 100%; box-sizing: border-box;
padding: 8px 10px; border: 0; border-radius: 10px;
background: transparent; color: var(--ink); font: inherit; text-align: left;
text-decoration: none; cursor: pointer;
}
.draco-menu-item:hover, .draco-menu-item:focus-visible { background: var(--surface-2); }
.draco-menu-sep { height: 1px; margin: 6px 10px; background: var(--line); }
.draco-compte-nom {
padding: 8px 10px 10px; font-weight: 700; font-size: 14px;
border-bottom: 1px solid var(--line); margin-bottom: 6px;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.draco-apps-item {
display: flex; align-items: center; gap: 10px;
padding: 8px 10px; border-radius: 10px;
@@ -122,8 +147,10 @@
}
.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;
width: 34px; height: 34px; border: 0; padding: 0; border-radius: 50%;
display: grid; place-items: center; flex: none;
background: #2f6fed; color: #fff; font: inherit; font-weight: 700;
cursor: pointer;
}
/* Rappel de l'en-tête masqué — onglet ancré au bord supérieur (v6) */
+403 -40
View File
@@ -1,5 +1,10 @@
/* Barre d'apps commune dracodev — comportement (v0.1.0, ACC-0.2 ;
* v0.1.3 : menu des applications ouvert par la marque ✦).
* v0.1.3 : menu des applications ouvert par la marque ✦ ;
* v0.1.5 : noms publics par configuration — aucun nom public d'un
* environnement particulier n'est codé en dur, voir « URL » ci-dessous ;
* v0.1.6 (prochaine publication — VERSION non bumpée dans cette PR,
* la publication est séparée) : dérivation -dev par l'hôte courant,
* menu « ••• » par défaut et menu compte fonctionnels, voir ci-dessous).
*
* 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
@@ -10,19 +15,158 @@
* current: "tiktak", // app courante
* domain: "pro", // domaine actif
* counts: { tiktak: 3, kanbante: 3, alertes: 3, spark: 0 },
* user: "Bruno", // nom affiché (v0.1.6) ;
* // ou { display_name: "Bruno", username: "bruno" }
* urls: { // bases des applications
* obsigate: "https://og.dracodev.net/",
* flowdeck: "https://flowdeck.dracodev.net/",
* tiktak: "https://tiktak.dracodev.net/",
* kanbante: "https://kanbante.dracodev.net/",
* agent: "https://agent.dracodev.net/",
* spark: "https://spark.dracodev.net/"
* },
* onAction: function (act, el) { … } // l'app relaie les actions
* } // qu'elle ne connaît pas
*
* URL des applications (contrat dev/prod, v0.1.5 puis v0.1.6) —
* résolution, dans l'ordre STRICT, pour chaque app :
* 1. DRACO_BAR_CONFIG.urls.<app>, injectée par l'app hôte (rendu
* serveur ou client) depuis SA configuration d'environnement —
* explicite, donc prioritaire, même sur un hôte -dev ;
* 2. (v0.1.6) SI l'hôte courant est un hôte -dev dracodev.net
* (première étiquette se terminant par -dev : tiktak-dev,
* kanbante-dev, og-dev, flowdeck-dev, agent-dev…), l'URL sœur
* dérivée avec -dev (voir DEV_URLS). C'est le correctif du bug
* constaté sur tiktak-dev / kanbante-dev, où les liens suivaient
* les noms SANS -dev faute de configuration explicite ;
* 3. le href déjà rendu dans le fragment HTML, c.-à-d. le jeton
* {{URL_<APP>}} remplacé côté serveur par l'app hôte ;
* 4. le défaut officiel ci-dessous (production, noms SANS suffixe).
* Un jeton {{URL_…}} non remplacé n'est jamais une URL : il est ignoré
* partout (config injectée comme href rendu) et ne peut pas être exposé.
* Cas spark (v0.1.6) : AUCUNE dérivation inventée — aucun hôte
* spark-dev connu ; sur un hôte -dev, spark retombe sur l'étape 3
* (href rendu) puis 4 (défaut prod), sauf config explicite (étape 1).
* Les clés agent et spark ne correspondent à aucun onglet de la barre :
* elles sont résolues et exposées via DRACO_BAR.urls pour le panneau
* Spark (panneau-agent), qui suit le même contrat d'injection.
* 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…).
* Action INTERNE (jamais relayée) : apps — la marque ✦ ouvre le menu
* des applications, construit depuis les onglets ; c'est le moyen de
* changer d'app en mobile, où les onglets sont repliés (v0.1.2).
* alertes, spark, rappel-entete. L'app intégratrice décide de chacune
* (ouvrir la palette, le panneau Spark, changer de domaine…).
* Actions INTERNES (jamais relayées comme avant) : apps — la marque ✦
* ouvre le menu des applications, construit depuis les onglets ;
* c'est le moyen de changer d'app en mobile, où les onglets sont
* repliés (v0.1.2). Menu « ••• » et compte : voir ci-dessous.
*
* Menu « ••• » (v0.1.6) — audit des hôtes : ObsiGate est le seul hôte
* connu à gérer l'action "menu" via onAction (il déclenche son propre
* bouton d'en-tête #header-menu-btn) ; les autres hôtes ne la gèrent
* pas et le bouton restait mort (relais vers un onAction absent, puis
* événement draco:bar sans écouteur). Comportement désormais :
* - sans onAction : un menu par défaut FONCTIONNEL s'ouvre — accès à
* la console agent (URL agent résolue, donc dérivée -dev sur un
* hôte -dev) et aux quatre applications (URL résolues) ; fermeture
* par clic extérieur, Échap ou choix d'un lien ;
* - avec onAction : l'hôte garde la main (comportement historique,
* zéro régression pour ObsiGate) ; il peut demander explicitement
* le menu par défaut en retournant false pour l'action "menu".
* Menu compte (v0.1.6) — l'avatar, auparavant un <span> mort affichant
* un « B » codé en dur dans le fragment, est un vrai <button>
* (data-act="compte"). Son initiale vient de cfg.user ou, à défaut, de
* la première lettre du title {{UTILISATEUR}} remplacé ; sans nom connu,
* le neutre ✦ est conservé (jamais de lettre codée en dur). Le clic
* ouvre un petit menu affichant le nom de l'utilisateur ; les items
* Profil / Déconnexion sont relayés à l'hôte via onAction ou, à défaut,
* via l'événement draco:bar (détail { act: "profil" | "deconnexion" }),
* mais le menu affiche l'identité même sans gestionnaire hôte.
* Accessibilité : vrais <button>, aria-haspopup / aria-expanded,
* role="menu" / "menuitem", fermeture par Échap et clic extérieur.
*/
(function () {
"use strict";
var cfg = window.DRACO_BAR_CONFIG || {};
/* Défauts OFFICIELS (production) — les seuls noms publics présents
* dans ce fichier, documentés comme défauts (contrat dev/prod). */
var DEFAULT_URLS = {
obsigate: "https://og.dracodev.net/",
flowdeck: "https://flowdeck.dracodev.net/",
tiktak: "https://tiktak.dracodev.net/",
kanbante: "https://kanbante.dracodev.net/",
agent: "https://agent.dracodev.net/",
spark: "https://spark.dracodev.net/"
};
/* Dérivation -dev (v0.1.6) — appliquée UNIQUEMENT quand l'hôte courant
* est lui-même un hôte -dev dracodev.net (voir isDevHost). spark est
* volontairement absent : aucun hôte spark-dev connu, aucune URL
* inventée ; spark retombe sur href rendu puis défaut prod. */
var DEV_URLS = {
obsigate: "https://og-dev.dracodev.net/",
flowdeck: "https://flowdeck-dev.dracodev.net/",
tiktak: "https://tiktak-dev.dracodev.net/",
kanbante: "https://kanbante-dev.dracodev.net/",
agent: "https://agent-dev.dracodev.net/"
};
var APP_LABELS = {
obsigate: "ObsiGate", flowdeck: "Flowdeck",
tiktak: "TikTak", kanbante: "Kanbante"
};
/* Vrai si hostname est un hôte -dev dracodev.net : première étiquette
* se terminant par -dev ET domaine dracodev.net (un suffixe -dev sur
* un autre domaine, ou un hôte piège du type x-dev.dracodev.net.evil,
* ne déclenche rien). Insensible à la casse. */
function isDevHost(hostname) {
if (!hostname) return false;
var h = String(hostname).toLowerCase();
if (h.slice(-13) !== ".dracodev.net") return false;
var first = h.split(".")[0];
return first.length > 4 && first.slice(-4) === "-dev";
}
/* Un jeton {{…}} non remplacé n'est pas une URL : l'ignorer. */
function cleanUrl(u) {
if (!u || typeof u !== "string") return null;
if (u.indexOf("{{") !== -1) return null;
if (!u.replace(/\s/g, "")) return null;
return u;
}
function withSlash(u) {
return u.charAt(u.length - 1) !== "/" ? u + "/" : u;
}
function resolveUrls() {
var host = "";
try { host = (window.location && window.location.hostname) || ""; } catch (e) { host = ""; }
var dev = isDevHost(host);
var urls = {};
Object.keys(DEFAULT_URLS).forEach(function (app) {
/* 1. Config explicite de l'hôte — prioritaire, même sur -dev. */
var u = cleanUrl(cfg.urls && cfg.urls[app]);
/* 2. Dérivation -dev par l'hôte courant (spark : pas de dérivée). */
if (!u && dev) u = DEV_URLS[app] || null;
/* 3. href rendu dans le fragment (jeton remplacé côté serveur). */
if (!u) {
var tab = document.querySelector('.draco-apptab[data-app="' + app + '"]');
var href = tab ? tab.getAttribute("href") : null;
u = cleanUrl(href);
}
/* 4. Défaut officiel (production). */
if (!u) u = DEFAULT_URLS[app];
urls[app] = withSlash(u);
});
return urls;
}
function applyUrls(urls) {
document.querySelectorAll(".draco-apptab, .draco-apps-item").forEach(function (t) {
if (t.dataset.app && urls[t.dataset.app]) t.setAttribute("href", urls[t.dataset.app]);
});
}
var resolvedUrls = resolveUrls();
function setCounts(counts) {
Object.keys(counts || {}).forEach(function (k) {
/* Toutes les occurrences : l'onglet ET sa ligne dans le menu. */
@@ -43,26 +187,122 @@
});
}
/* ── Menu des applications (v0.1.3) ──────────────────────────────
* Construit depuis les onglets présents dans la barre : mêmes
* liens, mêmes icônes, mêmes badges. Ouvert par la marque ✦
* (data-act="apps"), fermé par un clic extérieur, Échap, ou le
* choix d'une application (la navigation suit son cours). */
/* ── Identité / avatar (v0.1.6) ───────────────────────────────────
* Nom : cfg.user (chaîne, ou objet display_name / name / username),
* sinon le title de l'avatar quand le jeton {{UTILISATEUR}} a bien été
* remplacé côté serveur. L'initiale en découle ; sans nom, le neutre
* ✦ du fragment est conservé — jamais de lettre codée en dur. */
function userName() {
var u = cfg.user;
if (typeof u === "string" && u.replace(/\s/g, "")) return u.trim();
if (u && typeof u === "object") {
var n = u.display_name || u.name || u.username;
if (n && String(n).replace(/\s/g, "")) return String(n).trim();
}
return null;
}
function titleName() {
var av = document.querySelector(".draco-avatar");
var t = av ? av.getAttribute("title") : null;
if (t && t.indexOf("{{") === -1 && t.replace(/\s/g, "")) return t.trim();
return null;
}
function displayName() { return userName() || titleName(); }
function syncAvatar() {
var av = document.querySelector(".draco-avatar");
if (!av) return;
/* Compatibilité avec un fragment vendangé antérieur (avatar <span>
* sans data-act) : le rendre déclencheur du menu compte. */
if (!av.getAttribute("data-act")) av.setAttribute("data-act", "compte");
if (!av.getAttribute("aria-haspopup")) av.setAttribute("aria-haspopup", "menu");
var name = displayName();
if (name) {
av.textContent = name.charAt(0).toUpperCase();
av.setAttribute("title", name);
av.setAttribute("aria-label", "Compte : " + name);
}
}
/* ── Menus déroulants (apps, •••, compte) ──────────────────────────
* Un seul menu ouvert à la fois ; fermeture par clic extérieur,
* Échap, ou choix d'un item. aria-expanded tenu à jour sur chaque
* déclencheur. Les href des menus sont (re)synchronisés avec
* resolvedUrls à chaque ouverture, pour que setUrls et la dérivation
* -dev s'y reflètent même si le menu a été construit avant. */
var bar = document.querySelector(".draco-bar");
var brand = bar ? bar.querySelector(".draco-brand") : null;
var menu = null;
var moreBtn = bar ? bar.querySelector('[data-act="menu"]') : null;
var avatarBtn = document.querySelector(".draco-avatar");
var appsMenu = null, moreMenu = null, compteMenu = null;
function buildMenu() {
if (!bar || !brand || menu) return;
menu = document.createElement("div");
menu.className = "draco-apps-menu";
menu.hidden = true;
menu.setAttribute("role", "menu");
menu.setAttribute("aria-label", "Applications");
function setExpanded(btn, open) {
if (btn) btn.setAttribute("aria-expanded", open ? "true" : "false");
}
function refreshMenuHrefs(menu) {
if (!menu) return;
menu.querySelectorAll(".draco-apps-item").forEach(function (a) {
if (a.dataset.app && resolvedUrls[a.dataset.app]) a.setAttribute("href", resolvedUrls[a.dataset.app]);
});
}
function togglePopup(menu, btn, force) {
if (!menu) return;
var open = typeof force === "boolean" ? force : menu.hidden;
menu.hidden = !open;
setExpanded(btn, open);
}
function closeAllPopups() {
if (appsMenu) togglePopup(appsMenu, brand, false);
if (moreMenu) togglePopup(moreMenu, moreBtn, false);
if (compteMenu) togglePopup(compteMenu, avatarBtn, false);
}
function tabLabel(app) {
var tab = document.querySelector('.draco-apptab[data-app="' + app + '"]');
var fort = tab ? tab.querySelector(".draco-apptab-txt strong") : null;
return fort ? fort.textContent : (APP_LABELS[app] || app);
}
function tabRole(app) {
var tab = document.querySelector('.draco-apptab[data-app="' + app + '"]');
var petit = tab ? tab.querySelector(".draco-apptab-txt small") : null;
return petit ? petit.textContent : "";
}
function menuLink(app, nom, role) {
var a = document.createElement("a");
a.className = "draco-apps-item draco-menu-item";
a.href = resolvedUrls[app] || "#";
a.dataset.app = app;
a.setAttribute("role", "menuitem");
var txt = document.createElement("span");
txt.className = "draco-apps-item-txt";
var n = document.createElement("span");
n.className = "draco-apps-item-nom";
n.textContent = nom;
txt.appendChild(n);
if (role) {
var r = document.createElement("span");
r.className = "draco-apps-item-role";
r.textContent = role;
txt.appendChild(r);
}
a.appendChild(txt);
return a;
}
/* Menu des applications (v0.1.3) — construit depuis les onglets
* présents dans la barre : mêmes liens, mêmes icônes, mêmes badges.
* Ouvert par la marque ✦ (data-act="apps"), fermé par un clic
* extérieur, Échap, ou le choix d'une application (la navigation
* suit son cours). */
function buildAppsMenu() {
if (!bar || !brand || appsMenu) return;
appsMenu = document.createElement("div");
appsMenu.className = "draco-apps-menu";
appsMenu.hidden = true;
appsMenu.setAttribute("role", "menu");
appsMenu.setAttribute("aria-label", "Applications");
document.querySelectorAll(".draco-apptab").forEach(function (t) {
var a = document.createElement("a");
a.className = "draco-apps-item";
a.href = t.getAttribute("href");
a.href = resolvedUrls[t.dataset.app] || t.getAttribute("href");
a.dataset.app = t.dataset.app;
a.setAttribute("role", "menuitem");
var ico = t.querySelector(".draco-apptab-ico");
@@ -84,44 +324,167 @@
a.appendChild(txt);
var badge = t.querySelector(".draco-badge");
if (badge) a.appendChild(badge.cloneNode(true));
menu.appendChild(a);
appsMenu.appendChild(a);
});
bar.appendChild(menu);
bar.appendChild(appsMenu);
}
function toggleAppsMenu(force) {
buildAppsMenu();
if (!appsMenu) return;
if (force !== false) refreshMenuHrefs(appsMenu);
togglePopup(appsMenu, brand, force);
}
function toggleMenu(force) {
buildMenu();
if (!menu) return;
var open = typeof force === "boolean" ? force : menu.hidden;
menu.hidden = !open;
if (brand) brand.setAttribute("aria-expanded", open ? "true" : "false");
/* Menu « ••• » par défaut (v0.1.6) — console agent + applications.
* Construit depuis les URL résolues (donc dérivées -dev sur un hôte
* -dev) ; l'hôte qui gère "menu" via onAction garde la main (voir
* l'en-tête) et ne voit jamais ce menu, sauf s'il retourne false. */
function buildMoreMenu() {
if (!bar || !moreBtn || moreMenu) return;
moreMenu = document.createElement("div");
moreMenu.className = "draco-more-menu";
moreMenu.hidden = true;
moreMenu.setAttribute("role", "menu");
moreMenu.setAttribute("aria-label", "Plus d'actions");
moreMenu.appendChild(menuLink("agent", "Console agent", "Agent commun"));
var sep = document.createElement("div");
sep.className = "draco-menu-sep";
sep.setAttribute("aria-hidden", "true");
moreMenu.appendChild(sep);
["obsigate", "flowdeck", "tiktak", "kanbante"].forEach(function (app) {
moreMenu.appendChild(menuLink(app, tabLabel(app), tabRole(app)));
});
bar.appendChild(moreMenu);
}
function toggleMoreMenu(force) {
buildMoreMenu();
if (!moreMenu) return;
if (force !== false) refreshMenuHrefs(moreMenu);
togglePopup(moreMenu, moreBtn, force);
}
/* Menu compte (v0.1.6) — en-tête d'identité + Profil / Déconnexion
* relayés à l'hôte. L'en-tête est rafraîchi à chaque ouverture, pour
* refléter un title posé après coup par l'hôte (ex. ObsiGate, qui
* remplit l'avatar depuis /api/auth/me après chargement). */
function buildCompteMenu() {
if (!bar || !avatarBtn || compteMenu) return;
compteMenu = document.createElement("div");
compteMenu.className = "draco-compte-menu";
compteMenu.hidden = true;
compteMenu.setAttribute("role", "menu");
compteMenu.setAttribute("aria-label", "Compte");
var head = document.createElement("div");
head.className = "draco-compte-nom";
compteMenu.appendChild(head);
[["profil", "Profil"], ["deconnexion", "Déconnexion"]].forEach(function (pair) {
var b = document.createElement("button");
b.type = "button";
b.className = "draco-menu-item";
b.setAttribute("data-compte-act", pair[0]);
b.setAttribute("role", "menuitem");
b.textContent = pair[1];
compteMenu.appendChild(b);
});
bar.appendChild(compteMenu);
}
function refreshCompteMenu() {
if (!compteMenu) return;
var head = compteMenu.querySelector(".draco-compte-nom");
var name = displayName();
if (head) head.textContent = name || "Compte";
}
function toggleCompteMenu(force) {
buildCompteMenu();
if (!compteMenu) return;
if (force !== false) { syncAvatar(); refreshCompteMenu(); }
togglePopup(compteMenu, avatarBtn, force);
}
/* Relais d'une action vers l'hôte : onAction si présent, sinon
* événement draco:bar (contrat historique des actions relayées). */
function relay(act, el, ev) {
if (typeof cfg.onAction === "function") cfg.onAction(act, el, ev);
else document.dispatchEvent(new CustomEvent("draco:bar", { detail: { act: act, el: el } }));
}
document.addEventListener("click", function (ev) {
var el = ev.target.closest("[data-act]");
if (el && el.dataset.act === "apps") {
ev.preventDefault();
toggleMenu();
/* Items du menu compte : relais + fermeture, jamais de navigation. */
var compteItem = ev.target.closest ? ev.target.closest("[data-compte-act]") : null;
if (compteItem) {
relay(compteItem.getAttribute("data-compte-act"), compteItem, ev);
toggleCompteMenu(false);
return;
}
if (menu && !menu.hidden && !ev.target.closest(".draco-apps-menu") && !ev.target.closest(".draco-brand")) {
toggleMenu(false);
var el = ev.target.closest ? ev.target.closest("[data-act]") : null;
if (el && el.dataset.act === "apps") {
ev.preventDefault();
if (moreMenu) toggleMoreMenu(false);
if (compteMenu) toggleCompteMenu(false);
toggleAppsMenu();
return;
}
if (el && el.dataset.act === "menu") {
if (appsMenu) toggleAppsMenu(false);
if (compteMenu) toggleCompteMenu(false);
/* Hôte prioritaire : il garde la main, sauf s'il demande
* explicitement le menu par défaut en retournant false.
* Sans onAction, le menu par défaut s'ouvre (il ne restait
* auparavant qu'un événement draco:bar sans écouteur). */
if (typeof cfg.onAction === "function") {
var handled = cfg.onAction("menu", el, ev);
if (handled === false) toggleMoreMenu();
} else {
toggleMoreMenu();
}
return;
}
if (el && el.dataset.act === "compte") {
if (appsMenu) toggleAppsMenu(false);
if (moreMenu) toggleMoreMenu(false);
toggleCompteMenu();
return;
}
/* Clic extérieur : ferme tout menu ouvert (clic dans un menu ou
* sur son déclencheur déjà traité ci-dessus). */
var inPopup = ev.target.closest ? (ev.target.closest(".draco-apps-menu") ||
ev.target.closest(".draco-more-menu") || ev.target.closest(".draco-compte-menu")) : null;
if (!inPopup && ((appsMenu && !appsMenu.hidden) || (moreMenu && !moreMenu.hidden) || (compteMenu && !compteMenu.hidden))) {
closeAllPopups();
}
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 } }));
relay(act, el, ev);
});
document.addEventListener("keydown", function (ev) {
if (ev.key === "Escape") toggleMenu(false);
if (ev.key === "Escape") closeAllPopups();
});
/* API publique : l'app met les compteurs à jour sans recharger. */
window.DRACO_BAR = { setCounts: setCounts, setCurrent: setCurrent, setDomain: setDomain };
/* API publique : l'app met les compteurs à jour sans recharger.
* setUrls(urls) fusionne de nouvelles bases (même contrat que
* DRACO_BAR_CONFIG.urls — donc prioritaires sur la dérivation -dev)
* et les ré-applique aux onglets et aux menus ; DRACO_BAR.urls expose
* les bases résolues (défauts inclus), pour le panneau Spark et tout
* autre consommateur de l'hôte. */
window.DRACO_BAR = {
setCounts: setCounts, setCurrent: setCurrent, setDomain: setDomain,
urls: resolvedUrls,
setUrls: function (urls) {
cfg.urls = Object.assign({}, cfg.urls, urls || {});
resolvedUrls = resolveUrls();
window.DRACO_BAR.urls = resolvedUrls;
applyUrls(resolvedUrls);
}
};
buildMenu();
applyUrls(resolvedUrls);
buildAppsMenu();
buildMoreMenu();
buildCompteMenu();
syncAvatar();
refreshCompteMenu();
if (cfg.current) setCurrent(cfg.current);
if (cfg.domain) setDomain(cfg.domain);
if (cfg.counts) setCounts(cfg.counts);
+3 -3
View File
@@ -332,7 +332,7 @@
<!-- 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.4
Snippet FIGÉ vendangé depuis Projets/dracodev-commons v0.1.6
(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. -->
@@ -386,8 +386,8 @@
<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>
<button type="button" class="draco-icon-btn" data-act="menu" aria-label="Plus d'actions" aria-haspopup="menu" aria-expanded="false">•••</button>
<button type="button" class="draco-avatar" id="draco-avatar" data-act="compte" aria-label="Compte" aria-haspopup="menu" aria-expanded="false" title="">✦</button>
</div>
</header>
<div class="draco-bar-rappel" data-act="rappel-entete" hidden>✦ dracodev — afficher l'en-tête</div>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "obsigate",
"version": "2.81.0",
"version": "2.81.2",
"description": "**Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive.",
"main": "patch.js",
"directories": {