openvibers/openvibe.chat Commits

No text arrows: drawn glyphs (Design rule 169) (#565)

OP OpenVibers committed 2 h ago · c0ba858533 · 10 files, +17 −10

* No text arrows: drawn glyphs in Chat, words in paths and logs

* Remove the committed node_modules link

Changes

ddiff --git a/public/web/feed-view.css b/public/web/feed-view.css
iindex ee2fe76..f0bc983 100644
---- a/public/web/feed-view.css
++++ b/public/web/feed-view.css
@@@ -23,6 +23,7 @@
.fv-prompt, .fv-answer, .fv-step, .fv-count, .fv-more, .fv-working { display: flex; gap: 7px; align-items: baseline; margin: 3px 0; min-width: 0; }
.fv-prompt { font-weight: 600; }
.fv-ic { flex: none; width: 1.1em; text-align: center; color: var(--fv-muted); }
++.fv-ic svg { width: 14px; height: 14px; vertical-align: -2px; }
.fv-prompt .fv-ic { color: var(--fv-accent); }
.fv-answer .fv-ic { color: var(--fv-ok); }
.fv-txt { overflow-wrap: anywhere; }
ddiff --git a/public/web/feed-view.js b/public/web/feed-view.js
iindex d726db9..aff2fde 100644
---- a/public/web/feed-view.js
++++ b/public/web/feed-view.js
@@@ -5,7 +5,8 @@
ones fold to their prompt. Everything is set with textContent. In a frame it tells the parent its height. */
(function () {
'use strict';
-- var ICON = { prompt: '›', response: '✓', thinking: '…', 'tool.call': '⚙', 'file.change': '✎', 'file.save': '✎', 'session.status': '•' };
++ var ICON = { response: '✓', thinking: '…', 'tool.call': '⚙', 'file.change': '✎', 'file.save': '✎', 'session.status': '•' };
++ var PROMPT_ICON = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="m9 6 6 6-6 6"/></svg>';
var LIVE_MS = 3 * 60 * 1000;
function el(tag, cls, text) { var e = document.createElement(tag); if (cls) e.className = cls; if (text != null) e.textContent = text; return e; }
function store(fn) { try { return fn(window.localStorage); } catch (e) { return null; } }
@@@ -75,7 +76,7 @@
turns.forEach(function (turn, i) {
var last = i === turns.length - 1;
var li = el('li', 'fv-turn' + (last ? ' fv-cur' : ''));
-- if (turn.prompt) { var p = el('p', 'fv-prompt'); p.appendChild(el('span', 'fv-ic', ICON.prompt)); p.appendChild(el('span', 'fv-txt', turn.prompt.summary)); li.appendChild(p); }
++ if (turn.prompt) { var p = el('p', 'fv-prompt'); var pi = el('span', 'fv-ic'); pi.innerHTML = PROMPT_ICON; p.appendChild(pi); p.appendChild(el('span', 'fv-txt', turn.prompt.summary)); li.appendChild(p); }
if (last || !turn.prompt) {
var steps = el('ol', 'fv-steps');
var shown = turn.steps.slice(-6);
ddiff --git a/public/web/party.css b/public/web/party.css
iindex 894c68f..46cea38 100644
---- a/public/web/party.css
++++ b/public/web/party.css
@@@ -36,6 +36,7 @@ main[data-party] [hidden] { display: none !important; }
.pt-title { flex: 1 1 auto; min-width: 0; margin: 0; font-size: 1.15rem; font-weight: 800; letter-spacing: -.01em; line-height: 1.25; overflow-wrap: anywhere; text-wrap: balance; }
.pt-from { flex: none; color: var(--pt-muted); font-size: 13.5px; text-decoration: none; }
.pt-from:hover, .pt-from:focus-visible { color: var(--pt-text); text-decoration: underline; }
++.pt-from .pt-open { margin-left: 5px; vertical-align: -2px; }
[data-party-invite] { flex: none; }
.pt-invite { min-height: 40px; padding: 0 18px; border: 0; border-radius: 999px; background: var(--pt-accent); color: var(--pt-on-accent); font: inherit; font-size: 14px; font-weight: 750; cursor: pointer; }
.pt-invite:hover { filter: brightness(1.08); }
ddiff --git a/public/web/party.js b/public/web/party.js
iindex b5b2506..4038102 100644
---- a/public/web/party.js
++++ b/public/web/party.js
@@@ -155,7 +155,8 @@
if (shellHead) head.classList.add('pt-head');
else head.appendChild(el('h1', 'pt-title', data.title || 'Watch party'));
if (data.source_url) {
-- var from = el('a', 'pt-from', 'Open on its page ↗');
++ var from = el('a', 'pt-from', 'Open on its page ');
++ from.insertAdjacentHTML('beforeend', '<svg class="pt-open" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M14 4h6v6M20 4l-9 9M18 14v4a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4"/></svg>');
from.href = data.source_url; from.target = '_blank'; from.rel = 'noopener';
// Beside "Started by …" when the shell has that line; otherwise in the header.
var by = shellHead && shellHead.querySelector('p');
ddiff --git a/server/chat/chat-server.js b/server/chat/chat-server.js
iindex e7bfbbe..d5e592f 100644
---- a/server/chat/chat-server.js
++++ b/server/chat/chat-server.js
@@@ -3336,7 +3336,7 @@ class ChatServer {
if (client.user?.id === streamOwnerId && client.streamId === streamId) {
this.sendTo(ws, {
type: 'system',
-- message: `⏳ New IP needs approval: ${username} (${ip}). Open Dashboard → Moderation to review.`,
++ message: `⏳ New IP needs approval: ${username} (${ip}). Open Dashboard, then Moderation, to review.`,
ip_approval_alert: true,
});
break;
ddiff --git a/server/chat/deploy-notice.js b/server/chat/deploy-notice.js
iindex 313a576..2177215 100644
---- a/server/chat/deploy-notice.js
++++ b/server/chat/deploy-notice.js
@@@ -130,7 +130,7 @@ function broadcast({ chatServer, saved, list, log = console }) {
}
return n;
};
-- ATTEMPTS_MS.forEach((ms, i) => { const t = setTimeout(() => { const n = push(); if (n) log.log(`[Deploy notice] ${list.length} commit(s) → ${n} client(s) (pass ${i + 1})`); }, ms); if (t.unref) t.unref(); });
++ ATTEMPTS_MS.forEach((ms, i) => { const t = setTimeout(() => { const n = push(); if (n) log.log(`[Deploy notice] ${list.length} commit(s) sent to ${n} client(s) (pass ${i + 1})`); }, ms); if (t.unref) t.unref(); });
}
/** The release a live.release.deployed envelope announces ({ head, commits }), or an 'ignored:*' outcome. No I/O. */
ddiff --git a/server/events/subscriptions.js b/server/events/subscriptions.js
iindex e3ea910..f16a7a1 100644
---- a/server/events/subscriptions.js
++++ b/server/events/subscriptions.js
@@@ -114,7 +114,7 @@ function startAtBoot({ config, port, fetchImpl, log = console, retryMs = BOOT_RE
if (stopped) return resolve(null);
try {
const results = await ensure({ config, endpoint, action: 'create', fetchImpl });
-- for (const r of results) log.log(`[Events] subscription ${r.result}: ${r.id} (${r.topic} → ${endpoint}${r.enabled === false ? ', DISABLED' : ''})`);
++ for (const r of results) log.log(`[Events] subscription ${r.result}: ${r.id} (${r.topic} to ${endpoint}${r.enabled === false ? ', DISABLED' : ''})`);
return resolve(results);
} catch (err) {
const next = retryMs[i + 1];
ddiff --git a/server/web/icons.js b/server/web/icons.js
iindex 6781019..2621d12 100644
---- a/server/web/icons.js
++++ b/server/web/icons.js
@@@ -7,6 +7,8 @@ const svg = (d) => `<svg class="oc-i" viewBox="0 0 24 24" aria-hidden="true" foc
const ICONS = {
back: svg('<path d="M15 5l-7 7 7 7"/>'),
++ up: svg('<path d="M12 20V4M6 10l6-6 6 6"/>'),
++ down: svg('<path d="M12 4v16M6 14l6 6 6-6"/>'),
send: svg('<path d="M12 19V5M5 12l7-7 7 7"/>'),
image: svg('<rect x="3" y="5" width="18" height="14" rx="3"/><circle cx="9" cy="10" r="1.6"/><path d="m21 16-5-5-8 8"/>'),
trash: svg('<path d="M4 7h16M10 11v6M14 11v6M6 7l1 12a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2l1-12M9 7V4h6v3"/>'),
ddiff --git a/server/web/pages.js b/server/web/pages.js
iindex a5d17f6..c219bfe 100644
---- a/server/web/pages.js
++++ b/server/web/pages.js
@@@ -31,6 +31,7 @@
* 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 { ICONS } = require('./icons');
const shell = require('./shell');
const path = require('path');
const express = require('express');
@@@ -671,7 +672,7 @@ ${composer}`,
await page(req, res, 200, {
actor, title, path: `/call/${ch.id}`, active: 'messages', robots: 'noindex, nofollow', callScript: true,
page: { view: 'call', me: me.id, call: { channel: ch.id, join: true, talk: true, ring: ringing ? { id: Number(ringing.id), name: nameOf(ringing) } : null } },
-- body: `<p><a href="${backLink}">← Back to the conversation</a></p>
++ body: `<p><a href="${backLink}">${ICONS.back} Back to the conversation</a></p>
<section class="oc-call oc-call-page" aria-labelledby="oc-call-h" id="oc-call">
<h1 id="oc-call-h">${esc(title)} <span class="oc-muted oc-call-count" id="oc-call-count">${ch.participantCount ? `· ${ch.participantCount} in` : ''}</span></h1>
<p class="oc-muted" id="oc-call-how">A voice call, peer to peer: your microphone goes only to the people in it.</p>
@@@ -1511,7 +1512,7 @@ ${entries.length ? `<ul class="oc-list">${entries.map(entryView).join('')}</ul>`
res.set('Cache-Control', 'no-store');
await page(req, res, 200, {
actor, title: `${room.name}: new hook`, path: req.path, active: 'rooms', robots: 'noindex, nofollow',
-- body: `<p><a href="/r/${esc(room.slug)}/settings#oc-hooks">← Room settings</a></p><h1>Hook "${esc(made.hook.label)}" is ready</h1>
++ body: `<p><a href="/r/${esc(room.slug)}/settings#oc-hooks">${ICONS.back} Room settings</a></p><h1>Hook "${esc(made.hook.label)}" is ready</h1>
${notice('ok', 'Copy the secret now: it is not shown again. Anyone with the address and the secret can post in the room.')}
<label class="oc-form">Address <input readonly value="${esc(address)}"></label>
<label class="oc-form">Secret <input readonly value="${esc(made.secret)}"></label>
ddiff --git a/server/web/server-pages.js b/server/web/server-pages.js
iindex 10a3a8e..7ee72b3 100644
---- a/server/web/server-pages.js
++++ b/server/web/server-pages.js
@@@ -525,7 +525,7 @@ ${roles.length ? `<fieldset class="oc-set oc-role-set"><legend id="oc-onboard-ro
const active = people.filter((p) => p.role !== 'blocked');
const banned = people.filter((p) => p.role === 'blocked');
const chanRow = (c) => `<li><span>${c.kind === 'forum' ? '▤' : c.kind === 'stage' ? '◉' : c.kind === 'voice' ? VOICE_ICON : '#'} <strong>${esc(c.name)}</strong>${c.topic ? ` <span class="oc-muted">${esc(c.topic)}</span>` : ''}</span>
--<form class="oc-inline oc-moves" method="post" action="/s/${esc(server.slug)}/channels/${esc(c.slug)}"><button type="submit" name="move" value="up" class="oc-move" aria-label="Move ${esc(c.name)} up" title="Move up">↑</button><button type="submit" name="move" value="down" class="oc-move" aria-label="Move ${esc(c.name)} down" title="Move down">↓</button></form>
++<form class="oc-inline oc-moves" method="post" action="/s/${esc(server.slug)}/channels/${esc(c.slug)}"><button type="submit" name="move" value="up" class="oc-move" aria-label="Move ${esc(c.name)} up" title="Move up">${IC.up}</button><button type="submit" name="move" value="down" class="oc-move" aria-label="Move ${esc(c.name)} down" title="Move down">${IC.down}</button></form>
<details class="oc-chan-edit"><summary>Edit</summary><form class="oc-form" method="post" action="/s/${esc(server.slug)}/channels/${esc(c.slug)}">
<label>Name <input name="name" required maxlength="40" value="${esc(c.name)}"></label>
<label>Topic <input name="topic" maxlength="200" value="${esc(c.topic || '')}"></label>
@@@ -545,7 +545,7 @@ ${can.manage && c.kind !== 'voice' && require('../rooms/ai-members').enabled() ?
you: me ? { name: me.display_name || me.username, status: 'Online', avatarUrl: me.avatar_url || '' } : {},
}),
sideLabel: server.name,
-- main: `<p><a href="/s/${esc(server.slug)}">← ${esc(server.name)}</a></p><h1>Server settings</h1>${saved}${errorOf(req)}
++ main: `<p><a href="/s/${esc(server.slug)}">${IC.back} ${esc(server.name)}</a></p><h1>Server settings</h1>${saved}${errorOf(req)}
${can.manage ? `<h2>Overview</h2><form class="oc-form" method="post" action="/s/${esc(server.slug)}/settings">
<label>Name <input name="name" required minlength="2" maxlength="40" value="${esc(server.name)}"></label>
<label>Description <input name="description" maxlength="300" value="${esc(server.description || '')}"></label>