@hanzo/rooms 0.1.82 → 0.1.84

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,36 @@
1
+ /**
2
+ * A title fit to show: a conversation's or a run's title as the server kept it,
3
+ * less the markup a first message can carry (`<workspace-files>{…}</workspace-files>`,
4
+ * a lone tag, a leading JSON object), its whitespace one space, cut to `max`
5
+ * characters with an ellipsis; `fallback` when nothing readable is left.
6
+ */
7
+ export function tidy(title, max = 80, fallback = 'Untitled') {
8
+ let t = (title ?? '').replace(/<([a-z][\w-]*)[^>]*>[\s\S]*?<\/\1>/gi, ' ').replace(/<\/?[a-z][\w-]*[^>]*>/gi, ' ');
9
+ // A JSON object the message opened with, balanced, goes whole.
10
+ const m = t.match(/^\s*\{/);
11
+ if (m) {
12
+ let depth = 0;
13
+ let quoted = false;
14
+ for (let i = m[0].length - 1; i < t.length; i++) {
15
+ const c = t[i];
16
+ if (quoted) {
17
+ if (c === '\\')
18
+ i++;
19
+ else if (c === '"')
20
+ quoted = false;
21
+ }
22
+ else if (c === '"')
23
+ quoted = true;
24
+ else if (c === '{')
25
+ depth++;
26
+ else if (c === '}' && --depth === 0) {
27
+ t = t.slice(i + 1);
28
+ break;
29
+ }
30
+ }
31
+ }
32
+ t = t.replace(/\s+/g, ' ').trim();
33
+ if (!t)
34
+ return fallback;
35
+ return t.length > max ? `${t.slice(0, max - 1).trimEnd()}…` : t;
36
+ }
package/dist/seek.d.ts CHANGED
@@ -33,5 +33,14 @@ export declare function rank<T extends Hit>(rows: T[], query: string, cap?: numb
33
33
  * `CAP`, a group with nothing left out.
34
34
  */
35
35
  export declare function find<T extends Hit>(query: string, chats: T[], groups: Group<T>[]): Group<T>[];
36
+ /**
37
+ * The same search for any product: an empty field lists the newest `RECENT` of
38
+ * `recent` under its label (a product's own recents: Chat's conversations, Dev's
39
+ * runs), and a query ranks every group in the order given.
40
+ */
41
+ export declare function findIn<T extends Hit>(query: string, recent: {
42
+ label: string;
43
+ items: T[];
44
+ }, groups: Group<T>[]): Group<T>[];
36
45
  /** Whether a key press opens the palette: ⌘K or Ctrl+K, once a press, from anywhere, a field included. */
37
46
  export declare const summons: (e: Press) => boolean;
package/dist/seek.js CHANGED
@@ -43,13 +43,19 @@ export function rank(rows, query, cap = CAP) {
43
43
  * `CAP`, a group with nothing left out.
44
44
  */
45
45
  export function find(query, chats, groups) {
46
+ return findIn(query, { label: 'Recent chats', items: chats }, [{ id: 'chats', label: 'Chats', items: chats }, ...groups]);
47
+ }
48
+ /**
49
+ * The same search for any product: an empty field lists the newest `RECENT` of
50
+ * `recent` under its label (a product's own recents: Chat's conversations, Dev's
51
+ * runs), and a query ranks every group in the order given.
52
+ */
53
+ export function findIn(query, recent, groups) {
46
54
  if (!query.trim()) {
47
- const newest = [...chats].sort((a, b) => (b.at ?? '').localeCompare(a.at ?? '')).slice(0, RECENT);
48
- return newest.length ? [{ id: 'recent', label: 'Recent chats', items: newest }] : [];
55
+ const newest = [...recent.items].sort((a, b) => (b.at ?? '').localeCompare(a.at ?? '')).slice(0, RECENT);
56
+ return newest.length ? [{ id: 'recent', label: recent.label, items: newest }] : [];
49
57
  }
50
- return [{ id: 'chats', label: 'Chats', items: chats }, ...groups]
51
- .map((g) => ({ ...g, items: rank(g.items, query) }))
52
- .filter((g) => g.items.length);
58
+ return groups.map((g) => ({ ...g, items: rank(g.items, query) })).filter((g) => g.items.length);
53
59
  }
54
60
  /** Whether a key press opens the palette: ⌘K or Ctrl+K, once a press, from anywhere, a field included. */
55
61
  export const summons = (e) => (e.metaKey || e.ctrlKey) && !e.altKey && !e.shiftKey && !e.repeat && !e.isComposing && (e.key ?? '').toLowerCase() === 'k';
package/dist/thread.js CHANGED
@@ -20,6 +20,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
20
20
  // flight over what it last read, and reads the store again once it answers.
21
21
  // A pin is the server's, never this browser's: it follows the person to every
22
22
  // device they sign in on.
23
+ import { tidy } from './lib/title.js';
23
24
  import { createContext, useCallback, useContext, useEffect, useId, useMemo, useRef, useState, useSyncExternalStore, } from 'react';
24
25
  import { useAi } from '@hanzo/ai/react';
25
26
  import { Archive, ArchiveRestore, Clock, ExternalLink, Link2, MoreHorizontal, Pencil, Pin, PinOff, Share2, Trash2 } from 'lucide-react';
@@ -128,7 +129,7 @@ export function useThreadActions() {
128
129
  };
129
130
  }, [ai]);
130
131
  }
131
- const titleOf = (t) => t.title || 'Untitled';
132
+ const titleOf = (t) => tidy(t.title);
132
133
  /** A conversation's own address, whole: the app at `/?chat=<id>` (lib/host `talk`), wherever the rooms are drawn. */
133
134
  export const href = (id) => new URL(site(talk(id)), window.location.href).href;
134
135
  /** Opens a conversation in a tab of its own. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/rooms",
3
- "version": "0.1.82",
3
+ "version": "0.1.84",
4
4
  "description": "The workspace rooms — Home, Chat, Inbox, Contacts, Meet, Cal, Drive, Board, Work, Bots — and the frame that holds them, once, for every Hanzo surface.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",