diff --git a/CHANGELOG.md b/CHANGELOG.md index f58f82b..25502bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.38.0 — 2026-10-28 — Page Projets : copie d'URL et refonte du menu ⋯ + +- **Copie d'URL (#44)** : chaque tuile projet (grille, y compris Épinglés) propose désormais « 🔗 Copier l'URL du projet » dans son menu ⋯ — l'URL Gitea du dépôt est copiée dans le presse-papiers, avec confirmation « URL du projet copiée » et repli (textarea + execCommand) quand l'API Clipboard n'est pas disponible. +- **Menu ⋯ (#45)** : refonte sur les jetons de la barre d'apps commune (draco-barre.css, prototype Console v7.2 / console Spark) — fond opaque, bordure, rayon 14 px, ombre portée, survol/focus visibles, séparateur avant l'action de copie. Corrige la cause du menu transparent : il utilisait `var(--surface-1)`, variable jamais définie. +- **Thèmes sombre et clair** : jetons clairs par défaut, variante sombre via `[data-theme="dark"]` sur `` et repli `prefers-color-scheme`, comme la barre Fusion. + ## 0.37.6 — 2026-10-28 — Corrections console/recherche : CORS console Spark - **CORS recherche (PR #43)** : la recherche Kanbante autorise désormais la console Spark (agent prod+dev) en inter-origines — les pages console peuvent appeler la recherche de l'hôte sans erreur CORS/réseau. diff --git a/app/config.py b/app/config.py index 2dc2ec3..a2f5123 100644 --- a/app/config.py +++ b/app/config.py @@ -177,7 +177,7 @@ from urllib.parse import urlparse BASE_DIR = Path(__file__).resolve().parent.parent -APP_VERSION = "0.37.6" +APP_VERSION = "0.38.0" def _env_bool(name: str, default: bool) -> bool: diff --git a/app/templates/dashboard.html b/app/templates/dashboard.html index 16e77a2..11247c1 100644 --- a/app/templates/dashboard.html +++ b/app/templates/dashboard.html @@ -109,6 +109,8 @@ 📖 Récit de sprint {% if repo.html_url %} 🔗 Gitea ↗ + + {% endif %} diff --git a/static/css/style.css b/static/css/style.css index 18e41ed..6cf1be7 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -1486,20 +1486,60 @@ body.sidebar-collapsed .side-foot { justify-content: center; } padding: 2px 7px; border-radius: 8px; line-height: 1; } .card-menu-btn:hover { background: var(--surface-2); color: var(--text); } +.card-menu-btn[aria-expanded="true"] { background: var(--surface-2); color: var(--text); } +/* Menu ⋯ des tuiles projets — refonte issues #44/#45. + Jetons repris de la barre d'apps commune (draco-barre.css, + prototype validé Console v7.2, aussi utilisé par la console + Spark) : clair par défaut, sombre via [data-theme="dark"] + ou prefers-color-scheme. Correctif du bug d'origine : le + menu utilisait var(--surface-1), variable jamais définie, + donc aucun fond — le menu était transparent et illisible. */ .card-menu { + --menu-bg: #ffffff; --menu-bg-2: #e9eae6; --menu-ink: #1b1d1a; + --menu-ink-2: #5c615a; --menu-line: #dedfd9; --menu-agent: #c2437e; position: absolute; right: 0; top: calc(100% + 6px); z-index: 60; - min-width: 210px; display: flex; flex-direction: column; gap: 2px; - background: var(--surface-1); border: 1px solid var(--border); - border-radius: 12px; padding: 6px; - box-shadow: 0 14px 34px rgba(0, 0, 0, 0.45); + min-width: 248px; display: flex; flex-direction: column; gap: 2px; + background: var(--menu-bg); border: 1px solid var(--menu-line); + border-radius: 14px; padding: 6px; color: var(--menu-ink); + box-shadow: 0 16px 44px rgb(0 0 0 / .28); +} +@media (prefers-color-scheme: dark) { + .card-menu { + --menu-bg: #1a1c1a; --menu-bg-2: #242624; --menu-ink: #eceee9; + --menu-ink-2: #a6aba2; --menu-line: #2e302c; + } +} +[data-theme="dark"] .card-menu { + --menu-bg: #1a1c1a; --menu-bg-2: #242624; --menu-ink: #eceee9; + --menu-ink-2: #a6aba2; --menu-line: #2e302c; } .card-menu[hidden] { display: none !important; } -.card-menu a { +.card-menu a, .card-menu-item { display: flex; align-items: center; min-height: 32px; - padding: 8px 10px; border-radius: 8px; - color: var(--text-2); font-size: 0.92rem; + padding: 8px 10px; border-radius: 10px; + color: var(--menu-ink-2); font-size: 0.92rem; +} +.card-menu-item { + width: 100%; box-sizing: border-box; border: 0; + background: transparent; font: inherit; text-align: left; cursor: pointer; +} +.card-menu a:hover, .card-menu a:focus-visible, +.card-menu-item:hover, .card-menu-item:focus-visible { + background: var(--menu-bg-2); color: var(--menu-ink); text-decoration: none; +} +.card-menu a:focus-visible, .card-menu-item:focus-visible { + outline: 2px solid var(--menu-agent); outline-offset: 1px; +} +.card-menu-sep { height: 1px; margin: 6px 10px; background: var(--menu-line); } +/* Confirmation de copie — pastille fixe, lisible sur fond + clair comme sombre, même patron que la barre (draco). */ +.kanbante-toast { + position: fixed; left: 50%; bottom: 22px; transform: translateX(-50%); + z-index: 3000; background: #1f1f1f; color: #eee; + padding: 10px 16px; border-radius: 10px; + font: 500 13.5px system-ui, "Segoe UI", sans-serif; + box-shadow: 0 6px 24px rgb(0 0 0 / .35); } -.card-menu a:hover { background: var(--surface-2); color: var(--text); text-decoration: none; } .proj-table { width: calc(100% - 48px); margin: 0 24px 18px; border-collapse: collapse; font-size: 0.93rem; diff --git a/static/js/dashboard.js b/static/js/dashboard.js index 1c85d8b..e662aac 100644 --- a/static/js/dashboard.js +++ b/static/js/dashboard.js @@ -176,6 +176,47 @@ if (e.key === 'Escape') closeMenus(); }); + /* ── Copier l'URL du projet (#44) ─────────────────────── */ + function showCopyToast(message) { + var toast = document.createElement('div'); + toast.className = 'kanbante-toast'; + toast.setAttribute('role', 'status'); + toast.textContent = message; + document.body.appendChild(toast); + setTimeout(function () { toast.remove(); }, 2600); + } + function copyTextFallback(text) { + var area = document.createElement('textarea'); + area.value = text; + area.style.cssText = 'position:fixed;opacity:0;pointer-events:none;'; + document.body.appendChild(area); + area.select(); + var ok = false; + try { ok = document.execCommand('copy'); } catch (e) { ok = false; } + area.remove(); + return ok; + } + function copyText(text) { + if (navigator.clipboard && window.isSecureContext) { + return navigator.clipboard.writeText(text).then( + function () { return true; }, + function () { return copyTextFallback(text); } + ); + } + return Promise.resolve(copyTextFallback(text)); + } + document.querySelectorAll('[data-copy-url]').forEach(function (btn) { + btn.addEventListener('click', function (e) { + e.stopPropagation(); + var url = btn.getAttribute('data-copy-url') || ''; + if (!url) return; + copyText(url).then(function (ok) { + closeMenus(); + showCopyToast(ok ? 'URL du projet copiée' : 'La copie a échoué'); + }); + }); + }); + /* Le tri des tableaux denses (#22) vit dans tablesort.js, partagé avec /forecast et /health (0.19.0). */ })(); diff --git a/tests/test_dashboard_menu_copy.py b/tests/test_dashboard_menu_copy.py new file mode 100644 index 0000000..41d97a4 --- /dev/null +++ b/tests/test_dashboard_menu_copy.py @@ -0,0 +1,34 @@ +"""Menu ⋯ des tuiles projets : copie d'URL (#44) et thème +opaque barre Fusion / Spark, sombre et clair (#45). + +Le menu était transparent parce que son fond utilisait +var(--surface-1), variable jamais définie dans :root. +""" + + +def test_card_menu_offers_copy_url(client, fake_gitea): + page = client.get("/") + assert "Copier l'URL du projet" in page.text + assert 'data-copy-url="http://gitea.test/Projets/demo"' in page.text + + +def test_card_menu_theme_is_opaque_and_dual(client, fake_gitea): + css = client.get("/static/css/style.css").text + # Le menu ne repose plus sur la variable indéfinie. + menu = css.split(".card-menu {", 1)[1].split("}", 1)[0] + assert "var(--surface-1)" not in menu + assert "var(--menu-bg)" in menu + # Jetons barre Fusion / Console v7.2, clair par défaut… + assert "--menu-bg: #ffffff" in css + # …et variante sombre explicite, comme draco-barre.css. + assert '[data-theme="dark"] .card-menu' in css + assert "--menu-bg: #1a1c1a" in css + assert "prefers-color-scheme: dark" in css + + +def test_dashboard_js_copy_contract(client, fake_gitea): + js = client.get("/static/js/dashboard.js").text + assert "data-copy-url" in js + assert "navigator.clipboard" in js + assert "execCommand('copy')" in js + assert "URL du projet copiée" in js