box-annotations 5.2.1-beta.5 → 5.2.1-beta.7

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.
@@ -12,9 +12,9 @@ object-assign
12
12
 
13
13
  /*!
14
14
  * Box Annotations
15
- *
15
+ *
16
16
  * Copyright 2016-present Box, Inc. All Rights Reserved.
17
- *
17
+ *
18
18
  * This source code is licensed under the Box Software License Agreement found
19
19
  * in the LICENSE file in the root directory of this source tree. Additional
20
20
  * third party license disclosures can be found in the THIRD_PARTY_LICENSES
@@ -30,46 +30,6 @@ object-assign
30
30
  * Licensed under MIT License
31
31
  */
32
32
 
33
- /**
34
- * @license React
35
- * react-dom.production.min.js
36
- *
37
- * Copyright (c) Facebook, Inc. and its affiliates.
38
- *
39
- * This source code is licensed under the MIT license found in the
40
- * LICENSE file in the root directory of this source tree.
41
- */
42
-
43
- /**
44
- * @license React
45
- * react.production.min.js
46
- *
47
- * Copyright (c) Facebook, Inc. and its affiliates.
48
- *
49
- * This source code is licensed under the MIT license found in the
50
- * LICENSE file in the root directory of this source tree.
51
- */
52
-
53
- /**
54
- * @license React
55
- * scheduler.production.min.js
56
- *
57
- * Copyright (c) Facebook, Inc. and its affiliates.
58
- *
59
- * This source code is licensed under the MIT license found in the
60
- * LICENSE file in the root directory of this source tree.
61
- */
62
-
63
- /**
64
- * @license React
65
- * use-sync-external-store-with-selector.production.min.js
66
- *
67
- * Copyright (c) Facebook, Inc. and its affiliates.
68
- *
69
- * This source code is licensed under the MIT license found in the
70
- * LICENSE file in the root directory of this source tree.
71
- */
72
-
73
33
  /** @license React v0.18.0
74
34
  * scheduler.production.min.js
75
35
  *
package/i18n/en-US.js CHANGED
@@ -8,6 +8,8 @@ export default {
8
8
  "ba.boundingBoxHighlight.viewPrevReference": "View previous reference",
9
9
  "ba.popup.reply.comment": "Comment",
10
10
  "ba.popup.reply.field": "Type a comment",
11
+ "ba.popup.reply.mentionLoading": "Loading users...",
12
+ "ba.popup.reply.mentionSelector": "Mention a collaborator",
11
13
  "ba.popups.cancel": "Cancel",
12
14
  "ba.popups.drawing.addComment": "Add Comment",
13
15
  "ba.popups.drawing.delete": "Delete",
@@ -16,6 +16,10 @@ ba.boundingBoxHighlight.viewPrevReference = View previous reference
16
16
  ba.popup.reply.comment = Comment
17
17
  # Aria label description for annotation comment field
18
18
  ba.popup.reply.field = Type a comment
19
+ # Aria label for mention selector loading state
20
+ ba.popup.reply.mentionLoading = Loading users...
21
+ # Aria role description for mention user selector
22
+ ba.popup.reply.mentionSelector = Mention a collaborator
19
23
  # Button label for cancelling the creation of a description, comment, or reply
20
24
  ba.popups.cancel = Cancel
21
25
  # Button label for adding a comment in the drawing toolbar
@@ -1 +1 @@
1
- [{"id":"ba.popup.reply.comment","description":"Aria label description for reply popup","defaultMessage":"Comment"},{"id":"ba.popup.reply.field","description":"Aria label description for annotation comment field","defaultMessage":"Type a comment"},{"id":"ba.popups.cancel","description":"Button label for cancelling the creation of a description, comment, or reply","defaultMessage":"Cancel"},{"id":"ba.popups.post","description":"Button label for creating a description, comment, or reply","defaultMessage":"Post"},{"id":"ba.popups.drawing.addComment","description":"Button label for adding a comment in the drawing toolbar","defaultMessage":"Add Comment"},{"id":"ba.popups.drawing.delete","description":"Button title for deleting a staged drawing","defaultMessage":"Delete"},{"id":"ba.popups.drawing.redo","description":"Button title for redoing a staged drawing","defaultMessage":"Redo"},{"id":"ba.popups.drawing.undo","description":"Button title for undoing a staged drawing","defaultMessage":"Undo"},{"id":"ba.popups.replyField.placeholder","description":"Placeholder for reply field editor","defaultMessage":"Type a comment..."},{"id":"ba.popups.popupHighlight.promoter","description":"Popup message for highlight promoter","defaultMessage":"Highlight and Comment"},{"id":"ba.popups.popupHighlight.restrictedPrompt","description":"Prompt message when selection crosses multiple pages","defaultMessage":"Comments restricted to single page"},{"id":"ba.popups.popupList.prompt","description":"Prompt message for empty popup list","defaultMessage":"Mention someone to notify them"},{"id":"ba.popups.popupCursor.regionPrompt","description":"Prompt message following cursor in region annotations mode","defaultMessage":"Draw a box to comment"}]
1
+ [{"id":"ba.popup.reply.comment","description":"Aria label description for reply popup","defaultMessage":"Comment"},{"id":"ba.popup.reply.mentionLoading","description":"Aria label for mention selector loading state","defaultMessage":"Loading users..."},{"id":"ba.popup.reply.mentionSelector","description":"Aria role description for mention user selector","defaultMessage":"Mention a collaborator"},{"id":"ba.popup.reply.field","description":"Aria label description for annotation comment field","defaultMessage":"Type a comment"},{"id":"ba.popups.cancel","description":"Button label for cancelling the creation of a description, comment, or reply","defaultMessage":"Cancel"},{"id":"ba.popups.post","description":"Button label for creating a description, comment, or reply","defaultMessage":"Post"},{"id":"ba.popups.drawing.addComment","description":"Button label for adding a comment in the drawing toolbar","defaultMessage":"Add Comment"},{"id":"ba.popups.drawing.delete","description":"Button title for deleting a staged drawing","defaultMessage":"Delete"},{"id":"ba.popups.drawing.redo","description":"Button title for redoing a staged drawing","defaultMessage":"Redo"},{"id":"ba.popups.drawing.undo","description":"Button title for undoing a staged drawing","defaultMessage":"Undo"},{"id":"ba.popups.replyField.placeholder","description":"Placeholder for reply field editor","defaultMessage":"Type a comment..."},{"id":"ba.popups.popupHighlight.promoter","description":"Popup message for highlight promoter","defaultMessage":"Highlight and Comment"},{"id":"ba.popups.popupHighlight.restrictedPrompt","description":"Prompt message when selection crosses multiple pages","defaultMessage":"Comments restricted to single page"},{"id":"ba.popups.popupList.prompt","description":"Prompt message for empty popup list","defaultMessage":"Mention someone to notify them"},{"id":"ba.popups.popupCursor.regionPrompt","description":"Prompt message following cursor in region annotations mode","defaultMessage":"Draw a box to comment"}]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "box-annotations",
3
- "version": "5.2.1-beta.5",
3
+ "version": "5.2.1-beta.7",
4
4
  "description": "Box Annotations",
5
5
  "author": "Box (https://www.box.com/)",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -28,7 +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.12.2",
32
+ "@box/blueprint-web-assets": "^4.114.3",
33
+ "@box/collaboration-popover": "^1.60.2",
31
34
  "@box/languages": "^1.1.0",
35
+ "@box/readable-time": "^1.39.52",
36
+ "@box/threaded-annotations": "^1.80.0",
37
+ "@box/user-selector": "^1.74.52",
32
38
  "@cfaester/enzyme-adapter-react-18": "^0.8.0",
33
39
  "@commitlint/cli": "^8.3.5",
34
40
  "@commitlint/config-conventional": "^8.2.0",
@@ -50,11 +56,11 @@
50
56
  "@types/uuid": "^8.3.0",
51
57
  "@typescript-eslint/eslint-plugin": "^7.3.1",
52
58
  "@typescript-eslint/parser": "^7.3.1",
53
- "autoprefixer": "^9.7.6",
59
+ "autoprefixer": "^10.4.19",
54
60
  "axios": "^0.24.0",
55
61
  "babel-eslint": "^10.1.0",
56
62
  "babel-jest": "^24.9.0",
57
- "babel-loader": "^8.3.0",
63
+ "babel-loader": "^9.2.1",
58
64
  "babel-plugin-istanbul": "^6.0.0",
59
65
  "babel-plugin-react-intl": "^7.5.1",
60
66
  "babel-plugin-transform-require-ignore": "^0.1.1",
@@ -62,9 +68,11 @@
62
68
  "box-ui-elements": "^20.0.0",
63
69
  "circular-dependency-plugin": "^5.2.0",
64
70
  "classnames": "^2.2.5",
71
+ "clsx": "^2.1.1",
65
72
  "conventional-github-releaser": "^3.1.3",
66
73
  "core-js": "^3.38.1",
67
- "css-loader": "^3.5.2",
74
+ "css-loader": "^7.1.2",
75
+ "css-minimizer-webpack-plugin": "^7.0.4",
68
76
  "cypress": "^4.4.1",
69
77
  "draft-js": "0.10.5",
70
78
  "enzyme": "^3.11.0",
@@ -90,12 +98,10 @@
90
98
  "jest-environment-jsdom-sixteen": "^1.0.3",
91
99
  "lint-staged": "^9.5.0",
92
100
  "lodash": "^4.17.15",
93
- "mini-css-extract-plugin": "^0.9.0",
101
+ "mini-css-extract-plugin": "^2.10.1",
94
102
  "npm-run-all": "^4.1.5",
95
- "optimize-css-assets-webpack-plugin": "^5.0.3",
96
- "postcss-loader": "^3.0.0",
103
+ "postcss-loader": "^8.1.1",
97
104
  "prettier": "^1.19.1",
98
- "raw-loader": "^4.0.1",
99
105
  "react": "18.3.1",
100
106
  "react-dom": "18.3.1",
101
107
  "react-intl": "6.4.2",
@@ -105,27 +111,34 @@
105
111
  "redux": "^4.0.5",
106
112
  "regenerator-runtime": "^0.13.9",
107
113
  "sass": "^1.64.1",
108
- "sass-loader": "^8.0.2",
114
+ "sass-loader": "^16.0.4",
109
115
  "scroll-into-view-if-needed": "^2.2.24",
110
- "style-loader": "^1.1.4",
116
+ "style-loader": "^4.0.0",
111
117
  "stylelint": "^12.0.0",
112
118
  "stylelint-config-rational-order": "^0.1.2",
113
119
  "stylelint-config-standard": "^19.0.0",
114
120
  "stylelint-order": "^3.1.1",
115
121
  "tabbable": "^1.1.3",
116
- "terser-webpack-plugin": "^4.2.3",
117
122
  "typescript": "4.9.5",
118
123
  "uuid": "^8.3.1",
119
124
  "wait-on": "^4.0.2",
120
- "webpack": "^4.47.0",
121
- "webpack-cli": "^3.3.11",
122
- "webpack-dev-server": "^3.10.3",
125
+ "webpack": "^5.105.4",
126
+ "webpack-cli": "^5.1.4",
127
+ "webpack-dev-server": "^5.2.3",
123
128
  "worker-farm": "^1.7.0"
124
129
  },
130
+ "peerDependencies": {
131
+ "@box/blueprint-web": "^14.12.2",
132
+ "@box/blueprint-web-assets": "^4.114.3",
133
+ "@box/collaboration-popover": "^1.60.2",
134
+ "@box/readable-time": "^1.39.52",
135
+ "@box/threaded-annotations": "^1.80.0",
136
+ "@box/user-selector": "^1.74.52"
137
+ },
125
138
  "scripts": {
126
139
  "build": "yarn setup && yarn build:prod:dist",
127
140
  "build:i18n": "props2es",
128
- "build:dev:dist": "LANGUAGE=en-US BABEL_ENV=dev NODE_ENV=dev webpack --config scripts/webpack.config.js --mode development --progress --colors",
141
+ "build:dev:dist": "LANGUAGE=en-US BABEL_ENV=dev NODE_ENV=dev webpack --config scripts/webpack.config.js --mode development --progress",
129
142
  "build:prod:dist": "LANGUAGE=en-US BABEL_ENV=production NODE_ENV=production webpack --config scripts/webpack.config.js --mode production",
130
143
  "clean": "rm -rf dist reports/* i18n/json i18n/*.js",
131
144
  "cy:open": "yarn cy:wait; yarn cypress open",
@@ -12,6 +12,11 @@ export interface Annotation {
12
12
  modified_by: User;
13
13
  permissions: Permissions;
14
14
  replies?: Array<Reply>;
15
+ resolution?: {
16
+ resolved_at: string;
17
+ resolved_by: User;
18
+ } | null;
19
+ status?: 'open' | 'resolved';
15
20
  target: Target;
16
21
  type: 'annotation';
17
22
  }
@@ -0,0 +1,155 @@
1
+ import type { DocumentNodeV2, MentionNodeV2, ParagraphNodeV2, TextMessageTypeV2, TextNodeV2 } from '@box/threaded-annotations';
2
+
3
+ import type { Annotation, Collaborator, Reply, UserMini } from '../@types';
4
+
5
+ const MENTION_REGEX = /@\[(\d+):([^\]]+)\]/g;
6
+
7
+ /**
8
+ * Parses a plain-text line into paragraph content nodes.
9
+ * Recognizes @[userId:userName] mention markup and converts to MentionNode / TextNode.
10
+ */
11
+ const parseLine = (line: string): (MentionNodeV2 | TextNodeV2)[] => {
12
+ const nodes: (MentionNodeV2 | TextNodeV2)[] = [];
13
+ let lastIndex = 0;
14
+
15
+ // Reset regex state since it's global
16
+ MENTION_REGEX.lastIndex = 0;
17
+
18
+ let match = MENTION_REGEX.exec(line);
19
+ while (match !== null) {
20
+ const [fullMatch, userId, userName] = match;
21
+ const { index } = match;
22
+
23
+ if (index > lastIndex) {
24
+ nodes.push({ type: 'text', text: line.slice(lastIndex, index) });
25
+ }
26
+
27
+ nodes.push({
28
+ type: 'mention',
29
+ attrs: {
30
+ authorId: '',
31
+ mentionId: userId,
32
+ mentionedUserId: userId,
33
+ mentionedUserName: userName,
34
+ },
35
+ });
36
+
37
+ lastIndex = index + fullMatch.length;
38
+ match = MENTION_REGEX.exec(line);
39
+ }
40
+
41
+ if (lastIndex < line.length) {
42
+ nodes.push({ type: 'text', text: line.slice(lastIndex) });
43
+ }
44
+
45
+ return nodes;
46
+ };
47
+
48
+ /**
49
+ * Deserializes plain text with @[userId:userName] mention markup into a TipTap DocumentNode.
50
+ * This is the reverse of serializeMentionMarkup from @box/threaded-annotations.
51
+ *
52
+ * Newlines are treated as paragraph breaks.
53
+ */
54
+ export const deserializeMentionMarkup = (text: string): DocumentNodeV2 => {
55
+ if (!text) {
56
+ return { type: 'doc', content: [] };
57
+ }
58
+
59
+ const lines = text.split('\n');
60
+ const content: ParagraphNodeV2[] = lines.map(line => {
61
+ const nodes = parseLine(line);
62
+
63
+ return {
64
+ type: 'paragraph',
65
+ ...(nodes.length > 0 ? { content: nodes } : {}),
66
+ };
67
+ });
68
+
69
+ return { type: 'doc', content };
70
+ };
71
+
72
+ /**
73
+ * Converts a box-annotations Reply to a threaded-annotations TextMessageType.
74
+ */
75
+ export const replyToTextMessage = (reply: Reply): TextMessageTypeV2 => ({
76
+ author: {
77
+ email: reply.created_by?.login ?? '',
78
+ id: parseInt(reply.created_by?.id ?? '0', 10),
79
+ name: reply.created_by?.name ?? '',
80
+ },
81
+ createdAt: new Date(reply.created_at).getTime(),
82
+ id: reply.id,
83
+ message: deserializeMentionMarkup(reply.message),
84
+ permissions: {
85
+ canDelete: false,
86
+ canEdit: false,
87
+ canReply: true,
88
+ canResolve: false,
89
+ },
90
+ });
91
+
92
+ /**
93
+ * Converts an Annotation's description (root message) to a TextMessageType.
94
+ * The root message gets resolve permission from the annotation's permissions.
95
+ * Falls back to annotation-level fields when the description object is sparse
96
+ * (the list endpoint returns description with only { message }).
97
+ */
98
+ const descriptionToTextMessage = (
99
+ annotation: Annotation,
100
+ reply: Reply,
101
+ ): TextMessageTypeV2 => ({
102
+ author: {
103
+ email: (reply.created_by ?? annotation.created_by)?.login ?? '',
104
+ id: parseInt((reply.created_by ?? annotation.created_by)?.id ?? '0', 10),
105
+ name: (reply.created_by ?? annotation.created_by)?.name ?? '',
106
+ },
107
+ createdAt: new Date(reply.created_at ?? annotation.created_at).getTime(),
108
+ id: reply.id ?? annotation.id,
109
+ message: deserializeMentionMarkup(reply.message),
110
+ permissions: {
111
+ canDelete: annotation.permissions?.can_delete ?? false,
112
+ canEdit: annotation.permissions?.can_edit ?? false,
113
+ canReply: true,
114
+ canResolve: annotation.permissions?.can_resolve ?? false,
115
+ },
116
+ });
117
+
118
+ /**
119
+ * Maps a full Annotation (description + replies) to an array of TextMessageType
120
+ * suitable for ThreadedAnnotationsV2.
121
+ */
122
+ export const annotationToMessages = (annotation: Annotation): TextMessageTypeV2[] => {
123
+ const messages: TextMessageTypeV2[] = [];
124
+
125
+ if (annotation.description) {
126
+ messages.push(descriptionToTextMessage(annotation, annotation.description));
127
+ }
128
+
129
+ if (annotation.replies) {
130
+ annotation.replies.forEach(reply => {
131
+ messages.push(replyToTextMessage(reply));
132
+ });
133
+ }
134
+
135
+ return messages;
136
+ };
137
+
138
+ /**
139
+ * Converts a box-annotations Collaborator to a @box/user-selector UserContactType.
140
+ */
141
+ export const collaboratorToUserContact = (collaborator: Collaborator): {
142
+ email: string;
143
+ id: number;
144
+ name: string;
145
+ value: string;
146
+ } => {
147
+ const item = collaborator.item as UserMini | undefined;
148
+
149
+ return {
150
+ email: item?.email ?? item?.login ?? '',
151
+ id: parseInt(item?.id ?? collaborator.id, 10),
152
+ name: item?.name ?? collaborator.name,
153
+ value: collaborator.id,
154
+ };
155
+ };
package/src/api/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Annotation, Collaborator, NewAnnotation, Permissions, Token } from '../@types';
1
+ import { Annotation, Collaborator, NewAnnotation, Permissions, Reply, Token } from '../@types';
2
2
 
3
3
  export type APICollection<R> = {
4
4
  entries: R[];
@@ -33,6 +33,23 @@ export interface AnnotationsAPI {
33
33
  errorCallback: (error: APIError) => void,
34
34
  ): Promise<void>;
35
35
 
36
+ createAnnotationReply(
37
+ fileId: string | null,
38
+ annotationId: string,
39
+ permissions: Permissions,
40
+ message: string,
41
+ successCallback: (result: Reply) => void,
42
+ errorCallback: (error: APIError) => void,
43
+ ): Promise<void>;
44
+
45
+ deleteAnnotation(
46
+ fileId: string | null,
47
+ annotationId: string,
48
+ permissions: Permissions,
49
+ successCallback: () => void,
50
+ errorCallback: (error: APIError) => void,
51
+ ): Promise<void>;
52
+
36
53
  getAnnotations(
37
54
  fileId: string | null,
38
55
  fileVersionId: string | null,
@@ -41,6 +58,16 @@ export interface AnnotationsAPI {
41
58
  errorCallback: (error: APIError) => void,
42
59
  limit?: number,
43
60
  shouldFetchAll?: boolean,
61
+ shouldFetchReplies?: boolean,
62
+ ): Promise<void>;
63
+
64
+ updateAnnotation(
65
+ fileId: string | null,
66
+ annotationId: string,
67
+ permissions: Permissions,
68
+ payload: { message?: string; status?: string },
69
+ successCallback: (result: Annotation) => void,
70
+ errorCallback: (error: APIError) => void,
44
71
  ): Promise<void>;
45
72
 
46
73
  destroy(): void;
@@ -94,11 +94,13 @@ export default class BaseAnnotator extends EventEmitter {
94
94
  },
95
95
  common: { color: initialColor, mode: initialMode },
96
96
  options: {
97
+ apiHost,
97
98
  features,
98
99
  fileId: file.id,
99
100
  fileVersionId: fileOptionsVersionId ?? fileVersionId,
100
101
  isCurrentFileVersion: !fileOptionsVersionId || fileOptionsVersionId === currentFileVersionId,
101
102
  permissions: file.permissions,
103
+ token,
102
104
  viewMode: initialViewMode,
103
105
  },
104
106
  };
@@ -8,8 +8,8 @@ import './BoundingBoxHighlightNav.scss';
8
8
  type Props = {
9
9
  currentIndex: number;
10
10
  total: number;
11
- onPrev: (event: React.MouseEvent) => void;
12
- onNext: (event: React.MouseEvent) => void;
11
+ onPrev: (event: React.MouseEvent | KeyboardEvent) => void;
12
+ onNext: (event: React.MouseEvent | KeyboardEvent) => void;
13
13
  };
14
14
 
15
15
  const BoundingBoxHighlightNav = ({ currentIndex, total, onPrev, onNext }: Props): JSX.Element => {
@@ -17,6 +17,25 @@ const BoundingBoxHighlightNav = ({ currentIndex, total, onPrev, onNext }: Props)
17
17
  const isPrevDisabled = currentIndex === 0;
18
18
  const isNextDisabled = currentIndex === total - 1;
19
19
 
20
+ React.useEffect(() => {
21
+ const handleKeyDown = (event: KeyboardEvent): void => {
22
+ if (event.key === 'ArrowLeft') {
23
+ event.stopPropagation();
24
+ if (!isPrevDisabled) {
25
+ onPrev(event);
26
+ }
27
+ } else if (event.key === 'ArrowRight') {
28
+ event.stopPropagation();
29
+ if (!isNextDisabled) {
30
+ onNext(event);
31
+ }
32
+ }
33
+ };
34
+
35
+ document.addEventListener('keydown', handleKeyDown, { capture: true });
36
+ return () => document.removeEventListener('keydown', handleKeyDown, { capture: true });
37
+ }, [isPrevDisabled, isNextDisabled, onPrev, onNext]);
38
+
20
39
  return (
21
40
  <div className="ba-BoundingBoxHighlightNav" data-testid="ba-BoundingBoxHighlightNav">
22
41
  <button
@@ -40,14 +40,14 @@ const BoundingBoxHighlightRect = ({
40
40
  onSelect?.(id);
41
41
  };
42
42
 
43
- const handlePrev = (event: React.MouseEvent): void => {
43
+ const handlePrev = (event: React.MouseEvent | KeyboardEvent): void => {
44
44
  event.stopPropagation();
45
45
  if (prevId) {
46
46
  onNavigate?.(prevId);
47
47
  }
48
48
  };
49
49
 
50
- const handleNext = (event: React.MouseEvent): void => {
50
+ const handleNext = (event: React.MouseEvent | KeyboardEvent): void => {
51
51
  event.stopPropagation();
52
52
  if (nextId) {
53
53
  onNavigate?.(nextId);
@@ -3,21 +3,19 @@ import Popper from '@popperjs/core';
3
3
  import { useSelector } from 'react-redux';
4
4
  import { useIntl } from 'react-intl';
5
5
  import FocusTrap from 'box-ui-elements/es/components/focus-trap/FocusTrap';
6
+
7
+ import ReplyForm from '../ReplyForm';
8
+
9
+ import { getRotation, getScale } from '../../store/options';
10
+
6
11
  import messages from './messages';
7
12
  import PopupBase from './PopupBase';
8
- import PopupReplyV2 from './PopupReplyV2';
9
- import ReplyForm from '../ReplyForm';
10
- import { getScale, getRotation } from '../../store/options';
11
13
  import { PopupReference } from './Popper';
12
14
  import './PopupReply.scss';
13
15
 
14
- type BaseProps = {
16
+ export type Props = {
15
17
  className?: string;
16
- };
17
-
18
- type LegacyProps = BaseProps & {
19
18
  isPending: boolean;
20
- isThreadedAnnotation?: false;
21
19
  onCancel: (text?: string) => void;
22
20
  onChange: (text?: string) => void;
23
21
  onSubmit: (text: string) => void;
@@ -25,18 +23,6 @@ type LegacyProps = BaseProps & {
25
23
  value?: string;
26
24
  };
27
25
 
28
- type ThreadedProps = BaseProps & {
29
- isThreadedAnnotation: true;
30
- isPending?: boolean;
31
- onCancel?: (text?: string) => void;
32
- onChange?: (text?: string) => void;
33
- onSubmit?: (text: string) => void;
34
- reference?: PopupReference;
35
- value?: string;
36
- };
37
-
38
- export type Props = LegacyProps | ThreadedProps;
39
-
40
26
  const isIE = (): boolean => {
41
27
  const { userAgent } = navigator;
42
28
  return userAgent.indexOf('Trident/') > 0;
@@ -97,7 +83,7 @@ const getOptions = (): Partial<Popper.Options> => {
97
83
  export default function PopupReply(props: Props): JSX.Element {
98
84
  const intl = useIntl();
99
85
  const popupRef = React.useRef<PopupBase>(null);
100
- const popupOptions = React.useRef<Partial<Popper.Options>>(getOptions()); // Keep the options reference the same between renders
86
+ const popupOptions = React.useRef<Partial<Popper.Options>>(getOptions());
101
87
  const rotation = useSelector(getRotation);
102
88
  const scale = useSelector(getScale);
103
89
 
@@ -109,13 +95,7 @@ export default function PopupReply(props: Props): JSX.Element {
109
95
  }
110
96
  }, [popupRef, rotation, scale]);
111
97
 
112
- // TODO: PopupReplyV2 is a placeholder stub. Props (isPending, onCancel, etc.)
113
- // are not yet forwarded and will need to be wired up when V2 is implemented.
114
- if (props.isThreadedAnnotation) {
115
- return <PopupReplyV2 />;
116
- }
117
-
118
- const { isPending, onCancel, onChange, onSubmit, value = '', isThreadedAnnotation: _, ...rest } = props as LegacyProps;
98
+ const { isPending, onCancel, onChange, onSubmit, value = '', ...rest } = props;
119
99
 
120
100
  return (
121
101
  <FocusTrap>
@@ -0,0 +1,25 @@
1
+ .ba-PopupV2 {
2
+ z-index: 1;
3
+ width: 348px;
4
+ }
5
+
6
+ // Specificity must beat .ba selectors from box-ui-elements globals
7
+ // (forms, buttons, links) that cascade into threaded-annotations components.
8
+ .ba .ba-PopupV2 {
9
+ div[contenteditable='true'],
10
+ div[contenteditable='true']:hover,
11
+ div[contenteditable='true']:focus {
12
+ width: auto;
13
+ padding: 0;
14
+ color: inherit;
15
+ border: none;
16
+ border-radius: 0;
17
+ outline: none;
18
+ box-shadow: none;
19
+ transition: none;
20
+ }
21
+
22
+ button svg {
23
+ pointer-events: auto;
24
+ }
25
+ }