From b8502fd3fcbc219f0c1708e2b1e6191196f3d07b Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Tue, 28 Apr 2026 11:45:44 +0700 Subject: [PATCH] feat(ui): phase-02 url state, gitlab tooltip, hide empty kim card MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Persist source/view/sort toggles via URL query params (?s=…&v=…&o=…). Short keys keep shared URLs readable; defaults are stripped to keep the bare URL clean. history.replaceState (no history pollution). Init seeds toggles from URL with clamp-to-default validation. - View class is applied BEFORE first render so renderGrid sees the correct show-all state on direct deep-link loads. - Add #source-note pill next to the source tag; shows "91 ngôn ngữ — palette riêng so với GitHub" when GitLab selected, hidden otherwise. Plus title attr on the source-toggle group with the full GitHub-vs-GitLab disparity. - In TIOBE view, hide cards with zero TIOBE-ranked langs (currently KIM has 0 of 19) — all-langs view still shows every Ngũ Hành card so the wheel stays intact. - source-tag firstChild text-node overwrite preserves the source-note span sibling. Closes phase-02 of plan 260428-1003-implement-all-remaining (items 2, 3, 4). --- index.html | 2 +- js/main.js | 74 ++++++++++++++++--- js/render-elements.js | 5 ++ .../plan.md | 2 +- style.css | 12 +++ 5 files changed, 84 insertions(+), 11 deletions(-) diff --git a/index.html b/index.html index 034abdd..4da5d23 100644 --- a/index.html +++ b/index.html @@ -40,7 +40,7 @@

Ngũ Hành & ngôn ngữ

-

Nguồn: GitHub Linguist

+

Nguồn: GitHub Linguist

Nguồn màu diff --git a/js/main.js b/js/main.js index cac8a3a..241a6e8 100644 --- a/js/main.js +++ b/js/main.js @@ -28,12 +28,34 @@ const SORT_OPTIONS = [ { key: 'hue', label: 'Cầu vồng' }, ]; +// Short keys keep the URL readable when shared. +const QUERY_KEYS = { source: 's', view: 'v', sort: 'o' }; + +function readQueryState() { + const params = new URLSearchParams(window.location.search); + return { + source: params.get(QUERY_KEYS.source) || DEFAULT_SOURCE, + view: params.get(QUERY_KEYS.view) || 'tiobe', + sort: params.get(QUERY_KEYS.sort) || 'tiobe', + }; +} + +function writeQueryParam(key, value, defaultValue) { + const params = new URLSearchParams(window.location.search); + if (value === defaultValue) params.delete(QUERY_KEYS[key]); + else params.set(QUERY_KEYS[key], value); + const qs = params.toString(); + const next = qs ? `?${qs}${window.location.hash}` : window.location.pathname + window.location.hash; + history.replaceState(null, '', next); +} + const refs = { grid: null, legend: null, section: null, debug: null, sourceTag: null, + sourceNote: null, }; let lastBuckets = null; @@ -98,7 +120,22 @@ async function loadAndRender(sourceKey) { lastBuckets = buckets; applySortAndRender(); if (refs.legend) refs.legend.textContent = LEGEND_TEXT; - if (refs.sourceTag) refs.sourceTag.textContent = `Nguồn: ${source.label} Linguist`; + if (refs.sourceTag) { + // First child is the text node; preserve the `#source-note` span sibling. + const first = refs.sourceTag.firstChild; + const tagText = `Nguồn: ${source.label} Linguist `; + if (first && first.nodeType === Node.TEXT_NODE) first.nodeValue = tagText; + else refs.sourceTag.prepend(document.createTextNode(tagText)); + } + if (refs.sourceNote) { + if (sourceKey === 'gitlab') { + refs.sourceNote.hidden = false; + refs.sourceNote.textContent = '91 ngôn ngữ — palette riêng so với GitHub'; + } else { + refs.sourceNote.hidden = true; + refs.sourceNote.textContent = ''; + } + } renderDebugPanel({ skipped, borderline }, refs.debug); } catch (err) { console.error('[programming-fengshui] failed to render modern grid:', err); @@ -116,29 +153,48 @@ function init() { refs.section = section; refs.debug = document.getElementById('debug-panel'); refs.sourceTag = document.getElementById('source-tag'); + refs.sourceNote = document.getElementById('source-note'); + + // Seed toggle defaults from URL query params; clamp unknown values. + const initial = readQueryState(); + const validSource = SOURCES[initial.source] ? initial.source : DEFAULT_SOURCE; + const validView = VIEW_OPTIONS.some((o) => o.key === initial.view) ? initial.view : 'tiobe'; + const validSort = SORT_OPTIONS.some((o) => o.key === initial.sort) ? initial.sort : 'tiobe'; + + currentSort = validSort; + // Apply view class BEFORE first render so renderGrid sees the right state + // (drives the empty-card hide logic in render-elements.js). + if (validView === 'all') section?.classList.add('show-all'); + + const sourceToggleEl = document.getElementById('source-toggle'); + sourceToggleEl?.setAttribute('title', 'GitHub có 664 ngôn ngữ; GitLab có 91 và dùng palette riêng'); mountSegmentedControl( - document.getElementById('source-toggle'), + sourceToggleEl, Object.entries(SOURCES).map(([key, s]) => ({ key, label: s.label })), - DEFAULT_SOURCE, - loadAndRender, + validSource, + (key) => { writeQueryParam('source', key, DEFAULT_SOURCE); loadAndRender(key); }, 'Nguồn dữ liệu màu', ); mountSegmentedControl( document.getElementById('view-toggle'), VIEW_OPTIONS, - 'tiobe', - (key) => section?.classList.toggle('show-all', key === 'all'), + validView, + (key) => { + writeQueryParam('view', key, 'tiobe'); + section?.classList.toggle('show-all', key === 'all'); + applySortAndRender(); + }, 'Phạm vi hiển thị ngôn ngữ', ); mountSegmentedControl( document.getElementById('sort-toggle'), SORT_OPTIONS, - 'tiobe', - (key) => { currentSort = key; applySortAndRender(); }, + validSort, + (key) => { writeQueryParam('sort', key, 'tiobe'); currentSort = key; applySortAndRender(); }, 'Sắp xếp ngôn ngữ', ); - loadAndRender(DEFAULT_SOURCE); + loadAndRender(validSource); } if (document.readyState === 'loading') { diff --git a/js/render-elements.js b/js/render-elements.js index a206b5e..5c221c5 100644 --- a/js/render-elements.js +++ b/js/render-elements.js @@ -39,10 +39,15 @@ function buildChip(name, color, { rank = null } = {}) { export function renderGrid(buckets, mountEl) { if (!mountEl) return; + // In TIOBE view, skip cards with zero TIOBE-ranked langs — empty + // "0 ngôn ngữ" headings look broken. All-langs view always shows + // every element so the Ngũ Hành wheel stays intact. + const showAll = mountEl.closest('.elements')?.classList.contains('show-all') ?? false; const fragment = document.createDocumentFragment(); for (const { key, label } of ELEMENTS) { const langs = buckets[key] || []; const tiobeCount = langs.filter((l) => l.rank).length; + if (!showAll && tiobeCount === 0) continue; const card = document.createElement('article'); card.className = `card ${key}`; const h3 = document.createElement('h3'); diff --git a/plans/260428-1003-implement-all-remaining/plan.md b/plans/260428-1003-implement-all-remaining/plan.md index 448c0db..63c8129 100644 --- a/plans/260428-1003-implement-all-remaining/plan.md +++ b/plans/260428-1003-implement-all-remaining/plan.md @@ -16,7 +16,7 @@ Single pass through every remaining item in `plans/todo.md` and the post-cleanup | # | Title | Status | Items | |---|-------|--------|-------| | 01 | Style & markup polish | **done** | items 5–10, 13, 14, 15 (CSS one-liners + chip role + decision docs) | -| 02 | Functional toggles & tooltip | pending | items 2, 3, 4 (URL persistence, GitLab tooltip, hide empty KIM in TIOBE view) | +| 02 | Functional toggles & tooltip | **done** | items 2, 3, 4 (URL persistence, GitLab tooltip, hide empty KIM in TIOBE view) | | 03 | Additive features | pending | items 11, 12 (Top-5 non-TIOBE peek, lunar SVG body texture) | | 04 | OG social card image | pending | item 1 (generate `assets/og-card.png`, may block on tooling) | diff --git a/style.css b/style.css index b171727..3405e1c 100644 --- a/style.css +++ b/style.css @@ -138,6 +138,18 @@ body { text-align: center; font-size: 0.85rem; font-style: italic; color: var(--muted); font-weight: 400; margin: -0.5rem 0 1rem; } +.source-note { + display: inline-block; + margin-left: 0.4rem; + padding: 0.05rem 0.5rem; + border-radius: 999px; + background: var(--bg-tint); + color: var(--accent-ink); + font-size: 0.7rem; + font-style: normal; + font-weight: 600; + letter-spacing: 0.2px; +} /* ---------- Card grid ---------- */ /* Grid stays auto-fit (not forced 5-col) — the Ngũ-Hành wheel reads at any