box-annotations 5.2.1-beta.13 → 5.2.1-beta.17
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/annotations.css +1 -1
- package/dist/annotations.js +1 -1
- package/package.json +13 -13
- package/src/@types/api.ts +3 -2
- package/src/@types/events.ts +27 -10
- package/src/@types/model.ts +1 -0
- package/src/adapters/threadedAnnotationsAdapters.ts +17 -1
- package/src/api/APIFactory.ts +6 -1
- package/src/api/types.ts +23 -1
- package/src/common/BaseAnnotator.scss +2 -1
- package/src/common/BaseAnnotator.ts +27 -2
- package/src/components/Popups/PopupV2.tsx +46 -10
- package/src/document/DocumentAnnotator.ts +1 -1
- package/src/document/docUtil.ts +2 -1
- package/src/highlight/HighlightAnnotations.tsx +27 -14
- package/src/highlight/HighlightContainer.tsx +3 -0
- package/src/highlight/HighlightCreatorManager.ts +8 -5
- package/src/highlight/HighlightListener.ts +4 -2
- package/src/highlight/HighlightManager.tsx +13 -2
- package/src/popup/PopupLayer.tsx +32 -4
- package/src/store/annotations/actions.ts +89 -0
- package/src/store/annotations/reducer.ts +48 -0
- package/src/store/eventing/create.ts +3 -3
- package/src/store/eventing/delete.ts +27 -0
- package/src/store/eventing/middleware.ts +40 -4
- package/src/store/eventing/replyCreate.ts +34 -0
- package/src/store/eventing/replyDelete.ts +35 -0
- package/src/store/eventing/replyUpdate.ts +39 -0
- package/src/store/eventing/types.ts +4 -4
- package/src/store/eventing/update.ts +29 -0
- package/src/store/highlight/actions.ts +32 -3
- package/src/store/highlight/types.ts +1 -0
- package/src/store/options/selectors.ts +2 -2
- package/src/store/options/types.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "box-annotations",
|
|
3
|
-
"version": "5.2.1-beta.
|
|
3
|
+
"version": "5.2.1-beta.17",
|
|
4
4
|
"description": "Box Annotations",
|
|
5
5
|
"author": "Box (https://www.box.com/)",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -28,13 +28,13 @@
|
|
|
28
28
|
"@babel/preset-react": "^7.25.9",
|
|
29
29
|
"@babel/preset-typescript": "^7.25.9",
|
|
30
30
|
"@box/frontend": "^10.0.0",
|
|
31
|
-
"@box/blueprint-web": "^14.
|
|
32
|
-
"@box/blueprint-web-assets": "^4.
|
|
33
|
-
"@box/collaboration-popover": "^1.62.
|
|
31
|
+
"@box/blueprint-web": "^14.38.0",
|
|
32
|
+
"@box/blueprint-web-assets": "^4.120.5",
|
|
33
|
+
"@box/collaboration-popover": "^1.62.27",
|
|
34
34
|
"@box/languages": "^1.1.0",
|
|
35
|
-
"@box/readable-time": "^1.41.
|
|
36
|
-
"@box/threaded-annotations": "^1.
|
|
37
|
-
"@box/user-selector": "^1.76.
|
|
35
|
+
"@box/readable-time": "^1.41.27",
|
|
36
|
+
"@box/threaded-annotations": "^1.93.2",
|
|
37
|
+
"@box/user-selector": "^1.76.27",
|
|
38
38
|
"@cfaester/enzyme-adapter-react-18": "^0.8.0",
|
|
39
39
|
"@commitlint/cli": "^8.3.5",
|
|
40
40
|
"@commitlint/config-conventional": "^8.2.0",
|
|
@@ -128,12 +128,12 @@
|
|
|
128
128
|
"worker-farm": "^1.7.0"
|
|
129
129
|
},
|
|
130
130
|
"peerDependencies": {
|
|
131
|
-
"@box/blueprint-web": "^14.
|
|
132
|
-
"@box/blueprint-web-assets": "^4.
|
|
133
|
-
"@box/collaboration-popover": "^1.62.
|
|
134
|
-
"@box/readable-time": "^1.41.
|
|
135
|
-
"@box/threaded-annotations": "^1.
|
|
136
|
-
"@box/user-selector": "^1.76.
|
|
131
|
+
"@box/blueprint-web": "^14.38.0",
|
|
132
|
+
"@box/blueprint-web-assets": "^4.120.5",
|
|
133
|
+
"@box/collaboration-popover": "^1.62.27",
|
|
134
|
+
"@box/readable-time": "^1.41.27",
|
|
135
|
+
"@box/threaded-annotations": "^1.93.2",
|
|
136
|
+
"@box/user-selector": "^1.76.27"
|
|
137
137
|
},
|
|
138
138
|
"scripts": {
|
|
139
139
|
"build": "yarn setup && yarn build:prod:dist",
|
package/src/@types/api.ts
CHANGED
|
@@ -10,5 +10,6 @@ export interface Permissions {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export type TokenLiteral = null | undefined | string | { read: string; write?: string };
|
|
13
|
-
export type
|
|
14
|
-
export type
|
|
13
|
+
export type TokenMap = { [typedFileId: string]: TokenLiteral };
|
|
14
|
+
export type TokenResolver = (typedFileId?: string) => TokenLiteral | TokenMap | Promise<TokenLiteral | TokenMap>;
|
|
15
|
+
export type Token = TokenLiteral | TokenMap | TokenResolver;
|
package/src/@types/events.ts
CHANGED
|
@@ -1,19 +1,36 @@
|
|
|
1
1
|
enum Event {
|
|
2
|
-
|
|
2
|
+
// Inbound: emitted by the host; box-annotations listens.
|
|
3
3
|
ACTIVE_SET = 'annotations_active_set',
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
ANNOTATION_REMOVE = 'annotations_remove',
|
|
5
|
+
BOUNDING_BOX_HIGHLIGHTS_SET = 'bounding_box_highlights_set',
|
|
6
|
+
BOUNDING_BOX_HIGHLIGHT_SELECT = 'bounding_box_highlight_select',
|
|
7
|
+
COLOR_SET = 'annotations_color_set',
|
|
8
|
+
VIEW_MODE_SET = 'view_mode_set',
|
|
9
|
+
VISIBLE_SET = 'annotations_visible_set',
|
|
10
|
+
|
|
11
|
+
// Outbound: emitted by box-annotations; the host listens.
|
|
12
|
+
ACTIVE_CHANGE = 'annotations_active_change',
|
|
6
13
|
ANNOTATION_CREATE = 'annotations_create',
|
|
14
|
+
ANNOTATION_DELETE = 'annotations_delete',
|
|
7
15
|
ANNOTATION_FETCH_ERROR = 'annotations_fetch_error',
|
|
8
|
-
|
|
16
|
+
ANNOTATION_REPLY_CREATE = 'annotations_reply_create',
|
|
17
|
+
ANNOTATION_REPLY_DELETE = 'annotations_reply_delete',
|
|
18
|
+
ANNOTATION_REPLY_UPDATE = 'annotations_reply_update',
|
|
19
|
+
ANNOTATION_UPDATE = 'annotations_update',
|
|
9
20
|
ANNOTATIONS_INITIALIZED = 'annotations_initialized',
|
|
10
21
|
ANNOTATIONS_MODE_CHANGE = 'annotations_mode_change',
|
|
11
|
-
|
|
12
|
-
|
|
22
|
+
CREATOR_STAGED_CHANGE = 'creator_staged_change',
|
|
23
|
+
CREATOR_STATUS_CHANGE = 'creator_status_change',
|
|
24
|
+
|
|
25
|
+
// Internal: box-annotations emits and listens to itself (redux state change → DOM side effect).
|
|
13
26
|
BOUNDING_BOX_HIGHLIGHT_NAVIGATE = 'bounding_box_highlight_navigate',
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
enum SidebarEvent {
|
|
30
|
+
SIDEBAR_ANNOTATION_UPDATE = 'sidebar.annotations_update',
|
|
31
|
+
SIDEBAR_REPLY_CREATE = 'sidebar.annotations_reply_create',
|
|
32
|
+
SIDEBAR_REPLY_DELETE = 'sidebar.annotations_reply_delete',
|
|
33
|
+
SIDEBAR_REPLY_UPDATE = 'sidebar.annotations_reply_update',
|
|
17
34
|
}
|
|
18
35
|
|
|
19
36
|
// Existing legacy events, don't rename
|
|
@@ -23,4 +40,4 @@ enum LegacyEvent {
|
|
|
23
40
|
SCALE = 'scaleannotations',
|
|
24
41
|
}
|
|
25
42
|
|
|
26
|
-
export { Event, LegacyEvent };
|
|
43
|
+
export { Event, LegacyEvent, SidebarEvent };
|
package/src/@types/model.ts
CHANGED
|
@@ -69,6 +69,20 @@ export const deserializeMentionMarkup = (text: string): DocumentNodeV2 => {
|
|
|
69
69
|
return { type: 'doc', content };
|
|
70
70
|
};
|
|
71
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Returns the edit timestamp consumers use to render an edited indicator.
|
|
74
|
+
* Compares parsed instants, not raw strings, so equivalent ISO formats
|
|
75
|
+
* (Z vs +00:00, fractional precision) are treated as unedited.
|
|
76
|
+
*/
|
|
77
|
+
const toUpdatedAt = (createdAt: string, modifiedAt: string | undefined): number | undefined => {
|
|
78
|
+
if (!modifiedAt) return undefined;
|
|
79
|
+
const modifiedMs = new Date(modifiedAt).getTime();
|
|
80
|
+
if (Number.isNaN(modifiedMs)) return undefined;
|
|
81
|
+
const createdMs = new Date(createdAt).getTime();
|
|
82
|
+
if (modifiedMs === createdMs) return undefined;
|
|
83
|
+
return modifiedMs;
|
|
84
|
+
};
|
|
85
|
+
|
|
72
86
|
/**
|
|
73
87
|
* Converts a box-annotations Reply to a threaded-annotations TextMessageType.
|
|
74
88
|
*/
|
|
@@ -83,10 +97,11 @@ export const replyToTextMessage = (reply: Reply): TextMessageTypeV2 => ({
|
|
|
83
97
|
message: deserializeMentionMarkup(reply.message),
|
|
84
98
|
permissions: {
|
|
85
99
|
canDelete: reply.permissions?.can_delete ?? false,
|
|
86
|
-
canEdit: false,
|
|
100
|
+
canEdit: reply.permissions?.can_edit ?? false,
|
|
87
101
|
canReply: reply.permissions?.can_reply ?? false,
|
|
88
102
|
canResolve: reply.permissions?.can_resolve ?? false,
|
|
89
103
|
},
|
|
104
|
+
updatedAt: toUpdatedAt(reply.created_at, reply.modified_at),
|
|
90
105
|
});
|
|
91
106
|
|
|
92
107
|
// The root message shares the annotation's author and permissions; description
|
|
@@ -106,6 +121,7 @@ const descriptionToTextMessage = (annotation: Annotation): TextMessageTypeV2 =>
|
|
|
106
121
|
canReply: annotation.permissions?.can_reply ?? false,
|
|
107
122
|
canResolve: annotation.permissions?.can_resolve ?? false,
|
|
108
123
|
},
|
|
124
|
+
updatedAt: toUpdatedAt(annotation.created_at, annotation.modified_at),
|
|
109
125
|
});
|
|
110
126
|
|
|
111
127
|
/**
|
package/src/api/APIFactory.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import Annotations from 'box-ui-elements/es/api/Annotations';
|
|
2
2
|
import FileCollaborators from 'box-ui-elements/es/api/FileCollaborators';
|
|
3
|
+
import ThreadedComments from 'box-ui-elements/es/api/ThreadedComments';
|
|
3
4
|
import { DEFAULT_HOSTNAME_API } from 'box-ui-elements/es/constants';
|
|
4
|
-
import { AnnotationsAPI, CollaboratorsAPI, APIOptions } from './types';
|
|
5
|
+
import { AnnotationsAPI, CollaboratorsAPI, APIOptions, ThreadedCommentsAPI } from './types';
|
|
5
6
|
|
|
6
7
|
export default class APIFactory {
|
|
7
8
|
options: APIOptions;
|
|
@@ -21,4 +22,8 @@ export default class APIFactory {
|
|
|
21
22
|
getCollaboratorsAPI(): CollaboratorsAPI {
|
|
22
23
|
return new FileCollaborators(this.options);
|
|
23
24
|
}
|
|
25
|
+
|
|
26
|
+
getThreadedCommentsAPI(): ThreadedCommentsAPI {
|
|
27
|
+
return new ThreadedComments(this.options);
|
|
28
|
+
}
|
|
24
29
|
}
|
package/src/api/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Annotation, Collaborator, NewAnnotation, Permissions, Reply, Token } from '../@types';
|
|
1
|
+
import { Annotation, Collaborator, NewAnnotation, Permissions, Reply, ReplyPermissions, Token } from '../@types';
|
|
2
2
|
|
|
3
3
|
export type APICollection<R> = {
|
|
4
4
|
entries: R[];
|
|
@@ -73,6 +73,28 @@ export interface AnnotationsAPI {
|
|
|
73
73
|
destroy(): void;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
export interface ThreadedCommentsAPI {
|
|
77
|
+
deleteComment(args: {
|
|
78
|
+
commentId: string;
|
|
79
|
+
errorCallback: (error: APIError) => void;
|
|
80
|
+
fileId: string | null;
|
|
81
|
+
permissions: ReplyPermissions;
|
|
82
|
+
successCallback: () => void;
|
|
83
|
+
}): void;
|
|
84
|
+
|
|
85
|
+
updateComment(args: {
|
|
86
|
+
commentId: string;
|
|
87
|
+
errorCallback: (error: APIError) => void;
|
|
88
|
+
fileId: string | null;
|
|
89
|
+
message?: string;
|
|
90
|
+
permissions: ReplyPermissions;
|
|
91
|
+
status?: string;
|
|
92
|
+
successCallback: (reply: Reply) => void;
|
|
93
|
+
}): void;
|
|
94
|
+
|
|
95
|
+
destroy(): void;
|
|
96
|
+
}
|
|
97
|
+
|
|
76
98
|
export interface CollaboratorsAPI {
|
|
77
99
|
getFileCollaborators(
|
|
78
100
|
fileId: string | null,
|
|
@@ -6,7 +6,7 @@ import DeselectManager from './DeselectManager';
|
|
|
6
6
|
import EventEmitter from './EventEmitter';
|
|
7
7
|
import i18n from '../utils/i18n';
|
|
8
8
|
import messages from '../messages';
|
|
9
|
-
import { Event, IntlOptions, LegacyEvent, Permissions } from '../@types';
|
|
9
|
+
import { Event, IntlOptions, LegacyEvent, Permissions, SidebarEvent, Token } from '../@types';
|
|
10
10
|
import { BoundingBox, getBoundingBoxHighlights } from '../store/boundingBoxHighlights';
|
|
11
11
|
import { ViewMode } from '../store/options/types';
|
|
12
12
|
import { Features } from '../BoxAnnotations';
|
|
@@ -45,7 +45,7 @@ export type Options = {
|
|
|
45
45
|
intl: IntlOptions;
|
|
46
46
|
locale?: string;
|
|
47
47
|
onCopyLink?: (params: { annotationId: string; fileVersionId: string }) => void;
|
|
48
|
-
token:
|
|
48
|
+
token: Token;
|
|
49
49
|
};
|
|
50
50
|
|
|
51
51
|
export const CSS_CONTAINER_CLASS = 'ba';
|
|
@@ -131,6 +131,11 @@ export default class BaseAnnotator extends EventEmitter {
|
|
|
131
131
|
this.addListener(Event.BOUNDING_BOX_HIGHLIGHT_SELECT, this.handleSelectBoundingBoxHighlight);
|
|
132
132
|
this.addListener(Event.VIEW_MODE_SET, this.handleSetViewMode);
|
|
133
133
|
|
|
134
|
+
this.addListener(SidebarEvent.SIDEBAR_ANNOTATION_UPDATE, this.handleSidebarAnnotationUpdate);
|
|
135
|
+
this.addListener(SidebarEvent.SIDEBAR_REPLY_CREATE, this.handleSidebarReplyCreate);
|
|
136
|
+
this.addListener(SidebarEvent.SIDEBAR_REPLY_UPDATE, this.handleSidebarReplyUpdate);
|
|
137
|
+
this.addListener(SidebarEvent.SIDEBAR_REPLY_DELETE, this.handleSidebarReplyDelete);
|
|
138
|
+
|
|
134
139
|
// Load any required data at startup
|
|
135
140
|
this.hydrate();
|
|
136
141
|
}
|
|
@@ -164,6 +169,10 @@ export default class BaseAnnotator extends EventEmitter {
|
|
|
164
169
|
this.removeListener(Event.BOUNDING_BOX_HIGHLIGHT_NAVIGATE, this.handleNavigateBoundingBoxHighlight);
|
|
165
170
|
this.removeListener(Event.BOUNDING_BOX_HIGHLIGHT_SELECT, this.handleSelectBoundingBoxHighlight);
|
|
166
171
|
this.removeListener(Event.VIEW_MODE_SET, this.handleSetViewMode);
|
|
172
|
+
this.removeListener(SidebarEvent.SIDEBAR_ANNOTATION_UPDATE, this.handleSidebarAnnotationUpdate);
|
|
173
|
+
this.removeListener(SidebarEvent.SIDEBAR_REPLY_CREATE, this.handleSidebarReplyCreate);
|
|
174
|
+
this.removeListener(SidebarEvent.SIDEBAR_REPLY_UPDATE, this.handleSidebarReplyUpdate);
|
|
175
|
+
this.removeListener(SidebarEvent.SIDEBAR_REPLY_DELETE, this.handleSidebarReplyDelete);
|
|
167
176
|
}
|
|
168
177
|
|
|
169
178
|
public init(scale = 1, rotation = 0): void {
|
|
@@ -357,6 +366,22 @@ export default class BaseAnnotator extends EventEmitter {
|
|
|
357
366
|
this.setViewMode(viewMode);
|
|
358
367
|
};
|
|
359
368
|
|
|
369
|
+
protected handleSidebarAnnotationUpdate = (annotation: store.SidebarAnnotationUpdatePayload): void => {
|
|
370
|
+
this.store.dispatch(store.applySidebarAnnotationUpdateAction(annotation));
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
protected handleSidebarReplyCreate = (payload: store.SidebarReplyMutationPayload): void => {
|
|
374
|
+
this.store.dispatch(store.applySidebarReplyCreateAction(payload));
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
protected handleSidebarReplyUpdate = (payload: store.SidebarReplyMutationPayload): void => {
|
|
378
|
+
this.store.dispatch(store.applySidebarReplyUpdateAction(payload));
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
protected handleSidebarReplyDelete = ({ annotationId, id }: { annotationId: string; id: string }): void => {
|
|
382
|
+
this.store.dispatch(store.applySidebarReplyDeleteAction({ annotationId, replyId: id }));
|
|
383
|
+
};
|
|
384
|
+
|
|
360
385
|
protected hydrate(): void {
|
|
361
386
|
// Redux dispatch method signature doesn't seem to like async actions
|
|
362
387
|
this.store.dispatch<any>(store.fetchAnnotationsAction()); // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
@@ -21,13 +21,16 @@ import AnnotationCallbacksContext from '../../common/AnnotationCallbacksContext'
|
|
|
21
21
|
import {
|
|
22
22
|
createReplyAction,
|
|
23
23
|
deleteAnnotationAction,
|
|
24
|
+
deleteReplyAction,
|
|
24
25
|
setActiveAnnotationIdAction,
|
|
25
26
|
updateAnnotationAction,
|
|
27
|
+
updateReplyAction,
|
|
26
28
|
} from '../../store/annotations/actions';
|
|
27
29
|
import { getAnnotation } from '../../store/annotations/selectors';
|
|
28
|
-
import { getApiHost, getFileVersionId, getToken } from '../../store/options';
|
|
30
|
+
import { getApiHost, getFileId, getFileVersionId, getToken } from '../../store/options';
|
|
29
31
|
import { fetchCollaboratorsAction } from '../../store/users/actions';
|
|
30
32
|
|
|
33
|
+
import type { Token, TokenLiteral, TokenMap } from '../../@types';
|
|
31
34
|
import type { AppState, AppThunkDispatch } from '../../store/types';
|
|
32
35
|
|
|
33
36
|
import createPopper, { PopupReference } from './Popper';
|
|
@@ -63,12 +66,32 @@ const createDocumentNode = (content: JSONContent | null): DocumentNodeV2 => {
|
|
|
63
66
|
return { type: 'doc', content: [content] } as DocumentNodeV2;
|
|
64
67
|
};
|
|
65
68
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
+
const literalToString = (literal: TokenLiteral): string | null => {
|
|
70
|
+
if (!literal) return null;
|
|
71
|
+
if (typeof literal === 'string') return literal;
|
|
72
|
+
return literal.read ?? literal.write ?? null;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const resolveStringToken = async (token: Token, typedFileId: string): Promise<string | null> => {
|
|
76
|
+
const resolved = typeof token === 'function' ? await token(typedFileId) : token;
|
|
77
|
+
if (!resolved) return null;
|
|
78
|
+
if (typeof resolved === 'string') return resolved;
|
|
79
|
+
if (typedFileId in resolved) return literalToString((resolved as TokenMap)[typedFileId]);
|
|
80
|
+
return literalToString(resolved as TokenLiteral);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const fetchAvatarBlob = async (
|
|
84
|
+
apiHost: string,
|
|
85
|
+
token: Token,
|
|
86
|
+
fileId: string | null,
|
|
87
|
+
userId: string,
|
|
88
|
+
): Promise<string | null> => {
|
|
69
89
|
try {
|
|
90
|
+
if (!fileId) return null;
|
|
91
|
+
const stringToken = await resolveStringToken(token, `file_${fileId}`);
|
|
92
|
+
if (!stringToken) return null;
|
|
70
93
|
const response = await fetch(`${apiHost}/2.0/users/${userId}/avatar?pic_type=large`, {
|
|
71
|
-
headers: { Authorization: `Bearer ${
|
|
94
|
+
headers: { Authorization: `Bearer ${stringToken}` },
|
|
72
95
|
});
|
|
73
96
|
if (!response.ok) return null;
|
|
74
97
|
const blob = await response.blob();
|
|
@@ -87,6 +110,7 @@ const PopupV2 = ({ annotationId, onSubmit, popupPortalEl, reference }: Props): J
|
|
|
87
110
|
const optionsRef = React.useRef<Partial<Options>>(getPopupOptions());
|
|
88
111
|
|
|
89
112
|
const apiHost = useSelector(getApiHost);
|
|
113
|
+
const fileId = useSelector(getFileId);
|
|
90
114
|
const fileVersionId = useSelector(getFileVersionId);
|
|
91
115
|
const token = useSelector(getToken);
|
|
92
116
|
const onCopyLink = React.useMemo(
|
|
@@ -111,7 +135,7 @@ const PopupV2 = ({ annotationId, onSubmit, popupPortalEl, reference }: Props): J
|
|
|
111
135
|
if (cached) return cached;
|
|
112
136
|
const capturedApiHost = apiHost;
|
|
113
137
|
const capturedToken = token;
|
|
114
|
-
const url = await fetchAvatarBlob(capturedApiHost, capturedToken, userId);
|
|
138
|
+
const url = await fetchAvatarBlob(capturedApiHost, capturedToken, fileId, userId);
|
|
115
139
|
if (!url) return null;
|
|
116
140
|
if (
|
|
117
141
|
credentialsRef.current.apiHost !== capturedApiHost ||
|
|
@@ -128,7 +152,7 @@ const PopupV2 = ({ annotationId, onSubmit, popupPortalEl, reference }: Props): J
|
|
|
128
152
|
avatarCacheRef.current.set(userId, url);
|
|
129
153
|
return url;
|
|
130
154
|
},
|
|
131
|
-
[apiHost, token],
|
|
155
|
+
[apiHost, fileId, token],
|
|
132
156
|
);
|
|
133
157
|
|
|
134
158
|
React.useEffect(() => {
|
|
@@ -247,10 +271,14 @@ const PopupV2 = ({ annotationId, onSubmit, popupPortalEl, reference }: Props): J
|
|
|
247
271
|
|
|
248
272
|
const handleEdit = React.useCallback(
|
|
249
273
|
async (id: string, content: JSONContent | null): Promise<void> => {
|
|
250
|
-
if (!annotationId
|
|
274
|
+
if (!annotationId) return;
|
|
251
275
|
const doc = createDocumentNode(content);
|
|
252
276
|
const { text } = serializeMentionMarkup(doc);
|
|
253
|
-
|
|
277
|
+
if (id === annotationId) {
|
|
278
|
+
await dispatch(updateAnnotationAction({ annotationId, payload: { message: text } }));
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
await dispatch(updateReplyAction({ annotationId, replyId: id, payload: { message: text } }));
|
|
254
282
|
},
|
|
255
283
|
[annotationId, dispatch],
|
|
256
284
|
);
|
|
@@ -264,6 +292,14 @@ const PopupV2 = ({ annotationId, onSubmit, popupPortalEl, reference }: Props): J
|
|
|
264
292
|
[annotationId, dispatch],
|
|
265
293
|
);
|
|
266
294
|
|
|
295
|
+
const handleDelete = React.useCallback(
|
|
296
|
+
async (id: string): Promise<void> => {
|
|
297
|
+
if (!annotationId || id === annotationId) return;
|
|
298
|
+
await dispatch(deleteReplyAction({ annotationId, replyId: id }));
|
|
299
|
+
},
|
|
300
|
+
[annotationId, dispatch],
|
|
301
|
+
);
|
|
302
|
+
|
|
267
303
|
const handleResolve = React.useCallback(
|
|
268
304
|
async (): Promise<void> => {
|
|
269
305
|
if (!annotationId) return;
|
|
@@ -314,7 +350,7 @@ const PopupV2 = ({ annotationId, onSubmit, popupPortalEl, reference }: Props): J
|
|
|
314
350
|
messages={threadMessages}
|
|
315
351
|
onAvatarClick={noop}
|
|
316
352
|
onCopyLink={onCopyLink}
|
|
317
|
-
onDelete={
|
|
353
|
+
onDelete={handleDelete}
|
|
318
354
|
onEdit={handleEdit}
|
|
319
355
|
onPost={handlePost}
|
|
320
356
|
onResolve={handleResolve}
|
|
@@ -118,7 +118,7 @@ export default class DocumentAnnotator extends BaseAnnotator {
|
|
|
118
118
|
);
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
managers.add(new HighlightManager({ location: pageNumber, referenceEl: pageReferenceEl, resinTags }));
|
|
121
|
+
managers.add(new HighlightManager({ location: pageNumber, popupPortalEl: this.popupPortalEl, referenceEl: pageReferenceEl, resinTags }));
|
|
122
122
|
managers.add(new RegionManager({ location: pageNumber, referenceEl: pageReferenceEl, resinTags }));
|
|
123
123
|
|
|
124
124
|
const canvasLayerEl = pageEl.querySelector<HTMLElement>('.canvasWrapper');
|
package/src/document/docUtil.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
type Selection = {
|
|
2
|
+
containerEl: HTMLElement;
|
|
2
3
|
containerRect: DOMRect;
|
|
3
4
|
hasError?: boolean;
|
|
4
5
|
location: number;
|
|
@@ -61,5 +62,5 @@ export function getSelection(): Selection | null {
|
|
|
61
62
|
location = getPageNumber(selection.focusNode as Element) ?? endPage;
|
|
62
63
|
}
|
|
63
64
|
|
|
64
|
-
return { containerRect: containerEl.getBoundingClientRect(), hasError, location, range };
|
|
65
|
+
return { containerEl: containerEl as HTMLElement, containerRect: containerEl.getBoundingClientRect(), hasError, location, range };
|
|
65
66
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
+
import ReactDOM from 'react-dom';
|
|
2
3
|
import HighlightCanvas from './HighlightCanvas';
|
|
3
4
|
import HighlightList from './HighlightList';
|
|
4
5
|
import HighlightSvg from './HighlightSvg';
|
|
@@ -20,6 +21,8 @@ type Props = {
|
|
|
20
21
|
isPromoting: boolean;
|
|
21
22
|
isSelecting: boolean;
|
|
22
23
|
location: number;
|
|
24
|
+
popupPortalEl?: HTMLElement | null;
|
|
25
|
+
rotation: number;
|
|
23
26
|
selection: SelectionItem | null;
|
|
24
27
|
setActiveAnnotationId: (annotationId: string | null) => void;
|
|
25
28
|
setIsPromoting: (isPromoting: boolean) => void;
|
|
@@ -38,6 +41,8 @@ const HighlightAnnotations = (props: Props): JSX.Element => {
|
|
|
38
41
|
isPending = false,
|
|
39
42
|
isPromoting = false,
|
|
40
43
|
isSelecting = false,
|
|
44
|
+
popupPortalEl,
|
|
45
|
+
rotation,
|
|
41
46
|
selection,
|
|
42
47
|
setActiveAnnotationId,
|
|
43
48
|
setIsPromoting,
|
|
@@ -121,22 +126,30 @@ const HighlightAnnotations = (props: Props): JSX.Element => {
|
|
|
121
126
|
)}
|
|
122
127
|
|
|
123
128
|
{/* Layer 3a: Annotations promoter to promote selection to staged */}
|
|
124
|
-
{!isCreating && selection && !selection.hasError && (
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
129
|
+
{!isCreating && selection && !selection.hasError && (() => {
|
|
130
|
+
const popupShape = selection.popupRect || getBoundingRect(selection.rects);
|
|
131
|
+
const popup = (
|
|
132
|
+
<div className="ba-HighlightAnnotations-popup">
|
|
133
|
+
<PopupHighlight
|
|
134
|
+
onCancel={handleCancel}
|
|
135
|
+
onSubmit={handlePromote}
|
|
136
|
+
shape={popupShape}
|
|
137
|
+
/>
|
|
138
|
+
</div>
|
|
139
|
+
);
|
|
140
|
+
return rotation && popupPortalEl ? ReactDOM.createPortal(popup, popupPortalEl) : popup;
|
|
141
|
+
})()}
|
|
133
142
|
|
|
134
143
|
{/* Layer 3b: Highlight error popup */}
|
|
135
|
-
{selection && selection.hasError && (
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
144
|
+
{selection && selection.hasError && (() => {
|
|
145
|
+
const popupShape = selection.popupRect || getBoundingRect(selection.rects);
|
|
146
|
+
const popup = (
|
|
147
|
+
<div className="ba-HighlightAnnotations-popup">
|
|
148
|
+
<PopupHighlightError onCancel={handleCancel} shape={popupShape} />
|
|
149
|
+
</div>
|
|
150
|
+
);
|
|
151
|
+
return rotation && popupPortalEl ? ReactDOM.createPortal(popup, popupPortalEl) : popup;
|
|
152
|
+
})()}
|
|
140
153
|
</>
|
|
141
154
|
);
|
|
142
155
|
};
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
getCreatorStatus,
|
|
14
14
|
getIsPromoting,
|
|
15
15
|
getIsSelecting,
|
|
16
|
+
getRotation,
|
|
16
17
|
getSelectionForLocation,
|
|
17
18
|
isCreatorStagedHighlight,
|
|
18
19
|
Mode,
|
|
@@ -33,6 +34,7 @@ export type Props = {
|
|
|
33
34
|
isPending: boolean;
|
|
34
35
|
isPromoting: boolean;
|
|
35
36
|
isSelecting: boolean;
|
|
37
|
+
rotation: number;
|
|
36
38
|
selection: SelectionItem | null;
|
|
37
39
|
staged: CreatorItemHighlight | null;
|
|
38
40
|
};
|
|
@@ -47,6 +49,7 @@ export const mapStateToProps = (state: AppState, { location }: { location: numbe
|
|
|
47
49
|
isPending: getCreatorStatus(state) === CreatorStatus.pending,
|
|
48
50
|
isPromoting: getIsPromoting(state),
|
|
49
51
|
isSelecting: getIsSelecting(state),
|
|
52
|
+
rotation: getRotation(state),
|
|
50
53
|
selection: getSelectionForLocation(state, location),
|
|
51
54
|
staged: isCreatorStagedHighlight(staged) ? staged : null,
|
|
52
55
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AppStore,
|
|
3
3
|
getIsSelecting,
|
|
4
|
+
getRotation,
|
|
4
5
|
SelectionArg as Selection,
|
|
5
6
|
setIsSelectingAction,
|
|
6
7
|
setSelectionAction,
|
|
@@ -41,18 +42,18 @@ export default class HighlightCreatorManager implements Manager {
|
|
|
41
42
|
this.store.dispatch(setSelectionAction(null));
|
|
42
43
|
|
|
43
44
|
this.referenceEl.addEventListener('mousedown', this.handleMouseDown);
|
|
44
|
-
|
|
45
|
+
document.addEventListener('mouseup', this.handleMouseUp);
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
style(
|
|
48
|
-
return
|
|
48
|
+
style(): CSSStyleDeclaration {
|
|
49
|
+
return this.referenceEl.style;
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
destroy(): void {
|
|
52
53
|
clearTimeout(this.selectionChangeTimer);
|
|
53
54
|
|
|
54
55
|
this.referenceEl.removeEventListener('mousedown', this.handleMouseDown);
|
|
55
|
-
|
|
56
|
+
document.removeEventListener('mouseup', this.handleMouseUp);
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
handleMouseDown = ({ buttons }: MouseEvent): void => {
|
|
@@ -72,7 +73,9 @@ export default class HighlightCreatorManager implements Manager {
|
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
this.selectionChangeTimer = window.setTimeout(() => {
|
|
75
|
-
this.
|
|
76
|
+
const selection = this.getSelection();
|
|
77
|
+
const rotation = getRotation(this.store.getState());
|
|
78
|
+
this.store.dispatch(setSelectionAction(selection ? { ...selection, rotation } : null));
|
|
76
79
|
this.store.dispatch(setIsSelectingAction(false));
|
|
77
80
|
}, this.selectionChangeDelay);
|
|
78
81
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import debounce from 'lodash/debounce';
|
|
2
|
-
import { AppStore, getIsSelecting, SelectionArg as Selection, setSelectionAction } from '../store';
|
|
2
|
+
import { AppStore, getIsSelecting, getRotation, SelectionArg as Selection, setSelectionAction } from '../store';
|
|
3
3
|
|
|
4
4
|
export type Options = {
|
|
5
5
|
getSelection: () => Selection | null;
|
|
@@ -30,7 +30,9 @@ export default class HighlightListener {
|
|
|
30
30
|
return;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
this.
|
|
33
|
+
const selection = this.getSelection();
|
|
34
|
+
const rotation = getRotation(this.store.getState());
|
|
35
|
+
this.store.dispatch(setSelectionAction(selection ? { ...selection, rotation } : null));
|
|
34
36
|
};
|
|
35
37
|
|
|
36
38
|
debounceHandleSelectionChange = debounce(this.handleSelectionChange, SELECTION_CHANGE_DEBOUNCE);
|
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import * as ReactDOM from 'react-dom/client';
|
|
3
|
-
import BaseManager, { Props } from '../common/BaseManager';
|
|
3
|
+
import BaseManager, { Options as BaseOptions, Props } from '../common/BaseManager';
|
|
4
4
|
import HighlightContainer from './HighlightContainer';
|
|
5
5
|
|
|
6
|
+
export type Options = BaseOptions & {
|
|
7
|
+
popupPortalEl?: HTMLElement | null;
|
|
8
|
+
};
|
|
9
|
+
|
|
6
10
|
export default class HighlightManager extends BaseManager {
|
|
11
|
+
popupPortalEl?: HTMLElement | null;
|
|
12
|
+
|
|
13
|
+
constructor({ popupPortalEl, ...options }: Options) {
|
|
14
|
+
super(options);
|
|
15
|
+
this.popupPortalEl = popupPortalEl;
|
|
16
|
+
}
|
|
17
|
+
|
|
7
18
|
decorate(): void {
|
|
8
19
|
this.reactEl.classList.add('ba-Layer--highlight');
|
|
9
20
|
this.reactEl.dataset.testid = 'ba-Layer--highlight';
|
|
@@ -14,6 +25,6 @@ export default class HighlightManager extends BaseManager {
|
|
|
14
25
|
this.root = ReactDOM.createRoot(this.reactEl);
|
|
15
26
|
}
|
|
16
27
|
|
|
17
|
-
this.root.render(<HighlightContainer location={this.location} {...props} />);
|
|
28
|
+
this.root.render(<HighlightContainer location={this.location} popupPortalEl={this.popupPortalEl} {...props} />);
|
|
18
29
|
}
|
|
19
30
|
}
|