@semiont/react-ui 0.4.13 → 0.4.15
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/README.md +18 -12
- package/dist/KnowledgeBaseSessionContext-CpYaCbnC.d.mts +174 -0
- package/dist/{PdfAnnotationCanvas.client-CW6SKH2U.mjs → PdfAnnotationCanvas.client-CHDCGQBR.mjs} +3 -3
- package/dist/{chunk-HNZOXH4L.mjs → chunk-OZICDVH7.mjs} +5 -3
- package/dist/chunk-OZICDVH7.mjs.map +1 -0
- package/dist/chunk-R2U7P4TK.mjs +865 -0
- package/dist/chunk-R2U7P4TK.mjs.map +1 -0
- package/dist/{chunk-BQJWOK4C.mjs → chunk-VN5NY4SN.mjs} +9 -8
- package/dist/chunk-VN5NY4SN.mjs.map +1 -0
- package/dist/index.d.mts +147 -171
- package/dist/index.mjs +2215 -1961
- package/dist/index.mjs.map +1 -1
- package/dist/test-utils.d.mts +13 -62
- package/dist/test-utils.mjs +40 -21
- package/dist/test-utils.mjs.map +1 -1
- package/package.json +5 -3
- package/src/components/ProtectedErrorBoundary.tsx +95 -0
- package/src/components/Toolbar.tsx +13 -13
- package/src/components/__tests__/ProtectedErrorBoundary.test.tsx +197 -0
- package/src/components/modals/PermissionDeniedModal.tsx +140 -0
- package/src/components/modals/ReferenceWizardModal.tsx +3 -2
- package/src/components/modals/SessionExpiredModal.tsx +101 -0
- package/src/components/modals/__tests__/PermissionDeniedModal.test.tsx +150 -0
- package/src/components/modals/__tests__/SessionExpiredModal.test.tsx +115 -0
- package/src/components/resource/AnnotationHistory.tsx +5 -6
- package/src/components/resource/HistoryEvent.tsx +7 -7
- package/src/components/resource/__tests__/AnnotationHistory.test.tsx +33 -34
- package/src/components/resource/__tests__/HistoryEvent.test.tsx +17 -19
- package/src/components/resource/__tests__/event-formatting.test.ts +70 -94
- package/src/components/resource/event-formatting.ts +56 -56
- package/src/components/resource/panels/CollaborationPanel.tsx +9 -1
- package/src/components/resource/panels/ReferenceEntry.tsx +7 -5
- package/src/components/resource/panels/ResourceInfoPanel.tsx +8 -6
- package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +12 -12
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +1 -0
- package/src/features/resource-viewer/__tests__/AnnotationCreationPending.test.tsx +1 -1
- package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +4 -4
- package/src/features/resource-viewer/__tests__/AnnotationProgressDismissal.test.tsx +5 -10
- package/src/features/resource-viewer/__tests__/BindFlowIntegration.test.tsx +23 -54
- package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +6 -6
- package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +7 -19
- package/src/features/resource-viewer/__tests__/ToastNotifications.test.tsx +1 -1
- package/src/features/resource-viewer/__tests__/YieldFlowIntegration.test.tsx +18 -44
- package/src/features/resource-viewer/__tests__/annotation-progress-flow.test.tsx +6 -6
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +31 -26
- package/src/styles/patterns/panels-base.css +12 -0
- package/dist/TranslationManager-CudgH3gw.d.mts +0 -107
- package/dist/chunk-BQJWOK4C.mjs.map +0 -1
- package/dist/chunk-HNZOXH4L.mjs.map +0 -1
- package/dist/chunk-OL5UST25.mjs +0 -413
- package/dist/chunk-OL5UST25.mjs.map +0 -1
- /package/dist/{PdfAnnotationCanvas.client-CW6SKH2U.mjs.map → PdfAnnotationCanvas.client-CHDCGQBR.mjs.map} +0 -0
|
@@ -26,41 +26,41 @@ describe('event-formatting', () => {
|
|
|
26
26
|
|
|
27
27
|
describe('formatEventType', () => {
|
|
28
28
|
it('returns translation key for resource events', () => {
|
|
29
|
-
expect(formatEventType('
|
|
30
|
-
expect(formatEventType('
|
|
31
|
-
expect(formatEventType('
|
|
32
|
-
expect(formatEventType('
|
|
29
|
+
expect(formatEventType('yield:created', t)).toBe('resourceCreated');
|
|
30
|
+
expect(formatEventType('yield:cloned', t)).toBe('resourceCloned');
|
|
31
|
+
expect(formatEventType('mark:archived', t)).toBe('resourceArchived');
|
|
32
|
+
expect(formatEventType('mark:unarchived', t)).toBe('resourceUnarchived');
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
it('returns motivation-specific key for
|
|
36
|
-
expect(formatEventType('
|
|
37
|
-
expect(formatEventType('
|
|
38
|
-
expect(formatEventType('
|
|
39
|
-
expect(formatEventType('
|
|
35
|
+
it('returns motivation-specific key for mark:added', () => {
|
|
36
|
+
expect(formatEventType('mark:added', t, { annotation: { motivation: 'highlighting' } })).toBe('highlightAdded');
|
|
37
|
+
expect(formatEventType('mark:added', t, { annotation: { motivation: 'linking' } })).toBe('referenceCreated');
|
|
38
|
+
expect(formatEventType('mark:added', t, { annotation: { motivation: 'assessing' } })).toBe('assessmentAdded');
|
|
39
|
+
expect(formatEventType('mark:added', t, { annotation: { motivation: 'commenting' } })).toBe('annotationAdded');
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
-
it('returns annotationRemoved for
|
|
43
|
-
expect(formatEventType('
|
|
42
|
+
it('returns annotationRemoved for mark:removed', () => {
|
|
43
|
+
expect(formatEventType('mark:removed', t)).toBe('annotationRemoved');
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
-
it('returns annotationBodyUpdated for
|
|
47
|
-
expect(formatEventType('
|
|
46
|
+
it('returns annotationBodyUpdated for mark:body-updated', () => {
|
|
47
|
+
expect(formatEventType('mark:body-updated', t)).toBe('annotationBodyUpdated');
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
it('returns entitytag keys', () => {
|
|
51
|
-
expect(formatEventType('
|
|
52
|
-
expect(formatEventType('
|
|
51
|
+
expect(formatEventType('mark:entity-tag-added', t)).toBe('entitytagAdded');
|
|
52
|
+
expect(formatEventType('mark:entity-tag-removed', t)).toBe('entitytagRemoved');
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
it('returns jobEvent for job types', () => {
|
|
56
|
-
expect(formatEventType('job
|
|
57
|
-
expect(formatEventType('job
|
|
58
|
-
expect(formatEventType('job
|
|
56
|
+
expect(formatEventType('job:completed', t)).toBe('jobEvent');
|
|
57
|
+
expect(formatEventType('job:started', t)).toBe('jobEvent');
|
|
58
|
+
expect(formatEventType('job:failed', t)).toBe('jobEvent');
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
-
it('returns representationEvent for representation types', () => {
|
|
62
|
-
expect(formatEventType('representation
|
|
63
|
-
expect(formatEventType('representation
|
|
61
|
+
it('returns representationEvent for yield:representation types', () => {
|
|
62
|
+
expect(formatEventType('yield:representation-added', t)).toBe('representationEvent');
|
|
63
|
+
expect(formatEventType('yield:representation-removed', t)).toBe('representationEvent');
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
it('returns raw type for unknown event types', () => {
|
|
@@ -70,33 +70,33 @@ describe('event-formatting', () => {
|
|
|
70
70
|
|
|
71
71
|
describe('getEventEmoji', () => {
|
|
72
72
|
it('returns document emoji for resource events', () => {
|
|
73
|
-
expect(getEventEmoji('
|
|
74
|
-
expect(getEventEmoji('
|
|
73
|
+
expect(getEventEmoji('yield:created')).toBe('📄');
|
|
74
|
+
expect(getEventEmoji('yield:cloned')).toBe('📄');
|
|
75
75
|
});
|
|
76
76
|
|
|
77
|
-
it('returns motivation-specific emoji for
|
|
78
|
-
expect(getEventEmoji('
|
|
79
|
-
expect(getEventEmoji('
|
|
80
|
-
expect(getEventEmoji('
|
|
77
|
+
it('returns motivation-specific emoji for mark:added', () => {
|
|
78
|
+
expect(getEventEmoji('mark:added', { annotation: { motivation: 'highlighting' } })).toBe('🟡');
|
|
79
|
+
expect(getEventEmoji('mark:added', { annotation: { motivation: 'linking' } })).toBeTruthy();
|
|
80
|
+
expect(getEventEmoji('mark:added', { annotation: { motivation: 'assessing' } })).toBe('🔴');
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
-
it('returns trash emoji for
|
|
84
|
-
expect(getEventEmoji('
|
|
83
|
+
it('returns trash emoji for mark:removed', () => {
|
|
84
|
+
expect(getEventEmoji('mark:removed')).toBe('🗑️');
|
|
85
85
|
});
|
|
86
86
|
|
|
87
|
-
it('returns pencil emoji for
|
|
88
|
-
expect(getEventEmoji('
|
|
87
|
+
it('returns pencil emoji for mark:body-updated', () => {
|
|
88
|
+
expect(getEventEmoji('mark:body-updated')).toBe('✏️');
|
|
89
89
|
});
|
|
90
90
|
|
|
91
91
|
it('returns tag emoji for entitytag events', () => {
|
|
92
|
-
expect(getEventEmoji('
|
|
93
|
-
expect(getEventEmoji('
|
|
92
|
+
expect(getEventEmoji('mark:entity-tag-added')).toBe('🏷️');
|
|
93
|
+
expect(getEventEmoji('mark:entity-tag-removed')).toBe('🏷️');
|
|
94
94
|
});
|
|
95
95
|
|
|
96
96
|
it('returns appropriate emoji for job events', () => {
|
|
97
|
-
expect(getEventEmoji('job
|
|
98
|
-
expect(getEventEmoji('job
|
|
99
|
-
expect(getEventEmoji('job
|
|
97
|
+
expect(getEventEmoji('job:completed')).toBe('🔗');
|
|
98
|
+
expect(getEventEmoji('job:started')).toBe('⚙️');
|
|
99
|
+
expect(getEventEmoji('job:failed')).toBe('❌');
|
|
100
100
|
});
|
|
101
101
|
|
|
102
102
|
it('returns default emoji for unknown', () => {
|
|
@@ -137,55 +137,43 @@ describe('event-formatting', () => {
|
|
|
137
137
|
});
|
|
138
138
|
|
|
139
139
|
describe('getEventDisplayContent', () => {
|
|
140
|
-
it('returns resource name for
|
|
141
|
-
const event = {
|
|
142
|
-
event: { type: 'resource.created' as const, payload: { name: 'My Document' }, userId: 'u1', timestamp: '' },
|
|
143
|
-
} as any;
|
|
140
|
+
it('returns resource name for yield:created', () => {
|
|
141
|
+
const event = { type: 'yield:created' as const, payload: { name: 'My Document' }, userId: 'u1', timestamp: '' } as any;
|
|
144
142
|
const result = getEventDisplayContent(event, [], []);
|
|
145
143
|
expect(result).toEqual({ exact: 'My Document', isQuoted: false, isTag: false });
|
|
146
144
|
});
|
|
147
145
|
|
|
148
|
-
it('returns resource name for
|
|
149
|
-
const event = {
|
|
150
|
-
event: { type: 'resource.cloned' as const, payload: { name: 'Cloned Doc' }, userId: 'u1', timestamp: '' },
|
|
151
|
-
} as any;
|
|
146
|
+
it('returns resource name for yield:cloned', () => {
|
|
147
|
+
const event = { type: 'yield:cloned' as const, payload: { name: 'Cloned Doc' }, userId: 'u1', timestamp: '' } as any;
|
|
152
148
|
const result = getEventDisplayContent(event, [], []);
|
|
153
149
|
expect(result).toEqual({ exact: 'Cloned Doc', isQuoted: false, isTag: false });
|
|
154
150
|
});
|
|
155
151
|
|
|
156
152
|
it('returns entity type for entitytag events', () => {
|
|
157
|
-
const event = {
|
|
158
|
-
event: { type: 'entitytag.added' as const, payload: { entityType: 'Person' }, userId: 'u1', timestamp: '' },
|
|
159
|
-
} as any;
|
|
153
|
+
const event = { type: 'mark:entity-tag-added' as const, payload: { entityType: 'Person' }, userId: 'u1', timestamp: '' } as any;
|
|
160
154
|
const result = getEventDisplayContent(event, [], []);
|
|
161
155
|
expect(result).toEqual({ exact: 'Person', isQuoted: false, isTag: true });
|
|
162
156
|
});
|
|
163
157
|
|
|
164
|
-
it('returns null for job
|
|
165
|
-
const event = {
|
|
166
|
-
event: { type: 'job.started' as const, payload: {}, userId: 'u1', timestamp: '' },
|
|
167
|
-
} as any;
|
|
158
|
+
it('returns null for job:started', () => {
|
|
159
|
+
const event = { type: 'job:started' as const, payload: {}, userId: 'u1', timestamp: '' } as any;
|
|
168
160
|
expect(getEventDisplayContent(event, [], [])).toBeNull();
|
|
169
161
|
});
|
|
170
162
|
|
|
171
|
-
it('returns null for representation events', () => {
|
|
172
|
-
const event = {
|
|
173
|
-
event: { type: 'representation.added' as const, payload: {}, userId: 'u1', timestamp: '' },
|
|
174
|
-
} as any;
|
|
163
|
+
it('returns null for yield:representation events', () => {
|
|
164
|
+
const event = { type: 'yield:representation-added' as const, payload: {}, userId: 'u1', timestamp: '' } as any;
|
|
175
165
|
expect(getEventDisplayContent(event, [], [])).toBeNull();
|
|
176
166
|
});
|
|
177
167
|
});
|
|
178
168
|
|
|
179
169
|
describe('getEventEntityTypes', () => {
|
|
180
|
-
it('returns entity types from
|
|
170
|
+
it('returns entity types from mark:added with linking motivation', () => {
|
|
181
171
|
const event = {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
body: { entityTypes: ['Person', 'Place'] },
|
|
188
|
-
},
|
|
172
|
+
type: 'mark:added' as const,
|
|
173
|
+
payload: {
|
|
174
|
+
annotation: {
|
|
175
|
+
motivation: 'linking',
|
|
176
|
+
body: { entityTypes: ['Person', 'Place'] },
|
|
189
177
|
},
|
|
190
178
|
},
|
|
191
179
|
} as any;
|
|
@@ -194,33 +182,27 @@ describe('event-formatting', () => {
|
|
|
194
182
|
|
|
195
183
|
it('returns empty array for non-linking annotations', () => {
|
|
196
184
|
const event = {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
annotation: { motivation: 'highlighting', body: null },
|
|
201
|
-
},
|
|
185
|
+
type: 'mark:added' as const,
|
|
186
|
+
payload: {
|
|
187
|
+
annotation: { motivation: 'highlighting', body: null },
|
|
202
188
|
},
|
|
203
189
|
} as any;
|
|
204
190
|
expect(getEventEntityTypes(event)).toEqual([]);
|
|
205
191
|
});
|
|
206
192
|
|
|
207
193
|
it('returns empty array for non-annotation events', () => {
|
|
208
|
-
const event = {
|
|
209
|
-
event: { type: 'resource.created' as const, payload: { name: 'test' } },
|
|
210
|
-
} as any;
|
|
194
|
+
const event = { type: 'yield:created' as const, payload: { name: 'test' } } as any;
|
|
211
195
|
expect(getEventEntityTypes(event)).toEqual([]);
|
|
212
196
|
});
|
|
213
197
|
});
|
|
214
198
|
|
|
215
199
|
describe('getResourceCreationDetails', () => {
|
|
216
|
-
it('returns created details for
|
|
200
|
+
it('returns created details for yield:created', () => {
|
|
217
201
|
const event = {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
timestamp: '',
|
|
223
|
-
},
|
|
202
|
+
type: 'yield:created' as const,
|
|
203
|
+
payload: { name: 'Doc', creationMethod: 'upload' },
|
|
204
|
+
userId: 'user-1',
|
|
205
|
+
timestamp: '',
|
|
224
206
|
} as any;
|
|
225
207
|
const result = getResourceCreationDetails(event);
|
|
226
208
|
expect(result).toEqual({
|
|
@@ -231,14 +213,12 @@ describe('event-formatting', () => {
|
|
|
231
213
|
});
|
|
232
214
|
});
|
|
233
215
|
|
|
234
|
-
it('returns cloned details for
|
|
216
|
+
it('returns cloned details for yield:cloned', () => {
|
|
235
217
|
const event = {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
timestamp: '',
|
|
241
|
-
},
|
|
218
|
+
type: 'yield:cloned' as const,
|
|
219
|
+
payload: { name: 'Clone', creationMethod: 'clone', parentResourceId: 'parent-1' },
|
|
220
|
+
userId: 'user-2',
|
|
221
|
+
timestamp: '',
|
|
242
222
|
} as any;
|
|
243
223
|
const result = getResourceCreationDetails(event);
|
|
244
224
|
expect(result).toEqual({
|
|
@@ -253,20 +233,16 @@ describe('event-formatting', () => {
|
|
|
253
233
|
|
|
254
234
|
it('uses fallback method when creationMethod missing', () => {
|
|
255
235
|
const event = {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
timestamp: '',
|
|
261
|
-
},
|
|
236
|
+
type: 'yield:created' as const,
|
|
237
|
+
payload: { name: 'Doc' },
|
|
238
|
+
userId: 'u1',
|
|
239
|
+
timestamp: '',
|
|
262
240
|
} as any;
|
|
263
241
|
expect(getResourceCreationDetails(event)?.method).toBe('unknown');
|
|
264
242
|
});
|
|
265
243
|
|
|
266
244
|
it('returns null for non-creation events', () => {
|
|
267
|
-
const event = {
|
|
268
|
-
event: { type: 'annotation.added' as const, payload: {} },
|
|
269
|
-
} as any;
|
|
245
|
+
const event = { type: 'mark:added' as const, payload: {} } as any;
|
|
270
246
|
expect(getResourceCreationDetails(event)).toBeNull();
|
|
271
247
|
});
|
|
272
248
|
});
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* No React dependencies - safe to use in any JavaScript environment.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { StoredEventLike,
|
|
8
|
+
import type { StoredEventLike, PersistedEventType } from '@semiont/core';
|
|
9
9
|
import type { components } from '@semiont/core';
|
|
10
10
|
import { getExactText, getTargetSelector } from '@semiont/api-client';
|
|
11
11
|
import { ANNOTATORS } from '../../lib/annotation-registry';
|
|
@@ -20,44 +20,44 @@ type TranslateFn = (key: string, params?: Record<string, string | number>) => st
|
|
|
20
20
|
/**
|
|
21
21
|
* Format event type for display with i18n support
|
|
22
22
|
*/
|
|
23
|
-
export function formatEventType(type:
|
|
23
|
+
export function formatEventType(type: PersistedEventType, t: TranslateFn, payload?: any): string {
|
|
24
24
|
switch (type) {
|
|
25
|
-
case '
|
|
25
|
+
case 'yield:created':
|
|
26
26
|
return t('resourceCreated');
|
|
27
|
-
case '
|
|
27
|
+
case 'yield:cloned':
|
|
28
28
|
return t('resourceCloned');
|
|
29
|
-
case '
|
|
29
|
+
case 'mark:archived':
|
|
30
30
|
return t('resourceArchived');
|
|
31
|
-
case '
|
|
31
|
+
case 'mark:unarchived':
|
|
32
32
|
return t('resourceUnarchived');
|
|
33
33
|
|
|
34
|
-
case '
|
|
34
|
+
case 'mark:added': {
|
|
35
35
|
const motivation = payload?.annotation?.motivation;
|
|
36
36
|
if (motivation === 'highlighting') return t('highlightAdded');
|
|
37
37
|
if (motivation === 'linking') return t('referenceCreated');
|
|
38
38
|
if (motivation === 'assessing') return t('assessmentAdded');
|
|
39
39
|
return t('annotationAdded');
|
|
40
40
|
}
|
|
41
|
-
case '
|
|
41
|
+
case 'mark:removed': {
|
|
42
42
|
return t('annotationRemoved');
|
|
43
43
|
}
|
|
44
|
-
case '
|
|
44
|
+
case 'mark:body-updated': {
|
|
45
45
|
return t('annotationBodyUpdated');
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
case '
|
|
48
|
+
case 'mark:entity-tag-added':
|
|
49
49
|
return t('entitytagAdded');
|
|
50
|
-
case '
|
|
50
|
+
case 'mark:entity-tag-removed':
|
|
51
51
|
return t('entitytagRemoved');
|
|
52
52
|
|
|
53
|
-
case 'job
|
|
54
|
-
case 'job
|
|
55
|
-
case 'job
|
|
56
|
-
case 'job
|
|
53
|
+
case 'job:completed':
|
|
54
|
+
case 'job:started':
|
|
55
|
+
case 'job:progress':
|
|
56
|
+
case 'job:failed':
|
|
57
57
|
return t('jobEvent');
|
|
58
58
|
|
|
59
|
-
case 'representation
|
|
60
|
-
case 'representation
|
|
59
|
+
case 'yield:representation-added':
|
|
60
|
+
case 'yield:representation-removed':
|
|
61
61
|
return t('representationEvent');
|
|
62
62
|
|
|
63
63
|
default:
|
|
@@ -69,15 +69,15 @@ export function formatEventType(type: ResourceEventType, t: TranslateFn, payload
|
|
|
69
69
|
* Get emoji for event type
|
|
70
70
|
* For unified annotation events, pass the payload to determine motivation
|
|
71
71
|
*/
|
|
72
|
-
export function getEventEmoji(type:
|
|
72
|
+
export function getEventEmoji(type: PersistedEventType, payload?: any): string {
|
|
73
73
|
switch (type) {
|
|
74
|
-
case '
|
|
75
|
-
case '
|
|
76
|
-
case '
|
|
77
|
-
case '
|
|
74
|
+
case 'yield:created':
|
|
75
|
+
case 'yield:cloned':
|
|
76
|
+
case 'mark:archived':
|
|
77
|
+
case 'mark:unarchived':
|
|
78
78
|
return '📄';
|
|
79
79
|
|
|
80
|
-
case '
|
|
80
|
+
case 'mark:added': {
|
|
81
81
|
const motivation = payload?.annotation?.motivation;
|
|
82
82
|
// Use annotation registry as single source of truth for emojis
|
|
83
83
|
if (motivation === 'highlighting') return ANNOTATORS.highlight.iconEmoji || '📝';
|
|
@@ -85,27 +85,27 @@ export function getEventEmoji(type: ResourceEventType, payload?: any): string {
|
|
|
85
85
|
if (motivation === 'assessing') return ANNOTATORS.assessment.iconEmoji || '📝';
|
|
86
86
|
return '📝';
|
|
87
87
|
}
|
|
88
|
-
case '
|
|
88
|
+
case 'mark:removed': {
|
|
89
89
|
return '🗑️';
|
|
90
90
|
}
|
|
91
|
-
case '
|
|
91
|
+
case 'mark:body-updated': {
|
|
92
92
|
return '✏️';
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
case '
|
|
96
|
-
case '
|
|
95
|
+
case 'mark:entity-tag-added':
|
|
96
|
+
case 'mark:entity-tag-removed':
|
|
97
97
|
return '🏷️';
|
|
98
98
|
|
|
99
|
-
case 'job
|
|
99
|
+
case 'job:completed':
|
|
100
100
|
return '🔗'; // Link emoji for linked document creation
|
|
101
|
-
case 'job
|
|
102
|
-
case 'job
|
|
101
|
+
case 'job:started':
|
|
102
|
+
case 'job:progress':
|
|
103
103
|
return '⚙️'; // Gear for job processing
|
|
104
|
-
case 'job
|
|
104
|
+
case 'job:failed':
|
|
105
105
|
return '❌'; // X mark for failed jobs
|
|
106
106
|
|
|
107
|
-
case 'representation
|
|
108
|
-
case 'representation
|
|
107
|
+
case 'yield:representation-added':
|
|
108
|
+
case 'yield:representation-removed':
|
|
109
109
|
return '📄';
|
|
110
110
|
|
|
111
111
|
default:
|
|
@@ -148,18 +148,18 @@ export function getEventDisplayContent(
|
|
|
148
148
|
annotations: Annotation[], // Unified annotations array (all types)
|
|
149
149
|
allEvents: StoredEventLike[]
|
|
150
150
|
): { exact: string; isQuoted: boolean; isTag: boolean } | null {
|
|
151
|
-
const eventData = event
|
|
151
|
+
const eventData = event;
|
|
152
152
|
const payload = eventData.payload as any;
|
|
153
153
|
|
|
154
154
|
// Use type discriminators for proper narrowing
|
|
155
155
|
switch (eventData.type) {
|
|
156
|
-
case '
|
|
157
|
-
case '
|
|
156
|
+
case 'yield:created':
|
|
157
|
+
case 'yield:cloned': {
|
|
158
158
|
return { exact: payload.name, isQuoted: false, isTag: false };
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
// Unified annotation events
|
|
162
|
-
case '
|
|
162
|
+
case 'mark:body-updated': {
|
|
163
163
|
// Find current annotation to get its text
|
|
164
164
|
// payload.annotationId is just the UUID, but annotation.id is the full URI
|
|
165
165
|
const annotation = annotations.find(a =>
|
|
@@ -180,16 +180,16 @@ export function getEventDisplayContent(
|
|
|
180
180
|
return null;
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
case '
|
|
183
|
+
case 'mark:removed': {
|
|
184
184
|
// Find the original annotation.added event to get the text
|
|
185
185
|
// payload.annotationId is just the UUID, but annotation.id in the added event is the full URI
|
|
186
186
|
const addedEvent = allEvents.find(e =>
|
|
187
|
-
e.
|
|
188
|
-
(e.
|
|
187
|
+
e.type === 'mark:added' &&
|
|
188
|
+
(e.payload as any).annotation.id.endsWith(`/annotations/${payload.annotationId}`)
|
|
189
189
|
);
|
|
190
|
-
if (addedEvent && addedEvent.
|
|
190
|
+
if (addedEvent && addedEvent.type === 'mark:added') {
|
|
191
191
|
try {
|
|
192
|
-
const target = (addedEvent.
|
|
192
|
+
const target = (addedEvent.payload as any).annotation.target;
|
|
193
193
|
if (typeof target !== 'string' && target.selector) {
|
|
194
194
|
const exact = getExactText(target.selector);
|
|
195
195
|
if (exact) {
|
|
@@ -203,7 +203,7 @@ export function getEventDisplayContent(
|
|
|
203
203
|
return null;
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
case '
|
|
206
|
+
case 'mark:added': {
|
|
207
207
|
// New unified event structure - annotation is in payload
|
|
208
208
|
try {
|
|
209
209
|
const target = payload.annotation.target;
|
|
@@ -219,12 +219,12 @@ export function getEventDisplayContent(
|
|
|
219
219
|
return null;
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
case '
|
|
223
|
-
case '
|
|
222
|
+
case 'mark:entity-tag-added':
|
|
223
|
+
case 'mark:entity-tag-removed': {
|
|
224
224
|
return { exact: payload.entityType, isQuoted: false, isTag: true };
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
-
case 'job
|
|
227
|
+
case 'job:completed': {
|
|
228
228
|
// Find the annotation that was used to generate the resource
|
|
229
229
|
if (payload.annotationUri) {
|
|
230
230
|
const annotation = annotations.find(a =>
|
|
@@ -246,11 +246,11 @@ export function getEventDisplayContent(
|
|
|
246
246
|
return null;
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
-
case 'job
|
|
250
|
-
case 'job
|
|
251
|
-
case 'job
|
|
252
|
-
case 'representation
|
|
253
|
-
case 'representation
|
|
249
|
+
case 'job:started':
|
|
250
|
+
case 'job:progress':
|
|
251
|
+
case 'job:failed':
|
|
252
|
+
case 'yield:representation-added':
|
|
253
|
+
case 'yield:representation-removed':
|
|
254
254
|
return null;
|
|
255
255
|
|
|
256
256
|
default:
|
|
@@ -262,10 +262,10 @@ export function getEventDisplayContent(
|
|
|
262
262
|
* Get entity types from event payload
|
|
263
263
|
*/
|
|
264
264
|
export function getEventEntityTypes(event: StoredEventLike): string[] {
|
|
265
|
-
const eventData = event
|
|
265
|
+
const eventData = event;
|
|
266
266
|
const payload = eventData.payload as any;
|
|
267
267
|
|
|
268
|
-
if (eventData.type === '
|
|
268
|
+
if (eventData.type === 'mark:added') {
|
|
269
269
|
const motivation = payload.annotation.motivation;
|
|
270
270
|
const body = payload.annotation.body;
|
|
271
271
|
if (motivation === 'linking' && body && 'entityTypes' in body) {
|
|
@@ -292,10 +292,10 @@ export interface ResourceCreationDetails {
|
|
|
292
292
|
* Get resource creation details from event
|
|
293
293
|
*/
|
|
294
294
|
export function getResourceCreationDetails(event: StoredEventLike): ResourceCreationDetails | null {
|
|
295
|
-
const eventData = event
|
|
295
|
+
const eventData = event;
|
|
296
296
|
const payload = eventData.payload as any;
|
|
297
297
|
|
|
298
|
-
if (eventData.type === '
|
|
298
|
+
if (eventData.type === 'yield:created') {
|
|
299
299
|
return {
|
|
300
300
|
type: 'created',
|
|
301
301
|
method: payload.creationMethod || 'unknown',
|
|
@@ -304,7 +304,7 @@ export function getResourceCreationDetails(event: StoredEventLike): ResourceCrea
|
|
|
304
304
|
};
|
|
305
305
|
}
|
|
306
306
|
|
|
307
|
-
if (eventData.type === '
|
|
307
|
+
if (eventData.type === 'yield:cloned') {
|
|
308
308
|
return {
|
|
309
309
|
type: 'cloned',
|
|
310
310
|
method: payload.creationMethod || 'clone',
|
|
@@ -7,12 +7,14 @@ interface Props {
|
|
|
7
7
|
isConnected: boolean;
|
|
8
8
|
eventCount: number;
|
|
9
9
|
lastEventTimestamp?: string;
|
|
10
|
+
knowledgeBaseName?: string;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
export function CollaborationPanel({
|
|
13
14
|
isConnected,
|
|
14
15
|
eventCount,
|
|
15
|
-
lastEventTimestamp
|
|
16
|
+
lastEventTimestamp,
|
|
17
|
+
knowledgeBaseName
|
|
16
18
|
}: Props) {
|
|
17
19
|
const t = useTranslations('CollaborationPanel');
|
|
18
20
|
|
|
@@ -52,6 +54,12 @@ export function CollaborationPanel({
|
|
|
52
54
|
{t('title')}
|
|
53
55
|
</h3>
|
|
54
56
|
|
|
57
|
+
{knowledgeBaseName && (
|
|
58
|
+
<div style={{ padding: '0 0.75rem 0.5rem', fontSize: '0.8rem', color: 'var(--semiont-color-neutral-400)' }}>
|
|
59
|
+
{knowledgeBaseName}
|
|
60
|
+
</div>
|
|
61
|
+
)}
|
|
62
|
+
|
|
55
63
|
{/* Connection Status Section */}
|
|
56
64
|
<div className="semiont-collaboration-panel__section">
|
|
57
65
|
<h3 className="semiont-collaboration-panel__heading">
|
|
@@ -9,6 +9,7 @@ import { getAnnotationExactText, isBodyResolved, getBodySource, getFragmentSelec
|
|
|
9
9
|
import { getEntityTypes } from '@semiont/ontology';
|
|
10
10
|
import { getResourceIcon } from '../../../lib/resource-utils';
|
|
11
11
|
import { useEventBus } from '../../../contexts/EventBusContext';
|
|
12
|
+
import { useApiClient } from '../../../contexts/ApiClientContext';
|
|
12
13
|
import { useObservableExternalNavigation } from '../../../hooks/useObservableBrowse';
|
|
13
14
|
import { useHoverEmitter } from '../../../hooks/useBeckonFlow';
|
|
14
15
|
|
|
@@ -41,6 +42,7 @@ export function ReferenceEntry({
|
|
|
41
42
|
}: ReferenceEntryProps) {
|
|
42
43
|
const t = useTranslations('ReferencesPanel');
|
|
43
44
|
const eventBus = useEventBus();
|
|
45
|
+
const semiont = useApiClient();
|
|
44
46
|
const navigate = useObservableExternalNavigation();
|
|
45
47
|
const hoverProps = useHoverEmitter(reference.id);
|
|
46
48
|
|
|
@@ -74,11 +76,11 @@ export function ReferenceEntry({
|
|
|
74
76
|
|
|
75
77
|
const handleUnlink = () => {
|
|
76
78
|
if (source && resolvedResourceUri) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
});
|
|
79
|
+
semiont.bind.body(
|
|
80
|
+
resourceId(source),
|
|
81
|
+
annotationId(reference.id),
|
|
82
|
+
[{ op: 'remove', item: { type: 'SpecificResource', source: resolvedResourceUri } }],
|
|
83
|
+
).catch(() => { /* error handled by events-stream */ });
|
|
82
84
|
}
|
|
83
85
|
};
|
|
84
86
|
|
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
import { useTranslations } from '../../../contexts/TranslationContext';
|
|
4
4
|
import { useEventBus } from '../../../contexts/EventBusContext';
|
|
5
5
|
import { formatLocaleDisplay } from '@semiont/api-client';
|
|
6
|
-
import type
|
|
6
|
+
import { resourceId as makeResourceId, type components } from '@semiont/core';
|
|
7
7
|
import './ResourceInfoPanel.css';
|
|
8
8
|
|
|
9
9
|
type Agent = components['schemas']['Agent'];
|
|
10
10
|
|
|
11
11
|
interface Props {
|
|
12
|
+
resourceId: string;
|
|
12
13
|
documentEntityTypes: string[];
|
|
13
14
|
documentLocale?: string | undefined;
|
|
14
15
|
primaryMediaType?: string | undefined;
|
|
@@ -25,11 +26,12 @@ interface Props {
|
|
|
25
26
|
/**
|
|
26
27
|
* Panel for displaying resource metadata and management actions
|
|
27
28
|
*
|
|
28
|
-
* @emits yield:clone - Clone this resource
|
|
29
|
-
* @emits mark:unarchive - Unarchive this resource
|
|
30
|
-
* @emits mark:archive - Archive this resource
|
|
29
|
+
* @emits yield:clone - Clone this resource
|
|
30
|
+
* @emits mark:unarchive - Unarchive this resource
|
|
31
|
+
* @emits mark:archive - Archive this resource
|
|
31
32
|
*/
|
|
32
33
|
export function ResourceInfoPanel({
|
|
34
|
+
resourceId,
|
|
33
35
|
documentEntityTypes,
|
|
34
36
|
documentLocale,
|
|
35
37
|
primaryMediaType,
|
|
@@ -194,7 +196,7 @@ export function ResourceInfoPanel({
|
|
|
194
196
|
{isArchived ? (
|
|
195
197
|
<>
|
|
196
198
|
<button
|
|
197
|
-
onClick={() => eventBus.get('mark:unarchive').next(
|
|
199
|
+
onClick={() => eventBus.get('mark:unarchive').next({ resourceId: makeResourceId(resourceId) })}
|
|
198
200
|
className="semiont-resource-button semiont-resource-button--secondary"
|
|
199
201
|
>
|
|
200
202
|
📤 {t('unarchive')}
|
|
@@ -206,7 +208,7 @@ export function ResourceInfoPanel({
|
|
|
206
208
|
) : (
|
|
207
209
|
<>
|
|
208
210
|
<button
|
|
209
|
-
onClick={() => eventBus.get('mark:archive').next(
|
|
211
|
+
onClick={() => eventBus.get('mark:archive').next({ resourceId: makeResourceId(resourceId) })}
|
|
210
212
|
className="semiont-resource-button semiont-resource-button--archive"
|
|
211
213
|
>
|
|
212
214
|
📦 {t('archive')}
|