openvibers/openvibe.quest Commits
Quest pages describe the quest (why, where, what it counts, the badge) instead of the why line alone (#67)
Changes
@@@ -26,6 +26,17 @@ const TAGLINE = 'Quests across the whole network.';const SUBJECT_RE = /^usr_[0-9A-HJKMNP-TV-Z]{26}$/;const siteName = (domain) => (catalog.SITES[domain] || {}).name || domain;++/**++ * A quest page's meta description (≤ 160 characters): why it matters, where it happens, what it counts and its badge.++ * It was the "why" line alone ("An answer is worth more than a read."), which says nothing about the quest.++ */++function questDescription(quest) {++ const steps = quest.steps.map((st) => (st.count > 1 ? `${st.label} ×${st.count}` : st.label)).join(', ');++ const badge = quest.reward && quest.reward.badge && quest.reward.badge.name ? ` Badge: ${quest.reward.badge.name}.` : '';++ const text = `${String(quest.why || '').trim().replace(/[.!?]*$/, '.')} A quest on ${siteName(quest.site)}: ${steps}.${badge}`;++ return text.length <= 160 ? text : `${text.slice(0, 159).replace(/\s+\S*$/, '')}…`;++}++function createPageRoutes(ctx) {const { config, s, coins } = ctx;const r = asyncRouter();@@@ -153,7 +164,7 @@ ${completion ? html`<div class="notice" role="status">You completed this quest oreturn load().then((state) => page(req, res, {index: true, cache: mine ? null : PUBLIC_CACHE,title: quest.title,-- description: quest.why,++ description: questDescription(quest),crumbs: [{ label: 'Quests', href: '/' }, { label: quest.title }],body: html`<h1>${quest.title}</h1><p class="lede">${quest.why}</p>@@@ -118,6 +118,10 @@ function stubInventory() {try {const page = await t.get('/quests/say-hello-in-chat');assert.ok(page.text.includes('class="reward-item"') && page.text.includes('Sparkle'), 'the reward chip');++ // The page describes the quest (why, where, what it counts, the badge), not its "why" line alone.++ const desc = ((page.text.match(/<meta name="description" content="([^"]*)">/) || [])[1] || '').replace(/'/g, "'");++ assert.match(desc, /\. A quest on OpenVibe\.\w+: .+\.( Badge: .+\.)?$/, desc);++ assert.ok(desc.length <= 160, desc);const api = (await t.get('/api/v1/quests')).json();const q = (api.quests || api).find((x) => x.id === 'say-hello-in-chat');assert.deepStrictEqual(q.reward.item, { issuer: 'service:live', alias: 'px_sparkle', name: 'Sparkle', kind: 'Particle effect' });
@@ -26,6 +26,17 @@ const TAGLINE = 'Quests across the whole network.';
@@ -26,6 +26,17 @@ const TAGLINE = 'Quests across the whole network.';
const SUBJECT_RE = /^usr_[0-9A-HJKMNP-TV-Z]{26}$/;
const SUBJECT_RE = /^usr_[0-9A-HJKMNP-TV-Z]{26}$/;
const siteName = (domain) => (catalog.SITES[domain] || {}).name || domain;
const siteName = (domain) => (catalog.SITES[domain] || {}).name || domain;
+/**
+ * A quest page's meta description (≤ 160 characters): why it matters, where it happens, what it counts and its badge.
+ * It was the "why" line alone ("An answer is worth more than a read."), which says nothing about the quest.
+ */
+function questDescription(quest) {
+ const steps = quest.steps.map((st) => (st.count > 1 ? `${st.label} ×${st.count}` : st.label)).join(', ');
+ const badge = quest.reward && quest.reward.badge && quest.reward.badge.name ? ` Badge: ${quest.reward.badge.name}.` : '';
+ const text = `${String(quest.why || '').trim().replace(/[.!?]*$/, '.')} A quest on ${siteName(quest.site)}: ${steps}.${badge}`;
+ return text.length <= 160 ? text : `${text.slice(0, 159).replace(/\s+\S*$/, '')}…`;
+}
+
function createPageRoutes(ctx) {
function createPageRoutes(ctx) {
const { config, s, coins } = ctx;
const { config, s, coins } = ctx;
const r = asyncRouter();
const r = asyncRouter();
@@ -153,7 +164,7 @@ ${completion ? html`<div class="notice" role="status">You completed this quest o
@@ -153,7 +164,7 @@ ${completion ? html`<div class="notice" role="status">You completed this quest o
return load().then((state) => page(req, res, {
return load().then((state) => page(req, res, {
index: true, cache: mine ? null : PUBLIC_CACHE,
index: true, cache: mine ? null : PUBLIC_CACHE,
title: quest.title,
title: quest.title,
- description: quest.why,
+ description: questDescription(quest),
crumbs: [{ label: 'Quests', href: '/' }, { label: quest.title }],
crumbs: [{ label: 'Quests', href: '/' }, { label: quest.title }],
body: html`<h1>${quest.title}</h1>
body: html`<h1>${quest.title}</h1>
<p class="lede">${quest.why}</p>
<p class="lede">${quest.why}</p>
@@ -118,6 +118,10 @@ function stubInventory() {
@@ -118,6 +118,10 @@ function stubInventory() {
try {
try {
const page = await t.get('/quests/say-hello-in-chat');
const page = await t.get('/quests/say-hello-in-chat');
assert.ok(page.text.includes('class="reward-item"') && page.text.includes('Sparkle'), 'the reward chip');
assert.ok(page.text.includes('class="reward-item"') && page.text.includes('Sparkle'), 'the reward chip');
+ // The page describes the quest (why, where, what it counts, the badge), not its "why" line alone.
+ const desc = ((page.text.match(/<meta name="description" content="([^"]*)">/) || [])[1] || '').replace(/'/g, "'");
+ assert.match(desc, /\. A quest on OpenVibe\.\w+: .+\.( Badge: .+\.)?$/, desc);
+ assert.ok(desc.length <= 160, desc);
const api = (await t.get('/api/v1/quests')).json();
const api = (await t.get('/api/v1/quests')).json();
const q = (api.quests || api).find((x) => x.id === 'say-hello-in-chat');
const q = (api.quests || api).find((x) => x.id === 'say-hello-in-chat');
assert.deepStrictEqual(q.reward.item, { issuer: 'service:live', alias: 'px_sparkle', name: 'Sparkle', kind: 'Particle effect' });
assert.deepStrictEqual(q.reward.item, { issuer: 'service:live', alias: 'px_sparkle', name: 'Sparkle', kind: 'Particle effect' });