openvibers/openvibe.inventory Commits
Item pages describe the item (name, rarity, kind, its words, how many own it, how it is got) instead of its flavor text alone (#59)
Changes
@@@ -56,6 +56,21 @@ function card(d, { href = `/items/${d.id}`, extra = '', owned = null, worn = fal</li>`;}++/** An item page's meta description (≤ 160 characters): its name, rarity and kind, its own words, how many own it, how it is got. */++function itemDescription(d, k) {++ const what = `${d.name}, a ${RARITY_LABEL[d.rarity].toLowerCase()} ${k.name.toLowerCase()} on OpenVibe`;++ const words = d.description ? ` "${String(d.description).replace(/\s+/g, ' ').trim().replace(/[.!?]+$/, '')}".` : '.';++ const owned = Number(d.supply && d.supply.issued) || 0;++ const supply = d.supply && d.supply.cap != null ? ` ${owned} of ${d.supply.cap} given.` : ` Owned by ${owned} ${owned === 1 ? 'person' : 'people'}.`;++ const grantors = d.grantors || [];++ const how = d.status !== 'published' ? ' No longer given.'++ : grantors.includes('service:quest') ? ' Earned on OpenVibe.Quest.'++ : grantors.includes('service:games') ? ' Earned on OpenVibe.Games.'++ : ' Given by its issuer, never sold.';++ const text = `${what}${words}${supply}${how}`;++ return text.length <= 160 ? text : `${text.slice(0, 159).replace(/\s+\S*$/, '')}…`;++}++function createPageRoutes(ctx) {const { config, inv } = ctx;const r = asyncRouter();@@@ -145,7 +160,8 @@ ${shown.map((k) => html`<section class="inv-kind" aria-labelledby="k-${k.id.replpage(req, res, {index: d.status === 'published', cache: signedIn(req) ? null : PUBLIC_CACHE,title: `${d.name} · ${RARITY_LABEL[d.rarity]} ${k.name.toLowerCase()}`,-- description: d.description || `${d.name}, a ${RARITY_LABEL[d.rarity].toLowerCase()} ${k.name.toLowerCase()} on OpenVibe.`,++ // What the item is, how many people own it and how it is got; its flavor text alone ("Angelic") said none of that.++ description: itemDescription(d, k),body: html`<p class="crumbs"><a href="/items">Every item</a> › <a href="/items?kind=${encodeURIComponent(k.id)}">${k.name}</a></p><div class="inv-detail r-${d.rarity}">${art(d, { size: 'xl' })}@@@ -34,6 +34,12 @@ const { boot, check, done } = require('./helpers/boot');const cap = await t.get(`/items/${(await inv.byAlias('service:live', 'hat_crown')).id}`);assert.ok(!cap.text.includes('How to get it'), 'an item nobody else gives has no such section');assert.ok(cap.text.includes('Only its issuer can give this item'));++ // Each item page describes itself: name, rarity and kind, how many own it, how it is got (≤ 160 characters).++ const descOf = (html) => ((html.match(/<meta name="description" content="([^"]*)">/) || [])[1] || '').replace(/"/g, '"').replace(/'/g, "'");++ assert.match(descOf(page.text), /^.+, an? (common|uncommon|rare|epic|legendary) .+ on OpenVibe[.: ].* (Owned by \d+ (person|people)|\d+ of \d+ given)\. Earned on OpenVibe\.Quest\.$/);++ assert.match(descOf(golden.text), /Earned on OpenVibe\.Games\.$/);++ assert.match(descOf(cap.text), /Given by its issuer, never sold\.$|…$/);++ for (const html of [page.text, golden.text, cap.text]) assert.ok(descOf(html).length <= 160, descOf(html));const again = await require('../server/inventory/seed').seed(inv, { log: { log() {} } });assert.strictEqual(again.grantors, 0, 'a second boot changes nothing');});
@@ -56,6 +56,21 @@ function card(d, { href = `/items/${d.id}`, extra = '', owned = null, worn = fal
@@ -56,6 +56,21 @@ function card(d, { href = `/items/${d.id}`, extra = '', owned = null, worn = fal
</li>`;
</li>`;
}
}
+/** An item page's meta description (≤ 160 characters): its name, rarity and kind, its own words, how many own it, how it is got. */
+function itemDescription(d, k) {
+ const what = `${d.name}, a ${RARITY_LABEL[d.rarity].toLowerCase()} ${k.name.toLowerCase()} on OpenVibe`;
+ const words = d.description ? ` "${String(d.description).replace(/\s+/g, ' ').trim().replace(/[.!?]+$/, '')}".` : '.';
+ const owned = Number(d.supply && d.supply.issued) || 0;
+ const supply = d.supply && d.supply.cap != null ? ` ${owned} of ${d.supply.cap} given.` : ` Owned by ${owned} ${owned === 1 ? 'person' : 'people'}.`;
+ const grantors = d.grantors || [];
+ const how = d.status !== 'published' ? ' No longer given.'
+ : grantors.includes('service:quest') ? ' Earned on OpenVibe.Quest.'
+ : grantors.includes('service:games') ? ' Earned on OpenVibe.Games.'
+ : ' Given by its issuer, never sold.';
+ const text = `${what}${words}${supply}${how}`;
+ return text.length <= 160 ? text : `${text.slice(0, 159).replace(/\s+\S*$/, '')}…`;
+}
+
function createPageRoutes(ctx) {
function createPageRoutes(ctx) {
const { config, inv } = ctx;
const { config, inv } = ctx;
const r = asyncRouter();
const r = asyncRouter();
@@ -145,7 +160,8 @@ ${shown.map((k) => html`<section class="inv-kind" aria-labelledby="k-${k.id.repl
@@ -145,7 +160,8 @@ ${shown.map((k) => html`<section class="inv-kind" aria-labelledby="k-${k.id.repl
page(req, res, {
page(req, res, {
index: d.status === 'published', cache: signedIn(req) ? null : PUBLIC_CACHE,
index: d.status === 'published', cache: signedIn(req) ? null : PUBLIC_CACHE,
title: `${d.name} · ${RARITY_LABEL[d.rarity]} ${k.name.toLowerCase()}`,
title: `${d.name} · ${RARITY_LABEL[d.rarity]} ${k.name.toLowerCase()}`,
- description: d.description || `${d.name}, a ${RARITY_LABEL[d.rarity].toLowerCase()} ${k.name.toLowerCase()} on OpenVibe.`,
+ // What the item is, how many people own it and how it is got; its flavor text alone ("Angelic") said none of that.
+ description: itemDescription(d, k),
body: html`<p class="crumbs"><a href="/items">Every item</a> › <a href="/items?kind=${encodeURIComponent(k.id)}">${k.name}</a></p>
body: html`<p class="crumbs"><a href="/items">Every item</a> › <a href="/items?kind=${encodeURIComponent(k.id)}">${k.name}</a></p>
<div class="inv-detail r-${d.rarity}">
<div class="inv-detail r-${d.rarity}">
${art(d, { size: 'xl' })}
${art(d, { size: 'xl' })}
@@ -34,6 +34,12 @@ const { boot, check, done } = require('./helpers/boot');
@@ -34,6 +34,12 @@ const { boot, check, done } = require('./helpers/boot');
const cap = await t.get(`/items/${(await inv.byAlias('service:live', 'hat_crown')).id}`);
const cap = await t.get(`/items/${(await inv.byAlias('service:live', 'hat_crown')).id}`);
assert.ok(!cap.text.includes('How to get it'), 'an item nobody else gives has no such section');
assert.ok(!cap.text.includes('How to get it'), 'an item nobody else gives has no such section');
assert.ok(cap.text.includes('Only its issuer can give this item'));
assert.ok(cap.text.includes('Only its issuer can give this item'));
+ // Each item page describes itself: name, rarity and kind, how many own it, how it is got (≤ 160 characters).
+ const descOf = (html) => ((html.match(/<meta name="description" content="([^"]*)">/) || [])[1] || '').replace(/"/g, '"').replace(/'/g, "'");
+ assert.match(descOf(page.text), /^.+, an? (common|uncommon|rare|epic|legendary) .+ on OpenVibe[.: ].* (Owned by \d+ (person|people)|\d+ of \d+ given)\. Earned on OpenVibe\.Quest\.$/);
+ assert.match(descOf(golden.text), /Earned on OpenVibe\.Games\.$/);
+ assert.match(descOf(cap.text), /Given by its issuer, never sold\.$|…$/);
+ for (const html of [page.text, golden.text, cap.text]) assert.ok(descOf(html).length <= 160, descOf(html));
const again = await require('../server/inventory/seed').seed(inv, { log: { log() {} } });
const again = await require('../server/inventory/seed').seed(inv, { log: { log() {} } });
assert.strictEqual(again.grantors, 0, 'a second boot changes nothing');
assert.strictEqual(again.grantors, 0, 'a second boot changes nothing');
});
});