openvibers/openvibe.community Commits
Search: an untitled paste is named after its AI summary's first sentence or its first line (Search listed 'Untitled' as a result's title) (#93)
Changes
@@@ -40,6 +40,18 @@ const plain = (md) => String(md || '').replace(/^[ \t]*(#{1,6}|>|[-*+]|\d+\.)[ \t]+/gm, '').replace(/[*_~`]+/g, '');const AUTHORSHIP = { ai: 'ai_generated', imported: 'imported', discord: 'imported' };++// A paste saved without a title is stored as 'Untitled' (pastes/store.js), and Search showed "Untitled" as a result's++// title (a YouTube-downloader search, 2026-10-17). Name it after the first sentence of its AI summary, or its first++// line, at most 90 characters; only a paste with neither stays Untitled.++function pasteTitle(p, shot) {++ const own = clean(p.title, 500);++ if (own && own !== 'Untitled') return own;++ const sentence = clean(p.ai_summary, 400).split(/(?<=[.!?])\s/)[0];++ const line = shot ? '' : clean(String(p.content || '').split('\n').map((l) => l.trim()).find(Boolean), 400);++ const pick = (sentence || line).replace(/[.!?]+$/, '');++ if (!pick) return shot ? 'Screenshot' : 'Untitled';++ return pick.length <= 90 ? pick : `${pick.slice(0, 89).replace(/\s+\S*$/, '')}…`;++}const hashOf = (doc) => crypto.createHash('sha256').update(JSON.stringify(doc)).digest('hex').slice(0, 32);const sqlTime = (ms) => new Date(ms).toISOString().replace('T', ' ').slice(0, 19);@@@ -91,7 +103,7 @@ function createSearchDocuments({ db }) {const doc = {owner: 'community', type: 'paste', id: pasteDocId(p), deleted: false, visibility: 'public',canonical_url: `${config.baseUrl}/p/${encodeURIComponent(p.slug)}`,-- title: clean(p.title, 500) || (shot ? 'Screenshot' : 'Untitled'),++ title: pasteTitle(p, shot),summary: clean(p.ai_summary, 4000) || clean(shot ? '' : p.content, 300) || `A ${shot ? 'screenshot' : 'paste'} on OpenVibe.Community.`,body: shot ? clean(p.ai_summary, 8000) : String(p.content || '').slice(0, 40000),facets, authorship: AUTHORSHIP[p.origin] || 'human', publication_state: 'published',@@@ -83,6 +83,17 @@ const { createSearchDocuments } = require('../server/search/documents');ok((await index()).at(-1)); assert.deepStrictEqual((await index()).at(-1).payload, { type: 'paste', id: 'amber-fox-42', revision: 2 });});++ await check('an untitled paste is named after its AI summary, or its first line', async () => {++ const row = async (id) => await db.prepare('SELECT * FROM pastes WHERE id = ?').get(id);++ const a = await pastes.insertPaste(db, { slug: 'grand-river-59', owner_subject: USR, type: 'paste', title: '', content: 'yt mp3 limits', visibility: 'public' });++ await db.prepare('UPDATE pastes SET ai_summary = ? WHERE id = ?').run('A note about a Tools tab offering YouTube MP3 downloaders with usage limits. It varies by level.', a.id);++ const b = await pastes.insertPaste(db, { slug: 'quiet-moss-11', owner_subject: USR, type: 'paste', title: '', content: '\n\n npm install openvibe-sdk \nmore', visibility: 'public' });++ assert.strictEqual(await docs.publishPaste(await row(a.id)), 'sent');++ assert.strictEqual((await index()).at(-1).payload.title, 'A note about a Tools tab offering YouTube MP3 downloaders with usage limits');++ assert.strictEqual(await docs.publishPaste(await row(b.id)), 'sent');++ assert.strictEqual((await index()).at(-1).payload.title, 'npm install openvibe-sdk');++ });++await check('the scan finds a paste changed since the last scan; the refresh removes one that is gone', async () => {const now = Date.now();await docs.scan({ now });
@@ -40,6 +40,18 @@ const plain = (md) => String(md || '')
@@ -40,6 +40,18 @@ const plain = (md) => String(md || '')
.replace(/^[ \t]*(#{1,6}|>|[-*+]|\d+\.)[ \t]+/gm, '')
.replace(/^[ \t]*(#{1,6}|>|[-*+]|\d+\.)[ \t]+/gm, '')
.replace(/[*_~`]+/g, '');
.replace(/[*_~`]+/g, '');
const AUTHORSHIP = { ai: 'ai_generated', imported: 'imported', discord: 'imported' };
const AUTHORSHIP = { ai: 'ai_generated', imported: 'imported', discord: 'imported' };
+// A paste saved without a title is stored as 'Untitled' (pastes/store.js), and Search showed "Untitled" as a result's
+// title (a YouTube-downloader search, 2026-10-17). Name it after the first sentence of its AI summary, or its first
+// line, at most 90 characters; only a paste with neither stays Untitled.
+function pasteTitle(p, shot) {
+ const own = clean(p.title, 500);
+ if (own && own !== 'Untitled') return own;
+ const sentence = clean(p.ai_summary, 400).split(/(?<=[.!?])\s/)[0];
+ const line = shot ? '' : clean(String(p.content || '').split('\n').map((l) => l.trim()).find(Boolean), 400);
+ const pick = (sentence || line).replace(/[.!?]+$/, '');
+ if (!pick) return shot ? 'Screenshot' : 'Untitled';
+ return pick.length <= 90 ? pick : `${pick.slice(0, 89).replace(/\s+\S*$/, '')}…`;
+}
const hashOf = (doc) => crypto.createHash('sha256').update(JSON.stringify(doc)).digest('hex').slice(0, 32);
const hashOf = (doc) => crypto.createHash('sha256').update(JSON.stringify(doc)).digest('hex').slice(0, 32);
const sqlTime = (ms) => new Date(ms).toISOString().replace('T', ' ').slice(0, 19);
const sqlTime = (ms) => new Date(ms).toISOString().replace('T', ' ').slice(0, 19);
@@ -91,7 +103,7 @@ function createSearchDocuments({ db }) {
@@ -91,7 +103,7 @@ function createSearchDocuments({ db }) {
const doc = {
const doc = {
owner: 'community', type: 'paste', id: pasteDocId(p), deleted: false, visibility: 'public',
owner: 'community', type: 'paste', id: pasteDocId(p), deleted: false, visibility: 'public',
canonical_url: `${config.baseUrl}/p/${encodeURIComponent(p.slug)}`,
canonical_url: `${config.baseUrl}/p/${encodeURIComponent(p.slug)}`,
- title: clean(p.title, 500) || (shot ? 'Screenshot' : 'Untitled'),
+ title: pasteTitle(p, shot),
summary: clean(p.ai_summary, 4000) || clean(shot ? '' : p.content, 300) || `A ${shot ? 'screenshot' : 'paste'} on OpenVibe.Community.`,
summary: clean(p.ai_summary, 4000) || clean(shot ? '' : p.content, 300) || `A ${shot ? 'screenshot' : 'paste'} on OpenVibe.Community.`,
body: shot ? clean(p.ai_summary, 8000) : String(p.content || '').slice(0, 40000),
body: shot ? clean(p.ai_summary, 8000) : String(p.content || '').slice(0, 40000),
facets, authorship: AUTHORSHIP[p.origin] || 'human', publication_state: 'published',
facets, authorship: AUTHORSHIP[p.origin] || 'human', publication_state: 'published',
@@ -83,6 +83,17 @@ const { createSearchDocuments } = require('../server/search/documents');
@@ -83,6 +83,17 @@ const { createSearchDocuments } = require('../server/search/documents');
ok((await index()).at(-1)); assert.deepStrictEqual((await index()).at(-1).payload, { type: 'paste', id: 'amber-fox-42', revision: 2 });
ok((await index()).at(-1)); assert.deepStrictEqual((await index()).at(-1).payload, { type: 'paste', id: 'amber-fox-42', revision: 2 });
});
});
+ await check('an untitled paste is named after its AI summary, or its first line', async () => {
+ const row = async (id) => await db.prepare('SELECT * FROM pastes WHERE id = ?').get(id);
+ const a = await pastes.insertPaste(db, { slug: 'grand-river-59', owner_subject: USR, type: 'paste', title: '', content: 'yt mp3 limits', visibility: 'public' });
+ await db.prepare('UPDATE pastes SET ai_summary = ? WHERE id = ?').run('A note about a Tools tab offering YouTube MP3 downloaders with usage limits. It varies by level.', a.id);
+ const b = await pastes.insertPaste(db, { slug: 'quiet-moss-11', owner_subject: USR, type: 'paste', title: '', content: '\n\n npm install openvibe-sdk \nmore', visibility: 'public' });
+ assert.strictEqual(await docs.publishPaste(await row(a.id)), 'sent');
+ assert.strictEqual((await index()).at(-1).payload.title, 'A note about a Tools tab offering YouTube MP3 downloaders with usage limits');
+ assert.strictEqual(await docs.publishPaste(await row(b.id)), 'sent');
+ assert.strictEqual((await index()).at(-1).payload.title, 'npm install openvibe-sdk');
+ });
+
await check('the scan finds a paste changed since the last scan; the refresh removes one that is gone', async () => {
await check('the scan finds a paste changed since the last scan; the refresh removes one that is gone', async () => {
const now = Date.now();
const now = Date.now();
await docs.scan({ now });
await docs.scan({ now });