Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a75a477fea | ||
|
|
c8beccaa2e | ||
|
|
08e912d56f | ||
|
|
0464f6156e | ||
|
|
8bba621223 | ||
|
|
da5000b727 | ||
|
|
3539611aa7 | ||
|
|
1f82c2653a |
@@ -1,3 +1,18 @@
|
|||||||
|
## v7.84.13 (2026-11-04) — Barre commune v0.3.0 : liens Hub du menu compte (HUB-1)
|
||||||
|
|
||||||
|
- **Barre d'apps v0.3.0 vendangée (dracodev-commons, branche `hub-menu-compte-v0-3-0`, PR commons #9)** : `static/css/draco-barre.css` et `static/js/vendor/draco-barre.js` remplacés verbatim par la v0.3.0 ; fragment `app/templates/_draco_bar.html` porté (note de vendange + `urls.hub` dans la configuration), le reste de l'intégration Flowdeck inchangé.
|
||||||
|
- **Menu compte** : liens **Télécharger**, **À Propos**, **Aides** et **Profil** (devenu un lien vers `<hub>/profil`, plus un relais) construits par le JS vendangé depuis la base Hub résolue ; « Déconnexion » reste relayée à l'hôte, inchangée.
|
||||||
|
- **URL Hub par le même mécanisme que les autres** : `app/services/draco_urls.py` gagne la clé `hub` (défaut officiel `https://hub.dracodev.net` ; explicite `HUB_URL` / `settings.hub_url` ; dérivation par l'hôte de la requête — motif fermé étendu à `hub` — et par l'hôte de l'agent : `hub-dev` en dev), exposée dans `draco_urls.toutes()` et passée au gabarit en `urls.hub`. Les consommateurs par listes de clés (CORS, CSP, recherche fédérée) ne sont pas affectés : le Hub n'y entre pas.
|
||||||
|
- **Cache** : le bump 7.84.12 → 7.84.13 change aussi `asset_version`, donc les navigateurs ne garderont pas l'ancienne barre.
|
||||||
|
- Tests : `tests/test_draco_urls_devprod.py` (résolution Hub : défaut, dérivations, explicite) et `tests/test_draco_bar_texte.py` (marqueurs v0.3.0 du JS vendangé et du gabarit) étendus.
|
||||||
|
|
||||||
|
## v7.84.11 (2026-11-04) — Recherche fédérée : ShaarIt, 5e source de « Rechercher partout »
|
||||||
|
|
||||||
|
- **`static/js/recherche.js` re-vendangé depuis dracodev-commons v0.2.1** (source canonique `ui/recherche/`, contrat `contrats/recherche-federee.md`) : ShaarIt devient une source de la page `/recherche` comme les autres — bases prod/dev, onglet, normaliseur (lien profond canonique `{base}shaare/<shorturl>`, contexte « Bookmark · privé · tags », repli recherche native `/?q=`). Le fichier reste **strictement identique** chez les cinq hôtes.
|
||||||
|
- **Gabarit et route `/recherche`** : onglet et section « ShaarIt — bookmarks » ; la route expose désormais `bases.shaarit` (la clé existait dans `draco_urls.toutes()` depuis 7.84.10) et le gabarit rend `data-base-shaarit`.
|
||||||
|
- **CORS de recherche** (`app/middleware/recherche_cors.py`) : origines officielles ShaarIt prod et dev ajoutées aux listes explicites, `shaarit` ajoutée aux clés sœurs résolues — `GET /api/search` de Flowdeck devient lisible depuis les pages de ShaarIt, et réciproquement.
|
||||||
|
- Tests : `tests/recherche.test.js` porté à 5 sections (83 assertions, dont le normaliseur ShaarIt) ; `tests/test_recherche_onglets.py` (onglets attendus) et `tests/test_recherche.py` (allowlist à 12 origines) mis à jour. Fonds préexistant inchangé (`test_webhooks_crud`, échec confirmé sur main vierge).
|
||||||
|
|
||||||
## v7.84.10 (2026-11-03) — Barre commune v0.2.0 : onglet ShaarIt, garde data-theme
|
## v7.84.10 (2026-11-03) — Barre commune v0.2.0 : onglet ShaarIt, garde data-theme
|
||||||
|
|
||||||
- **Barre d'apps v0.2.0 vendangée (dracodev-commons @70c837a)** : `static/css/draco-barre.css` et `static/js/vendor/draco-barre.js` remplacés verbatim par la v0.2.0 (hachages SHA-256 vérifiés contre la source) ; fragment `app/templates/_draco_bar.html` porté à la v0.2.0, le reste de son intégration Flowdeck (URL `draco_urls()`, `onAction`, nonce CSP, `asset_version`) est inchangé.
|
- **Barre d'apps v0.2.0 vendangée (dracodev-commons @70c837a)** : `static/css/draco-barre.css` et `static/js/vendor/draco-barre.js` remplacés verbatim par la v0.2.0 (hachages SHA-256 vérifiés contre la source) ; fragment `app/templates/_draco_bar.html` porté à la v0.2.0, le reste de son intégration Flowdeck (URL `draco_urls()`, `onAction`, nonce CSP, `asset_version`) est inchangé.
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ class Settings(BaseSettings):
|
|||||||
# TIKTAK_URL=https://tiktak-dev.dracodev.net
|
# TIKTAK_URL=https://tiktak-dev.dracodev.net
|
||||||
# KANBANTE_URL=https://kanbante-dev.dracodev.net
|
# KANBANTE_URL=https://kanbante-dev.dracodev.net
|
||||||
# SHAARIT_URL=https://shaarit-dev.dracodev.net
|
# SHAARIT_URL=https://shaarit-dev.dracodev.net
|
||||||
|
# HUB_URL=https://hub-dev.dracodev.net
|
||||||
# FLOWDECK_URL=https://flowdeck-dev.dracodev.net
|
# FLOWDECK_URL=https://flowdeck-dev.dracodev.net
|
||||||
draco_sso_url: str = "https://og.dracodev.net" # ObsiGate (émettrice SSO)
|
draco_sso_url: str = "https://og.dracodev.net" # ObsiGate (émettrice SSO)
|
||||||
# ── Coffre ObsiGate, côté consommateur Flowdeck (Phase 4, issue #22) ──
|
# ── Coffre ObsiGate, côté consommateur Flowdeck (Phase 4, issue #22) ──
|
||||||
@@ -170,6 +171,7 @@ class Settings(BaseSettings):
|
|||||||
tiktak_url: str = "https://tiktak.dracodev.net"
|
tiktak_url: str = "https://tiktak.dracodev.net"
|
||||||
kanbante_url: str = "https://kanbante.dracodev.net"
|
kanbante_url: str = "https://kanbante.dracodev.net"
|
||||||
shaarit_url: str = "https://shaarit.dracodev.net"
|
shaarit_url: str = "https://shaarit.dracodev.net"
|
||||||
|
hub_url: str = "https://hub.dracodev.net"
|
||||||
# URL publique de CETTE instance ; vide = dérivée de
|
# URL publique de CETTE instance ; vide = dérivée de
|
||||||
# OAUTH_REDIRECT_URI, puis d'APP_BASE_URL (si non locale), puis du
|
# OAUTH_REDIRECT_URI, puis d'APP_BASE_URL (si non locale), puis du
|
||||||
# nom officiel.
|
# nom officiel.
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ PROD_ORIGINS = frozenset({
|
|||||||
"https://flowdeck.dracodev.net",
|
"https://flowdeck.dracodev.net",
|
||||||
"https://tiktak.dracodev.net",
|
"https://tiktak.dracodev.net",
|
||||||
"https://kanbante.dracodev.net",
|
"https://kanbante.dracodev.net",
|
||||||
|
"https://shaarit.dracodev.net",
|
||||||
})
|
})
|
||||||
|
|
||||||
DEV_ORIGINS = frozenset({
|
DEV_ORIGINS = frozenset({
|
||||||
@@ -50,6 +51,7 @@ DEV_ORIGINS = frozenset({
|
|||||||
"https://flowdeck-dev.dracodev.net",
|
"https://flowdeck-dev.dracodev.net",
|
||||||
"https://tiktak-dev.dracodev.net",
|
"https://tiktak-dev.dracodev.net",
|
||||||
"https://kanbante-dev.dracodev.net",
|
"https://kanbante-dev.dracodev.net",
|
||||||
|
"https://shaarit-dev.dracodev.net",
|
||||||
})
|
})
|
||||||
|
|
||||||
AGENT_ORIGINS = frozenset({
|
AGENT_ORIGINS = frozenset({
|
||||||
@@ -59,7 +61,7 @@ AGENT_ORIGINS = frozenset({
|
|||||||
|
|
||||||
|
|
||||||
# Clés de draco_urls.toutes() qui sont des applications sœurs.
|
# Clés de draco_urls.toutes() qui sont des applications sœurs.
|
||||||
_SOEUR_KEYS = ("obsigate", "flowdeck", "tiktak", "kanbante")
|
_SOEUR_KEYS = ("obsigate", "flowdeck", "tiktak", "kanbante", "shaarit")
|
||||||
|
|
||||||
|
|
||||||
def is_dev_host(hostname: str | None) -> bool:
|
def is_dev_host(hostname: str | None) -> bool:
|
||||||
|
|||||||
@@ -178,10 +178,17 @@ class ContentSecurityPolicyMiddleware(BaseHTTPMiddleware):
|
|||||||
# notifications des applications soeurs depuis cet hôte ;
|
# notifications des applications soeurs depuis cet hôte ;
|
||||||
# leurs origines rejoignent connect-src (même résolution
|
# leurs origines rejoignent connect-src (même résolution
|
||||||
# draco_urls que les liens de la barre et frame-src).
|
# draco_urls que les liens de la barre et frame-src).
|
||||||
|
# Fusion #111 : la recherche fédérée appelle en plus
|
||||||
|
# l'API de ShaarIt depuis la page /recherche — sans son
|
||||||
|
# origine ici, le navigateur bloque l'appel et la section
|
||||||
|
# ShaarIt affichait « Injoignable depuis cet hôte (CORS ou
|
||||||
|
# réseau) » alors que le CORS de ShaarIt était correct
|
||||||
|
# (retour d'usage du 2026-11-04).
|
||||||
_toutes69 = draco_urls.toutes()
|
_toutes69 = draco_urls.toutes()
|
||||||
sisters = " ".join(sorted({
|
sisters = " ".join(sorted({
|
||||||
draco_urls.origine(_toutes69[k])
|
draco_urls.origine(_toutes69[k])
|
||||||
for k in ("obsigate", "flowdeck", "tiktak", "kanbante")
|
for k in ("obsigate", "flowdeck", "tiktak", "kanbante",
|
||||||
|
"shaarit")
|
||||||
} - {""}))
|
} - {""}))
|
||||||
response.headers[self.CSP_HEADER] = self.CSP_VALUE.format(
|
response.headers[self.CSP_HEADER] = self.CSP_VALUE.format(
|
||||||
nonce=nonce, host=host, agent=agent, sisters=sisters
|
nonce=nonce, host=host, agent=agent, sisters=sisters
|
||||||
|
|||||||
@@ -70,5 +70,5 @@ def recherche_page(request: Request, q: str = Query(default="")):
|
|||||||
return HTMLResponse(template.render(
|
return HTMLResponse(template.render(
|
||||||
request=request, q=q.strip(), user=user,
|
request=request, q=q.strip(), user=user,
|
||||||
bases={k: bases[k] for k in ("obsigate", "flowdeck", "tiktak",
|
bases={k: bases[k] for k in ("obsigate", "flowdeck", "tiktak",
|
||||||
"kanbante", "agent")},
|
"kanbante", "shaarit", "agent")},
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ OFFICIELLES = {
|
|||||||
"kanbante": "https://kanbante.dracodev.net",
|
"kanbante": "https://kanbante.dracodev.net",
|
||||||
"shaarit": "https://shaarit.dracodev.net",
|
"shaarit": "https://shaarit.dracodev.net",
|
||||||
"flowdeck": "https://flowdeck.dracodev.net",
|
"flowdeck": "https://flowdeck.dracodev.net",
|
||||||
|
"hub": "https://hub.dracodev.net",
|
||||||
"gitea": "https://git.dracodev.net",
|
"gitea": "https://git.dracodev.net",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,6 +124,7 @@ _SLUGS_SOEURS = {
|
|||||||
"tiktak": "tiktak",
|
"tiktak": "tiktak",
|
||||||
"kanbante": "kanbante",
|
"kanbante": "kanbante",
|
||||||
"shaarit": "shaarit",
|
"shaarit": "shaarit",
|
||||||
|
"hub": "hub",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Hôte de l'agent : agent<SUFFIXE>.dracodev.net (SUFFIXE = ``-dev``…).
|
# Hôte de l'agent : agent<SUFFIXE>.dracodev.net (SUFFIXE = ``-dev``…).
|
||||||
@@ -133,7 +135,7 @@ _MOTIF_HOTE_AGENT = re.compile(r"^agent(-[a-z0-9-]+)?\.dracodev\.net$")
|
|||||||
# de TOUTE l'installation — jamais un suffixe inventé depuis un
|
# de TOUTE l'installation — jamais un suffixe inventé depuis un
|
||||||
# hôte arbitraire : hors motif (autre domaine, LAN, IP), None.
|
# hôte arbitraire : hors motif (autre domaine, LAN, IP), None.
|
||||||
_MOTIF_HOTE_PUBLIC = re.compile(
|
_MOTIF_HOTE_PUBLIC = re.compile(
|
||||||
r"^(og|flowdeck|tiktak|kanbante|agent)(-[a-z0-9-]+)?\.dracodev\.net$")
|
r"^(og|flowdeck|tiktak|kanbante|agent|hub)(-[a-z0-9-]+)?\.dracodev\.net$")
|
||||||
|
|
||||||
# Slugs pour la résolution par l'hôte de la requête (l'agent inclus).
|
# Slugs pour la résolution par l'hôte de la requête (l'agent inclus).
|
||||||
_SLUGS_TOUTES = {**_SLUGS_SOEURS, "agent": "agent"}
|
_SLUGS_TOUTES = {**_SLUGS_SOEURS, "agent": "agent"}
|
||||||
@@ -282,6 +284,14 @@ def shaarit_url() -> str:
|
|||||||
return _resoudre_soeur("SHAARIT_URL", "shaarit_url", "shaarit")
|
return _resoudre_soeur("SHAARIT_URL", "shaarit_url", "shaarit")
|
||||||
|
|
||||||
|
|
||||||
|
def hub_url() -> str:
|
||||||
|
"""Hub (barre v0.3.0, HUB-1) : même résolution que les sœurs —
|
||||||
|
il n'a pas d'onglet, sa base sert les liens du menu compte
|
||||||
|
(Télécharger, À Propos, Aides, Profil) via ``urls.hub`` de la
|
||||||
|
configuration de la barre."""
|
||||||
|
return _resoudre_soeur("HUB_URL", "hub_url", "hub")
|
||||||
|
|
||||||
|
|
||||||
def gitea_url() -> str:
|
def gitea_url() -> str:
|
||||||
return _resoudre("GITEA_URL", "gitea_url", "gitea")
|
return _resoudre("GITEA_URL", "gitea_url", "gitea")
|
||||||
|
|
||||||
@@ -366,6 +376,7 @@ def toutes() -> dict[str, str]:
|
|||||||
"kanbante": kanbante_url(),
|
"kanbante": kanbante_url(),
|
||||||
"shaarit": shaarit_url(),
|
"shaarit": shaarit_url(),
|
||||||
"flowdeck": flowdeck_url(),
|
"flowdeck": flowdeck_url(),
|
||||||
|
"hub": hub_url(),
|
||||||
"gitea": gitea_url(),
|
"gitea": gitea_url(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
{# Barre d'apps commune dracodev — Fusion ACC-1.1 (issue #17).
|
{# Barre d'apps commune dracodev — Fusion ACC-1.1 (issue #17).
|
||||||
Snippet FIGÉ vendangé depuis Projets/dracodev-commons v0.2.0
|
Snippet FIGÉ vendangé depuis Projets/dracodev-commons v0.3.0
|
||||||
(ui/barre-apps/, commit 70c837a de la branche
|
(ui/barre-apps/, branche hub-menu-compte-v0-3-0 de la PR commons
|
||||||
shaarit-phase3-barre-contrats) : ne pas modifier la structure ici ;
|
#9 — HUB-1, fusion #115) : ne pas modifier la structure ici ;
|
||||||
toute évolution passe par une nouvelle version de commons.
|
toute évolution passe par une nouvelle version de commons.
|
||||||
|
v0.3.0 : menu compte — liens Hub Télécharger / À Propos / Aides
|
||||||
|
et Profil devenu lien (base draco_urls().hub passée en urls.hub
|
||||||
|
de la configuration ci-dessous) ; « Déconnexion » reste relayée.
|
||||||
CSS/JS : /static/css/draco-barre.css, /static/js/vendor/draco-barre.js.
|
CSS/JS : /static/css/draco-barre.css, /static/js/vendor/draco-barre.js.
|
||||||
v7.82.0 (dev/prod) : les href des onglets ne sont plus figés —
|
v7.82.0 (dev/prod) : les href des onglets ne sont plus figés —
|
||||||
l'hôte les injecte depuis sa config (draco_urls(), noms officiels
|
l'hôte les injecte depuis sa config (draco_urls(), noms officiels
|
||||||
@@ -105,6 +108,12 @@
|
|||||||
repli si le module ne charge pas). */
|
repli si le module ne charge pas). */
|
||||||
window.DRACO_BAR_CONFIG = {
|
window.DRACO_BAR_CONFIG = {
|
||||||
current: "flowdeck",
|
current: "flowdeck",
|
||||||
|
/* Barre v0.3.0 (HUB-1) : le Hub n'a pas d'onglet — sa base
|
||||||
|
(draco_urls().hub : HUB_URL explicite, hôte de la requête,
|
||||||
|
dérivation par l'agent, défaut officiel) alimente les
|
||||||
|
liens du menu compte (Télécharger, À Propos, Aides,
|
||||||
|
Profil) via urls.hub. */
|
||||||
|
urls: { hub: "{{ draco_urls().hub }}/" },
|
||||||
domain: (function () { try { return localStorage.getItem("draco_domain") || "pro"; } catch (e) { return "pro"; } })(),
|
domain: (function () { try { return localStorage.getItem("draco_domain") || "pro"; } catch (e) { return "pro"; } })(),
|
||||||
counts: {},
|
counts: {},
|
||||||
onAction: function (act, el) {
|
onAction: function (act, el) {
|
||||||
|
|||||||
@@ -16,9 +16,10 @@
|
|||||||
data-base-flowdeck="{{ bases.flowdeck }}"
|
data-base-flowdeck="{{ bases.flowdeck }}"
|
||||||
data-base-tiktak="{{ bases.tiktak }}"
|
data-base-tiktak="{{ bases.tiktak }}"
|
||||||
data-base-kanbante="{{ bases.kanbante }}"
|
data-base-kanbante="{{ bases.kanbante }}"
|
||||||
|
data-base-shaarit="{{ bases.shaarit }}"
|
||||||
data-base-console="{{ bases.agent }}">
|
data-base-console="{{ bases.agent }}">
|
||||||
<h1>Rechercher partout</h1>
|
<h1>Rechercher partout</h1>
|
||||||
<p class="rp-hint">Notes ObsiGate, pages Flowdeck, tâches TikTak, cartes Kanbante — dans l'environnement courant (dev reste dev). « / » ou Ctrl+K pour rechercher, ↑/↓ puis Entrée pour ouvrir un résultat. « Tout voir » bascule vers l'onglet de l'application, sans quitter la page.</p>
|
<p class="rp-hint">Notes ObsiGate, pages Flowdeck, tâches TikTak, cartes Kanbante, bookmarks ShaarIt — dans l'environnement courant (dev reste dev). « / » ou Ctrl+K pour rechercher, ↑/↓ puis Entrée pour ouvrir un résultat. « Tout voir » bascule vers l'onglet de l'application, sans quitter la page.</p>
|
||||||
<div class="rp-form">
|
<div class="rp-form">
|
||||||
<input id="rp-q" class="rp-input" type="search" name="q" value="{{ q }}"
|
<input id="rp-q" class="rp-input" type="search" name="q" value="{{ q }}"
|
||||||
placeholder="Rechercher partout…" autocomplete="off" autofocus>
|
placeholder="Rechercher partout…" autocomplete="off" autofocus>
|
||||||
@@ -29,6 +30,7 @@
|
|||||||
<button type="button" class="rp-tab" role="tab" aria-selected="false" tabindex="-1" data-rp-tab="flowdeck">Flowdeck <span class="rp-tab-count"></span></button>
|
<button type="button" class="rp-tab" role="tab" aria-selected="false" tabindex="-1" data-rp-tab="flowdeck">Flowdeck <span class="rp-tab-count"></span></button>
|
||||||
<button type="button" class="rp-tab" role="tab" aria-selected="false" tabindex="-1" data-rp-tab="tiktak">TikTak <span class="rp-tab-count"></span></button>
|
<button type="button" class="rp-tab" role="tab" aria-selected="false" tabindex="-1" data-rp-tab="tiktak">TikTak <span class="rp-tab-count"></span></button>
|
||||||
<button type="button" class="rp-tab" role="tab" aria-selected="false" tabindex="-1" data-rp-tab="kanbante">Kanbante <span class="rp-tab-count"></span></button>
|
<button type="button" class="rp-tab" role="tab" aria-selected="false" tabindex="-1" data-rp-tab="kanbante">Kanbante <span class="rp-tab-count"></span></button>
|
||||||
|
<button type="button" class="rp-tab" role="tab" aria-selected="false" tabindex="-1" data-rp-tab="shaarit">ShaarIt <span class="rp-tab-count"></span></button>
|
||||||
<button type="button" class="rp-tab" role="tab" aria-selected="false" tabindex="-1" data-rp-tab="console">Console Spark <span class="rp-tab-count"></span></button>
|
<button type="button" class="rp-tab" role="tab" aria-selected="false" tabindex="-1" data-rp-tab="console">Console Spark <span class="rp-tab-count"></span></button>
|
||||||
</nav>
|
</nav>
|
||||||
<section class="rp-section" data-rp-section="obsigate">
|
<section class="rp-section" data-rp-section="obsigate">
|
||||||
@@ -47,6 +49,10 @@
|
|||||||
<div class="rp-head"><h2>Kanbante — cartes</h2><span class="rp-count"></span><a class="rp-all" href="#">Tout voir</a></div>
|
<div class="rp-head"><h2>Kanbante — cartes</h2><span class="rp-count"></span><a class="rp-all" href="#">Tout voir</a></div>
|
||||||
<div class="rp-body"><p class="rp-state">Tapez une recherche ci-dessus.</p></div>
|
<div class="rp-body"><p class="rp-state">Tapez une recherche ci-dessus.</p></div>
|
||||||
</section>
|
</section>
|
||||||
|
<section class="rp-section" data-rp-section="shaarit">
|
||||||
|
<div class="rp-head"><h2>ShaarIt — bookmarks</h2><span class="rp-count"></span><a class="rp-all" href="#">Tout voir</a></div>
|
||||||
|
<div class="rp-body"><p class="rp-state">Tapez une recherche ci-dessus.</p></div>
|
||||||
|
</section>
|
||||||
<section class="rp-section" data-rp-section="console">
|
<section class="rp-section" data-rp-section="console">
|
||||||
<div class="rp-head"><h2>Console Spark — discussions</h2><span class="rp-count"></span><a class="rp-all" href="#">Tout voir</a></div>
|
<div class="rp-head"><h2>Console Spark — discussions</h2><span class="rp-count"></span><a class="rp-all" href="#">Tout voir</a></div>
|
||||||
<div class="rp-body"><p class="rp-state">Tapez une recherche ci-dessus.</p></div>
|
<div class="rp-body"><p class="rp-state">Tapez une recherche ci-dessus.</p></div>
|
||||||
|
|||||||
@@ -20,6 +20,10 @@
|
|||||||
le clair via data-theme="light" héritait quand même du sombre
|
le clair via data-theme="light" héritait quand même du sombre
|
||||||
système (piège constaté dans les copies vendangées de
|
système (piège constaté dans les copies vendangées de
|
||||||
Kanbante/Flowdeck ; les re-vendanges de la v0.2.0 le corrigent).
|
Kanbante/Flowdeck ; les re-vendanges de la v0.2.0 le corrigent).
|
||||||
|
v0.3.0 (HUB-1) : aucun style nouveau — les liens du Hub du menu
|
||||||
|
compte (Télécharger, À Propos, Aides, Profil) réutilisent
|
||||||
|
.draco-menu-item (déjà ancre-compatible : text-decoration none)
|
||||||
|
et le séparateur .draco-menu-sep du menu des applications.
|
||||||
Jetons repris du prototype validé Console v7.2.
|
Jetons repris du prototype validé Console v7.2.
|
||||||
Thème : clair par défaut, sombre via [data-theme="dark"] sur <html>
|
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
|
ou prefers-color-scheme si l'app intégratrice ne force rien
|
||||||
|
|||||||
+39
-9
@@ -2,11 +2,13 @@
|
|||||||
* chantier 2+3 : onglets, « Tout voir », focus).
|
* chantier 2+3 : onglets, « Tout voir », focus).
|
||||||
*
|
*
|
||||||
* Vanilla, sans dépendance. Un seul fichier vendangé tel quel par chaque
|
* Vanilla, sans dépendance. Un seul fichier vendangé tel quel par chaque
|
||||||
* hôte (ObsiGate, Flowdeck, TikTak, Kanbante) : la configuration vient
|
* hôte (ObsiGate, Flowdeck, TikTak, Kanbante, ShaarIt) : la configuration
|
||||||
* des attributs data-* de #recherche, rendus côté serveur par l'hôte.
|
* vient des attributs data-* de #recherche, rendus côté serveur par
|
||||||
|
* l'hôte. Source canonique : dracodev-commons, ui/recherche/
|
||||||
|
* (contrat : contrats/recherche-federee.md).
|
||||||
*
|
*
|
||||||
* Présentation en onglets : Tout, ObsiGate, Flowdeck, TikTak, Kanbante,
|
* Présentation en onglets : Tout, ObsiGate, Flowdeck, TikTak, Kanbante,
|
||||||
* Console Spark. « Tout » = les 4 sections groupées, chaque liste
|
* ShaarIt, Console Spark. « Tout » = les 5 sections groupées, chaque liste
|
||||||
* tronquée à PER_APP_LIMIT ; un onglet application = tous les résultats
|
* tronquée à PER_APP_LIMIT ; un onglet application = tous les résultats
|
||||||
* de cette application (aucune troncature côté client — la requête
|
* de cette application (aucune troncature côté client — la requête
|
||||||
* ObsiGate part avec limit=FULL_LIMIT, les autres API renvoient leur
|
* ObsiGate part avec limit=FULL_LIMIT, les autres API renvoient leur
|
||||||
@@ -46,6 +48,11 @@
|
|||||||
* -> { groups: [{ full_name, rows: [{ issue, column }] }] }
|
* -> { groups: [{ full_name, rows: [{ issue, column }] }] }
|
||||||
* Lien profond : {base}board/<full_name> (la carte n'a pas d'URL propre
|
* Lien profond : {base}board/<full_name> (la carte n'a pas d'URL propre
|
||||||
* adressable — même limite que la page /search native de Kanbante).
|
* adressable — même limite que la page /search native de Kanbante).
|
||||||
|
* - ShaarIt : GET {base}/api/search?q=
|
||||||
|
* -> { links: [{ id, title, url, description, tags, shorturl,
|
||||||
|
* private }] }
|
||||||
|
* Lien profond (contrat liens-profonds) : {base}shaare/<shorturl>.
|
||||||
|
* Recherche native adressable : {base}?q=<q> (page d'accueil).
|
||||||
* - Console Spark : GET {base-console}/api/v1/recherche?q=&limite=
|
* - Console Spark : GET {base-console}/api/v1/recherche?q=&limite=
|
||||||
* (agent-commun, authentifié par le cookie SSO draco_session) ->
|
* (agent-commun, authentifié par le cookie SSO draco_session) ->
|
||||||
* { q, domain, limite_par_type, resultats[], par_type,
|
* { q, domain, limite_par_type, resultats[], par_type,
|
||||||
@@ -87,8 +94,9 @@
|
|||||||
* Authentification cross-origin : les appels sœurs partent avec
|
* Authentification cross-origin : les appels sœurs partent avec
|
||||||
* credentials:"include". Le navigateur envoie alors les cookies de
|
* credentials:"include". Le navigateur envoie alors les cookies de
|
||||||
* chaque origine, dont le cookie SSO partagé draco_session
|
* chaque origine, dont le cookie SSO partagé draco_session
|
||||||
* (Domain=.dracodev.net), accepté par les 4 API de recherche
|
* (Domain=.dracodev.net), accepté par les 5 API de recherche
|
||||||
* (ObsiGate et TikTak nativement, Flowdeck et Kanbante via #68)
|
* (ObsiGate et TikTak nativement, Flowdeck et Kanbante via #68,
|
||||||
|
* ShaarIt via la fédération v0.2.1 de commons)
|
||||||
* et par GET /api/v1/recherche de la console (identity() accepte
|
* et par GET /api/v1/recherche de la console (identity() accepte
|
||||||
* draco_session). Chaque sœur n'autorise en CORS que les origines
|
* draco_session). Chaque sœur n'autorise en CORS que les origines
|
||||||
* sœurs (GET sur l'endpoint de recherche uniquement, pas de "*") ;
|
* sœurs (GET sur l'endpoint de recherche uniquement, pas de "*") ;
|
||||||
@@ -109,12 +117,14 @@
|
|||||||
flowdeck: "https://flowdeck.dracodev.net/",
|
flowdeck: "https://flowdeck.dracodev.net/",
|
||||||
tiktak: "https://tiktak.dracodev.net/",
|
tiktak: "https://tiktak.dracodev.net/",
|
||||||
kanbante: "https://kanbante.dracodev.net/",
|
kanbante: "https://kanbante.dracodev.net/",
|
||||||
|
shaarit: "https://shaarit.dracodev.net/",
|
||||||
};
|
};
|
||||||
var DEV = {
|
var DEV = {
|
||||||
obsigate: "https://og-dev.dracodev.net/",
|
obsigate: "https://og-dev.dracodev.net/",
|
||||||
flowdeck: "https://flowdeck-dev.dracodev.net/",
|
flowdeck: "https://flowdeck-dev.dracodev.net/",
|
||||||
tiktak: "https://tiktak-dev.dracodev.net/",
|
tiktak: "https://tiktak-dev.dracodev.net/",
|
||||||
kanbante: "https://kanbante-dev.dracodev.net/",
|
kanbante: "https://kanbante-dev.dracodev.net/",
|
||||||
|
shaarit: "https://shaarit-dev.dracodev.net/",
|
||||||
};
|
};
|
||||||
var PROD_AGENT = "https://agent.dracodev.net/";
|
var PROD_AGENT = "https://agent.dracodev.net/";
|
||||||
var DEV_AGENT = "https://agent-dev.dracodev.net/";
|
var DEV_AGENT = "https://agent-dev.dracodev.net/";
|
||||||
@@ -125,6 +135,7 @@
|
|||||||
{ key: "flowdeck", label: "Flowdeck" },
|
{ key: "flowdeck", label: "Flowdeck" },
|
||||||
{ key: "tiktak", label: "TikTak" },
|
{ key: "tiktak", label: "TikTak" },
|
||||||
{ key: "kanbante", label: "Kanbante" },
|
{ key: "kanbante", label: "Kanbante" },
|
||||||
|
{ key: "shaarit", label: "ShaarIt" },
|
||||||
];
|
];
|
||||||
var CONSOLE_APP = { key: "console", label: "Console Spark" };
|
var CONSOLE_APP = { key: "console", label: "Console Spark" };
|
||||||
var CONSOLE_TYPE_LABELS = {
|
var CONSOLE_TYPE_LABELS = {
|
||||||
@@ -145,7 +156,7 @@
|
|||||||
planification: "/?section=planifie",
|
planification: "/?section=planifie",
|
||||||
identite: "/?section=identites",
|
identite: "/?section=identites",
|
||||||
};
|
};
|
||||||
var TABS = ["all", "obsigate", "flowdeck", "tiktak", "kanbante", "console"];
|
var TABS = ["all", "obsigate", "flowdeck", "tiktak", "kanbante", "shaarit", "console"];
|
||||||
var COLUMN_LABELS = {
|
var COLUMN_LABELS = {
|
||||||
backlog: "Backlog",
|
backlog: "Backlog",
|
||||||
in_progress: "En cours",
|
in_progress: "En cours",
|
||||||
@@ -227,7 +238,7 @@
|
|||||||
return TABS.indexOf(v) !== -1 ? v : "all";
|
return TABS.indexOf(v) !== -1 ? v : "all";
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sections visibles pour un onglet. « Tout » = les 4 sections
|
/* Sections visibles pour un onglet. « Tout » = les 5 sections
|
||||||
* groupées (vue #68) ; la console n'y figure pas (voir en-tête). */
|
* groupées (vue #68) ; la console n'y figure pas (voir en-tête). */
|
||||||
function visibleKeys(tab) {
|
function visibleKeys(tab) {
|
||||||
tab = parseTab(tab);
|
tab = parseTab(tab);
|
||||||
@@ -256,7 +267,7 @@
|
|||||||
* Chargement -> "…", ok/vide -> total ("0" honnête),
|
* Chargement -> "…", ok/vide -> total ("0" honnête),
|
||||||
* erreur/indisponible/inactif -> "" (pas de faux 0).
|
* erreur/indisponible/inactif -> "" (pas de faux 0).
|
||||||
* Tout : "…" tant qu'une section charge, sinon somme des totaux
|
* Tout : "…" tant qu'une section charge, sinon somme des totaux
|
||||||
* connus, "" si les 4 sections sont en erreur/inactives. */
|
* connus, "" si les 5 sections sont en erreur/inactives. */
|
||||||
function tabCounts(states) {
|
function tabCounts(states) {
|
||||||
states = states || {};
|
states = states || {};
|
||||||
var out = {};
|
var out = {};
|
||||||
@@ -367,6 +378,22 @@
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (app === "shaarit") {
|
||||||
|
return cut(data.links || []).map(function (l) {
|
||||||
|
var tags = (l.tags || []).filter(function (t) { return !!t; });
|
||||||
|
var ctx = "Bookmark";
|
||||||
|
if (l.private) ctx += " · privé";
|
||||||
|
if (tags.length) ctx += " · " + tags.join(", ");
|
||||||
|
return {
|
||||||
|
title: l.title || l.url || "Sans titre",
|
||||||
|
snippet: snippetOf(l.description || ""),
|
||||||
|
context: ctx,
|
||||||
|
/* Lien profond canonique (contrats/liens-profonds.md) ;
|
||||||
|
* sans shorturl, repli sur l'URL du bookmark lui-même. */
|
||||||
|
url: l.shorturl ? base + "shaare/" + l.shorturl : l.url || null,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,13 +482,16 @@
|
|||||||
if (app === "tiktak" || app === "kanbante") {
|
if (app === "tiktak" || app === "kanbante") {
|
||||||
return base + "search?q=" + encodeURIComponent(q || "");
|
return base + "search?q=" + encodeURIComponent(q || "");
|
||||||
}
|
}
|
||||||
|
if (app === "shaarit") {
|
||||||
|
return base + "?q=" + encodeURIComponent(q || "");
|
||||||
|
}
|
||||||
/* ObsiGate et Flowdeck n'ont pas de page de recherche adressable :
|
/* ObsiGate et Flowdeck n'ont pas de page de recherche adressable :
|
||||||
* leur recherche native vit dans la palette (Ctrl+K) de l'app. */
|
* leur recherche native vit dans la palette (Ctrl+K) de l'app. */
|
||||||
return base;
|
return base;
|
||||||
}
|
}
|
||||||
|
|
||||||
function nativeSearchLabel(app) {
|
function nativeSearchLabel(app) {
|
||||||
if (app === "tiktak" || app === "kanbante") {
|
if (app === "tiktak" || app === "kanbante" || app === "shaarit") {
|
||||||
return "Ouvrir la recherche dans " + appLabel(app);
|
return "Ouvrir la recherche dans " + appLabel(app);
|
||||||
}
|
}
|
||||||
return "Ouvrir " + appLabel(app) + " (recherche via la palette Ctrl+K)";
|
return "Ouvrir " + appLabel(app) + " (recherche via la palette Ctrl+K)";
|
||||||
|
|||||||
Vendored
+86
-15
@@ -14,6 +14,21 @@
|
|||||||
* contrat que les autres apps), entrée APP_LABELS ; l'onglet
|
* contrat que les autres apps), entrée APP_LABELS ; l'onglet
|
||||||
* (data-app="shaarit", après Kanbante, avant agent) est repris
|
* (data-app="shaarit", après Kanbante, avant agent) est repris
|
||||||
* automatiquement par la construction du menu de la marque.
|
* automatiquement par la construction du menu de la marque.
|
||||||
|
* v0.3.0 (carte HUB-1, fusion #115) : le Hub, point central de la
|
||||||
|
* suite — nouvelle clé d'URL « hub » (jeton {{URL_HUB}} côté hôte,
|
||||||
|
* DRACO_BAR_CONFIG.urls.hub), défaut officiel
|
||||||
|
* https://hub.dracodev.net/, dérivation -dev hub-dev comme les
|
||||||
|
* sœurs. Le menu compte gagne trois vrais liens (patron
|
||||||
|
* buildAppsMenu : <a href>, classe draco-menu-item) — Télécharger
|
||||||
|
* → <hub>/telecharger, À Propos → <hub>/a-propos, Aides →
|
||||||
|
* <hub>/aide — et « Profil » devient lui aussi un lien, vers
|
||||||
|
* <hub>/profil (il n'est plus relayé aux hôtes : un seul Profil,
|
||||||
|
* celui du Hub). « Déconnexion » reste l'action relayée
|
||||||
|
* (data-compte-act="deconnexion" / onAction), inchangée.
|
||||||
|
* refreshMenuHrefs() resynchronise aussi les liens du Hub à
|
||||||
|
* l'ouverture des menus. Le Hub n'a pas d'onglet dans la barre :
|
||||||
|
* comme spark, sa clé est résolue et exposée via DRACO_BAR.urls,
|
||||||
|
* sans étape « href rendu » (voir « URL » ci-dessous).
|
||||||
*
|
*
|
||||||
* Aucun gestionnaire inline : un seul écouteur délégué sur le document
|
* 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
|
* (patron validé par le prototype — les onclick du HTML généré sont
|
||||||
@@ -33,7 +48,8 @@
|
|||||||
* kanbante: "https://kanbante.dracodev.net/",
|
* kanbante: "https://kanbante.dracodev.net/",
|
||||||
* shaarit: "https://shaarit.dracodev.net/",
|
* shaarit: "https://shaarit.dracodev.net/",
|
||||||
* agent: "https://agent.dracodev.net/",
|
* agent: "https://agent.dracodev.net/",
|
||||||
* spark: "https://spark.dracodev.net/"
|
* spark: "https://spark.dracodev.net/",
|
||||||
|
* hub: "https://hub.dracodev.net/"
|
||||||
* },
|
* },
|
||||||
* onAction: function (act, el) { … } // l'app relaie les actions
|
* onAction: function (act, el) { … } // l'app relaie les actions
|
||||||
* } // qu'elle ne connaît pas
|
* } // qu'elle ne connaît pas
|
||||||
@@ -67,6 +83,12 @@
|
|||||||
* la marque et DRACO_BAR.urls. Même résolution pour shaarit (v0.2.0) :
|
* la marque et DRACO_BAR.urls. Même résolution pour shaarit (v0.2.0) :
|
||||||
* explicite > dérivation -dev shaarit-dev > href rendu
|
* explicite > dérivation -dev shaarit-dev > href rendu
|
||||||
* ({{URL_SHAARIT}}) > défaut officiel.
|
* ({{URL_SHAARIT}}) > défaut officiel.
|
||||||
|
* Cas hub (v0.3.0) : PAS d'onglet, donc pas d'étape « href rendu » —
|
||||||
|
* explicite (DRACO_BAR_CONFIG.urls.hub, alimenté par le jeton
|
||||||
|
* {{URL_HUB}} remplacé côté serveur par l'hôte) > dérivation -dev
|
||||||
|
* hub-dev sur hôte -dev > défaut officiel
|
||||||
|
* https://hub.dracodev.net/. La base résolue sert les quatre liens
|
||||||
|
* du menu compte (telecharger, a-propos, aide, profil).
|
||||||
* Actions émises (data-act) : rail, palette, domaine (data-dom),
|
* Actions émises (data-act) : rail, palette, domaine (data-dom),
|
||||||
* alertes, spark, rappel-entete. L'app intégratrice décide de chacune
|
* alertes, spark, rappel-entete. L'app intégratrice décide de chacune
|
||||||
* (ouvrir la palette, le panneau Spark, changer de domaine…).
|
* (ouvrir la palette, le panneau Spark, changer de domaine…).
|
||||||
@@ -141,6 +163,14 @@
|
|||||||
* mais le menu affiche l'identité même sans gestionnaire hôte.
|
* mais le menu affiche l'identité même sans gestionnaire hôte.
|
||||||
* Accessibilité : vrais <button>, aria-haspopup / aria-expanded,
|
* Accessibilité : vrais <button>, aria-haspopup / aria-expanded,
|
||||||
* role="menu" / "menuitem", fermeture par Échap et clic extérieur.
|
* role="menu" / "menuitem", fermeture par Échap et clic extérieur.
|
||||||
|
* Menu compte (v0.3.0) — Profil n'est PLUS relayé : c'est un lien
|
||||||
|
* vers <hub>/profil, comme Télécharger / À Propos / Aides (liens
|
||||||
|
* Hub, data-hub-link, href resynchronisés à chaque ouverture via
|
||||||
|
* refreshCompteHrefs / refreshMenuHrefs). Seule « Déconnexion »
|
||||||
|
* reste relayée à l'hôte (data-compte-act="deconnexion"), inchangée.
|
||||||
|
* Les hôtes qui traitaient onAction("profil") (Kanbante → /profile)
|
||||||
|
* voient ce comportement remplacé par le lien Hub — décision du
|
||||||
|
* plan Hub : un seul Profil, celui du Hub.
|
||||||
*/
|
*/
|
||||||
(function () {
|
(function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
@@ -155,7 +185,8 @@
|
|||||||
kanbante: "https://kanbante.dracodev.net/",
|
kanbante: "https://kanbante.dracodev.net/",
|
||||||
shaarit: "https://shaarit.dracodev.net/",
|
shaarit: "https://shaarit.dracodev.net/",
|
||||||
agent: "https://agent.dracodev.net/",
|
agent: "https://agent.dracodev.net/",
|
||||||
spark: "https://spark.dracodev.net/"
|
spark: "https://spark.dracodev.net/",
|
||||||
|
hub: "https://hub.dracodev.net/"
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Dérivation -dev (v0.1.6) — appliquée UNIQUEMENT quand l'hôte courant
|
/* Dérivation -dev (v0.1.6) — appliquée UNIQUEMENT quand l'hôte courant
|
||||||
@@ -168,7 +199,8 @@
|
|||||||
tiktak: "https://tiktak-dev.dracodev.net/",
|
tiktak: "https://tiktak-dev.dracodev.net/",
|
||||||
kanbante: "https://kanbante-dev.dracodev.net/",
|
kanbante: "https://kanbante-dev.dracodev.net/",
|
||||||
shaarit: "https://shaarit-dev.dracodev.net/",
|
shaarit: "https://shaarit-dev.dracodev.net/",
|
||||||
agent: "https://agent-dev.dracodev.net/"
|
agent: "https://agent-dev.dracodev.net/",
|
||||||
|
hub: "https://hub-dev.dracodev.net/"
|
||||||
};
|
};
|
||||||
|
|
||||||
var APP_LABELS = {
|
var APP_LABELS = {
|
||||||
@@ -307,6 +339,28 @@
|
|||||||
menu.querySelectorAll(".draco-apps-item").forEach(function (a) {
|
menu.querySelectorAll(".draco-apps-item").forEach(function (a) {
|
||||||
if (a.dataset.app && resolvedUrls[a.dataset.app]) a.setAttribute("href", resolvedUrls[a.dataset.app]);
|
if (a.dataset.app && resolvedUrls[a.dataset.app]) a.setAttribute("href", resolvedUrls[a.dataset.app]);
|
||||||
});
|
});
|
||||||
|
/* v0.3.0 : les liens du Hub (menu compte) sont resynchronisés
|
||||||
|
* eux aussi à l'ouverture d'un menu, pour refléter setUrls et
|
||||||
|
* la dérivation -dev même s'ils ont été construits avant. */
|
||||||
|
refreshCompteHrefs();
|
||||||
|
}
|
||||||
|
/* Liens du Hub dans le menu compte (v0.3.0) : chemin → libellé.
|
||||||
|
* « Profil » est le quatrième lien (vers <hub>/profil) ; il
|
||||||
|
* remplace l'ancien item relayé « profil ». */
|
||||||
|
var HUB_LINKS = [
|
||||||
|
["telecharger", "Télécharger"],
|
||||||
|
["a-propos", "À Propos"],
|
||||||
|
["aide", "Aides"],
|
||||||
|
["profil", "Profil"]
|
||||||
|
];
|
||||||
|
function hubHref(path) {
|
||||||
|
return withSlash(resolvedUrls.hub || DEFAULT_URLS.hub) + path;
|
||||||
|
}
|
||||||
|
function refreshCompteHrefs() {
|
||||||
|
if (!compteMenu) return;
|
||||||
|
compteMenu.querySelectorAll("[data-hub-link]").forEach(function (a) {
|
||||||
|
a.setAttribute("href", hubHref(a.getAttribute("data-hub-link")));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
function togglePopup(menu, btn, force) {
|
function togglePopup(menu, btn, force) {
|
||||||
if (!menu) return;
|
if (!menu) return;
|
||||||
@@ -429,10 +483,14 @@
|
|||||||
if (!appsMenu.hidden) lastOpener = brand;
|
if (!appsMenu.hidden) lastOpener = brand;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Menu compte (v0.1.6) — en-tête d'identité + Profil / Déconnexion
|
/* Menu compte (v0.1.6 ; v0.3.0 pour les liens) — en-tête
|
||||||
* relayés à l'hôte. L'en-tête est rafraîchi à chaque ouverture, pour
|
* d'identité, puis les liens du Hub (Télécharger, À Propos,
|
||||||
* refléter un title posé après coup par l'hôte (ex. ObsiGate, qui
|
* Aides, Profil — vrais <a href>, même patron que le menu des
|
||||||
* remplit l'avatar depuis /api/auth/me après chargement). */
|
* applications), un séparateur, puis « Déconnexion » relayée à
|
||||||
|
* l'hôte. L'en-tête et les href sont rafraîchis à 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) et la résolution d'URL la plus récente. */
|
||||||
function buildCompteMenu() {
|
function buildCompteMenu() {
|
||||||
if (!bar || !avatarBtn || compteMenu) return;
|
if (!bar || !avatarBtn || compteMenu) return;
|
||||||
compteMenu = document.createElement("div");
|
compteMenu = document.createElement("div");
|
||||||
@@ -443,15 +501,26 @@
|
|||||||
var head = document.createElement("div");
|
var head = document.createElement("div");
|
||||||
head.className = "draco-compte-nom";
|
head.className = "draco-compte-nom";
|
||||||
compteMenu.appendChild(head);
|
compteMenu.appendChild(head);
|
||||||
[["profil", "Profil"], ["deconnexion", "Déconnexion"]].forEach(function (pair) {
|
HUB_LINKS.forEach(function (pair) {
|
||||||
var b = document.createElement("button");
|
var a = document.createElement("a");
|
||||||
b.type = "button";
|
a.className = "draco-menu-item";
|
||||||
b.className = "draco-menu-item";
|
a.href = hubHref(pair[0]);
|
||||||
b.setAttribute("data-compte-act", pair[0]);
|
a.setAttribute("data-hub-link", pair[0]);
|
||||||
b.setAttribute("role", "menuitem");
|
a.setAttribute("role", "menuitem");
|
||||||
b.textContent = pair[1];
|
a.textContent = pair[1];
|
||||||
compteMenu.appendChild(b);
|
compteMenu.appendChild(a);
|
||||||
});
|
});
|
||||||
|
var sep = document.createElement("div");
|
||||||
|
sep.className = "draco-menu-sep";
|
||||||
|
sep.setAttribute("aria-hidden", "true");
|
||||||
|
compteMenu.appendChild(sep);
|
||||||
|
var b = document.createElement("button");
|
||||||
|
b.type = "button";
|
||||||
|
b.className = "draco-menu-item";
|
||||||
|
b.setAttribute("data-compte-act", "deconnexion");
|
||||||
|
b.setAttribute("role", "menuitem");
|
||||||
|
b.textContent = "Déconnexion";
|
||||||
|
compteMenu.appendChild(b);
|
||||||
bar.appendChild(compteMenu);
|
bar.appendChild(compteMenu);
|
||||||
}
|
}
|
||||||
function refreshCompteMenu() {
|
function refreshCompteMenu() {
|
||||||
@@ -459,6 +528,7 @@
|
|||||||
var head = compteMenu.querySelector(".draco-compte-nom");
|
var head = compteMenu.querySelector(".draco-compte-nom");
|
||||||
var name = displayName();
|
var name = displayName();
|
||||||
if (head) head.textContent = name || "Compte";
|
if (head) head.textContent = name || "Compte";
|
||||||
|
refreshCompteHrefs();
|
||||||
}
|
}
|
||||||
function toggleCompteMenu(force) {
|
function toggleCompteMenu(force) {
|
||||||
buildCompteMenu();
|
buildCompteMenu();
|
||||||
@@ -555,6 +625,7 @@
|
|||||||
resolvedUrls = resolveUrls();
|
resolvedUrls = resolveUrls();
|
||||||
window.DRACO_BAR.urls = resolvedUrls;
|
window.DRACO_BAR.urls = resolvedUrls;
|
||||||
applyUrls(resolvedUrls);
|
applyUrls(resolvedUrls);
|
||||||
|
refreshCompteHrefs();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+21
-5
@@ -61,8 +61,18 @@ items = rp.normalize("kanbante", { groups: [{ full_name: "Projets/site", rows: [
|
|||||||
ok(items[0].title === "#12 Bug" && items[0].url === "https://kanbante-dev.dracodev.net/board/Projets/site", "lien tableau Kanbante");
|
ok(items[0].title === "#12 Bug" && items[0].url === "https://kanbante-dev.dracodev.net/board/Projets/site", "lien tableau Kanbante");
|
||||||
ok(items[0].context === "Projets/site · En cours", "contexte Kanbante");
|
ok(items[0].context === "Projets/site · En cours", "contexte Kanbante");
|
||||||
|
|
||||||
|
/* normalize ShaarIt (commons v0.2.1) : lien profond shaare/<shorturl>,
|
||||||
|
* contexte privé + tags, repli sur l'URL du bookmark sans shorturl */
|
||||||
|
items = rp.normalize("shaarit", { links: [{ title: "Yandex", url: "https://yandex.com", description: "moteur", tags: ["recherche", "web"], shorturl: "xYz9", private: true }] }, "https://shaarit-dev.dracodev.net/", "recherche");
|
||||||
|
ok(items[0].title === "Yandex" && items[0].url === "https://shaarit-dev.dracodev.net/shaare/xYz9", "lien profond ShaarIt (shaare/<shorturl>)");
|
||||||
|
ok(items[0].context === "Bookmark · privé · recherche, web", "contexte ShaarIt (privé + tags)");
|
||||||
|
ok(items[0].snippet === "moteur", "extrait ShaarIt = description");
|
||||||
|
items = rp.normalize("shaarit", { links: [{ title: "", url: "https://exemple.fr", tags: [], shorturl: "" }] }, "https://shaarit.dracodev.net/", "x");
|
||||||
|
ok(items[0].url === "https://exemple.fr" && items[0].title === "https://exemple.fr", "ShaarIt sans shorturl : repli URL + titre");
|
||||||
|
|
||||||
/* nativeSearchUrl */
|
/* nativeSearchUrl */
|
||||||
ok(rp.nativeSearchUrl("tiktak", "https://tiktak-dev.dracodev.net/", "q") === "https://tiktak-dev.dracodev.net/search?q=q", "repli TikTak");
|
ok(rp.nativeSearchUrl("tiktak", "https://tiktak-dev.dracodev.net/", "q") === "https://tiktak-dev.dracodev.net/search?q=q", "repli TikTak");
|
||||||
|
ok(rp.nativeSearchUrl("shaarit", "https://shaarit-dev.dracodev.net/", "q") === "https://shaarit-dev.dracodev.net/?q=q", "repli ShaarIt = accueil ?q=");
|
||||||
ok(rp.nativeSearchUrl("obsigate", "https://og-dev.dracodev.net/", "q") === "https://og-dev.dracodev.net/", "repli ObsiGate = accueil (palette)");
|
ok(rp.nativeSearchUrl("obsigate", "https://og-dev.dracodev.net/", "q") === "https://og-dev.dracodev.net/", "repli ObsiGate = accueil (palette)");
|
||||||
|
|
||||||
|
|
||||||
@@ -70,15 +80,17 @@ ok(rp.nativeSearchUrl("obsigate", "https://og-dev.dracodev.net/", "q") === "http
|
|||||||
|
|
||||||
/* parseTab */
|
/* parseTab */
|
||||||
ok(rp.parseTab("obsigate") === "obsigate", "onglet valide conservé");
|
ok(rp.parseTab("obsigate") === "obsigate", "onglet valide conservé");
|
||||||
|
ok(rp.parseTab("shaarit") === "shaarit", "onglet shaarit valide");
|
||||||
ok(rp.parseTab("console") === "console", "onglet console valide");
|
ok(rp.parseTab("console") === "console", "onglet console valide");
|
||||||
ok(rp.parseTab("bidon") === "all", "onglet inconnu -> Tout");
|
ok(rp.parseTab("bidon") === "all", "onglet inconnu -> Tout");
|
||||||
ok(rp.parseTab(undefined) === "all", "onglet absent -> Tout");
|
ok(rp.parseTab(undefined) === "all", "onglet absent -> Tout");
|
||||||
|
|
||||||
/* visibleKeys : Tout = les 4 sections groupées, sans la console */
|
/* visibleKeys : Tout = les 5 sections groupées, sans la console */
|
||||||
ok(JSON.stringify(rp.visibleKeys("all")) === JSON.stringify(["obsigate", "flowdeck", "tiktak", "kanbante"]), "Tout = 4 sections, pas la console");
|
ok(JSON.stringify(rp.visibleKeys("all")) === JSON.stringify(["obsigate", "flowdeck", "tiktak", "kanbante", "shaarit"]), "Tout = 5 sections, pas la console");
|
||||||
ok(JSON.stringify(rp.visibleKeys("tiktak")) === JSON.stringify(["tiktak"]), "onglet app = sa seule section");
|
ok(JSON.stringify(rp.visibleKeys("tiktak")) === JSON.stringify(["tiktak"]), "onglet app = sa seule section");
|
||||||
|
ok(JSON.stringify(rp.visibleKeys("shaarit")) === JSON.stringify(["shaarit"]), "onglet shaarit = sa seule section");
|
||||||
ok(JSON.stringify(rp.visibleKeys("console")) === JSON.stringify(["console"]), "onglet console = sa seule section");
|
ok(JSON.stringify(rp.visibleKeys("console")) === JSON.stringify(["console"]), "onglet console = sa seule section");
|
||||||
ok(JSON.stringify(rp.visibleKeys("bidon")) === JSON.stringify(["obsigate", "flowdeck", "tiktak", "kanbante"]), "onglet inconnu -> Tout");
|
ok(JSON.stringify(rp.visibleKeys("bidon")) === JSON.stringify(["obsigate", "flowdeck", "tiktak", "kanbante", "shaarit"]), "onglet inconnu -> Tout");
|
||||||
|
|
||||||
/* pageItems : troncature Tout (8), complet dans l'onglet de l'app */
|
/* pageItems : troncature Tout (8), complet dans l'onglet de l'app */
|
||||||
let douze = Array.from({ length: 12 }, (_, i) => ({ title: "T" + i }));
|
let douze = Array.from({ length: 12 }, (_, i) => ({ title: "T" + i }));
|
||||||
@@ -91,9 +103,11 @@ let c = rp.tabCounts({
|
|||||||
flowdeck: { status: "ok", total: 3 },
|
flowdeck: { status: "ok", total: 3 },
|
||||||
tiktak: { status: "empty", total: 0 },
|
tiktak: { status: "empty", total: 0 },
|
||||||
kanbante: { status: "error", total: 0 },
|
kanbante: { status: "error", total: 0 },
|
||||||
|
shaarit: { status: "ok", total: 2 },
|
||||||
console: { status: "idle", total: 0 },
|
console: { status: "idle", total: 0 },
|
||||||
});
|
});
|
||||||
ok(c.obsigate === "…", "compteur … pendant le chargement");
|
ok(c.obsigate === "…", "compteur … pendant le chargement");
|
||||||
|
ok(c.shaarit === "2", "compteur shaarit");
|
||||||
ok(c.flowdeck === "3" && c.tiktak === "0", "compteur total, vide honnête à 0");
|
ok(c.flowdeck === "3" && c.tiktak === "0", "compteur total, vide honnête à 0");
|
||||||
ok(c.kanbante === "" && c.console === "", "erreur/inactif : pas de faux 0");
|
ok(c.kanbante === "" && c.console === "", "erreur/inactif : pas de faux 0");
|
||||||
ok(c.all === "…", "Tout = … tant qu'une section charge");
|
ok(c.all === "…", "Tout = … tant qu'une section charge");
|
||||||
@@ -102,18 +116,20 @@ c = rp.tabCounts({
|
|||||||
flowdeck: { status: "ok", total: 2 },
|
flowdeck: { status: "ok", total: 2 },
|
||||||
tiktak: { status: "empty", total: 0 },
|
tiktak: { status: "empty", total: 0 },
|
||||||
kanbante: { status: "error", total: 0 },
|
kanbante: { status: "error", total: 0 },
|
||||||
|
shaarit: { status: "ok", total: 1 },
|
||||||
console: { status: "ok", total: 4 },
|
console: { status: "ok", total: 4 },
|
||||||
});
|
});
|
||||||
ok(c.all === "7", "Tout = somme des totaux connus");
|
ok(c.all === "8", "Tout = somme des totaux connus (5 sections)");
|
||||||
ok(c.console === "4", "compteur console");
|
ok(c.console === "4", "compteur console");
|
||||||
c = rp.tabCounts({
|
c = rp.tabCounts({
|
||||||
obsigate: { status: "error", total: 0 },
|
obsigate: { status: "error", total: 0 },
|
||||||
flowdeck: { status: "error", total: 0 },
|
flowdeck: { status: "error", total: 0 },
|
||||||
tiktak: { status: "error", total: 0 },
|
tiktak: { status: "error", total: 0 },
|
||||||
kanbante: { status: "error", total: 0 },
|
kanbante: { status: "error", total: 0 },
|
||||||
|
shaarit: { status: "error", total: 0 },
|
||||||
console: { status: "idle", total: 0 },
|
console: { status: "idle", total: 0 },
|
||||||
});
|
});
|
||||||
ok(c.all === "", "Tout vide si les 4 sections sont en erreur");
|
ok(c.all === "", "Tout vide si les 5 sections sont en erreur");
|
||||||
|
|
||||||
/* buildSearchUrl : état onglet partageable */
|
/* buildSearchUrl : état onglet partageable */
|
||||||
ok(rp.buildSearchUrl("café", "tiktak") === "?q=caf%C3%A9&tab=tiktak", "URL q+onglet");
|
ok(rp.buildSearchUrl("café", "tiktak") === "?q=caf%C3%A9&tab=tiktak", "URL q+onglet");
|
||||||
|
|||||||
@@ -53,6 +53,25 @@ def test_barre_v020_onglet_shaarit_et_garde_data_theme():
|
|||||||
assert 'shaarit: "https://shaarit-dev.dracodev.net/"' in js
|
assert 'shaarit: "https://shaarit-dev.dracodev.net/"' in js
|
||||||
|
|
||||||
|
|
||||||
|
def test_barre_v030_liens_hub_menu_compte():
|
||||||
|
"""Barre v0.3.0 (commons, HUB-1 / fusion #115) : JS vendangé avec
|
||||||
|
la clé hub (défaut + dérivation hub-dev) et les liens du menu
|
||||||
|
compte ; le gabarit passe draco_urls().hub en urls.hub."""
|
||||||
|
js = (ROOT / "static/js/vendor/draco-barre.js").read_text()
|
||||||
|
assert 'hub: "https://hub.dracodev.net/"' in js
|
||||||
|
assert 'hub: "https://hub-dev.dracodev.net/"' in js
|
||||||
|
assert '"telecharger", "Télécharger"' in js
|
||||||
|
assert '"a-propos", "À Propos"' in js
|
||||||
|
assert '"aide", "Aides"' in js
|
||||||
|
assert '"profil", "Profil"' in js
|
||||||
|
assert "data-hub-link" in js
|
||||||
|
assert 'data-compte-act", "profil"' not in js
|
||||||
|
assert 'data-compte-act", "deconnexion"' in js
|
||||||
|
gabarit = (ROOT / "app/templates/_draco_bar.html").read_text()
|
||||||
|
rendu = _hors_commentaires(gabarit)
|
||||||
|
assert 'urls: { hub: "{{ draco_urls().hub }}/" }' in rendu
|
||||||
|
|
||||||
|
|
||||||
def test_barre_intacte_apres_suppression():
|
def test_barre_intacte_apres_suppression():
|
||||||
"""Le 5e onglet Console Spark et la marque survivent a la suppression."""
|
"""Le 5e onglet Console Spark et la marque survivent a la suppression."""
|
||||||
gabarit = (ROOT / "app/templates/_draco_bar.html").read_text()
|
gabarit = (ROOT / "app/templates/_draco_bar.html").read_text()
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import pytest
|
|||||||
from app.services import draco_urls
|
from app.services import draco_urls
|
||||||
|
|
||||||
VARS = ("DRACO_SSO_URL", "DRACO_AGENT_URL", "TIKTAK_URL", "KANBANTE_URL",
|
VARS = ("DRACO_SSO_URL", "DRACO_AGENT_URL", "TIKTAK_URL", "KANBANTE_URL",
|
||||||
"SHAARIT_URL", "FLOWDECK_URL", "OAUTH_REDIRECT_URI",
|
"SHAARIT_URL", "HUB_URL", "FLOWDECK_URL", "OAUTH_REDIRECT_URI",
|
||||||
"AGENT_EVENEMENTS_URL")
|
"AGENT_EVENEMENTS_URL")
|
||||||
|
|
||||||
|
|
||||||
@@ -63,6 +63,24 @@ def test_shaarit_defaut_derivation_et_explicite(monkeypatch, avec_hote):
|
|||||||
assert draco_urls.shaarit_url() == "https://bm.example.net"
|
assert draco_urls.shaarit_url() == "https://bm.example.net"
|
||||||
|
|
||||||
|
|
||||||
|
def test_hub_defaut_derivation_et_explicite(monkeypatch, avec_hote):
|
||||||
|
# Barre v0.3.0 (HUB-1) : le Hub suit le même mécanisme que les
|
||||||
|
# autres sœurs — défaut officiel, dérivation -dev (agent connu
|
||||||
|
# puis hôte de la requête), explicite prioritaire. Pas d'onglet :
|
||||||
|
# sa base sert les liens du menu compte via urls.hub.
|
||||||
|
assert draco_urls.hub_url() == "https://hub.dracodev.net"
|
||||||
|
assert draco_urls.toutes()["hub"] == "https://hub.dracodev.net"
|
||||||
|
monkeypatch.setenv(
|
||||||
|
"AGENT_EVENEMENTS_URL",
|
||||||
|
"https://agent-dev.dracodev.net/api/v1/evenements")
|
||||||
|
assert draco_urls.hub_url() == "https://hub-dev.dracodev.net"
|
||||||
|
monkeypatch.delenv("AGENT_EVENEMENTS_URL")
|
||||||
|
avec_hote("flowdeck-dev.dracodev.net")
|
||||||
|
assert draco_urls.hub_url() == "https://hub-dev.dracodev.net"
|
||||||
|
monkeypatch.setenv("HUB_URL", "https://hub.example.net/")
|
||||||
|
assert draco_urls.hub_url() == "https://hub.example.net"
|
||||||
|
|
||||||
|
|
||||||
def test_flowdeck_url_derivée_oauth_redirect(monkeypatch):
|
def test_flowdeck_url_derivée_oauth_redirect(monkeypatch):
|
||||||
monkeypatch.setenv(
|
monkeypatch.setenv(
|
||||||
"OAUTH_REDIRECT_URI", "https://flowdeck-dev.dracodev.net/auth/callback")
|
"OAUTH_REDIRECT_URI", "https://flowdeck-dev.dracodev.net/auth/callback")
|
||||||
|
|||||||
@@ -38,17 +38,20 @@ def test_origin_of():
|
|||||||
|
|
||||||
|
|
||||||
def test_sister_origins_dev():
|
def test_sister_origins_dev():
|
||||||
# Correctif 2026-10-28 : 10 origines officielles (8 sœurs + 2 agent), prod ET dev,
|
# Correctif 2026-10-28 : origines officielles prod ET dev, quelle
|
||||||
# quelle que soit la résolution.
|
# que soit la résolution ; ShaarIt ajoutée par la fédération
|
||||||
|
# v0.2.1 (12 origines : 10 sœurs + 2 agent).
|
||||||
assert sister_origins(RESOLVED_DEV) == {
|
assert sister_origins(RESOLVED_DEV) == {
|
||||||
"https://og.dracodev.net",
|
"https://og.dracodev.net",
|
||||||
"https://flowdeck.dracodev.net",
|
"https://flowdeck.dracodev.net",
|
||||||
"https://tiktak.dracodev.net",
|
"https://tiktak.dracodev.net",
|
||||||
"https://kanbante.dracodev.net",
|
"https://kanbante.dracodev.net",
|
||||||
|
"https://shaarit.dracodev.net",
|
||||||
"https://og-dev.dracodev.net",
|
"https://og-dev.dracodev.net",
|
||||||
"https://flowdeck-dev.dracodev.net",
|
"https://flowdeck-dev.dracodev.net",
|
||||||
"https://tiktak-dev.dracodev.net",
|
"https://tiktak-dev.dracodev.net",
|
||||||
"https://kanbante-dev.dracodev.net",
|
"https://kanbante-dev.dracodev.net",
|
||||||
|
"https://shaarit-dev.dracodev.net",
|
||||||
"https://agent.dracodev.net",
|
"https://agent.dracodev.net",
|
||||||
"https://agent-dev.dracodev.net",
|
"https://agent-dev.dracodev.net",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
"""Recherche fédérée « Rechercher partout » (fusion #111) — garde-fous
|
||||||
|
côté Flowdeck : l'API /api/search et les origines sœurs sont couvertes
|
||||||
|
par les suites Node et les tests CORS ; ici, la CSP des pages, qui
|
||||||
|
conditionne les appels cross-origin de la page /recherche."""
|
||||||
|
|
||||||
|
|
||||||
|
def test_csp_connect_src_contient_shaarit(client):
|
||||||
|
"""La CSP des pages doit autoriser l'origine de ShaarIt en
|
||||||
|
connect-src, sinon le navigateur bloque l'appel de la page
|
||||||
|
/recherche avant même le CORS et la section ShaarIt affiche
|
||||||
|
« Injoignable depuis cet hôte » (retour d'usage du 2026-11-04 :
|
||||||
|
la liste des sœurs du connect-src, écrite pour les Alertes
|
||||||
|
#69, n'avait pas été étendue à ShaarIt)."""
|
||||||
|
r = client.get("/")
|
||||||
|
csp = r.headers.get("content-security-policy", "")
|
||||||
|
assert "connect-src" in csp
|
||||||
|
assert "https://shaarit.dracodev.net" in csp, csp
|
||||||
@@ -40,14 +40,15 @@ from pathlib import Path
|
|||||||
ROOT = Path(__file__).resolve().parent.parent
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
ONGLETS_ATTENDUS = ["all", "obsigate", "flowdeck", "tiktak", "kanbante",
|
ONGLETS_ATTENDUS = ["all", "obsigate", "flowdeck", "tiktak", "kanbante",
|
||||||
"console"]
|
"shaarit", "console"]
|
||||||
|
|
||||||
|
|
||||||
def test_gabarit_recherche_onglets_complets():
|
def test_gabarit_recherche_onglets_complets():
|
||||||
gabarit = (ROOT / "app/templates/recherche.html").read_text()
|
gabarit = (ROOT / "app/templates/recherche.html").read_text()
|
||||||
trouves = re.findall(r'data-rp-tab="([^"]+)"', gabarit)
|
trouves = re.findall(r'data-rp-tab="([^"]+)"', gabarit)
|
||||||
assert trouves == ONGLETS_ATTENDUS, trouves
|
assert trouves == ONGLETS_ATTENDUS, trouves
|
||||||
for section in ("obsigate", "flowdeck", "tiktak", "kanbante", "console"):
|
for section in ("obsigate", "flowdeck", "tiktak", "kanbante", "shaarit",
|
||||||
|
"console"):
|
||||||
assert f'data-rp-section="{section}"' in gabarit
|
assert f'data-rp-section="{section}"' in gabarit
|
||||||
assert 'id="rp-q"' in gabarit and "autofocus" in gabarit
|
assert 'id="rp-q"' in gabarit and "autofocus" in gabarit
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user