openvibers/openvibe.wiki Commits

A wiki page's head says what a reader may check before trusting it (market outcomes) (#176)

OP OpenVibers committed 12 days ago · 2c75c87c02 · 4 files, +47 −2

Under the title: how many sources it cites (to its sources page), how many revisions it has had (to
its history), when the text being read was accepted, whether a newer edit is waiting and since
when, and whether a person reviewed it (with the date) or not yet. The revision count and the
waiting edit are now computed for every reader, not only editors. Wiki outcomes 2, 3 and 5.

Changes

ddiff --git a/public/css/wiki.css b/public/css/wiki.css
iindex 31ebd0d..528cd5c 100644
---- a/public/css/wiki.css
++++ b/public/css/wiki.css
@@@ -231,3 +231,13 @@ main .wk-h-group h3 { display: flex; flex-wrap: wrap; align-items: baseline; gap
.wk-fold-block .sc-sec { padding: 0; }
}
@media (max-width: 640px) { .wk-fold-block { margin-top: 24px; } .wk-fold-block > summary span { display: none; } }
++/* What a reader may check before trusting a page, in its head (market outcomes 2, 3 and 5) */
++.wk-facts { flex-basis: 100%; margin: 6px 0 0; display: flex; flex-wrap: wrap; align-items: center; gap: 4px 0; font-size: .86rem; color: var(--text-secondary, #96a7c2); }
++.wk-facts a { color: var(--text-secondary, #96a7c2); text-decoration-color: color-mix(in srgb, currentColor 40%, transparent); }
++.wk-facts a:hover { color: var(--accent, #3b82f6); }
++.wk-facts > span[aria-hidden] { padding: 0 6px; color: var(--text-muted, #7386a3); }
++.wk-fact-wait { color: var(--warning-text, var(--warning, #f59e0b)); }
++.wk-review { margin-left: 10px; padding: 2px 10px; border-radius: 999px; font-size: .76rem; font-weight: 700; white-space: nowrap; }
++.wk-review--ok { color: var(--success-text, var(--success, #22c55e)); background: color-mix(in srgb, var(--success, #22c55e) 12%, transparent); }
++.wk-review--no { color: var(--warning-text, var(--warning, #f59e0b)); background: color-mix(in srgb, var(--warning, #f59e0b) 12%, transparent); }
++@media (max-width: 600px) { .wk-review { margin-left: 0; margin-top: 4px; } }
ddiff --git a/server/render/views.js b/server/render/views.js
iindex 7917464..dba3b56 100644
---- a/server/render/views.js
++++ b/server/render/views.js
@@@ -387,6 +387,29 @@ ${comments.length ? html`<ul class="wk-comments">${comments.map((c) => html`<li>
${form}</section>`;
}
++/**
++ * What a reader may want to check before trusting a page, in its head (market outcomes 2, 3 and 5): how many sources it
++ * cites and how many revisions it has had, when the text they read was accepted, whether a newer edit is waiting, and
++ * whether a person reviewed it. No other wiki shows these together.
++ */
++function pageFacts(v, base) {
++ const { revision: rev, page } = v;
++ const n = (x, one, many = `${one}s`) => `${x} ${x === 1 ? one : many}`;
++ const facts = [
++ html`<a href="${base}/sources${v.isPublishedRevision ? '' : `?rev=${rev.number}`}">${n(v.citations.length, 'source')} cited</a>`,
++ html`<a href="${base}/history">${n(Number(v.headNumber || rev.number), 'revision')}</a>`,
++ v.isPublishedRevision ? html`this text accepted ${t(rev.createdAt)}` : html`revision ${String(rev.number)}, saved ${t(rev.createdAt)}`,
++ ];
++ if (v.offeredAt && v.isPublishedRevision) facts.push(html`<span class="wk-fact-wait">a newer edit, offered ${t(v.offeredAt)}, is waiting</span>`);
++ const r = v.review;
++ const reviewed = !!(r && /approv|accept/i.test(String(r.decision || '')));
++ const ai = !!(v.authorship && v.authorship.mode && v.authorship.mode !== 'human');
++ const badge = reviewed
++ ? html`<span class="wk-review wk-review--ok">Reviewed by a person${r.reviewed_at || r.reviewedAt ? html` ${t(r.reviewed_at || r.reviewedAt)}` : ''}</span>`
++ : ai ? html`<span class="wk-review wk-review--no">Not yet reviewed by a person</span>` : '';
++ return html`<p class="wk-facts">${facts.map((f, i) => html`${i ? html`<span aria-hidden="true"> · </span>` : ''}<span>${f}</span>`)}${badge ? html` ${badge}` : ''}</p>`;
++}
++
function articlePage(v, { html: contentHtml, discussion, actor, mediaUrl, resolve, flash }) {
const { space, page, revision: rev } = v;
const base = wpath(space, page);
@@@ -405,7 +428,7 @@ function articlePage(v, { html: contentHtml, discussion, actor, mediaUrl, resolv
if (v.canEdit) actions.unshift(html`<a href="${base}/edit">Edit</a>`), actions.push(html`<a href="${base}/settings">Page settings</a>`);
return html`${crumbs(trail)}
<article class="wk-article" data-page-id="${page.id}" data-revision="${String(rev.number)}">
--<header><h1>${rev.fields.title || page.title}</h1><nav class="wk-actions" aria-label="Page actions">${actions.map((a, i) => html`${i ? ' · ' : ''}${a}`)}</nav></header>
++<header><h1>${rev.fields.title || page.title}</h1><nav class="wk-actions" aria-label="Page actions">${actions.map((a, i) => html`${i ? ' · ' : ''}${a}`)}</nav>${pageFacts(v, base)}</header>
${banners}
${raw(infoboxHtml(v.infobox, resolve, outRel(space), { media: (id) => { const a = v.attachments.find((x) => x.mediaId === id); return a && mediaUrl ? figureHtml(a, { urlFor: mediaUrl, unavailableText: UNAVAILABLE[a.brokenReason] || UNAVAILABLE.not_found }) : null; } }))}
<div class="wk-content">${raw(contentHtml)}</div>
ddiff --git a/server/wiki/service.js b/server/wiki/service.js
iindex 0628322..276fb19 100644
---- a/server/wiki/service.js
++++ b/server/wiki/service.js
@@@ -1048,8 +1048,10 @@ function createWikiService({ db, stores, outbox, config, community = null, vip =
actor && actor.subject ? await q.isWatching.get(page.id, actor.subject) : null,
canEdit ? await q.proposalsOfPage.all(page.id) : [],
canEdit ? scheduler.jobs(page.id) : [],
-- canEdit ? revisions.headNumber(page.id) : null,
++ // Everyone sees how many revisions a page has had and whether a newer edit waits (market outcomes 2-3).
++ revisions.headNumber(page.id),
]);
++ const offered = headNumber && headNumber > n ? await revisions.get(page.id, headNumber) : null;
const rec = rev.meta.authorship || null;
const facts = gateFactsWith(space, page, rev, cites.length, review);
const decision = page.state === 'published' && n === page.published_revision
@@@ -1073,6 +1075,7 @@ function createWikiService({ db, stores, outbox, config, community = null, vip =
pendingProposals: proposals.filter((p) => p.status === 'pending'),
jobs: jobs.filter((j) => j.status === 'pending' || j.status === 'running'),
headNumber,
++ offeredAt: offered ? offered.createdAt : null,
};
},
ddiff --git a/test/review.test.js b/test/review.test.js
iindex d40667c..7361ddf 100644
---- a/test/review.test.js
++++ b/test/review.test.js
@@@ -87,6 +87,15 @@ const data = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'seeds', 'ope
assert.ok(r.text.includes('<meta name="robots" content="index, follow">'));
assert.ok(r.text.includes('Reviewed by a person.'));
assert.ok(!r.text.includes('Not yet reviewed by a person.'));
++ // The head says what a reader may check (market outcomes 2, 3 and 5): sources, revisions, when this text was
++ // accepted, and the review, with its date.
++ const facts = (/<p class="wk-facts">[\s\S]*?<\/p>/.exec(r.text) || [''])[0];
++ assert.match(facts, /<a href="\/w\/openvibe\/openvibe-search\/sources">\d+ sources? cited<\/a>/);
++ assert.match(facts, /<a href="\/w\/openvibe\/openvibe-search\/history">\d+ revisions?<\/a>/);
++ assert.match(facts, /this text accepted <time/);
++ assert.match(facts, /<span class="wk-review wk-review--ok">Reviewed by a person <time/);
++ const waiting = await H.req(h, 'GET', `/w/openvibe/${legacy.page.slug}`);
++ assert.match(waiting.text, /<span class="wk-review wk-review--no">Not yet reviewed by a person<\/span>/, 'an AI-assisted page nobody reviewed says so in its head');
assert.ok((await H.req(h, 'GET', '/sitemaps/pages-1.xml')).text.includes('http://wiki.test/w/openvibe/openvibe-search'));
assert.ok((await H.req(h, 'GET', '/feed.atom')).text.includes('http://wiki.test/w/openvibe/openvibe-search'));
assert.ok((await H.req(h, 'GET', '/feed.json')).json.items.some((i) => i.url === 'http://wiki.test/w/openvibe/openvibe-search'));