openvibers/openvibe.chat Commits
Chat: the front page says what OpenVibe.Chat does, under the global room (#88)
Changes
@@@ -28,6 +28,8 @@ a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible.oc-tabs a[aria-current="page"], .oc-tabs a:hover { color: var(--oc-text); background: var(--oc-panel); }.oc-account { font-size: 14px; color: var(--oc-muted); margin-left: auto; }.oc-main { max-width: 960px; margin: 0 auto; padding: 16px 16px 48px; overflow-wrap: anywhere; }++/* The front page's showcase section under the room: the page column already has the gutter. */++.oc-main .sc-sec { padding-left: 0; padding-right: 0; margin-top: 40px; }h1 { font-size: 1.6rem; line-height: 1.25; margin: .3em 0 .2em; }.oc-muted { color: var(--oc-muted); }.oc-live { color: var(--oc-ok); font-size: .85em; margin-left: 6px; }@@@ -91,6 +91,7 @@ ${head}${appIcon.headTags({ site: 'network' })}<link rel="stylesheet" href="${asset('chat.css')}"><link rel="stylesheet" href="${ovServe.url('items.css')}">++${o.showcase ? `<link rel="stylesheet" href="${ovServe.url('showcase.css')}">` : ''}<script src="${ovServe.url('theme-loader.js')}" defer></script><script src="${ovServe.url('navbar.js')}" defer></script><script src="${ovServe.url('footer.js')}" defer></script>@@@ -26,6 +26,7 @@* tables; the only openvibe.live URLs are links to people's profiles (navigation, never fetched).* test/live-down.test.js renders every page and uses the core APIs with Live refusing and hanging.*/++const showcase = require('openvibe-shared/showcase');const path = require('path');const express = require('express');const rateLimit = require('express-rate-limit');@@@ -143,6 +144,21 @@ function createWebRoutes({ config }) {const n = async (fn) => { try { return Number(await fn()) || 0; } catch { return 0; } };return { messages: await n(async () => await dm.getTotalUnread(actor.user.id)), rooms: await n(async () => await require('../rooms/rooms').unreadTotal(actor.user)) };};++ // Under the global room on the front page: what openvibe.chat is for (openvibe-shared/showcase), each card a real++ // place to go. Every line is true today (calls run since CHAT_CALLS=1; rooms, messages and settings are pages here).++ const whatChatDoes = (signedIn) => showcase.features({++ id: 'what-chat-does', title: 'What OpenVibe.Chat does',++ lede: 'The chat behind every OpenVibe site, with one account and one set of preferences everywhere.',++ items: [++ { icon: 'ov:chat', title: 'One room for the network', text: 'Global chat reaches everyone on Live, Tools, Games and the rest, and reads without an account.', href: '#oc-h' },++ { icon: 'ov:live', title: 'Stream chat on Live', text: 'Every channel on OpenVibe.Live has its room, with the streamer\'s moderators, timeouts, bans, emotes and text to speech.', href: 'https://openvibe.live/' },++ { icon: 'ov:community', title: 'Rooms of your own', text: 'Start a public room anyone can read, or a private one that stays invisible to everyone else.', href: '/rooms' },++ { icon: 'ov:account', title: 'Direct messages', text: 'One-to-one and group conversations. You choose who may start one, and whether groups may add you.', href: signedIn ? '/messages' : '/auth/login?next=%2Fmessages' },++ { icon: 'ov:stream', title: 'Voice and video calls', text: 'Call a person, open a call room, or join a stream\'s call: up to eight people, with mute, camera and kick controls.', href: '/rooms' },++ { icon: 'ov:theme', title: 'Your preferences everywhere', text: 'Compact mode, timestamps, text size, badges and emotes follow your account to every site.', href: signedIn ? '/settings' : '/auth/login?next=%2Fsettings' },++ ],++ });++const page = async (req, res, status, o) => {const actor = o.actor || await viewerOf(req);res.status(status).set('Cache-Control', o.cache || cache.htmlHeaders({ private: true })).type('html')@@@ -208,7 +224,9 @@ function createWebRoutes({ config }) {${error}<ol class="oc-feed" id="oc-feed" aria-live="polite" aria-label="Messages">${shown.map(globalMessage).join('\n') || '<li class="oc-empty oc-muted">No messages yet. Say hello.</li>'}</ol>${composer}--</section>`,++</section>++${whatChatDoes(actor.kind === 'user')}`,++ showcase: true,});});@@@ -43,6 +43,13 @@ t('global chat reads without JavaScript; signing in shows the composer', async (assert.ok(!/id="oc-compose"/.test(r.text), 'no composer for a guest');const me = await req('GET', '/', { headers: cookie(alice) });assert.match(me.text, /id="oc-compose"/);++ // Under the room, what openvibe.chat does: six cards, each a place to go, with the showcase stylesheet. A guest's++ // messages and settings cards go through sign-in; a signed-in person's go straight there.++ const what = (/<section class="sc-sec" id="what-chat-does"[\s\S]*?<\/section>/.exec(r.text) || [''])[0];++ assert.strictEqual((what.match(/class="sc-card"/g) || []).length, 6, 'six cards');++ assert.ok(what.includes('href="/rooms"') && what.includes('href="/auth/login?next=%2Fmessages"'), what.slice(0, 300));++ assert.match(r.text, /<link rel="stylesheet" href="\/shared\/showcase\.css\?v=/);++ assert.ok(me.text.includes('href="/messages"') && me.text.includes('href="/settings"'), 'signed in: straight to messages and settings');});t('the composer posts without JavaScript, through the API\'s checks; other origins are refused', async () => {
@@ -28,6 +28,8 @@ a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible
@@ -28,6 +28,8 @@ a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible
.oc-tabs a[aria-current="page"], .oc-tabs a:hover { color: var(--oc-text); background: var(--oc-panel); }
.oc-tabs a[aria-current="page"], .oc-tabs a:hover { color: var(--oc-text); background: var(--oc-panel); }
.oc-account { font-size: 14px; color: var(--oc-muted); margin-left: auto; }
.oc-account { font-size: 14px; color: var(--oc-muted); margin-left: auto; }
.oc-main { max-width: 960px; margin: 0 auto; padding: 16px 16px 48px; overflow-wrap: anywhere; }
.oc-main { max-width: 960px; margin: 0 auto; padding: 16px 16px 48px; overflow-wrap: anywhere; }
+/* The front page's showcase section under the room: the page column already has the gutter. */
+.oc-main .sc-sec { padding-left: 0; padding-right: 0; margin-top: 40px; }
h1 { font-size: 1.6rem; line-height: 1.25; margin: .3em 0 .2em; }
h1 { font-size: 1.6rem; line-height: 1.25; margin: .3em 0 .2em; }
.oc-muted { color: var(--oc-muted); }
.oc-muted { color: var(--oc-muted); }
.oc-live { color: var(--oc-ok); font-size: .85em; margin-left: 6px; }
.oc-live { color: var(--oc-ok); font-size: .85em; margin-left: 6px; }
@@ -91,6 +91,7 @@ ${head}
@@ -91,6 +91,7 @@ ${head}
${appIcon.headTags({ site: 'network' })}
${appIcon.headTags({ site: 'network' })}
<link rel="stylesheet" href="${asset('chat.css')}">
<link rel="stylesheet" href="${asset('chat.css')}">
<link rel="stylesheet" href="${ovServe.url('items.css')}">
<link rel="stylesheet" href="${ovServe.url('items.css')}">
+${o.showcase ? `<link rel="stylesheet" href="${ovServe.url('showcase.css')}">` : ''}
<script src="${ovServe.url('theme-loader.js')}" defer></script>
<script src="${ovServe.url('theme-loader.js')}" defer></script>
<script src="${ovServe.url('navbar.js')}" defer></script>
<script src="${ovServe.url('navbar.js')}" defer></script>
<script src="${ovServe.url('footer.js')}" defer></script>
<script src="${ovServe.url('footer.js')}" defer></script>
@@ -26,6 +26,7 @@
@@ -26,6 +26,7 @@
* tables; the only openvibe.live URLs are links to people's profiles (navigation, never fetched).
* tables; the only openvibe.live URLs are links to people's profiles (navigation, never fetched).
* test/live-down.test.js renders every page and uses the core APIs with Live refusing and hanging.
* test/live-down.test.js renders every page and uses the core APIs with Live refusing and hanging.
*/
*/
+const showcase = require('openvibe-shared/showcase');
const path = require('path');
const path = require('path');
const express = require('express');
const express = require('express');
const rateLimit = require('express-rate-limit');
const rateLimit = require('express-rate-limit');
@@ -143,6 +144,21 @@ function createWebRoutes({ config }) {
@@ -143,6 +144,21 @@ function createWebRoutes({ config }) {
const n = async (fn) => { try { return Number(await fn()) || 0; } catch { return 0; } };
const n = async (fn) => { try { return Number(await fn()) || 0; } catch { return 0; } };
return { messages: await n(async () => await dm.getTotalUnread(actor.user.id)), rooms: await n(async () => await require('../rooms/rooms').unreadTotal(actor.user)) };
return { messages: await n(async () => await dm.getTotalUnread(actor.user.id)), rooms: await n(async () => await require('../rooms/rooms').unreadTotal(actor.user)) };
};
};
+ // Under the global room on the front page: what openvibe.chat is for (openvibe-shared/showcase), each card a real
+ // place to go. Every line is true today (calls run since CHAT_CALLS=1; rooms, messages and settings are pages here).
+ const whatChatDoes = (signedIn) => showcase.features({
+ id: 'what-chat-does', title: 'What OpenVibe.Chat does',
+ lede: 'The chat behind every OpenVibe site, with one account and one set of preferences everywhere.',
+ items: [
+ { icon: 'ov:chat', title: 'One room for the network', text: 'Global chat reaches everyone on Live, Tools, Games and the rest, and reads without an account.', href: '#oc-h' },
+ { icon: 'ov:live', title: 'Stream chat on Live', text: 'Every channel on OpenVibe.Live has its room, with the streamer\'s moderators, timeouts, bans, emotes and text to speech.', href: 'https://openvibe.live/' },
+ { icon: 'ov:community', title: 'Rooms of your own', text: 'Start a public room anyone can read, or a private one that stays invisible to everyone else.', href: '/rooms' },
+ { icon: 'ov:account', title: 'Direct messages', text: 'One-to-one and group conversations. You choose who may start one, and whether groups may add you.', href: signedIn ? '/messages' : '/auth/login?next=%2Fmessages' },
+ { icon: 'ov:stream', title: 'Voice and video calls', text: 'Call a person, open a call room, or join a stream\'s call: up to eight people, with mute, camera and kick controls.', href: '/rooms' },
+ { icon: 'ov:theme', title: 'Your preferences everywhere', text: 'Compact mode, timestamps, text size, badges and emotes follow your account to every site.', href: signedIn ? '/settings' : '/auth/login?next=%2Fsettings' },
+ ],
+ });
+
const page = async (req, res, status, o) => {
const page = async (req, res, status, o) => {
const actor = o.actor || await viewerOf(req);
const actor = o.actor || await viewerOf(req);
res.status(status).set('Cache-Control', o.cache || cache.htmlHeaders({ private: true })).type('html')
res.status(status).set('Cache-Control', o.cache || cache.htmlHeaders({ private: true })).type('html')
@@ -208,7 +224,9 @@ function createWebRoutes({ config }) {
@@ -208,7 +224,9 @@ function createWebRoutes({ config }) {
${error}
${error}
<ol class="oc-feed" id="oc-feed" aria-live="polite" aria-label="Messages">${shown.map(globalMessage).join('\n') || '<li class="oc-empty oc-muted">No messages yet. Say hello.</li>'}</ol>
<ol class="oc-feed" id="oc-feed" aria-live="polite" aria-label="Messages">${shown.map(globalMessage).join('\n') || '<li class="oc-empty oc-muted">No messages yet. Say hello.</li>'}</ol>
${composer}
${composer}
-</section>`,
+</section>
+${whatChatDoes(actor.kind === 'user')}`,
+ showcase: true,
});
});
});
});
@@ -43,6 +43,13 @@ t('global chat reads without JavaScript; signing in shows the composer', async (
@@ -43,6 +43,13 @@ t('global chat reads without JavaScript; signing in shows the composer', async (
assert.ok(!/id="oc-compose"/.test(r.text), 'no composer for a guest');
assert.ok(!/id="oc-compose"/.test(r.text), 'no composer for a guest');
const me = await req('GET', '/', { headers: cookie(alice) });
const me = await req('GET', '/', { headers: cookie(alice) });
assert.match(me.text, /id="oc-compose"/);
assert.match(me.text, /id="oc-compose"/);
+ // Under the room, what openvibe.chat does: six cards, each a place to go, with the showcase stylesheet. A guest's
+ // messages and settings cards go through sign-in; a signed-in person's go straight there.
+ const what = (/<section class="sc-sec" id="what-chat-does"[\s\S]*?<\/section>/.exec(r.text) || [''])[0];
+ assert.strictEqual((what.match(/class="sc-card"/g) || []).length, 6, 'six cards');
+ assert.ok(what.includes('href="/rooms"') && what.includes('href="/auth/login?next=%2Fmessages"'), what.slice(0, 300));
+ assert.match(r.text, /<link rel="stylesheet" href="\/shared\/showcase\.css\?v=/);
+ assert.ok(me.text.includes('href="/messages"') && me.text.includes('href="/settings"'), 'signed in: straight to messages and settings');
});
});
t('the composer posts without JavaScript, through the API\'s checks; other origins are refused', async () => {
t('the composer posts without JavaScript, through the API\'s checks; other origins are refused', async () => {