@backmatter/core 0.0.26 → 0.0.27
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.
- package/package.json +1 -1
- package/src/preview-channel.ts +56 -0
- package/src/preview-client.ts +46 -3
- package/src/preview-pointer.ts +63 -0
- package/src/preview.ts +13 -0
package/package.json
CHANGED
package/src/preview-channel.ts
CHANGED
|
@@ -49,12 +49,38 @@ export type PreviewStatusMessage = Envelope & {
|
|
|
49
49
|
message?: string;
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
+
export function normalizePointerQuote(quote: string): string {
|
|
53
|
+
return quote.replace(/\s+/g, " ").trim().slice(0, 240);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type PreviewPointMessage = Envelope & {
|
|
57
|
+
type: "backmatter:point";
|
|
58
|
+
on: boolean;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export type PreviewGrabMessage = Envelope & {
|
|
62
|
+
type: "backmatter:grab";
|
|
63
|
+
blockId: string;
|
|
64
|
+
blockType: string | null;
|
|
65
|
+
blockIndex: number | null;
|
|
66
|
+
quote: string;
|
|
67
|
+
shift: boolean;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export type PreviewHighlightMessage = Envelope & {
|
|
71
|
+
type: "backmatter:highlight";
|
|
72
|
+
blockId: string | null;
|
|
73
|
+
};
|
|
74
|
+
|
|
52
75
|
export type PreviewChannelMessage =
|
|
53
76
|
| PreviewHelloMessage
|
|
54
77
|
| PreviewDocMessage
|
|
55
78
|
| PreviewNavigateMessage
|
|
56
79
|
| PreviewRemovedMessage
|
|
57
80
|
| PreviewStatusMessage
|
|
81
|
+
| PreviewPointMessage
|
|
82
|
+
| PreviewGrabMessage
|
|
83
|
+
| PreviewHighlightMessage
|
|
58
84
|
| PreviewHotkeyMessage;
|
|
59
85
|
|
|
60
86
|
export type PreviewTransport = {
|
|
@@ -139,6 +165,36 @@ export function parsePreviewMessage(data: unknown): PreviewChannelMessage | null
|
|
|
139
165
|
...(typeof data.message === "string" ? { message: data.message } : {}),
|
|
140
166
|
};
|
|
141
167
|
}
|
|
168
|
+
case "backmatter:point": {
|
|
169
|
+
if (typeof data.on !== "boolean") return null;
|
|
170
|
+
return { type: "backmatter:point", ...envelope, on: data.on };
|
|
171
|
+
}
|
|
172
|
+
case "backmatter:grab": {
|
|
173
|
+
if (typeof data.blockId !== "string" || data.blockId.length === 0) return null;
|
|
174
|
+
if (data.blockType !== null && typeof data.blockType !== "string") return null;
|
|
175
|
+
if (
|
|
176
|
+
data.blockIndex !== null &&
|
|
177
|
+
(typeof data.blockIndex !== "number" || !Number.isInteger(data.blockIndex) || data.blockIndex < 0)
|
|
178
|
+
) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
if (typeof data.quote !== "string" || typeof data.shift !== "boolean") return null;
|
|
182
|
+
return {
|
|
183
|
+
type: "backmatter:grab",
|
|
184
|
+
...envelope,
|
|
185
|
+
blockId: data.blockId,
|
|
186
|
+
blockType: data.blockType,
|
|
187
|
+
blockIndex: data.blockIndex,
|
|
188
|
+
quote: normalizePointerQuote(data.quote),
|
|
189
|
+
shift: data.shift,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
case "backmatter:highlight": {
|
|
193
|
+
if (data.blockId !== null && (typeof data.blockId !== "string" || data.blockId.length === 0)) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
return { type: "backmatter:highlight", ...envelope, blockId: data.blockId };
|
|
197
|
+
}
|
|
142
198
|
default:
|
|
143
199
|
return null;
|
|
144
200
|
}
|
package/src/preview-client.ts
CHANGED
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
// that only a tombstone (`raw: ""`) from SSE still wins.
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
|
+
normalizePointerQuote,
|
|
8
9
|
samePreviewRef,
|
|
9
10
|
type PreviewChannelMessage,
|
|
10
11
|
type PreviewChannelRef,
|
|
12
|
+
type PreviewGrabMessage,
|
|
11
13
|
type PreviewTransport,
|
|
12
14
|
} from "./preview-channel";
|
|
13
15
|
|
|
@@ -35,6 +37,8 @@ export type PreviewPageHandlers = {
|
|
|
35
37
|
onError?: (message: string) => void;
|
|
36
38
|
onRemoved?: () => void;
|
|
37
39
|
onNavigate?: (path: string) => void;
|
|
40
|
+
onPoint?: (on: boolean) => void;
|
|
41
|
+
onHighlight?: (blockId: string | null) => void;
|
|
38
42
|
};
|
|
39
43
|
|
|
40
44
|
type Job = {
|
|
@@ -47,6 +51,8 @@ export type PreviewPageSession = {
|
|
|
47
51
|
close(): void;
|
|
48
52
|
/** True after the first `doc` for this page's ref. */
|
|
49
53
|
editorAttached(): boolean;
|
|
54
|
+
grab(grab: Omit<PreviewGrabMessage, "type" | "v" | "ref">): void;
|
|
55
|
+
point(on: boolean): void;
|
|
50
56
|
};
|
|
51
57
|
|
|
52
58
|
/**
|
|
@@ -154,6 +160,12 @@ export function createPreviewPage(options: {
|
|
|
154
160
|
case "backmatter:navigate":
|
|
155
161
|
handlers.onNavigate?.(message.path);
|
|
156
162
|
break;
|
|
163
|
+
case "backmatter:point":
|
|
164
|
+
handlers.onPoint?.(message.on);
|
|
165
|
+
break;
|
|
166
|
+
case "backmatter:highlight":
|
|
167
|
+
handlers.onHighlight?.(message.blockId);
|
|
168
|
+
break;
|
|
157
169
|
default:
|
|
158
170
|
break;
|
|
159
171
|
}
|
|
@@ -186,6 +198,18 @@ export function createPreviewPage(options: {
|
|
|
186
198
|
|
|
187
199
|
return {
|
|
188
200
|
editorAttached: () => attached,
|
|
201
|
+
grab(grab) {
|
|
202
|
+
send({
|
|
203
|
+
type: "backmatter:grab",
|
|
204
|
+
v: 1,
|
|
205
|
+
ref: options.ref,
|
|
206
|
+
...grab,
|
|
207
|
+
quote: normalizePointerQuote(grab.quote),
|
|
208
|
+
});
|
|
209
|
+
},
|
|
210
|
+
point(on) {
|
|
211
|
+
send({ type: "backmatter:point", v: 1, ref: options.ref, on });
|
|
212
|
+
},
|
|
189
213
|
close() {
|
|
190
214
|
if (closed) return;
|
|
191
215
|
closed = true;
|
|
@@ -215,6 +239,8 @@ export type PreviewEditorSession = {
|
|
|
215
239
|
setDoc(doc: PreviewRenderDoc): void;
|
|
216
240
|
navigate(path: string): void;
|
|
217
241
|
removed(): void;
|
|
242
|
+
point(on: boolean): void;
|
|
243
|
+
highlight(blockId: string | null): void;
|
|
218
244
|
close(): void;
|
|
219
245
|
};
|
|
220
246
|
|
|
@@ -225,7 +251,9 @@ export type PreviewEditorSession = {
|
|
|
225
251
|
export function createPreviewEditor(options: {
|
|
226
252
|
transport: PreviewTransport;
|
|
227
253
|
ref: PreviewChannelRef | (() => PreviewChannelRef);
|
|
228
|
-
onHello?: (ref: PreviewChannelRef) => void;
|
|
254
|
+
onHello?: (ref: PreviewChannelRef, capabilities?: string[]) => void;
|
|
255
|
+
onGrab?: (grab: PreviewGrabMessage) => void;
|
|
256
|
+
onPoint?: (on: boolean) => void;
|
|
229
257
|
onStatus?: (status: { seq: number; state: "rendered" | "error"; message?: string }) => void;
|
|
230
258
|
schedule?: PreviewSchedule;
|
|
231
259
|
}): PreviewEditorSession {
|
|
@@ -254,14 +282,21 @@ export function createPreviewEditor(options: {
|
|
|
254
282
|
|
|
255
283
|
const unsubscribe = options.transport.subscribe((message) => {
|
|
256
284
|
if (closed) return;
|
|
257
|
-
if (message.type === "backmatter:hello")
|
|
258
|
-
|
|
285
|
+
if (message.type === "backmatter:hello") {
|
|
286
|
+
options.onHello?.(message.ref, message.capabilities);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (message.type === "backmatter:hotkey") return;
|
|
290
|
+
if (!samePreviewRef(message.ref, currentRef())) return;
|
|
291
|
+
if (message.type === "backmatter:status") {
|
|
259
292
|
options.onStatus?.({
|
|
260
293
|
seq: message.seq,
|
|
261
294
|
state: message.state,
|
|
262
295
|
...(message.message ? { message: message.message } : {}),
|
|
263
296
|
});
|
|
264
297
|
}
|
|
298
|
+
if (message.type === "backmatter:grab") options.onGrab?.(message);
|
|
299
|
+
if (message.type === "backmatter:point") options.onPoint?.(message.on);
|
|
265
300
|
});
|
|
266
301
|
|
|
267
302
|
return {
|
|
@@ -279,6 +314,14 @@ export function createPreviewEditor(options: {
|
|
|
279
314
|
if (closed) return;
|
|
280
315
|
options.transport.send({ type: "backmatter:removed", v: 1, ref: currentRef() });
|
|
281
316
|
},
|
|
317
|
+
point(on) {
|
|
318
|
+
if (closed) return;
|
|
319
|
+
options.transport.send({ type: "backmatter:point", v: 1, ref: currentRef(), on });
|
|
320
|
+
},
|
|
321
|
+
highlight(blockId) {
|
|
322
|
+
if (closed) return;
|
|
323
|
+
options.transport.send({ type: "backmatter:highlight", v: 1, ref: currentRef(), blockId });
|
|
324
|
+
},
|
|
282
325
|
close() {
|
|
283
326
|
if (closed) return;
|
|
284
327
|
closed = true;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// packages/core/src/preview-pointer.ts
|
|
2
|
+
|
|
3
|
+
export type PointerArmSource = "editor" | "devbar";
|
|
4
|
+
|
|
5
|
+
export type PointerHit = {
|
|
6
|
+
blockId: string | null;
|
|
7
|
+
blockType: string | null;
|
|
8
|
+
blockIndex: number | null;
|
|
9
|
+
quote: string;
|
|
10
|
+
shift: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type RoutedGrab = {
|
|
14
|
+
blockId: string;
|
|
15
|
+
blockType: string | null;
|
|
16
|
+
blockIndex: number | null;
|
|
17
|
+
quote: string;
|
|
18
|
+
shift: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type PointerRoute =
|
|
22
|
+
| { to: "editor"; grab: RoutedGrab }
|
|
23
|
+
| { to: "devbar"; grab: Omit<PointerHit, "shift"> }
|
|
24
|
+
| { to: "none" };
|
|
25
|
+
|
|
26
|
+
export function routePointerHit(source: PointerArmSource | null, hit: PointerHit): PointerRoute {
|
|
27
|
+
if (hit.shift && hit.blockId) {
|
|
28
|
+
return {
|
|
29
|
+
to: "editor",
|
|
30
|
+
grab: {
|
|
31
|
+
blockId: hit.blockId,
|
|
32
|
+
blockType: hit.blockType,
|
|
33
|
+
blockIndex: hit.blockIndex,
|
|
34
|
+
quote: hit.quote,
|
|
35
|
+
shift: true,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (source === "devbar") {
|
|
40
|
+
return {
|
|
41
|
+
to: "devbar",
|
|
42
|
+
grab: {
|
|
43
|
+
blockId: hit.blockId,
|
|
44
|
+
blockType: hit.blockType,
|
|
45
|
+
blockIndex: hit.blockIndex,
|
|
46
|
+
quote: hit.quote,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (source === "editor" && hit.blockId) {
|
|
51
|
+
return {
|
|
52
|
+
to: "editor",
|
|
53
|
+
grab: {
|
|
54
|
+
blockId: hit.blockId,
|
|
55
|
+
blockType: hit.blockType,
|
|
56
|
+
blockIndex: hit.blockIndex,
|
|
57
|
+
quote: hit.quote,
|
|
58
|
+
shift: false,
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return { to: "none" };
|
|
63
|
+
}
|
package/src/preview.ts
CHANGED
|
@@ -304,6 +304,7 @@ export {
|
|
|
304
304
|
createWindowPreviewTransport,
|
|
305
305
|
isAllowedPreviewOrigin,
|
|
306
306
|
normalizePreviewOrigin,
|
|
307
|
+
normalizePointerQuote,
|
|
307
308
|
parsePreviewMessage,
|
|
308
309
|
PREVIEW_CHANNEL_VERSION,
|
|
309
310
|
samePreviewRef,
|
|
@@ -313,14 +314,26 @@ export type {
|
|
|
313
314
|
PreviewChannelMessage,
|
|
314
315
|
PreviewChannelRef,
|
|
315
316
|
PreviewDocMessage,
|
|
317
|
+
PreviewGrabMessage,
|
|
316
318
|
PreviewHelloMessage,
|
|
319
|
+
PreviewHighlightMessage,
|
|
317
320
|
PreviewMessageEvent,
|
|
318
321
|
PreviewNavigateMessage,
|
|
322
|
+
PreviewPointMessage,
|
|
319
323
|
PreviewRemovedMessage,
|
|
320
324
|
PreviewStatusMessage,
|
|
321
325
|
PreviewTransport,
|
|
322
326
|
PreviewWindowLike,
|
|
323
327
|
} from "./preview-channel";
|
|
328
|
+
export {
|
|
329
|
+
routePointerHit,
|
|
330
|
+
} from "./preview-pointer";
|
|
331
|
+
export type {
|
|
332
|
+
PointerArmSource,
|
|
333
|
+
PointerHit,
|
|
334
|
+
PointerRoute,
|
|
335
|
+
RoutedGrab,
|
|
336
|
+
} from "./preview-pointer";
|
|
324
337
|
export { createPreviewEditor, createPreviewPage } from "./preview-client";
|
|
325
338
|
export type {
|
|
326
339
|
PreviewEditorSession,
|