@dxos/plugin-thread 0.6.12-main.7907542 → 0.6.12-main.ac23639
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/dist/lib/browser/{ThreadArticle-4VU752F4.mjs → ThreadArticle-GBPIH47W.mjs} +3 -3
- package/dist/lib/browser/{ThreadMain-ML3P4XSD.mjs → ThreadMain-PGF7W5EV.mjs} +3 -3
- package/dist/lib/browser/{chunk-7NUYW5IW.mjs → chunk-5G3KL6TC.mjs} +4 -6
- package/dist/lib/browser/chunk-5G3KL6TC.mjs.map +7 -0
- package/dist/lib/browser/{chunk-GJRBBPXI.mjs → chunk-JFQZ5W2I.mjs} +2 -2
- package/dist/lib/browser/{chunk-GJRBBPXI.mjs.map → chunk-JFQZ5W2I.mjs.map} +1 -1
- package/dist/lib/browser/{chunk-XF5QKI75.mjs → chunk-O4YRHIZC.mjs} +2 -2
- package/dist/lib/browser/index.mjs +168 -148
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/meta.mjs +1 -1
- package/dist/lib/browser/types.mjs +2 -2
- package/dist/lib/node/{ThreadArticle-ERUHPHVT.cjs → ThreadArticle-GH5UBWFU.cjs} +7 -7
- package/dist/lib/node/{ThreadMain-6DN7FDNB.cjs → ThreadMain-CO6I3DNX.cjs} +7 -7
- package/dist/lib/node/{chunk-PZVUW4W2.cjs → chunk-FDYRG7HE.cjs} +9 -9
- package/dist/lib/node/{chunk-FXD3DYMO.cjs → chunk-FOZUQZF4.cjs} +6 -6
- package/dist/lib/node/{chunk-FXD3DYMO.cjs.map → chunk-FOZUQZF4.cjs.map} +1 -1
- package/dist/lib/node/{chunk-6EVXCSDS.cjs → chunk-JIJ7Z6NO.cjs} +6 -8
- package/dist/lib/node/chunk-JIJ7Z6NO.cjs.map +7 -0
- package/dist/lib/node/index.cjs +196 -178
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.cjs +4 -4
- package/dist/lib/node/meta.cjs.map +1 -1
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/types.cjs +3 -3
- package/dist/lib/node/types.cjs.map +1 -1
- package/dist/lib/node-esm/{ThreadArticle-NKI5F4W5.mjs → ThreadArticle-ZTYD52TR.mjs} +3 -3
- package/dist/lib/node-esm/{ThreadMain-2A3VQLCW.mjs → ThreadMain-LEHGOF7R.mjs} +3 -3
- package/dist/lib/node-esm/{chunk-Y475KZTX.mjs → chunk-DC5ZA6H5.mjs} +4 -6
- package/dist/lib/node-esm/chunk-DC5ZA6H5.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-A44C4MTQ.mjs → chunk-XOVJ65DG.mjs} +2 -2
- package/dist/lib/node-esm/{chunk-YO46JGAE.mjs → chunk-ZXIPFXSA.mjs} +2 -2
- package/dist/lib/node-esm/{chunk-YO46JGAE.mjs.map → chunk-ZXIPFXSA.mjs.map} +1 -1
- package/dist/lib/node-esm/index.mjs +168 -148
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/meta.mjs +1 -1
- package/dist/lib/node-esm/types.mjs +2 -2
- package/dist/types/src/ThreadPlugin.d.ts.map +1 -1
- package/dist/types/src/components/ThreadComplementary.d.ts +2 -2
- package/dist/types/src/components/ThreadComplementary.d.ts.map +1 -1
- package/dist/types/src/extensions/index.d.ts +2 -0
- package/dist/types/src/extensions/index.d.ts.map +1 -0
- package/dist/types/src/extensions/threads.d.ts +9 -0
- package/dist/types/src/extensions/threads.d.ts.map +1 -0
- package/dist/types/src/meta.d.ts +3 -6
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +5 -0
- package/dist/types/src/types.d.ts.map +1 -1
- package/package.json +28 -28
- package/src/ThreadPlugin.tsx +53 -136
- package/src/components/ThreadComplementary.tsx +4 -4
- package/src/extensions/index.ts +5 -0
- package/src/extensions/threads.ts +117 -0
- package/src/{meta.tsx → meta.ts} +3 -4
- package/src/types.ts +7 -2
- package/dist/lib/browser/chunk-7NUYW5IW.mjs.map +0 -7
- package/dist/lib/node/chunk-6EVXCSDS.cjs.map +0 -7
- package/dist/lib/node-esm/chunk-Y475KZTX.mjs.map +0 -7
- /package/dist/lib/browser/{ThreadArticle-4VU752F4.mjs.map → ThreadArticle-GBPIH47W.mjs.map} +0 -0
- /package/dist/lib/browser/{ThreadMain-ML3P4XSD.mjs.map → ThreadMain-PGF7W5EV.mjs.map} +0 -0
- /package/dist/lib/browser/{chunk-XF5QKI75.mjs.map → chunk-O4YRHIZC.mjs.map} +0 -0
- /package/dist/lib/node/{ThreadArticle-ERUHPHVT.cjs.map → ThreadArticle-GH5UBWFU.cjs.map} +0 -0
- /package/dist/lib/node/{ThreadMain-6DN7FDNB.cjs.map → ThreadMain-CO6I3DNX.cjs.map} +0 -0
- /package/dist/lib/node/{chunk-PZVUW4W2.cjs.map → chunk-FDYRG7HE.cjs.map} +0 -0
- /package/dist/lib/node-esm/{ThreadArticle-NKI5F4W5.mjs.map → ThreadArticle-ZTYD52TR.mjs.map} +0 -0
- /package/dist/lib/node-esm/{ThreadMain-2A3VQLCW.mjs.map → ThreadMain-LEHGOF7R.mjs.map} +0 -0
- /package/dist/lib/node-esm/{chunk-A44C4MTQ.mjs.map → chunk-XOVJ65DG.mjs.map} +0 -0
package/src/ThreadPlugin.tsx
CHANGED
|
@@ -2,12 +2,10 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { computed, effect } from '@preact/signals-core';
|
|
6
5
|
import React from 'react';
|
|
7
6
|
|
|
8
7
|
import {
|
|
9
8
|
type IntentPluginProvides,
|
|
10
|
-
LayoutAction,
|
|
11
9
|
NavigationAction,
|
|
12
10
|
type LocationProvides,
|
|
13
11
|
type Plugin,
|
|
@@ -28,39 +26,27 @@ import { type ActionGroup, createExtension, isActionGroup, toSignal } from '@dxo
|
|
|
28
26
|
import { ObservabilityAction } from '@dxos/plugin-observability/meta';
|
|
29
27
|
import { SpaceAction } from '@dxos/plugin-space';
|
|
30
28
|
import { ThreadType, MessageType, ChannelType } from '@dxos/plugin-space/types';
|
|
31
|
-
import {
|
|
32
|
-
getSpace,
|
|
33
|
-
getTextInRange,
|
|
34
|
-
createDocAccessor,
|
|
35
|
-
fullyQualifiedId,
|
|
36
|
-
loadObjectReferences,
|
|
37
|
-
} from '@dxos/react-client/echo';
|
|
38
|
-
import { comments, createExternalCommentSync, listener } from '@dxos/react-ui-editor';
|
|
29
|
+
import { getSpace, fullyQualifiedId, loadObjectReferences } from '@dxos/react-client/echo';
|
|
39
30
|
import { translations as threadTranslations } from '@dxos/react-ui-thread';
|
|
40
|
-
import { nonNullable } from '@dxos/util';
|
|
41
31
|
|
|
42
32
|
import { ThreadMain, ThreadSettings, ChatContainer, ChatHeading, ThreadArticle } from './components';
|
|
43
33
|
import { ThreadComplementary } from './components/ThreadComplementary';
|
|
34
|
+
import { threads } from './extensions';
|
|
44
35
|
import meta, { THREAD_ITEM, THREAD_PLUGIN } from './meta';
|
|
45
36
|
import translations from './translations';
|
|
46
|
-
import { ThreadAction, type ThreadPluginProvides, type ThreadSettingsProps } from './types';
|
|
47
|
-
|
|
48
|
-
type ThreadState = {
|
|
49
|
-
/** An in-memory staging area for threads that are being drafted. */
|
|
50
|
-
staging: Record<string, ThreadType[]>;
|
|
51
|
-
current?: string | undefined;
|
|
52
|
-
};
|
|
37
|
+
import { ThreadAction, type ThreadPluginProvides, type ThreadSettingsProps, type ThreadState } from './types';
|
|
53
38
|
|
|
54
39
|
type SubjectId = string;
|
|
40
|
+
|
|
55
41
|
const initialViewState = { showResolvedThreads: false };
|
|
56
|
-
type ViewStore = Record<SubjectId, typeof initialViewState>;
|
|
57
42
|
|
|
58
|
-
|
|
43
|
+
type ViewStore = Record<SubjectId, typeof initialViewState>;
|
|
59
44
|
|
|
60
45
|
// TODO(Zan): Every instance of `cursor` should be replaced with `anchor`.
|
|
46
|
+
// NOTE(burdon): Review/discuss CursorConverter semantics.
|
|
61
47
|
export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
62
48
|
const settings = new LocalStorageStore<ThreadSettingsProps>(THREAD_PLUGIN);
|
|
63
|
-
const state = create<ThreadState>({
|
|
49
|
+
const state = create<ThreadState>({ drafts: {} });
|
|
64
50
|
|
|
65
51
|
const viewStore = create<ViewStore>({});
|
|
66
52
|
const getViewState = (subjectId: string) => {
|
|
@@ -262,8 +248,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
262
248
|
return data.plugin === meta.id ? <ThreadSettings settings={settings.values} /> : null;
|
|
263
249
|
}
|
|
264
250
|
|
|
265
|
-
case 'article':
|
|
266
|
-
case 'complementary': {
|
|
251
|
+
case 'article': {
|
|
267
252
|
const location = navigationPlugin?.provides.location;
|
|
268
253
|
|
|
269
254
|
if (data.object instanceof ChannelType && data.object.threads[0]) {
|
|
@@ -291,6 +276,10 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
291
276
|
);
|
|
292
277
|
}
|
|
293
278
|
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
case 'complementary--comments': {
|
|
294
283
|
if (
|
|
295
284
|
data.subject &&
|
|
296
285
|
typeof data.subject === 'object' &&
|
|
@@ -303,12 +292,14 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
303
292
|
<ThreadComplementary
|
|
304
293
|
role={role}
|
|
305
294
|
subject={data.subject}
|
|
306
|
-
|
|
295
|
+
drafts={state.drafts[fullyQualifiedId(data.subject)]}
|
|
307
296
|
current={state.current}
|
|
308
297
|
showResolvedThreads={showResolvedThreads}
|
|
309
298
|
/>
|
|
310
299
|
);
|
|
311
300
|
}
|
|
301
|
+
|
|
302
|
+
break;
|
|
312
303
|
}
|
|
313
304
|
}
|
|
314
305
|
|
|
@@ -327,7 +318,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
327
318
|
try {
|
|
328
319
|
// Static schema will throw an error if subject does not support threads array property.
|
|
329
320
|
subject.threads = [];
|
|
330
|
-
} catch (
|
|
321
|
+
} catch (err) {
|
|
331
322
|
log.error('Subject does not support threads array', subject?.typename);
|
|
332
323
|
return;
|
|
333
324
|
}
|
|
@@ -335,11 +326,11 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
335
326
|
|
|
336
327
|
const subjectId = fullyQualifiedId(subject);
|
|
337
328
|
const thread = create(ThreadType, { name, anchor: cursor, messages: [], status: 'staged' });
|
|
338
|
-
const
|
|
339
|
-
if (
|
|
340
|
-
|
|
329
|
+
const draft = state.drafts[subjectId];
|
|
330
|
+
if (draft) {
|
|
331
|
+
draft.push(thread);
|
|
341
332
|
} else {
|
|
342
|
-
state.
|
|
333
|
+
state.drafts[subjectId] = [thread];
|
|
343
334
|
}
|
|
344
335
|
|
|
345
336
|
return {
|
|
@@ -350,12 +341,12 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
350
341
|
action: ThreadAction.SELECT,
|
|
351
342
|
data: { current: fullyQualifiedId(thread) },
|
|
352
343
|
},
|
|
344
|
+
],
|
|
345
|
+
[
|
|
353
346
|
{
|
|
354
|
-
action:
|
|
347
|
+
action: NavigationAction.OPEN,
|
|
355
348
|
data: {
|
|
356
|
-
|
|
357
|
-
subject: subjectId,
|
|
358
|
-
state: true,
|
|
349
|
+
activeParts: { complementary: 'comments' },
|
|
359
350
|
},
|
|
360
351
|
},
|
|
361
352
|
],
|
|
@@ -363,13 +354,30 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
363
354
|
};
|
|
364
355
|
} else {
|
|
365
356
|
// NOTE: This is the standalone thread creation case.
|
|
366
|
-
return {
|
|
357
|
+
return {
|
|
358
|
+
data: create(ChannelType, { threads: [create(ThreadType, { messages: [] })] }),
|
|
359
|
+
};
|
|
367
360
|
}
|
|
368
361
|
}
|
|
369
362
|
|
|
370
363
|
case ThreadAction.SELECT: {
|
|
371
364
|
state.current = intent.data?.current;
|
|
372
|
-
|
|
365
|
+
|
|
366
|
+
return {
|
|
367
|
+
data: true,
|
|
368
|
+
intents: !intent.data?.skipOpen
|
|
369
|
+
? [
|
|
370
|
+
[
|
|
371
|
+
{
|
|
372
|
+
action: NavigationAction.OPEN,
|
|
373
|
+
data: {
|
|
374
|
+
activeParts: { complementary: 'comments' },
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
],
|
|
378
|
+
]
|
|
379
|
+
: [],
|
|
380
|
+
};
|
|
373
381
|
}
|
|
374
382
|
|
|
375
383
|
case ThreadAction.TOGGLE_RESOLVED: {
|
|
@@ -388,6 +396,7 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
388
396
|
const spaceId = space?.id;
|
|
389
397
|
|
|
390
398
|
return {
|
|
399
|
+
data: true,
|
|
391
400
|
intents: [
|
|
392
401
|
[
|
|
393
402
|
{
|
|
@@ -409,13 +418,12 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
409
418
|
}
|
|
410
419
|
|
|
411
420
|
const subjectId = fullyQualifiedId(subject);
|
|
412
|
-
const
|
|
413
|
-
if (
|
|
414
|
-
// Check if we're deleting a
|
|
415
|
-
|
|
416
|
-
const index = stagingArea.findIndex((t) => t.id === thread.id);
|
|
421
|
+
const draft = state.drafts[subjectId];
|
|
422
|
+
if (draft) {
|
|
423
|
+
// Check if we're deleting a draft; if so, remove it.
|
|
424
|
+
const index = draft.findIndex((t) => t.id === thread.id);
|
|
417
425
|
if (index !== -1) {
|
|
418
|
-
|
|
426
|
+
draft.splice(index, 1);
|
|
419
427
|
return { data: true };
|
|
420
428
|
}
|
|
421
429
|
}
|
|
@@ -482,12 +490,11 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
482
490
|
const intents = [];
|
|
483
491
|
const analyticsProperties = { threadId: thread.id, spaceId: space?.id };
|
|
484
492
|
|
|
485
|
-
if (state.
|
|
486
|
-
// Move
|
|
493
|
+
if (state.drafts[subjectId]?.find((t) => t === thread)) {
|
|
494
|
+
// Move draft to document.
|
|
487
495
|
thread.status = 'active';
|
|
488
496
|
subject.threads ? subject.threads.push(thread) : (subject.threads = [thread]);
|
|
489
|
-
state.
|
|
490
|
-
|
|
497
|
+
state.drafts[subjectId] = state.drafts[subjectId]?.filter(({ id }) => id !== thread.id);
|
|
491
498
|
intents.push({
|
|
492
499
|
action: ObservabilityAction.SEND_EVENT,
|
|
493
500
|
data: { name: 'threads.thread-created', properties: analyticsProperties },
|
|
@@ -555,13 +562,11 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
555
562
|
} else if (intent.undo) {
|
|
556
563
|
const message = intent.data?.message;
|
|
557
564
|
const messageIndex = intent.data?.messageIndex;
|
|
558
|
-
|
|
559
565
|
if (!(message instanceof MessageType) || !(typeof messageIndex === 'number')) {
|
|
560
566
|
return;
|
|
561
567
|
}
|
|
562
568
|
|
|
563
569
|
thread.messages.splice(messageIndex, 0, message);
|
|
564
|
-
|
|
565
570
|
return {
|
|
566
571
|
data: true,
|
|
567
572
|
intents: [
|
|
@@ -582,96 +587,8 @@ export const ThreadPlugin = (): PluginDefinition<ThreadPluginProvides> => {
|
|
|
582
587
|
},
|
|
583
588
|
},
|
|
584
589
|
markdown: {
|
|
585
|
-
// TODO(burdon): Factor out extension factory into separate file (for simplicity).
|
|
586
590
|
extensions: ({ document: doc }) => {
|
|
587
|
-
|
|
588
|
-
if (!doc || !space) {
|
|
589
|
-
// Include no-op comments extension here to ensure that the facets are always present when they are expected.
|
|
590
|
-
// TODO(wittjosiah): The Editor should only look for these facets when comments are available.
|
|
591
|
-
return [comments()];
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
// TODO(Zan): When we have the deepsignal specific equivalent of this we should use that instead.
|
|
595
|
-
const threads = computed(() =>
|
|
596
|
-
[...doc.threads.filter(nonNullable), ...(state.staging[fullyQualifiedId(doc)] ?? [])].filter(
|
|
597
|
-
(thread) => !(thread?.status === 'resolved'),
|
|
598
|
-
),
|
|
599
|
-
);
|
|
600
|
-
|
|
601
|
-
return [
|
|
602
|
-
listener({
|
|
603
|
-
onChange: () => {
|
|
604
|
-
doc.threads.forEach((thread) => {
|
|
605
|
-
if (thread?.anchor) {
|
|
606
|
-
const [start, end] = thread.anchor.split(':');
|
|
607
|
-
const name = doc.content && getTextInRange(createDocAccessor(doc.content, ['content']), start, end);
|
|
608
|
-
// TODO(burdon): This seems unsafe; review.
|
|
609
|
-
// Only update if the name has changed, otherwise this will cause an infinite loop.
|
|
610
|
-
// Skip if the name is empty - this means comment text was deleted, but thread name should remain.
|
|
611
|
-
if (name && name !== thread.name) {
|
|
612
|
-
thread.name = name;
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
});
|
|
616
|
-
},
|
|
617
|
-
}),
|
|
618
|
-
createExternalCommentSync(
|
|
619
|
-
fullyQualifiedId(doc),
|
|
620
|
-
(sink) => effect(() => sink()),
|
|
621
|
-
() =>
|
|
622
|
-
threads.value
|
|
623
|
-
.filter((thread) => thread?.anchor)
|
|
624
|
-
.map((thread) => ({ id: fullyQualifiedId(thread), cursor: thread.anchor! })),
|
|
625
|
-
),
|
|
626
|
-
comments({
|
|
627
|
-
id: fullyQualifiedId(doc),
|
|
628
|
-
onCreate: ({ cursor }) => {
|
|
629
|
-
const [start, end] = cursor.split(':');
|
|
630
|
-
const name = doc.content && getTextInRange(createDocAccessor(doc.content, ['content']), start, end);
|
|
631
|
-
|
|
632
|
-
void intentPlugin?.provides.intent.dispatch({
|
|
633
|
-
action: ThreadAction.CREATE,
|
|
634
|
-
data: {
|
|
635
|
-
cursor,
|
|
636
|
-
name,
|
|
637
|
-
subject: doc,
|
|
638
|
-
},
|
|
639
|
-
});
|
|
640
|
-
},
|
|
641
|
-
onDelete: ({ id }) => {
|
|
642
|
-
// If the thread is in the staging area, remove it.
|
|
643
|
-
const stagingArea = state.staging[fullyQualifiedId(doc)];
|
|
644
|
-
if (stagingArea) {
|
|
645
|
-
const index = stagingArea.findIndex((thread) => fullyQualifiedId(thread) === id);
|
|
646
|
-
if (index !== -1) {
|
|
647
|
-
stagingArea.splice(index, 1);
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
const thread = doc.threads.find((thread) => thread?.id === id);
|
|
652
|
-
if (thread) {
|
|
653
|
-
thread.anchor = undefined;
|
|
654
|
-
}
|
|
655
|
-
},
|
|
656
|
-
onUpdate: ({ id, cursor }) => {
|
|
657
|
-
const thread =
|
|
658
|
-
state.staging[fullyQualifiedId(doc)]?.find((thread) => fullyQualifiedId(thread) === id) ??
|
|
659
|
-
doc.threads.find((thread) => thread?.id === id);
|
|
660
|
-
|
|
661
|
-
if (thread instanceof ThreadType && thread.anchor) {
|
|
662
|
-
const [start, end] = thread.anchor.split(':');
|
|
663
|
-
thread.name = doc.content && getTextInRange(createDocAccessor(doc.content, ['content']), start, end);
|
|
664
|
-
thread.anchor = cursor;
|
|
665
|
-
}
|
|
666
|
-
},
|
|
667
|
-
onSelect: ({ selection: { current, closest } }) => {
|
|
668
|
-
const dispatch = intentPlugin?.provides.intent.dispatch;
|
|
669
|
-
if (dispatch) {
|
|
670
|
-
void dispatch([{ action: ThreadAction.SELECT, data: { current: current ?? closest } }]);
|
|
671
|
-
}
|
|
672
|
-
},
|
|
673
|
-
}),
|
|
674
|
-
];
|
|
591
|
+
return threads(state, doc, intentPlugin?.provides.intent.dispatch);
|
|
675
592
|
},
|
|
676
593
|
},
|
|
677
594
|
},
|
|
@@ -21,13 +21,13 @@ const providesThreadsConfig = (plugin: any): Plugin<ThreadProvides<any>> | undef
|
|
|
21
21
|
export const ThreadComplementary = ({
|
|
22
22
|
role,
|
|
23
23
|
subject,
|
|
24
|
-
|
|
24
|
+
drafts,
|
|
25
25
|
current,
|
|
26
26
|
showResolvedThreads,
|
|
27
27
|
}: {
|
|
28
28
|
role: string;
|
|
29
29
|
subject: any;
|
|
30
|
-
|
|
30
|
+
drafts: ThreadType[] | undefined;
|
|
31
31
|
current?: string;
|
|
32
32
|
showResolvedThreads?: boolean;
|
|
33
33
|
}) => {
|
|
@@ -42,8 +42,8 @@ export const ThreadComplementary = ({
|
|
|
42
42
|
const sort = useMemo(() => createSort?.(subject), [createSort, subject]);
|
|
43
43
|
|
|
44
44
|
const threads = useMemo(() => {
|
|
45
|
-
return subject.threads.concat(
|
|
46
|
-
}, [JSON.stringify(subject.threads), JSON.stringify(
|
|
45
|
+
return subject.threads.concat(drafts ?? []).filter(nonNullable) as ThreadType[];
|
|
46
|
+
}, [JSON.stringify(subject.threads), JSON.stringify(drafts)]);
|
|
47
47
|
|
|
48
48
|
const detachedIds = useMemo(() => {
|
|
49
49
|
return threads.filter(({ anchor }) => !anchor).map((thread) => fullyQualifiedId(thread));
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Extension } from '@codemirror/state';
|
|
6
|
+
import { EditorView } from '@codemirror/view';
|
|
7
|
+
import { computed, effect } from '@preact/signals-core';
|
|
8
|
+
|
|
9
|
+
import { type IntentDispatcher } from '@dxos/app-framework';
|
|
10
|
+
import { type DocumentType } from '@dxos/plugin-markdown/types';
|
|
11
|
+
import { ThreadType } from '@dxos/plugin-space/types';
|
|
12
|
+
import { getSpace, getTextInRange, createDocAccessor, fullyQualifiedId } from '@dxos/react-client/echo';
|
|
13
|
+
import { comments, createExternalCommentSync } from '@dxos/react-ui-editor';
|
|
14
|
+
import { nonNullable } from '@dxos/util';
|
|
15
|
+
|
|
16
|
+
import { ThreadAction, type ThreadState } from '../types';
|
|
17
|
+
|
|
18
|
+
// TODO(burdon): Factor out.
|
|
19
|
+
const getName = (doc: DocumentType, anchor: string): string | undefined => {
|
|
20
|
+
if (doc.content) {
|
|
21
|
+
const [start, end] = anchor.split(':');
|
|
22
|
+
return getTextInRange(createDocAccessor(doc.content, ['content']), start, end);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Construct plugins.
|
|
28
|
+
*/
|
|
29
|
+
export const threads = (state: ThreadState, doc?: DocumentType, dispatch?: IntentDispatcher): Extension => {
|
|
30
|
+
const space = doc && getSpace(doc);
|
|
31
|
+
if (!doc || !space || !dispatch) {
|
|
32
|
+
// Include no-op comments extension here to ensure that the facets are always present when they are expected.
|
|
33
|
+
// TODO(wittjosiah): The Editor should only look for these facets when comments are available.
|
|
34
|
+
return [comments()];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// TODO(Zan): When we have the deepsignal specific equivalent of this we should use that instead.
|
|
38
|
+
const threads = computed(() =>
|
|
39
|
+
[...doc.threads.filter(nonNullable), ...(state.drafts[fullyQualifiedId(doc)] ?? [])].filter(
|
|
40
|
+
(thread) => !(thread?.status === 'resolved'),
|
|
41
|
+
),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
return [
|
|
45
|
+
EditorView.updateListener.of((update) => {
|
|
46
|
+
if (update.docChanged) {
|
|
47
|
+
doc.threads.forEach((thread) => {
|
|
48
|
+
if (thread?.anchor) {
|
|
49
|
+
// Only update if the name has changed, otherwise this will cause an infinite loop.
|
|
50
|
+
// Skip if the name is empty; this means comment text was deleted, but thread name should remain.
|
|
51
|
+
const name = getName(doc, thread.anchor);
|
|
52
|
+
if (name && name !== thread.name) {
|
|
53
|
+
thread.name = name;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}),
|
|
59
|
+
|
|
60
|
+
createExternalCommentSync(
|
|
61
|
+
fullyQualifiedId(doc),
|
|
62
|
+
(sink) => effect(() => sink()),
|
|
63
|
+
() =>
|
|
64
|
+
threads.value
|
|
65
|
+
.filter((thread) => thread?.anchor)
|
|
66
|
+
.map((thread) => ({ id: fullyQualifiedId(thread), cursor: thread.anchor! })),
|
|
67
|
+
),
|
|
68
|
+
|
|
69
|
+
comments({
|
|
70
|
+
id: fullyQualifiedId(doc),
|
|
71
|
+
onCreate: ({ cursor }) => {
|
|
72
|
+
const name = getName(doc, cursor);
|
|
73
|
+
void dispatch({
|
|
74
|
+
action: ThreadAction.CREATE,
|
|
75
|
+
data: {
|
|
76
|
+
cursor,
|
|
77
|
+
name,
|
|
78
|
+
subject: doc,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
},
|
|
82
|
+
onDelete: ({ id }) => {
|
|
83
|
+
const draft = state.drafts[fullyQualifiedId(doc)];
|
|
84
|
+
if (draft) {
|
|
85
|
+
const index = draft.findIndex((thread) => fullyQualifiedId(thread) === id);
|
|
86
|
+
if (index !== -1) {
|
|
87
|
+
draft.splice(index, 1);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const thread = doc.threads.find((thread) => thread?.id === id);
|
|
92
|
+
if (thread) {
|
|
93
|
+
thread.anchor = undefined;
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
onUpdate: ({ id, cursor }) => {
|
|
97
|
+
const thread =
|
|
98
|
+
state.drafts[fullyQualifiedId(doc)]?.find((thread) => fullyQualifiedId(thread) === id) ??
|
|
99
|
+
doc.threads.find((thread) => thread?.id === id);
|
|
100
|
+
|
|
101
|
+
if (thread instanceof ThreadType && thread.anchor) {
|
|
102
|
+
thread.name = getName(doc, thread.anchor);
|
|
103
|
+
thread.anchor = cursor;
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
onSelect: ({ selection: { current, closest } }) => {
|
|
107
|
+
void dispatch({
|
|
108
|
+
action: ThreadAction.SELECT,
|
|
109
|
+
data: {
|
|
110
|
+
current: current ?? closest,
|
|
111
|
+
skipOpen: true,
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
},
|
|
115
|
+
}),
|
|
116
|
+
];
|
|
117
|
+
};
|
package/src/{meta.tsx → meta.ts}
RENAMED
|
@@ -2,15 +2,14 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { type PluginMeta } from '@dxos/app-framework';
|
|
6
6
|
|
|
7
7
|
export const THREAD_PLUGIN = 'dxos.org/plugin/thread';
|
|
8
8
|
export const THREAD_ITEM = `${THREAD_PLUGIN}/item`;
|
|
9
9
|
|
|
10
|
-
export default
|
|
10
|
+
export default {
|
|
11
11
|
id: THREAD_PLUGIN,
|
|
12
12
|
name: 'Threads',
|
|
13
|
-
// TODO(wittjosiah): Update once scope of threads expands.
|
|
14
13
|
description: 'Comment threads on documents.',
|
|
15
14
|
icon: 'ph--chat--regular',
|
|
16
|
-
}
|
|
15
|
+
} satisfies PluginMeta;
|
package/src/types.ts
CHANGED
|
@@ -26,8 +26,7 @@ export enum ThreadAction {
|
|
|
26
26
|
TOGGLE_RESOLVED = `${THREAD_ACTION}/toggle-resolved`,
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
// TODO(Zan): Move this to the plugin-space plugin or another common location
|
|
30
|
-
// when we implement threads in sheets.
|
|
29
|
+
// TODO(Zan): Move this to the plugin-space plugin or another common location when we implement threads in sheets.
|
|
31
30
|
export type ThreadProvides<T> = {
|
|
32
31
|
thread: {
|
|
33
32
|
predicate: (obj: any) => obj is T;
|
|
@@ -49,3 +48,9 @@ export type ThreadSettingsProps = { standalone?: boolean };
|
|
|
49
48
|
export interface ThreadModel {
|
|
50
49
|
root: ThreadType;
|
|
51
50
|
}
|
|
51
|
+
|
|
52
|
+
export type ThreadState = {
|
|
53
|
+
/** In-memory draft threads. */
|
|
54
|
+
drafts: Record<string, ThreadType[]>;
|
|
55
|
+
current?: string | undefined;
|
|
56
|
+
};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/meta.tsx"],
|
|
4
|
-
"sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport { pluginMeta } from '@dxos/app-framework';\n\nexport const THREAD_PLUGIN = 'dxos.org/plugin/thread';\nexport const THREAD_ITEM = `${THREAD_PLUGIN}/item`;\n\nexport default pluginMeta({\n id: THREAD_PLUGIN,\n name: 'Threads',\n // TODO(wittjosiah): Update once scope of threads expands.\n description: 'Comment threads on documents.',\n icon: 'ph--chat--regular',\n});\n"],
|
|
5
|
-
"mappings": ";AAIA,SAASA,kBAAkB;AAEpB,IAAMC,gBAAgB;AACtB,IAAMC,cAAc,GAAGD,aAAAA;AAE9B,IAAA,eAAeE,WAAW;EACxBC,IAAIH;EACJI,MAAM;;EAENC,aAAa;EACbC,MAAM;AACR,CAAA;",
|
|
6
|
-
"names": ["pluginMeta", "THREAD_PLUGIN", "THREAD_ITEM", "pluginMeta", "id", "name", "description", "icon"]
|
|
7
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/meta.tsx"],
|
|
4
|
-
"sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport { pluginMeta } from '@dxos/app-framework';\n\nexport const THREAD_PLUGIN = 'dxos.org/plugin/thread';\nexport const THREAD_ITEM = `${THREAD_PLUGIN}/item`;\n\nexport default pluginMeta({\n id: THREAD_PLUGIN,\n name: 'Threads',\n // TODO(wittjosiah): Update once scope of threads expands.\n description: 'Comment threads on documents.',\n icon: 'ph--chat--regular',\n});\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;AAIA,2BAA2B;AAEpB,IAAMA,gBAAgB;AACtB,IAAMC,cAAc,GAAGD,aAAAA;AAE9B,IAAA,mBAAeE,iCAAW;EACxBC,IAAIH;EACJI,MAAM;;EAENC,aAAa;EACbC,MAAM;AACR,CAAA;",
|
|
6
|
-
"names": ["THREAD_PLUGIN", "THREAD_ITEM", "pluginMeta", "id", "name", "description", "icon"]
|
|
7
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/meta.tsx"],
|
|
4
|
-
"sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport { pluginMeta } from '@dxos/app-framework';\n\nexport const THREAD_PLUGIN = 'dxos.org/plugin/thread';\nexport const THREAD_ITEM = `${THREAD_PLUGIN}/item`;\n\nexport default pluginMeta({\n id: THREAD_PLUGIN,\n name: 'Threads',\n // TODO(wittjosiah): Update once scope of threads expands.\n description: 'Comment threads on documents.',\n icon: 'ph--chat--regular',\n});\n"],
|
|
5
|
-
"mappings": ";;;AAIA,SAASA,kBAAkB;AAEpB,IAAMC,gBAAgB;AACtB,IAAMC,cAAc,GAAGD,aAAAA;AAE9B,IAAA,eAAeE,WAAW;EACxBC,IAAIH;EACJI,MAAM;;EAENC,aAAa;EACbC,MAAM;AACR,CAAA;",
|
|
6
|
-
"names": ["pluginMeta", "THREAD_PLUGIN", "THREAD_ITEM", "pluginMeta", "id", "name", "description", "icon"]
|
|
7
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/dist/lib/node-esm/{ThreadArticle-NKI5F4W5.mjs.map → ThreadArticle-ZTYD52TR.mjs.map}
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|