openvibers/openvibe.community Commits
Forum threads: Ask your agent chip in the action row (#288)
Changes
@@@ -15,6 +15,8 @@ const { markdownToText } = require('./markdown');const { timeTag, fmtDate, num, avatarUrl, mediaW, authorHtml, wearName } = require('./pages');const f = require('./forum');const labelsUi = require('./labels');++const kit = require('openvibe-shared/kit');++const config = require('../config');const STYLE_LABELS = { forum: 'Classic', feed: 'Feed' }; // the two styles, as the Forum page and the settings name themconst monthYear = (iso) => { const d = iso ? new Date(iso) : null; return d && !Number.isNaN(d.getTime()) ? d.toLocaleDateString('en-US', { month: 'short', year: 'numeric', timeZone: 'UTC' }) : null; };@@@ -238,7 +240,7 @@ function forumTopicPage({ space, thread, posts, page, pages, perPage = 50, viewe<h1>${thread.pinned ? '<i class="fa-solid fa-thumbtack" title="Pinned" aria-hidden="true"></i><span class="sr-only">Pinned</span> ' : ''}${thread.locked ? '<i class="fa-solid fa-lock" title="Locked" aria-hidden="true"></i><span class="sr-only">Locked</span> ' : ''}${esc(thread.title)}${f.vipBadge(thread)}</h1>${thread.status || thread.category ? `<p class="thread-tags">${f.statusBadge(thread)}${f.categoryBadge(thread, space)}</p>` : ''}${labelsUi.chips(labels)}-- <p class="small muted">${plural(thread.reply_count, 'reply', 'replies')} · ${plural(thread.views, 'view', 'views')} · <a class="rss-link" href="${esc(base)}/feed.xml" title="RSS feed of this thread"><i class="fa-solid fa-rss" aria-hidden="true"></i> Follow this thread by RSS</a></p>++ <p class="small muted">${plural(thread.reply_count, 'reply', 'replies')} · ${plural(thread.views, 'view', 'views')} · <a class="rss-link" href="${esc(base)}/feed.xml" title="RSS feed of this thread"><i class="fa-solid fa-rss" aria-hidden="true"></i> Follow this thread by RSS</a> ${kit.askAgent({ text: `About the forum thread “${thread.title}” (${config.baseUrl}${base}): ` })}</p></div></header>${f.crosspostBlock(crosspost, base)}@@@ -262,6 +264,7 @@ function forumTopicPage({ space, thread, posts, page, pages, perPage = 50, viewehistoryType: 'page',historyTitle: thread.title,footerVariant: 'compact',++ styles: ['kit.css'],...(labelBox ? { pageCss: ['labels.css'] } : {}),feeds: [...(space.visibility === 'public' && !space.members_only ? [{ title: `OpenVibe.Community — ${space.name}`, href: `/forum/${space.slug}/feed.xml` }] : []),@@@ -13,6 +13,8 @@ const { markdownToText } = require('./markdown');const labelsUi = require('./labels');const { timeTag, fmtDate, num, authorHtml } = require('./pages');const { RULES: MODERATION_RULES } = require('../forum/moderation');++const kit = require('openvibe-shared/kit');++const config = require('../config');const SORT_LABELS = { hot: 'Hot', new: 'New', top: 'Top' };const STATUS_LABELS = { open: 'Open', planned: 'Planned', in_progress: 'In progress', done: 'Done', declined: 'Declined', paused: 'Paused' };@@@ -300,7 +302,7 @@ function threadPage({ space, thread, posts, page, pages, perPage = 50, viewer, u${thread.status || thread.category ? `<p class="thread-tags">${statusBadge(thread)}${categoryBadge(thread, space)}</p>` : ''}${labelsUi.chips(labels)}<p class="paste-meta">${who(thread.author)} <span class="sep">·</span> <time datetime="${esc(thread.created_at || '')}">${esc(fmtDate(thread.created_at))}</time> <span class="sep">·</span> <span class="stat"><i class="fa-solid fa-comment" aria-hidden="true"></i> ${num(thread.reply_count)} ${thread.reply_count === 1 ? 'reply' : 'replies'}</span></p>-- <p class="thread-follow"><a class="rss-link" href="${esc(base)}/feed.xml" title="RSS feed of this thread"><i class="fa-solid fa-rss" aria-hidden="true"></i> Follow this thread by RSS</a></p>++ <p class="thread-follow"><a class="rss-link" href="${esc(base)}/feed.xml" title="RSS feed of this thread"><i class="fa-solid fa-rss" aria-hidden="true"></i> Follow this thread by RSS</a> ${kit.askAgent({ text: `About the forum thread “${thread.title}” (${config.baseUrl}${base}): ` })}</p></div></header>${crosspostBlock(crosspost, base)}@@@ -323,6 +325,7 @@ function threadPage({ space, thread, posts, page, pages, perPage = 50, viewer, uhistoryType: 'page',historyTitle: thread.title,footerVariant: 'compact',++ styles: ['kit.css'],...(labelBox ? { pageCss: ['labels.css'] } : {}),feeds: [...(space.visibility === 'public' && !space.members_only ? [{ title: `OpenVibe.Community — ${space.name}`, href: `/forum/${space.slug}/feed.xml` }] : []),@@@ -64,6 +64,13 @@ const form = (obj) => ({ method: 'POST', body: new URLSearchParams(obj).toStringassert.strictEqual(r.status, 200);has(r.text, '<title>How do I <b>embed</b> comments? · OpenVibe.Community</title>');has(r.text, `<link rel="canonical" href="https://openvibe.community${url}">`);++ // The Ask your agent chip (openvibe-shared 4.135.0): in the thread's action row, its title escaped, its address.++ const asks = r.text.match(/<button[^>]*data-ov-ask[^>]*>/g) || [];++ assert.strictEqual(asks.length, 1, 'one Ask your agent chip: ' + asks.join(' | '));++ assert.ok(asks[0].includes('data-ov-ask="About the forum thread “How do I <b>embed</b> comments?” (https://openvibe.community/forum/general/how-do-i-b-embed-b-comments): "'), asks[0]);++ assert.ok(asks[0].includes('class="ovk-ask ovk-btn'), 'the shared kit chip');++ assert.ok(!asks[0].includes('<b>'), 'the title is escaped in the chip');++ has(r.text, '/shared/kit.css', 'the kit’s look is on the page');has(r.text, '<meta property="og:type" content="article">');has(r.text, '<meta property="article:published_time"');has(r.text, '<strong>Live</strong>');@@@ -60,6 +60,15 @@ const forumStore = require('../server/forum/store');assert.strictEqual(forumStore.slugify('☃☃☃'), 'thread');});++ await check('a board-style thread page carries one Ask your agent chip in its action row', async () => {++ const page = await t.get('/forum/general/hello-world');++ assert.strictEqual(page.status, 200);++ const asks = page.text.match(/<button[^>]*data-ov-ask[^>]*>/g) || [];++ assert.strictEqual(asks.length, 1, 'one Ask your agent chip: ' + asks.join(' | '));++ assert.ok(asks[0].includes('data-ov-ask="About the forum thread “Hello, World!” (https://openvibe.community/forum/general/hello-world): "'), asks[0]);++ assert.ok(asks[0].includes('class="ovk-ask ovk-btn'), 'the shared kit chip');++ });++await check('a title that makes one of the forum\'s own route words takes -2, so /forum/<space>/<slug> stays a thread', async () => {for (const [title, slug] of [['New', 'new-2'], ['Log', 'log-2'], ['Settings', 'settings-2'], ['Members', 'members-2'], ['Chat room', 'chat-room-2'], ['Discuss', 'discuss-2']]) {const r = await call('/api/v1/spaces/general/threads', { method: 'POST', cookie: alexJwt, json: { title, body: 'reserved word' } });
@@ -15,6 +15,8 @@ const { markdownToText } = require('./markdown');
@@ -15,6 +15,8 @@ const { markdownToText } = require('./markdown');
const { timeTag, fmtDate, num, avatarUrl, mediaW, authorHtml, wearName } = require('./pages');
const { timeTag, fmtDate, num, avatarUrl, mediaW, authorHtml, wearName } = require('./pages');
const f = require('./forum');
const f = require('./forum');
const labelsUi = require('./labels');
const labelsUi = require('./labels');
+const kit = require('openvibe-shared/kit');
+const config = require('../config');
const STYLE_LABELS = { forum: 'Classic', feed: 'Feed' }; // the two styles, as the Forum page and the settings name them
const STYLE_LABELS = { forum: 'Classic', feed: 'Feed' }; // the two styles, as the Forum page and the settings name them
const monthYear = (iso) => { const d = iso ? new Date(iso) : null; return d && !Number.isNaN(d.getTime()) ? d.toLocaleDateString('en-US', { month: 'short', year: 'numeric', timeZone: 'UTC' }) : null; };
const monthYear = (iso) => { const d = iso ? new Date(iso) : null; return d && !Number.isNaN(d.getTime()) ? d.toLocaleDateString('en-US', { month: 'short', year: 'numeric', timeZone: 'UTC' }) : null; };
@@ -238,7 +240,7 @@ function forumTopicPage({ space, thread, posts, page, pages, perPage = 50, viewe
@@ -238,7 +240,7 @@ function forumTopicPage({ space, thread, posts, page, pages, perPage = 50, viewe
<h1>${thread.pinned ? '<i class="fa-solid fa-thumbtack" title="Pinned" aria-hidden="true"></i><span class="sr-only">Pinned</span> ' : ''}${thread.locked ? '<i class="fa-solid fa-lock" title="Locked" aria-hidden="true"></i><span class="sr-only">Locked</span> ' : ''}${esc(thread.title)}${f.vipBadge(thread)}</h1>
<h1>${thread.pinned ? '<i class="fa-solid fa-thumbtack" title="Pinned" aria-hidden="true"></i><span class="sr-only">Pinned</span> ' : ''}${thread.locked ? '<i class="fa-solid fa-lock" title="Locked" aria-hidden="true"></i><span class="sr-only">Locked</span> ' : ''}${esc(thread.title)}${f.vipBadge(thread)}</h1>
${thread.status || thread.category ? `<p class="thread-tags">${f.statusBadge(thread)}${f.categoryBadge(thread, space)}</p>` : ''}
${thread.status || thread.category ? `<p class="thread-tags">${f.statusBadge(thread)}${f.categoryBadge(thread, space)}</p>` : ''}
${labelsUi.chips(labels)}
${labelsUi.chips(labels)}
- <p class="small muted">${plural(thread.reply_count, 'reply', 'replies')} · ${plural(thread.views, 'view', 'views')} · <a class="rss-link" href="${esc(base)}/feed.xml" title="RSS feed of this thread"><i class="fa-solid fa-rss" aria-hidden="true"></i> Follow this thread by RSS</a></p>
+ <p class="small muted">${plural(thread.reply_count, 'reply', 'replies')} · ${plural(thread.views, 'view', 'views')} · <a class="rss-link" href="${esc(base)}/feed.xml" title="RSS feed of this thread"><i class="fa-solid fa-rss" aria-hidden="true"></i> Follow this thread by RSS</a> ${kit.askAgent({ text: `About the forum thread “${thread.title}” (${config.baseUrl}${base}): ` })}</p>
</div>
</div>
</header>
</header>
${f.crosspostBlock(crosspost, base)}
${f.crosspostBlock(crosspost, base)}
@@ -262,6 +264,7 @@ function forumTopicPage({ space, thread, posts, page, pages, perPage = 50, viewe
@@ -262,6 +264,7 @@ function forumTopicPage({ space, thread, posts, page, pages, perPage = 50, viewe
historyType: 'page',
historyType: 'page',
historyTitle: thread.title,
historyTitle: thread.title,
footerVariant: 'compact',
footerVariant: 'compact',
+ styles: ['kit.css'],
...(labelBox ? { pageCss: ['labels.css'] } : {}),
...(labelBox ? { pageCss: ['labels.css'] } : {}),
feeds: [
feeds: [
...(space.visibility === 'public' && !space.members_only ? [{ title: `OpenVibe.Community — ${space.name}`, href: `/forum/${space.slug}/feed.xml` }] : []),
...(space.visibility === 'public' && !space.members_only ? [{ title: `OpenVibe.Community — ${space.name}`, href: `/forum/${space.slug}/feed.xml` }] : []),
@@ -13,6 +13,8 @@ const { markdownToText } = require('./markdown');
@@ -13,6 +13,8 @@ const { markdownToText } = require('./markdown');
const labelsUi = require('./labels');
const labelsUi = require('./labels');
const { timeTag, fmtDate, num, authorHtml } = require('./pages');
const { timeTag, fmtDate, num, authorHtml } = require('./pages');
const { RULES: MODERATION_RULES } = require('../forum/moderation');
const { RULES: MODERATION_RULES } = require('../forum/moderation');
+const kit = require('openvibe-shared/kit');
+const config = require('../config');
const SORT_LABELS = { hot: 'Hot', new: 'New', top: 'Top' };
const SORT_LABELS = { hot: 'Hot', new: 'New', top: 'Top' };
const STATUS_LABELS = { open: 'Open', planned: 'Planned', in_progress: 'In progress', done: 'Done', declined: 'Declined', paused: 'Paused' };
const STATUS_LABELS = { open: 'Open', planned: 'Planned', in_progress: 'In progress', done: 'Done', declined: 'Declined', paused: 'Paused' };
@@ -300,7 +302,7 @@ function threadPage({ space, thread, posts, page, pages, perPage = 50, viewer, u
@@ -300,7 +302,7 @@ function threadPage({ space, thread, posts, page, pages, perPage = 50, viewer, u
${thread.status || thread.category ? `<p class="thread-tags">${statusBadge(thread)}${categoryBadge(thread, space)}</p>` : ''}
${thread.status || thread.category ? `<p class="thread-tags">${statusBadge(thread)}${categoryBadge(thread, space)}</p>` : ''}
${labelsUi.chips(labels)}
${labelsUi.chips(labels)}
<p class="paste-meta">${who(thread.author)} <span class="sep">·</span> <time datetime="${esc(thread.created_at || '')}">${esc(fmtDate(thread.created_at))}</time> <span class="sep">·</span> <span class="stat"><i class="fa-solid fa-comment" aria-hidden="true"></i> ${num(thread.reply_count)} ${thread.reply_count === 1 ? 'reply' : 'replies'}</span></p>
<p class="paste-meta">${who(thread.author)} <span class="sep">·</span> <time datetime="${esc(thread.created_at || '')}">${esc(fmtDate(thread.created_at))}</time> <span class="sep">·</span> <span class="stat"><i class="fa-solid fa-comment" aria-hidden="true"></i> ${num(thread.reply_count)} ${thread.reply_count === 1 ? 'reply' : 'replies'}</span></p>
- <p class="thread-follow"><a class="rss-link" href="${esc(base)}/feed.xml" title="RSS feed of this thread"><i class="fa-solid fa-rss" aria-hidden="true"></i> Follow this thread by RSS</a></p>
+ <p class="thread-follow"><a class="rss-link" href="${esc(base)}/feed.xml" title="RSS feed of this thread"><i class="fa-solid fa-rss" aria-hidden="true"></i> Follow this thread by RSS</a> ${kit.askAgent({ text: `About the forum thread “${thread.title}” (${config.baseUrl}${base}): ` })}</p>
</div>
</div>
</header>
</header>
${crosspostBlock(crosspost, base)}
${crosspostBlock(crosspost, base)}
@@ -323,6 +325,7 @@ function threadPage({ space, thread, posts, page, pages, perPage = 50, viewer, u
@@ -323,6 +325,7 @@ function threadPage({ space, thread, posts, page, pages, perPage = 50, viewer, u
historyType: 'page',
historyType: 'page',
historyTitle: thread.title,
historyTitle: thread.title,
footerVariant: 'compact',
footerVariant: 'compact',
+ styles: ['kit.css'],
...(labelBox ? { pageCss: ['labels.css'] } : {}),
...(labelBox ? { pageCss: ['labels.css'] } : {}),
feeds: [
feeds: [
...(space.visibility === 'public' && !space.members_only ? [{ title: `OpenVibe.Community — ${space.name}`, href: `/forum/${space.slug}/feed.xml` }] : []),
...(space.visibility === 'public' && !space.members_only ? [{ title: `OpenVibe.Community — ${space.name}`, href: `/forum/${space.slug}/feed.xml` }] : []),
@@ -64,6 +64,13 @@ const form = (obj) => ({ method: 'POST', body: new URLSearchParams(obj).toString
@@ -64,6 +64,13 @@ const form = (obj) => ({ method: 'POST', body: new URLSearchParams(obj).toString
assert.strictEqual(r.status, 200);
assert.strictEqual(r.status, 200);
has(r.text, '<title>How do I <b>embed</b> comments? · OpenVibe.Community</title>');
has(r.text, '<title>How do I <b>embed</b> comments? · OpenVibe.Community</title>');
has(r.text, `<link rel="canonical" href="https://openvibe.community${url}">`);
has(r.text, `<link rel="canonical" href="https://openvibe.community${url}">`);
+ // The Ask your agent chip (openvibe-shared 4.135.0): in the thread's action row, its title escaped, its address.
+ const asks = r.text.match(/<button[^>]*data-ov-ask[^>]*>/g) || [];
+ assert.strictEqual(asks.length, 1, 'one Ask your agent chip: ' + asks.join(' | '));
+ assert.ok(asks[0].includes('data-ov-ask="About the forum thread “How do I <b>embed</b> comments?” (https://openvibe.community/forum/general/how-do-i-b-embed-b-comments): "'), asks[0]);
+ assert.ok(asks[0].includes('class="ovk-ask ovk-btn'), 'the shared kit chip');
+ assert.ok(!asks[0].includes('<b>'), 'the title is escaped in the chip');
+ has(r.text, '/shared/kit.css', 'the kit’s look is on the page');
has(r.text, '<meta property="og:type" content="article">');
has(r.text, '<meta property="og:type" content="article">');
has(r.text, '<meta property="article:published_time"');
has(r.text, '<meta property="article:published_time"');
has(r.text, '<strong>Live</strong>');
has(r.text, '<strong>Live</strong>');
@@ -60,6 +60,15 @@ const forumStore = require('../server/forum/store');
@@ -60,6 +60,15 @@ const forumStore = require('../server/forum/store');
assert.strictEqual(forumStore.slugify('☃☃☃'), 'thread');
assert.strictEqual(forumStore.slugify('☃☃☃'), 'thread');
});
});
+ await check('a board-style thread page carries one Ask your agent chip in its action row', async () => {
+ const page = await t.get('/forum/general/hello-world');
+ assert.strictEqual(page.status, 200);
+ const asks = page.text.match(/<button[^>]*data-ov-ask[^>]*>/g) || [];
+ assert.strictEqual(asks.length, 1, 'one Ask your agent chip: ' + asks.join(' | '));
+ assert.ok(asks[0].includes('data-ov-ask="About the forum thread “Hello, World!” (https://openvibe.community/forum/general/hello-world): "'), asks[0]);
+ assert.ok(asks[0].includes('class="ovk-ask ovk-btn'), 'the shared kit chip');
+ });
+
await check('a title that makes one of the forum\'s own route words takes -2, so /forum/<space>/<slug> stays a thread', async () => {
await check('a title that makes one of the forum\'s own route words takes -2, so /forum/<space>/<slug> stays a thread', async () => {
for (const [title, slug] of [['New', 'new-2'], ['Log', 'log-2'], ['Settings', 'settings-2'], ['Members', 'members-2'], ['Chat room', 'chat-room-2'], ['Discuss', 'discuss-2']]) {
for (const [title, slug] of [['New', 'new-2'], ['Log', 'log-2'], ['Settings', 'settings-2'], ['Members', 'members-2'], ['Chat room', 'chat-room-2'], ['Discuss', 'discuss-2']]) {
const r = await call('/api/v1/spaces/general/threads', { method: 'POST', cookie: alexJwt, json: { title, body: 'reserved word' } });
const r = await call('/api/v1/spaces/general/threads', { method: 'POST', cookie: alexJwt, json: { title, body: 'reserved word' } });