openvibers/openvibe.chat Commits
Give public Chat room pages a real meta description (#311)
Changes
@@@ -827,10 +827,15 @@ ${people}const work = rooms.isWorkspace(room) ? await require('../rooms/workspace').list(room, me, { limit: 40 }) : null;const aiHere = ais.length ? `<p class="oc-ai-here">${ICONS.spark}<span>${ais.map((a) => `<strong>${esc(a.name)}</strong> <span class="oc-muted oc-ai-model">(${esc(aiMembers.modelOf(a))})</span>`).join(', ')} ${ais.length > 1 ? 'are' : 'is'} here: write ${ais.map((a) => `<code>@${esc(a.handle)}</code>`).join(' or ')} to ask</span></p>` : '';const memberCount = (await rooms.publicRoom(room)).members;++ // A public room's own line when it has one long enough to read as a description; otherwise its name, size++ // and what the page is for. Escaped by the layout's head builder like every other description.++ const topic = typeof room.topic === 'string' ? room.topic.trim() : '';++ const description = topic.length >= 40 ? topic++ : `${room.name}: a ${room.visibility === 'public' ? 'public' : 'private'} room on OpenVibe.Chat with ${memberCount} ${memberCount === 1 ? 'member' : 'members'}. Read along, or sign in to talk.`;await page(req, res, 200, {actor, title: room.name, path: `/r/${room.slug}`, active: 'rooms', robots: room.visibility === 'public' ? 'index, follow' : 'noindex, nofollow', script: true, callScript: callReady, feedView: !!feed,footer: 'compact', scripts: ais.length ? ['vibe-md.js'] : [], wide: !!work,-- description: room.topic || `${room.name}, a ${isCall ? 'call' : room.kind === 'system' ? 'announcements' : 'chat'} room on OpenVibe.`,++ description,page: { view: 'room', room: room.slug, kind: room.kind, pics: pictures.enabled() ? pictures.publicUrl() : null, ai: ais.map((a) => a.handle), latest: msgs.length ? msgs[msgs.length - 1].id : 0, me: me ? me.id : null, moderate: can.moderate, post: can.post, role: can.role,...(callReady ? { call: { channel: `room-${room.slug}`, join: can.join, talk: can.talk } } : {}) },body: `<header class="oc-room-head">@@@ -297,6 +297,12 @@ t('the site: kinds on the rooms page, the call panel, roles and attachments in sassert.match(r.text, /id="oc-call-join" hidden>Listen in</);assert.match(r.text, /you are a participant/);++ // Room pages carry a real description: the room's own line when it has one, else its name and size.++ assert.match((await page('/r/tea-talk', null)).text, /<meta name="description" content="Tea Talk: a public room on OpenVibe\.Chat with \d+ member/);++ const topic = 'Tea, biscuits and the small talk that makes a Tuesday afternoon go faster.';++ assert.strictEqual((await api('PATCH', '/tea-talk', own, { topic })).status, 200);++ assert.ok((await page('/r/tea-talk', null)).text.includes(`<meta name="description" content="${topic}">`), 'the room\'s own topic is the description');++r = await page('/r/open-stage/settings', own);for (const role of ['moderator', 'speaker', 'participant', 'viewer', 'blocked']) assert.ok(r.text.includes(`>${role}</option>`), role);assert.ok(!r.text.includes('>member</option>'), 'call rooms have no members, they have participants');
@@ -827,10 +827,15 @@ ${people}
@@ -827,10 +827,15 @@ ${people}
const work = rooms.isWorkspace(room) ? await require('../rooms/workspace').list(room, me, { limit: 40 }) : null;
const work = rooms.isWorkspace(room) ? await require('../rooms/workspace').list(room, me, { limit: 40 }) : null;
const aiHere = ais.length ? `<p class="oc-ai-here">${ICONS.spark}<span>${ais.map((a) => `<strong>${esc(a.name)}</strong> <span class="oc-muted oc-ai-model">(${esc(aiMembers.modelOf(a))})</span>`).join(', ')} ${ais.length > 1 ? 'are' : 'is'} here: write ${ais.map((a) => `<code>@${esc(a.handle)}</code>`).join(' or ')} to ask</span></p>` : '';
const aiHere = ais.length ? `<p class="oc-ai-here">${ICONS.spark}<span>${ais.map((a) => `<strong>${esc(a.name)}</strong> <span class="oc-muted oc-ai-model">(${esc(aiMembers.modelOf(a))})</span>`).join(', ')} ${ais.length > 1 ? 'are' : 'is'} here: write ${ais.map((a) => `<code>@${esc(a.handle)}</code>`).join(' or ')} to ask</span></p>` : '';
const memberCount = (await rooms.publicRoom(room)).members;
const memberCount = (await rooms.publicRoom(room)).members;
+ // A public room's own line when it has one long enough to read as a description; otherwise its name, size
+ // and what the page is for. Escaped by the layout's head builder like every other description.
+ const topic = typeof room.topic === 'string' ? room.topic.trim() : '';
+ const description = topic.length >= 40 ? topic
+ : `${room.name}: a ${room.visibility === 'public' ? 'public' : 'private'} room on OpenVibe.Chat with ${memberCount} ${memberCount === 1 ? 'member' : 'members'}. Read along, or sign in to talk.`;
await page(req, res, 200, {
await page(req, res, 200, {
actor, title: room.name, path: `/r/${room.slug}`, active: 'rooms', robots: room.visibility === 'public' ? 'index, follow' : 'noindex, nofollow', script: true, callScript: callReady, feedView: !!feed,
actor, title: room.name, path: `/r/${room.slug}`, active: 'rooms', robots: room.visibility === 'public' ? 'index, follow' : 'noindex, nofollow', script: true, callScript: callReady, feedView: !!feed,
footer: 'compact', scripts: ais.length ? ['vibe-md.js'] : [], wide: !!work,
footer: 'compact', scripts: ais.length ? ['vibe-md.js'] : [], wide: !!work,
- description: room.topic || `${room.name}, a ${isCall ? 'call' : room.kind === 'system' ? 'announcements' : 'chat'} room on OpenVibe.`,
+ description,
page: { view: 'room', room: room.slug, kind: room.kind, pics: pictures.enabled() ? pictures.publicUrl() : null, ai: ais.map((a) => a.handle), latest: msgs.length ? msgs[msgs.length - 1].id : 0, me: me ? me.id : null, moderate: can.moderate, post: can.post, role: can.role,
page: { view: 'room', room: room.slug, kind: room.kind, pics: pictures.enabled() ? pictures.publicUrl() : null, ai: ais.map((a) => a.handle), latest: msgs.length ? msgs[msgs.length - 1].id : 0, me: me ? me.id : null, moderate: can.moderate, post: can.post, role: can.role,
...(callReady ? { call: { channel: `room-${room.slug}`, join: can.join, talk: can.talk } } : {}) },
...(callReady ? { call: { channel: `room-${room.slug}`, join: can.join, talk: can.talk } } : {}) },
body: `<header class="oc-room-head">
body: `<header class="oc-room-head">
@@ -297,6 +297,12 @@ t('the site: kinds on the rooms page, the call panel, roles and attachments in s
@@ -297,6 +297,12 @@ t('the site: kinds on the rooms page, the call panel, roles and attachments in s
assert.match(r.text, /id="oc-call-join" hidden>Listen in</);
assert.match(r.text, /id="oc-call-join" hidden>Listen in</);
assert.match(r.text, /you are a participant/);
assert.match(r.text, /you are a participant/);
+ // Room pages carry a real description: the room's own line when it has one, else its name and size.
+ assert.match((await page('/r/tea-talk', null)).text, /<meta name="description" content="Tea Talk: a public room on OpenVibe\.Chat with \d+ member/);
+ const topic = 'Tea, biscuits and the small talk that makes a Tuesday afternoon go faster.';
+ assert.strictEqual((await api('PATCH', '/tea-talk', own, { topic })).status, 200);
+ assert.ok((await page('/r/tea-talk', null)).text.includes(`<meta name="description" content="${topic}">`), 'the room\'s own topic is the description');
+
r = await page('/r/open-stage/settings', own);
r = await page('/r/open-stage/settings', own);
for (const role of ['moderator', 'speaker', 'participant', 'viewer', 'blocked']) assert.ok(r.text.includes(`>${role}</option>`), role);
for (const role of ['moderator', 'speaker', 'participant', 'viewer', 'blocked']) assert.ok(r.text.includes(`>${role}</option>`), role);
assert.ok(!r.text.includes('>member</option>'), 'call rooms have no members, they have participants');
assert.ok(!r.text.includes('>member</option>'), 'call rooms have no members, they have participants');