openvibers/openvibe.wiki Commits
Home: every pane shows on a phone; the chips are links, not a picker (#189)
Changes
@@@ -191,25 +191,22 @@ main .wk-h-group h3 { display: flex; flex-wrap: wrap; align-items: baseline; gap.wk-h-rank > li > a::after { content: ''; position: absolute; left: 0; right: 0; top: -9px; bottom: -9px; }}--/* Pick (the owner's short-pages rule, 2026-10-30): on a phone the home's blocks of the same kind are one at a-- time, picked with a row of chips. Radio inputs and :has(), no script: the chips and the one-block rule live under-- 640 px and only where :has() is understood, so a wider screen, or an older browser, shows every block and no chips. */--.pick-head { display: none; }--.pick-row { display: none; }--@supports selector(:has(*)) { @media (max-width: 640px) {-- .pick-bar { margin-top: 0; }++/* Pick: on a phone every pane is shown, stacked, each with its own heading; the chips are a row of links that++ jump to a pane (the owner's rule, 2026-10-31: never hide content to shorten). A wide screen shows the panes as++ before, with no chips. */++.pick-head, .pick-row { display: none; }++@media (max-width: 640px) {++ .pick-bar { margin-top: 30px; }.pick-head { display: block; }-- .pick-head h2 { margin: 0 0 4px; padding: 0; border: 0; font-size: 1.3rem; }++ .pick-head h2 { margin: 0 0 4px; font: 700 clamp(20px, 2.4vw, 28px)/1.2 var(--ov-font, 'Inter', system-ui, sans-serif); letter-spacing: -.02em; }.pick-row { display: flex; gap: 8px; overflow-x: auto; margin: 10px -16px 0; padding: 2px 16px 4px; scrollbar-width: none; overscroll-behavior-x: contain; }.pick-row::-webkit-scrollbar { display: none; }-- .pick-row input { position: absolute; opacity: 0; width: 1px; height: 1px; pointer-events: none; }-- .pick-row label { display: inline-flex; align-items: center; flex: none; min-height: 44px; padding: 0 15px; border-radius: 999px; border: 1px solid var(--wk-line); color: var(--wk-muted); font-size: .9rem; font-weight: 650; white-space: nowrap; cursor: pointer; }-- .pick-row input:checked + label { color: var(--wk-text); border-color: var(--wk-accent); background: color-mix(in srgb, var(--wk-accent) 12%, transparent); }-- .pick-row input:focus-visible + label { outline: 2px solid var(--wk-accent); outline-offset: 2px; }-- .pick-pane { display: none; }-- /* The chip names the block, so the block's own heading gives way to it. */-- .pick-pane .sc-sec > h2, .pick-pane .wk-h-group > h3 { display: none; }--} }++ .pick-chip { display: inline-flex; align-items: center; flex: none; min-height: 44px; padding: 0 15px; border-radius: 999px; border: 1px solid var(--border, #1f2d47); color: var(--text-secondary, #96a7c2); font-size: .9rem; font-weight: 650; white-space: nowrap; text-decoration: none; }++ .pick-chip:hover, .pick-chip:focus-visible { color: var(--text-primary, #e6edf7); border-color: var(--accent, #3b82f6); }++ .pick-chip:focus-visible { outline: 2px solid var(--accent, #3b82f6); outline-offset: 2px; }++ .pick .pick-pane { scroll-margin-top: 72px; }++ .pick .pick-pane + .pick-pane { margin-top: 26px; }++}/* The fold (the owner's short-pages rule): closed on a phone, and where there is room it looks like the section itreplaces: a heading and its content, with no card around it. */@@@ -22,20 +22,12 @@ const esc = (s) => String(s == null ? '' : s).replace(/&/g, '&').replace(/</*/function pickBlock({ key, label, head = '', panes }) {if (!panes.length) return '';-- const chips = panes.map((p, i) => `<input type="radio" name="pick-${esc(key)}" id="pk-${esc(key)}-${esc(p.id)}" value="${esc(p.id)}"${i === 0 ? ' checked' : ''}><label for="pk-${esc(key)}-${esc(p.id)}">${esc(p.label)}</label>`).join('');++ const chips = panes.map((p) => `<a class="pick-chip" href="#pp-${esc(key)}-${esc(p.id)}">${esc(p.label)}</a>`).join('');const body = panes.map((p) => `<div class="pick-pane" id="pp-${esc(key)}-${esc(p.id)}">${p.html}</div>`).join('');-- return `<div class="pick" id="pick-${esc(key)}"><div class="pick-bar"><div class="pick-head"><h2>${esc(head || label)}</h2></div><div class="pick-row" role="radiogroup" aria-label="${esc(label)}">${chips}</div></div><div class="pick-body">${body}</div></div>`;++ return `<div class="pick" id="pick-${esc(key)}"><div class="pick-bar"><div class="pick-head"><h2>${esc(head || label)}</h2></div><nav class="pick-row" aria-label="${esc(label)}">${chips}</nav></div><div class="pick-body">${body}</div></div>`;}--/** The rule that shows the picked block of a pick block: one per block, so the CSS is written for the page. */--function pickRule(key, id) {-- return `.pick:has(#pk-${key}-${id}:checked) #pp-${key}-${id}{display:block}`;--}--/** The rules for every block of a pick block, in the order they appear. */--function pickRules(key, ids) {-- return ids.map((id) => pickRule(key, id)).join('');--}/*** A fold. The head is the heading the block had; the html is what it holds.@@@ -51,4 +43,4 @@ function foldScript(id) {return `<script>(function(){var d=document.getElementById(${JSON.stringify(id)});if(!d)return;var k='ov_fold_'+${JSON.stringify(id)};var s=null;try{s=localStorage.getItem(k)}catch(e){}if(s==='open'){d.open=true;return}if(s==='closed'){d.open=false;return}var t=0;if(window.matchMedia&&window.matchMedia('(max-width:640px)').matches){t=Date.now();d.open=false}d.addEventListener('toggle',function(){if(Date.now()-t<300)return;try{localStorage.setItem(k,d.open?'open':'closed')}catch(e){}})})();</script>`;}--module.exports = { pickBlock, pickRule, pickRules, foldBlock, foldScript };++module.exports = { pickBlock, foldBlock, foldScript };@@@ -7,7 +7,7 @@ const ssr = require('openvibe-publishing/ssr');const showcase = require('openvibe-shared/showcase');const { figureHtml } = require('openvibe-publishing/media');const { infoboxToText, mediaRefs } = require('../wiki/content');--const { pickBlock, pickRules, foldBlock } = require('./short');++const { pickBlock, foldBlock } = require('./short');const { html, raw } = ssr;const t = (v) => raw(ssr.timeTag(v));@@@ -109,7 +109,6 @@ function home({ spaces, recent, shelves = new Map(), stats = null, linked = [],panes: blocks.map((b) => ({ id: slug(b.name), label: b.name, html: String(groupHtml(b)) })),})): html`${blocks.map(groupHtml)}`;-- if (blocks.length > 1) rules.push(pickRules(`shelf-${s.slug}`, blocks.map((b) => slug(b.name))));const lede = `${s.description ? `${s.description.replace(/\.?\s*$/, '.')} ` : ''}${plural(shelf.total, 'page')}${day ? `, updated ${day}` : ''}.`;return html`<section class="sc-sec wk-h-shelf" id="inside-${s.slug}" aria-labelledby="wk-in-${s.slug}"><h2 id="wk-in-${s.slug}">The ${s.name} wiki</h2><p class="sc-lede">${lede}</p>${shelf.root ? html`<p class="wk-h-root"><a href="${wpath(s, shelf.root.page)}"><b>Start with ${shelf.root.page.title}</b>${shelf.root.summary ? html`<span>${shelf.root.summary}</span>` : ''}</a></p>` : ''}@@@ -143,7 +142,6 @@ ${stats && stats.pages ? html`<dl class="wk-h-stats">${tiles.slice(0, 4).map(([kconst list = panes.filter(Boolean);if (!list.length) return '';if (list.length === 1) return raw(list[0].html);-- rules.push(pickRules(key, list.map((p) => p.id)));return raw(pickBlock({ key, label, head, panes: list }));};const linkedPane = linked.length ? { id: 'linked', label: 'Most linked pages', html: String(html`<section class="sc-sec" aria-labelledby="wk-linked-h"><h2 id="wk-linked-h">Most linked pages</h2><p class="sc-lede">The pages other pages point to most often: a good place to start reading.</p>@@@ -40,10 +40,10 @@ const data = {const text = r.text;assert.ok(text.includes('<div class="pick" id="pick-read">'), 'the pages to start with are one pick block');-- assert.match(text, /<div class="pick-row" role="radiogroup" aria-label="Pages to start with">/);-- const chips = [...text.matchAll(/<input type="radio" name="pick-read" id="pk-read-([a-z]+)" value="\1"( checked)?><label for="pk-read-\1">([^<]+)<\/label>/g)];++ assert.match(text, /<nav class="pick-row" aria-label="Pages to start with">/);++ const chips = [...text.matchAll(/<a class="pick-chip" href="#(?:pp-read-)?([a-z]+)">()([^<]+)<\/a>/g)];assert.deepStrictEqual(chips.map((m) => m[1]), ['linked', 'recent'], 'a chip per list');-- assert.deepStrictEqual(chips.filter((m) => m[2]).map((m) => m[1]), ['linked'], 'the first list is picked');++ assert.ok(!/type="radio"/.test(text), 'no chip is a radio: nothing is picked, every pane shows');assert.strictEqual((text.match(/class="pick-pane"/g) || []).length, chips.length, 'one pane per chip');// Every list stays in the page for the crawlers.assert.ok(text.includes('Most linked pages') && text.includes('Recently revised'), 'both lists are in the page');@@@ -66,20 +66,14 @@ const data = {const rules = [];const grouped = String(views.home({ spaces, recent: [], shelves: new Map([[spaces[0].id, shelf]]), groups, actor: { kind: 'anonymous' }, rules }));assert.ok(grouped.includes('<div class="pick" id="pick-shelf-openvibe">'), 'the groups are one pick block');-- const groupChips = [...grouped.matchAll(/<input type="radio" name="pick-shelf-openvibe" id="pk-shelf-openvibe-([a-z-]+)" value="\1"( checked)?><label for="pk-shelf-openvibe-\1">([^<]+)<\/label>/g)];++ const groupChips = [...grouped.matchAll(/<a class="pick-chip" href="#(?:pp-shelf-openvibe-)?([a-z-]+)">()([^<]+)<\/a>/g)];assert.deepStrictEqual(groupChips.map((m) => m[1]), ['watch-stream', 'your-openvibe', 'platform-and-libraries'], 'a chip per group');-- assert.deepStrictEqual(groupChips.filter((m) => m[2]).map((m) => m[1]), ['watch-stream'], 'the first group is picked');++ assert.ok(!/type="radio"/.test(grouped), 'no chip is a radio: nothing is picked, every pane shows');// Every page stays in the page.assert.strictEqual((grouped.match(/class="sc-card wk-h-card"/g) || []).length, 3, 'every page card is in the page');-- // The rule that shows the picked group is written for the page.-- assert.ok(rules.join('').includes('.pick:has(#pk-shelf-openvibe-watch-stream:checked) #pp-shelf-openvibe-watch-stream{display:block}'), 'the picked group shows');-- // The CSS guard: the chips are hidden and the blocks all show until 640 px and where :has() is understood.const css = require('fs').readFileSync(require('path').join(__dirname, '..', 'public', 'css', 'wiki.css'), 'utf8');assert.match(css, /\.pick-row \{ display: none; \}/, 'the chips are hidden by default');-- assert.match(css, /@supports selector\(:has\(\*\)\) \{ @media \(max-width: 640px\) \{/, 'the phone rules are under :has() and 640 px');-- assert.match(css, /\.pick-row label \{[^}]*min-height: 44px/, 'a chip is a 44 px target');-- assert.match(css, /\.pick-pane \{ display: none; \}/, 'the blocks are hidden on a phone');console.log('home-short ok');} finally {
@@ -191,25 +191,22 @@ main .wk-h-group h3 { display: flex; flex-wrap: wrap; align-items: baseline; gap
@@ -191,25 +191,22 @@ main .wk-h-group h3 { display: flex; flex-wrap: wrap; align-items: baseline; gap
.wk-h-rank > li > a::after { content: ''; position: absolute; left: 0; right: 0; top: -9px; bottom: -9px; }
.wk-h-rank > li > a::after { content: ''; position: absolute; left: 0; right: 0; top: -9px; bottom: -9px; }
}
}
-/* Pick (the owner's short-pages rule, 2026-10-30): on a phone the home's blocks of the same kind are one at a
- time, picked with a row of chips. Radio inputs and :has(), no script: the chips and the one-block rule live under
- 640 px and only where :has() is understood, so a wider screen, or an older browser, shows every block and no chips. */
-.pick-head { display: none; }
-.pick-row { display: none; }
-@supports selector(:has(*)) { @media (max-width: 640px) {
- .pick-bar { margin-top: 0; }
+/* Pick: on a phone every pane is shown, stacked, each with its own heading; the chips are a row of links that
+ jump to a pane (the owner's rule, 2026-10-31: never hide content to shorten). A wide screen shows the panes as
+ before, with no chips. */
+.pick-head, .pick-row { display: none; }
+@media (max-width: 640px) {
+ .pick-bar { margin-top: 30px; }
.pick-head { display: block; }
.pick-head { display: block; }
- .pick-head h2 { margin: 0 0 4px; padding: 0; border: 0; font-size: 1.3rem; }
+ .pick-head h2 { margin: 0 0 4px; font: 700 clamp(20px, 2.4vw, 28px)/1.2 var(--ov-font, 'Inter', system-ui, sans-serif); letter-spacing: -.02em; }
.pick-row { display: flex; gap: 8px; overflow-x: auto; margin: 10px -16px 0; padding: 2px 16px 4px; scrollbar-width: none; overscroll-behavior-x: contain; }
.pick-row { display: flex; gap: 8px; overflow-x: auto; margin: 10px -16px 0; padding: 2px 16px 4px; scrollbar-width: none; overscroll-behavior-x: contain; }
.pick-row::-webkit-scrollbar { display: none; }
.pick-row::-webkit-scrollbar { display: none; }
- .pick-row input { position: absolute; opacity: 0; width: 1px; height: 1px; pointer-events: none; }
- .pick-row label { display: inline-flex; align-items: center; flex: none; min-height: 44px; padding: 0 15px; border-radius: 999px; border: 1px solid var(--wk-line); color: var(--wk-muted); font-size: .9rem; font-weight: 650; white-space: nowrap; cursor: pointer; }
- .pick-row input:checked + label { color: var(--wk-text); border-color: var(--wk-accent); background: color-mix(in srgb, var(--wk-accent) 12%, transparent); }
- .pick-row input:focus-visible + label { outline: 2px solid var(--wk-accent); outline-offset: 2px; }
- .pick-pane { display: none; }
- /* The chip names the block, so the block's own heading gives way to it. */
- .pick-pane .sc-sec > h2, .pick-pane .wk-h-group > h3 { display: none; }
-} }
+ .pick-chip { display: inline-flex; align-items: center; flex: none; min-height: 44px; padding: 0 15px; border-radius: 999px; border: 1px solid var(--border, #1f2d47); color: var(--text-secondary, #96a7c2); font-size: .9rem; font-weight: 650; white-space: nowrap; text-decoration: none; }
+ .pick-chip:hover, .pick-chip:focus-visible { color: var(--text-primary, #e6edf7); border-color: var(--accent, #3b82f6); }
+ .pick-chip:focus-visible { outline: 2px solid var(--accent, #3b82f6); outline-offset: 2px; }
+ .pick .pick-pane { scroll-margin-top: 72px; }
+ .pick .pick-pane + .pick-pane { margin-top: 26px; }
+}
/* The fold (the owner's short-pages rule): closed on a phone, and where there is room it looks like the section it
/* The fold (the owner's short-pages rule): closed on a phone, and where there is room it looks like the section it
replaces: a heading and its content, with no card around it. */
replaces: a heading and its content, with no card around it. */
@@ -22,20 +22,12 @@ const esc = (s) => String(s == null ? '' : s).replace(/&/g, '&').replace(/</
@@ -22,20 +22,12 @@ const esc = (s) => String(s == null ? '' : s).replace(/&/g, '&').replace(/</
*/
*/
function pickBlock({ key, label, head = '', panes }) {
function pickBlock({ key, label, head = '', panes }) {
if (!panes.length) return '';
if (!panes.length) return '';
- const chips = panes.map((p, i) => `<input type="radio" name="pick-${esc(key)}" id="pk-${esc(key)}-${esc(p.id)}" value="${esc(p.id)}"${i === 0 ? ' checked' : ''}><label for="pk-${esc(key)}-${esc(p.id)}">${esc(p.label)}</label>`).join('');
+ const chips = panes.map((p) => `<a class="pick-chip" href="#pp-${esc(key)}-${esc(p.id)}">${esc(p.label)}</a>`).join('');
const body = panes.map((p) => `<div class="pick-pane" id="pp-${esc(key)}-${esc(p.id)}">${p.html}</div>`).join('');
const body = panes.map((p) => `<div class="pick-pane" id="pp-${esc(key)}-${esc(p.id)}">${p.html}</div>`).join('');
- return `<div class="pick" id="pick-${esc(key)}"><div class="pick-bar"><div class="pick-head"><h2>${esc(head || label)}</h2></div><div class="pick-row" role="radiogroup" aria-label="${esc(label)}">${chips}</div></div><div class="pick-body">${body}</div></div>`;
+ return `<div class="pick" id="pick-${esc(key)}"><div class="pick-bar"><div class="pick-head"><h2>${esc(head || label)}</h2></div><nav class="pick-row" aria-label="${esc(label)}">${chips}</nav></div><div class="pick-body">${body}</div></div>`;
}
}
-/** The rule that shows the picked block of a pick block: one per block, so the CSS is written for the page. */
-function pickRule(key, id) {
- return `.pick:has(#pk-${key}-${id}:checked) #pp-${key}-${id}{display:block}`;
-}
-/** The rules for every block of a pick block, in the order they appear. */
-function pickRules(key, ids) {
- return ids.map((id) => pickRule(key, id)).join('');
-}
/**
/**
* A fold. The head is the heading the block had; the html is what it holds.
* A fold. The head is the heading the block had; the html is what it holds.
@@ -51,4 +43,4 @@ function foldScript(id) {
@@ -51,4 +43,4 @@ function foldScript(id) {
return `<script>(function(){var d=document.getElementById(${JSON.stringify(id)});if(!d)return;var k='ov_fold_'+${JSON.stringify(id)};var s=null;try{s=localStorage.getItem(k)}catch(e){}if(s==='open'){d.open=true;return}if(s==='closed'){d.open=false;return}var t=0;if(window.matchMedia&&window.matchMedia('(max-width:640px)').matches){t=Date.now();d.open=false}d.addEventListener('toggle',function(){if(Date.now()-t<300)return;try{localStorage.setItem(k,d.open?'open':'closed')}catch(e){}})})();</script>`;
return `<script>(function(){var d=document.getElementById(${JSON.stringify(id)});if(!d)return;var k='ov_fold_'+${JSON.stringify(id)};var s=null;try{s=localStorage.getItem(k)}catch(e){}if(s==='open'){d.open=true;return}if(s==='closed'){d.open=false;return}var t=0;if(window.matchMedia&&window.matchMedia('(max-width:640px)').matches){t=Date.now();d.open=false}d.addEventListener('toggle',function(){if(Date.now()-t<300)return;try{localStorage.setItem(k,d.open?'open':'closed')}catch(e){}})})();</script>`;
}
}
-module.exports = { pickBlock, pickRule, pickRules, foldBlock, foldScript };
+module.exports = { pickBlock, foldBlock, foldScript };
@@ -7,7 +7,7 @@ const ssr = require('openvibe-publishing/ssr');
@@ -7,7 +7,7 @@ const ssr = require('openvibe-publishing/ssr');
const showcase = require('openvibe-shared/showcase');
const showcase = require('openvibe-shared/showcase');
const { figureHtml } = require('openvibe-publishing/media');
const { figureHtml } = require('openvibe-publishing/media');
const { infoboxToText, mediaRefs } = require('../wiki/content');
const { infoboxToText, mediaRefs } = require('../wiki/content');
-const { pickBlock, pickRules, foldBlock } = require('./short');
+const { pickBlock, foldBlock } = require('./short');
const { html, raw } = ssr;
const { html, raw } = ssr;
const t = (v) => raw(ssr.timeTag(v));
const t = (v) => raw(ssr.timeTag(v));
@@ -109,7 +109,6 @@ function home({ spaces, recent, shelves = new Map(), stats = null, linked = [],
@@ -109,7 +109,6 @@ function home({ spaces, recent, shelves = new Map(), stats = null, linked = [],
panes: blocks.map((b) => ({ id: slug(b.name), label: b.name, html: String(groupHtml(b)) })),
panes: blocks.map((b) => ({ id: slug(b.name), label: b.name, html: String(groupHtml(b)) })),
}))
}))
: html`${blocks.map(groupHtml)}`;
: html`${blocks.map(groupHtml)}`;
- if (blocks.length > 1) rules.push(pickRules(`shelf-${s.slug}`, blocks.map((b) => slug(b.name))));
const lede = `${s.description ? `${s.description.replace(/\.?\s*$/, '.')} ` : ''}${plural(shelf.total, 'page')}${day ? `, updated ${day}` : ''}.`;
const lede = `${s.description ? `${s.description.replace(/\.?\s*$/, '.')} ` : ''}${plural(shelf.total, 'page')}${day ? `, updated ${day}` : ''}.`;
return html`<section class="sc-sec wk-h-shelf" id="inside-${s.slug}" aria-labelledby="wk-in-${s.slug}"><h2 id="wk-in-${s.slug}">The ${s.name} wiki</h2><p class="sc-lede">${lede}</p>
return html`<section class="sc-sec wk-h-shelf" id="inside-${s.slug}" aria-labelledby="wk-in-${s.slug}"><h2 id="wk-in-${s.slug}">The ${s.name} wiki</h2><p class="sc-lede">${lede}</p>
${shelf.root ? html`<p class="wk-h-root"><a href="${wpath(s, shelf.root.page)}"><b>Start with ${shelf.root.page.title}</b>${shelf.root.summary ? html`<span>${shelf.root.summary}</span>` : ''}</a></p>` : ''}
${shelf.root ? html`<p class="wk-h-root"><a href="${wpath(s, shelf.root.page)}"><b>Start with ${shelf.root.page.title}</b>${shelf.root.summary ? html`<span>${shelf.root.summary}</span>` : ''}</a></p>` : ''}
@@ -143,7 +142,6 @@ ${stats && stats.pages ? html`<dl class="wk-h-stats">${tiles.slice(0, 4).map(([k
@@ -143,7 +142,6 @@ ${stats && stats.pages ? html`<dl class="wk-h-stats">${tiles.slice(0, 4).map(([k
const list = panes.filter(Boolean);
const list = panes.filter(Boolean);
if (!list.length) return '';
if (!list.length) return '';
if (list.length === 1) return raw(list[0].html);
if (list.length === 1) return raw(list[0].html);
- rules.push(pickRules(key, list.map((p) => p.id)));
return raw(pickBlock({ key, label, head, panes: list }));
return raw(pickBlock({ key, label, head, panes: list }));
};
};
const linkedPane = linked.length ? { id: 'linked', label: 'Most linked pages', html: String(html`<section class="sc-sec" aria-labelledby="wk-linked-h"><h2 id="wk-linked-h">Most linked pages</h2><p class="sc-lede">The pages other pages point to most often: a good place to start reading.</p>
const linkedPane = linked.length ? { id: 'linked', label: 'Most linked pages', html: String(html`<section class="sc-sec" aria-labelledby="wk-linked-h"><h2 id="wk-linked-h">Most linked pages</h2><p class="sc-lede">The pages other pages point to most often: a good place to start reading.</p>
@@ -40,10 +40,10 @@ const data = {
@@ -40,10 +40,10 @@ const data = {
const text = r.text;
const text = r.text;
assert.ok(text.includes('<div class="pick" id="pick-read">'), 'the pages to start with are one pick block');
assert.ok(text.includes('<div class="pick" id="pick-read">'), 'the pages to start with are one pick block');
- assert.match(text, /<div class="pick-row" role="radiogroup" aria-label="Pages to start with">/);
- const chips = [...text.matchAll(/<input type="radio" name="pick-read" id="pk-read-([a-z]+)" value="\1"( checked)?><label for="pk-read-\1">([^<]+)<\/label>/g)];
+ assert.match(text, /<nav class="pick-row" aria-label="Pages to start with">/);
+ const chips = [...text.matchAll(/<a class="pick-chip" href="#(?:pp-read-)?([a-z]+)">()([^<]+)<\/a>/g)];
assert.deepStrictEqual(chips.map((m) => m[1]), ['linked', 'recent'], 'a chip per list');
assert.deepStrictEqual(chips.map((m) => m[1]), ['linked', 'recent'], 'a chip per list');
- assert.deepStrictEqual(chips.filter((m) => m[2]).map((m) => m[1]), ['linked'], 'the first list is picked');
+ assert.ok(!/type="radio"/.test(text), 'no chip is a radio: nothing is picked, every pane shows');
assert.strictEqual((text.match(/class="pick-pane"/g) || []).length, chips.length, 'one pane per chip');
assert.strictEqual((text.match(/class="pick-pane"/g) || []).length, chips.length, 'one pane per chip');
// Every list stays in the page for the crawlers.
// Every list stays in the page for the crawlers.
assert.ok(text.includes('Most linked pages') && text.includes('Recently revised'), 'both lists are in the page');
assert.ok(text.includes('Most linked pages') && text.includes('Recently revised'), 'both lists are in the page');
@@ -66,20 +66,14 @@ const data = {
@@ -66,20 +66,14 @@ const data = {
const rules = [];
const rules = [];
const grouped = String(views.home({ spaces, recent: [], shelves: new Map([[spaces[0].id, shelf]]), groups, actor: { kind: 'anonymous' }, rules }));
const grouped = String(views.home({ spaces, recent: [], shelves: new Map([[spaces[0].id, shelf]]), groups, actor: { kind: 'anonymous' }, rules }));
assert.ok(grouped.includes('<div class="pick" id="pick-shelf-openvibe">'), 'the groups are one pick block');
assert.ok(grouped.includes('<div class="pick" id="pick-shelf-openvibe">'), 'the groups are one pick block');
- const groupChips = [...grouped.matchAll(/<input type="radio" name="pick-shelf-openvibe" id="pk-shelf-openvibe-([a-z-]+)" value="\1"( checked)?><label for="pk-shelf-openvibe-\1">([^<]+)<\/label>/g)];
+ const groupChips = [...grouped.matchAll(/<a class="pick-chip" href="#(?:pp-shelf-openvibe-)?([a-z-]+)">()([^<]+)<\/a>/g)];
assert.deepStrictEqual(groupChips.map((m) => m[1]), ['watch-stream', 'your-openvibe', 'platform-and-libraries'], 'a chip per group');
assert.deepStrictEqual(groupChips.map((m) => m[1]), ['watch-stream', 'your-openvibe', 'platform-and-libraries'], 'a chip per group');
- assert.deepStrictEqual(groupChips.filter((m) => m[2]).map((m) => m[1]), ['watch-stream'], 'the first group is picked');
+ assert.ok(!/type="radio"/.test(grouped), 'no chip is a radio: nothing is picked, every pane shows');
// Every page stays in the page.
// Every page stays in the page.
assert.strictEqual((grouped.match(/class="sc-card wk-h-card"/g) || []).length, 3, 'every page card is in the page');
assert.strictEqual((grouped.match(/class="sc-card wk-h-card"/g) || []).length, 3, 'every page card is in the page');
- // The rule that shows the picked group is written for the page.
- assert.ok(rules.join('').includes('.pick:has(#pk-shelf-openvibe-watch-stream:checked) #pp-shelf-openvibe-watch-stream{display:block}'), 'the picked group shows');
- // The CSS guard: the chips are hidden and the blocks all show until 640 px and where :has() is understood.
const css = require('fs').readFileSync(require('path').join(__dirname, '..', 'public', 'css', 'wiki.css'), 'utf8');
const css = require('fs').readFileSync(require('path').join(__dirname, '..', 'public', 'css', 'wiki.css'), 'utf8');
assert.match(css, /\.pick-row \{ display: none; \}/, 'the chips are hidden by default');
assert.match(css, /\.pick-row \{ display: none; \}/, 'the chips are hidden by default');
- assert.match(css, /@supports selector\(:has\(\*\)\) \{ @media \(max-width: 640px\) \{/, 'the phone rules are under :has() and 640 px');
- assert.match(css, /\.pick-row label \{[^}]*min-height: 44px/, 'a chip is a 44 px target');
- assert.match(css, /\.pick-pane \{ display: none; \}/, 'the blocks are hidden on a phone');
console.log('home-short ok');
console.log('home-short ok');
} finally {
} finally {