box-annotations 5.3.0 → 5.3.1-beta.1

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.
Files changed (104) hide show
  1. package/dist/annotations.css +1 -1
  2. package/dist/annotations.js +1 -1
  3. package/dist/annotations.js.LICENSE.txt +0 -9
  4. package/i18n/bn-IN.js +24 -17
  5. package/i18n/da-DK.js +24 -17
  6. package/i18n/de-DE.js +24 -17
  7. package/i18n/en-AU.js +4 -17
  8. package/i18n/en-CA.js +2 -18
  9. package/i18n/en-GB.js +4 -17
  10. package/i18n/en-US.js +4 -24
  11. package/i18n/en-x-pseudo.js +6 -0
  12. package/i18n/es-419.js +24 -17
  13. package/i18n/es-ES.js +24 -17
  14. package/i18n/fi-FI.js +24 -17
  15. package/i18n/fr-CA.js +24 -17
  16. package/i18n/fr-FR.js +24 -17
  17. package/i18n/hi-IN.js +24 -17
  18. package/i18n/it-IT.js +24 -17
  19. package/i18n/ja-JP.js +2 -18
  20. package/i18n/ko-KR.js +24 -17
  21. package/i18n/nb-NO.js +24 -17
  22. package/i18n/nl-NL.js +24 -17
  23. package/i18n/pl-PL.js +24 -17
  24. package/i18n/pt-BR.js +24 -17
  25. package/i18n/ru-RU.js +24 -17
  26. package/i18n/sv-SE.js +24 -17
  27. package/i18n/tr-TR.js +24 -17
  28. package/i18n/zh-CN.js +24 -17
  29. package/i18n/zh-TW.js +24 -17
  30. package/package.json +29 -28
  31. package/src/BoxAnnotations.ts +1 -1
  32. package/src/adapters/threadedAnnotationsAdapters.ts +10 -2
  33. package/src/boundingBoxHighlight/BoundingBoxHighlightContainer.tsx +4 -1
  34. package/src/common/BaseAnnotator.scss +1 -1
  35. package/src/common/BaseAnnotator.ts +9 -0
  36. package/src/common/DeselectListener.tsx +1 -1
  37. package/src/common/EventEmitter.ts +11 -3
  38. package/src/components/BoundingBoxHighlight/BoundingBoxHighlightNav.scss +3 -3
  39. package/src/components/BoundingBoxHighlight/BoundingBoxHighlightRect.scss +11 -6
  40. package/src/components/Popups/PopupBase.scss +2 -2
  41. package/src/components/Popups/PopupDrawingToolbar.scss +4 -4
  42. package/src/components/Popups/PopupList.scss +1 -1
  43. package/src/components/Popups/PopupV2.scss +2 -2
  44. package/src/components/Popups/PopupV2.tsx +62 -44
  45. package/src/components/ReplyForm/ReplyForm.tsx +1 -8
  46. package/src/constants.ts +2 -3
  47. package/src/document/DocumentAnnotator.ts +54 -14
  48. package/src/document/docUtil.ts +12 -2
  49. package/src/drawing/DrawingAnnotations.tsx +24 -24
  50. package/src/drawing/DrawingCursor.ts +2 -1
  51. package/src/drawing/DrawingManager.tsx +9 -1
  52. package/src/highlight/HighlightAnnotations.tsx +23 -22
  53. package/src/highlight/HighlightCreator.scss +9 -2
  54. package/src/media/MediaAnnotator.ts +38 -16
  55. package/src/popup/PopupLayer.tsx +1 -0
  56. package/src/region/RegionAnnotations.tsx +2 -3
  57. package/src/region/RegionCreation.tsx +8 -4
  58. package/src/region/RegionCreationContainer.tsx +3 -0
  59. package/src/region/RegionCreationManager.tsx +8 -1
  60. package/src/region/RegionCreator.scss +9 -3
  61. package/src/region/RegionManager.tsx +8 -1
  62. package/src/region/RegionRect.scss +17 -9
  63. package/src/store/annotations/actions.ts +88 -104
  64. package/src/store/annotations/reducer.ts +6 -4
  65. package/src/store/creator/selectors.ts +6 -6
  66. package/src/store/eventing/create.ts +4 -2
  67. package/src/store/eventing/delete.ts +4 -5
  68. package/src/store/eventing/middleware.ts +13 -11
  69. package/src/store/eventing/replyCreate.ts +6 -10
  70. package/src/store/eventing/replyDelete.ts +5 -9
  71. package/src/store/eventing/replyUpdate.ts +6 -10
  72. package/src/store/eventing/update.ts +5 -6
  73. package/src/store/highlight/actions.ts +43 -41
  74. package/src/test-utils/videoAnnotations.tsx +11 -11
  75. package/src/utils/useVideoTiming.ts +9 -13
  76. package/i18n/bn-IN.properties +0 -32
  77. package/i18n/da-DK.properties +0 -32
  78. package/i18n/de-DE.properties +0 -32
  79. package/i18n/en-AU.properties +0 -32
  80. package/i18n/en-CA.properties +0 -32
  81. package/i18n/en-GB.properties +0 -32
  82. package/i18n/en-US.properties +0 -44
  83. package/i18n/en-x-pseudo.properties +0 -32
  84. package/i18n/es-419.properties +0 -32
  85. package/i18n/es-ES.properties +0 -32
  86. package/i18n/fi-FI.properties +0 -32
  87. package/i18n/fr-CA.properties +0 -32
  88. package/i18n/fr-FR.properties +0 -32
  89. package/i18n/hi-IN.properties +0 -32
  90. package/i18n/it-IT.properties +0 -32
  91. package/i18n/ja-JP.properties +0 -32
  92. package/i18n/json/src/components/BoundingBoxHighlight/messages.json +0 -1
  93. package/i18n/json/src/components/Popups/messages.json +0 -1
  94. package/i18n/json/src/messages.json +0 -1
  95. package/i18n/ko-KR.properties +0 -32
  96. package/i18n/nb-NO.properties +0 -32
  97. package/i18n/nl-NL.properties +0 -32
  98. package/i18n/pl-PL.properties +0 -32
  99. package/i18n/pt-BR.properties +0 -32
  100. package/i18n/ru-RU.properties +0 -32
  101. package/i18n/sv-SE.properties +0 -32
  102. package/i18n/tr-TR.properties +0 -32
  103. package/i18n/zh-CN.properties +0 -32
  104. package/i18n/zh-TW.properties +0 -32
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "box-annotations",
3
- "version": "5.3.0",
3
+ "version": "5.3.1-beta.1",
4
4
  "description": "Box Annotations",
5
5
  "author": "Box (https://www.box.com/)",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -36,16 +36,17 @@
36
36
  "@box/blueprint-web-assets": "^5.5.12",
37
37
  "@box/collaboration-popover": "^2.1.32",
38
38
  "@box/combobox-with-api": "^1.42.22",
39
- "@box/frontend": "^10.0.0",
39
+ "@box/frontend": "^11.0.1",
40
40
  "@box/item-icon": "^3.2.0",
41
41
  "@box/languages": "^1.1.0",
42
42
  "@box/readable-time": "^2.1.32",
43
- "@box/threaded-annotations": "^4.11.0",
43
+ "@box/threaded-annotations": "^4.11.14",
44
44
  "@box/types": "^2.1.8",
45
45
  "@box/user-selector": "^2.1.35",
46
46
  "@cfaester/enzyme-adapter-react-18": "^0.8.0",
47
- "@commitlint/cli": "^8.3.5",
48
- "@commitlint/config-conventional": "^8.2.0",
47
+ "@commitlint/cli": "^19.8.0",
48
+ "@commitlint/config-conventional": "^19.8.0",
49
+ "@formatjs/cli": "^6.16.26",
49
50
  "@formatjs/intl-pluralrules": "^1.5.5",
50
51
  "@formatjs/intl-relativetimeformat": "^4.5.12",
51
52
  "@popperjs/core": "^2.4.0",
@@ -65,24 +66,22 @@
65
66
  "@typescript-eslint/eslint-plugin": "^7.3.1",
66
67
  "@typescript-eslint/parser": "^7.3.1",
67
68
  "autoprefixer": "^10.4.19",
68
- "axios": "^0.24.0",
69
- "babel-eslint": "^10.1.0",
69
+ "axios": "^0.33.0",
70
70
  "babel-jest": "^29.7.0",
71
71
  "babel-loader": "^9.2.1",
72
72
  "babel-plugin-istanbul": "^6.0.0",
73
- "babel-plugin-react-intl": "^7.5.1",
74
73
  "babel-plugin-transform-require-ignore": "^0.1.1",
75
- "box-node-sdk": "^1.33.0",
76
74
  "box-ui-elements": "^20.0.0",
77
75
  "circular-dependency-plugin": "^5.2.0",
78
76
  "classnames": "^2.2.5",
79
77
  "clsx": "^2.1.1",
80
- "conventional-github-releaser": "^3.1.3",
78
+ "conventional-changelog": "^8.1.3",
79
+ "conventional-changelog-conventionalcommits": "^10.3.0",
81
80
  "core-js": "^3.38.1",
82
81
  "css-loader": "^7.1.2",
83
- "css-minimizer-webpack-plugin": "^7.0.4",
82
+ "css-minimizer-webpack-plugin": "^8.0.0",
84
83
  "cypress": "^15.20.1",
85
- "draft-js": "0.10.5",
84
+ "draft-js": "^0.11.7",
86
85
  "enzyme": "^3.11.0",
87
86
  "enzyme-to-json": "^3.4.4",
88
87
  "eslint": "^8.57.0",
@@ -99,7 +98,7 @@
99
98
  "eslint-plugin-prettier": "^5.2.1",
100
99
  "eslint-plugin-react": "^7.34.3",
101
100
  "eslint-plugin-react-hooks": "^4.6.0",
102
- "formik": "^2.1.4",
101
+ "formik": "^2.4.9",
103
102
  "husky": "^3.1.0",
104
103
  "jest": "^29.7.0",
105
104
  "jest-canvas-mock": "^2.5.2",
@@ -108,8 +107,9 @@
108
107
  "lodash": "^4.17.15",
109
108
  "mini-css-extract-plugin": "^2.10.1",
110
109
  "npm-run-all": "^4.1.5",
110
+ "postcss": "^8.5.18",
111
111
  "postcss-loader": "^8.1.1",
112
- "prettier": "^1.19.1",
112
+ "prettier": "^3.2.5",
113
113
  "react": "18.3.1",
114
114
  "react-dom": "18.3.1",
115
115
  "react-intl": "6.4.2",
@@ -118,21 +118,21 @@
118
118
  "react-textarea-autosize": "^7.1.2",
119
119
  "redux": "^4.0.5",
120
120
  "regenerator-runtime": "^0.13.9",
121
- "sass": "^1.64.1",
121
+ "sass": "1.77.6",
122
122
  "sass-loader": "^16.0.4",
123
123
  "scroll-into-view-if-needed": "^2.2.24",
124
124
  "style-loader": "^4.0.0",
125
- "stylelint": "^12.0.0",
126
- "stylelint-config-rational-order": "^0.1.2",
127
- "stylelint-config-standard": "^19.0.0",
128
- "stylelint-order": "^3.1.1",
125
+ "stylelint": "^16.26.1",
126
+ "stylelint-config-standard": "^38.0.0",
127
+ "stylelint-config-standard-scss": "^14.0.0",
128
+ "stylelint-order": "^6.0.4",
129
129
  "tabbable": "^1.1.3",
130
- "typescript": "4.9.5",
130
+ "typescript": "5.2.2",
131
131
  "uuid": "^8.3.1",
132
132
  "wait-on": "^9.0.5",
133
133
  "webpack": "^5.105.4",
134
134
  "webpack-cli": "^5.1.4",
135
- "webpack-dev-server": "^5.2.3",
135
+ "webpack-dev-server": "^5.2.6",
136
136
  "worker-farm": "^1.7.0"
137
137
  },
138
138
  "peerDependencies": {
@@ -142,22 +142,23 @@
142
142
  "@box/combobox-with-api": "^1.42.22",
143
143
  "@box/item-icon": "^3.2.0",
144
144
  "@box/readable-time": "^2.1.32",
145
- "@box/threaded-annotations": "^4.11.0",
145
+ "@box/threaded-annotations": "^4.11.14",
146
146
  "@box/types": "^2.1.8",
147
- "@box/user-selector": "^2.1.35"
147
+ "@box/user-selector": "^2.1.35",
148
+ "axios": "^0.33.0"
148
149
  },
149
150
  "scripts": {
150
151
  "build": "yarn setup && yarn build:prod:dist",
151
- "build:i18n": "props2es",
152
152
  "build:dev:dist": "LANGUAGE=en-US BABEL_ENV=dev NODE_ENV=dev webpack --config scripts/webpack.config.js --mode development --progress",
153
153
  "build:prod:dist": "LANGUAGE=en-US BABEL_ENV=production NODE_ENV=production webpack --config scripts/webpack.config.js --mode production",
154
- "clean": "rm -rf dist reports/* i18n/json i18n/*.js",
154
+ "clean": "rm -rf dist reports/* i18n/json",
155
+ "extract:i18n": "mkdir -p i18n/json && formatjs extract 'src/**/*.{js,ts,tsx}' --ignore='src/**/__tests__/**' --ignore='src/**/__mocks__/**' --out-file i18n/json/en-US.json",
155
156
  "cy:open": "yarn cy:wait; yarn cypress open",
156
157
  "cy:run": "yarn cy:wait; yarn cypress run --spec test/integration/**/*.test.js",
157
158
  "cy:wait": "wait-on http-get://127.0.0.1:8001",
158
159
  "lint": "npm-run-all lint:*",
159
160
  "lint:js": "eslint .",
160
- "lint:scss": "stylelint \"src/**/*.scss\" --syntax scss",
161
+ "lint:scss": "stylelint \"src/**/*.scss\"",
161
162
  "lint:ts": "tsc && eslint --ext=.tsx,.ts --max-warnings=0 .",
162
163
  "release:cdn": "yarn setup; node ./scripts/prod.js",
163
164
  "release:custom": "scripts/release.sh -c",
@@ -165,7 +166,7 @@
165
166
  "release:minor": "scripts/release.sh -n",
166
167
  "release:patch": "scripts/release.sh -p",
167
168
  "release:pre": "scripts/release.sh -x",
168
- "setup": "yarn install && yarn clean && yarn build:i18n",
169
+ "setup": "yarn install && yarn clean",
169
170
  "start": "yarn setup && yarn build:dev:dist --watch",
170
171
  "start:dev": "yarn setup && LANGUAGE=en-US BABEL_ENV=dev NODE_ENV=dev yarn webpack-dev-server --config scripts/webpack.config.js --mode development",
171
172
  "start:linked": "IS_LINKED=1 yarn start",
@@ -185,6 +186,6 @@
185
186
  "**/react-intl/**/@types/react": "^18.3.12",
186
187
  "**/@types/react": "^18.3.12",
187
188
  "**/@types/react-dom": "^18.2.0",
188
- "acorn": "8.14.0"
189
+ "draft-js/immutable": "^3.8.3"
189
190
  }
190
191
  }
@@ -70,7 +70,7 @@ const ANNOTATORS: Annotator[] = [
70
70
  CONSTRUCTOR: MediaAnnotator,
71
71
  NAME: 'Media',
72
72
  TYPES: [Type.region],
73
- VIEWERS: ['Dash','MP4'],
73
+ VIEWERS: ['Dash', 'MP4'],
74
74
  },
75
75
  ];
76
76
 
@@ -1,4 +1,10 @@
1
- import type { DocumentNodeV2, MentionNodeV2, ParagraphNodeV2, TextMessageTypeV2, TextNodeV2 } from '@box/threaded-annotations';
1
+ import type {
2
+ DocumentNodeV2,
3
+ MentionNodeV2,
4
+ ParagraphNodeV2,
5
+ TextMessageTypeV2,
6
+ TextNodeV2,
7
+ } from '@box/threaded-annotations';
2
8
  import { parseMessageMarkdown } from '@box/threaded-annotations';
3
9
 
4
10
  import type { Annotation, Collaborator, Reply, UserMini } from '../@types';
@@ -151,7 +157,9 @@ export const annotationToMessages = (annotation: Annotation, isRichTextEnabled =
151
157
  /**
152
158
  * Converts a box-annotations Collaborator to a @box/user-selector UserContactType.
153
159
  */
154
- export const collaboratorToUserContact = (collaborator: Collaborator): {
160
+ export const collaboratorToUserContact = (
161
+ collaborator: Collaborator,
162
+ ): {
155
163
  email: string;
156
164
  id: number;
157
165
  name: string;
@@ -15,7 +15,10 @@ export type Props = {
15
15
  location: number;
16
16
  };
17
17
 
18
- const mapStateToProps = (state: AppState, { location }: Props): {
18
+ const mapStateToProps = (
19
+ state: AppState,
20
+ { location }: Props,
21
+ ): {
19
22
  allBoundingBoxes: BoundingBox[];
20
23
  boundingBoxes: BoundingBox[];
21
24
  selectedId: string | null;
@@ -35,7 +35,7 @@
35
35
  pointer-events: none;
36
36
 
37
37
  .ba-Popup,
38
- .ba-PopupV2 {
38
+ .ba-PopupAnnotation {
39
39
  z-index: 150;
40
40
  pointer-events: auto;
41
41
  }
@@ -45,6 +45,8 @@ export type Options = {
45
45
  intl: IntlOptions;
46
46
  locale?: string;
47
47
  onCopyLink?: (params: { annotationId: string; fileVersionId: string }) => void;
48
+ // When false, skip annotation create. Existing annotations still render.
49
+ showAnnotationsControls?: boolean;
48
50
  token: Token;
49
51
  };
50
52
 
@@ -339,6 +341,13 @@ export default class BaseAnnotator extends EventEmitter {
339
341
  };
340
342
 
341
343
  protected handleSetActive = (annotationId: string | null): void => {
344
+ const state = this.store.getState();
345
+
346
+ // Ignore ids this pane does not own. Clear locally so the host is not told to deselect.
347
+ if (annotationId && state?.annotations?.isInitialized && !store.getAnnotation(state, annotationId)) {
348
+ this.store.dispatch(store.clearActiveAnnotationIdAction());
349
+ return;
350
+ }
342
351
  this.setActiveId(annotationId);
343
352
  };
344
353
 
@@ -8,7 +8,7 @@ export default function DeselectListener(): null {
8
8
  React.useEffect(() => {
9
9
  const handleMouseDown = (event: MouseEvent): void => {
10
10
  // Popup is portaled outside .ba-Layer; native mousedown still bubbles here.
11
- if ((event.target as Element | null)?.closest?.('.ba-PopupV2, .ba-Popup')) return;
11
+ if ((event.target as Element | null)?.closest?.('.ba-PopupAnnotation, .ba-Popup')) return;
12
12
  dispatch(setActiveAnnotationIdAction(null));
13
13
  };
14
14
 
@@ -1,7 +1,12 @@
1
1
  import eventManager from './EventManager';
2
2
 
3
+ type Listener = (...args: any[]) => void;
4
+
3
5
  export default class EventEmitter {
4
- addListener(event: string | symbol, listener: (...args: any[]) => void): void {
6
+ private ownListeners: Array<{ event: string | symbol; listener: Listener }> = [];
7
+
8
+ addListener(event: string | symbol, listener: Listener): void {
9
+ this.ownListeners.push({ event, listener });
5
10
  eventManager.addListener(event, listener);
6
11
  }
7
12
 
@@ -10,10 +15,13 @@ export default class EventEmitter {
10
15
  }
11
16
 
12
17
  removeAllListeners(): void {
13
- eventManager.removeAllListeners();
18
+ // Shared eventManager: remove only this instance's listeners.
19
+ this.ownListeners.forEach(({ event, listener }) => eventManager.removeListener(event, listener));
20
+ this.ownListeners = [];
14
21
  }
15
22
 
16
- removeListener(event: string | symbol, listener: (...args: any[]) => void): void {
23
+ removeListener(event: string | symbol, listener: Listener): void {
24
+ this.ownListeners = this.ownListeners.filter(own => !(own.event === event && own.listener === listener));
17
25
  eventManager.removeListener(event, listener);
18
26
  }
19
27
  }
@@ -1,5 +1,5 @@
1
1
  $ba-BoundingBoxHighlightNav-bg: #fff;
2
- $ba-BoundingBoxHighlightNav-shadow: 0 6px 20px rgba(0, 0, 0, .1);
2
+ $ba-BoundingBoxHighlightNav-shadow: 0 6px 20px rgb(0 0 0 / 10%);
3
3
  $ba-BoundingBoxHighlightNav-arrow: #909090;
4
4
  $ba-BoundingBoxHighlightNav-border: #e8e8e8;
5
5
  $ba-BoundingBoxHighlightNav-counter: #222;
@@ -45,11 +45,11 @@ $ba-BoundingBoxHighlightNav-counter: #222;
45
45
  }
46
46
 
47
47
  &:hover:not(:disabled) {
48
- background: rgba(0, 0, 0, .06);
48
+ background: rgb(0 0 0 / 6%);
49
49
  }
50
50
 
51
51
  &:active:not(:disabled) {
52
- background: rgba(0, 0, 0, .12);
52
+ background: rgb(0 0 0 / 12%);
53
53
  }
54
54
 
55
55
  &:disabled {
@@ -1,22 +1,27 @@
1
1
  $ba-BoundingBoxHighlight-color: #0061d5;
2
- $ba-BoundingBoxHighlight-bgColor--default: rgba($ba-BoundingBoxHighlight-color, .05);
3
- $ba-BoundingBoxHighlight-bgColor--hover: rgba($ba-BoundingBoxHighlight-color, .16);
4
- $ba-BoundingBoxHighlight-bgColor--selected: rgba($ba-BoundingBoxHighlight-color, .1);
2
+ $ba-BoundingBoxHighlight-bgColor--default: rgba($ba-BoundingBoxHighlight-color, 0.05);
3
+ $ba-BoundingBoxHighlight-bgColor--hover: rgba($ba-BoundingBoxHighlight-color, 0.16);
4
+ $ba-BoundingBoxHighlight-bgColor--selected: rgba($ba-BoundingBoxHighlight-color, 0.1);
5
5
 
6
6
  .ba-BoundingBoxHighlightRect {
7
7
  position: absolute;
8
8
  box-sizing: border-box;
9
9
  background-color: $ba-BoundingBoxHighlight-bgColor--default;
10
- border: 2px solid rgba($ba-BoundingBoxHighlight-color, .2);
10
+ border: 2px solid rgba($ba-BoundingBoxHighlight-color, 0.2);
11
11
  border-radius: 8px;
12
12
  outline: none;
13
13
  cursor: pointer;
14
- transition: background-color 200ms ease, border-color 200ms ease, box-shadow 200ms ease;
14
+ transition:
15
+ background-color 200ms ease,
16
+ border-color 200ms ease,
17
+ box-shadow 200ms ease;
15
18
  pointer-events: auto;
16
19
 
17
20
  &.is-selected {
18
21
  background-color: $ba-BoundingBoxHighlight-bgColor--selected;
19
22
  border-color: $ba-BoundingBoxHighlight-color;
20
- box-shadow: 0 0 0 2px rgba($ba-BoundingBoxHighlight-color, .3), 0 4px 12px rgba(0, 0, 0, .15);
23
+ box-shadow:
24
+ 0 0 0 2px rgba($ba-BoundingBoxHighlight-color, 0.3),
25
+ 0 4px 12px rgb(0 0 0 / 15%);
21
26
  }
22
27
  }
@@ -25,7 +25,7 @@
25
25
  }
26
26
 
27
27
  .ba-ReplyForm {
28
- opacity: .99; // Creates a new stacking context so that the collaborators list shows on top of the ba-Popup-footer
28
+ opacity: 0.99; // Creates a new stacking context so that the collaborators list shows on top of the ba-Popup-footer
29
29
  }
30
30
  }
31
31
 
@@ -34,7 +34,7 @@
34
34
  background-color: $popup-content-bg;
35
35
  border: 1px solid $popup-border-color;
36
36
  border-radius: 6px;
37
- box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .15);
37
+ box-shadow: 0 2px 12px 0 rgb(0 0 0 / 15%);
38
38
  }
39
39
 
40
40
  .ba-Popup-footer {
@@ -13,7 +13,7 @@
13
13
  border-radius: $bdl-border-radius-size;
14
14
  outline: none;
15
15
  cursor: pointer;
16
- opacity: .7;
16
+ opacity: 0.7;
17
17
  transition: opacity 200ms ease;
18
18
 
19
19
  &:focus,
@@ -23,7 +23,7 @@
23
23
 
24
24
  &[disabled] {
25
25
  cursor: default;
26
- opacity: .2;
26
+ opacity: 0.2;
27
27
  }
28
28
  }
29
29
 
@@ -53,7 +53,7 @@
53
53
  .ba-Popup-content {
54
54
  gap: 4px;
55
55
  padding: 8px;
56
- background-color: rgba(0, 0, 0, .8);
56
+ background-color: rgb(0 0 0 / 80%);
57
57
  border-radius: 46px;
58
58
  }
59
59
  }
@@ -75,7 +75,7 @@
75
75
 
76
76
  // Modernized border-left for adjacent groups
77
77
  .ba-PopupDrawingToolbar--modernized .ba-PopupDrawingToolbar-group + .ba-PopupDrawingToolbar-group {
78
- border-left: 1px solid rgb(255, 255, 255, 8%);
78
+ border-left: 1px solid rgb(255 255 255 / 8%);
79
79
  }
80
80
 
81
81
  .ba-PopupDrawingToolbar-delete,
@@ -8,7 +8,7 @@
8
8
  .ba-Popup-content {
9
9
  border: solid 1px $bdl-gray-30;
10
10
  border-radius: $bdl-border-radius-size;
11
- box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .05);
11
+ box-shadow: 0 1px 2px 0 rgb(0 0 0 / 5%);
12
12
  }
13
13
 
14
14
  .ba-PopupList-prompt {
@@ -1,4 +1,4 @@
1
- .ba-PopupV2 {
1
+ .ba-PopupAnnotation {
2
2
  position: absolute;
3
3
  top: 0;
4
4
  left: 0;
@@ -8,7 +8,7 @@
8
8
 
9
9
  // Specificity must beat .ba selectors from box-ui-elements globals
10
10
  // (forms, buttons, links) that cascade into threaded-annotations components.
11
- .ba .ba-PopupV2 {
11
+ .ba .ba-PopupAnnotation {
12
12
  div[contenteditable='true'],
13
13
  div[contenteditable='true']:hover,
14
14
  div[contenteditable='true']:focus {
@@ -19,6 +19,7 @@ import FocusTrap from 'box-ui-elements/es/components/focus-trap/FocusTrap';
19
19
 
20
20
  import { annotationToMessages, collaboratorToUserContact } from '../../adapters/threadedAnnotationsAdapters';
21
21
  import AnnotationCallbacksContext from '../../common/AnnotationCallbacksContext';
22
+ import useOutsideEvent from '../../common/useOutsideEvent';
22
23
  import {
23
24
  createReplyAction,
24
25
  deleteAnnotationAction,
@@ -41,6 +42,7 @@ import './PopupV2.scss';
41
42
 
42
43
  export type Props = {
43
44
  annotationId?: string;
45
+ onCancel?: () => void;
44
46
  onSubmit: (text: string) => void;
45
47
  popupPortalEl?: HTMLElement | null;
46
48
  reference: PopupReference;
@@ -70,6 +72,15 @@ const createDocumentNode = (content: JSONContent | null): DocumentNodeV2 => {
70
72
  const serializePopupMessage = (doc: DocumentNodeV2, isRichTextEnabled: boolean): string =>
71
73
  isRichTextEnabled ? serializeMessageToMarkdown(doc) : serializeMentionMarkup(doc).text;
72
74
 
75
+ const isEmptyEditorContent = (content: JSONContent | null, isRichTextEnabled: boolean): boolean => {
76
+ if (!content) {
77
+ return true;
78
+ }
79
+
80
+ const text = serializePopupMessage(createDocumentNode(content), isRichTextEnabled);
81
+ return text.trim().length === 0;
82
+ };
83
+
73
84
  const literalToString = (literal: TokenLiteral): string | null => {
74
85
  if (!literal) return null;
75
86
  if (typeof literal === 'string') return literal;
@@ -105,13 +116,14 @@ const fetchAvatarBlob = async (
105
116
  }
106
117
  };
107
118
 
108
- const PopupV2 = ({ annotationId, onSubmit, popupPortalEl, reference }: Props): JSX.Element | null => {
119
+ const PopupV2 = ({ annotationId, onCancel, onSubmit, popupPortalEl, reference }: Props): JSX.Element | null => {
109
120
  const intl = useIntl();
110
121
  const dispatch = useDispatch<AppThunkDispatch>();
111
122
  const { onCopyLink: consumerOnCopyLink } = React.useContext(AnnotationCallbacksContext);
112
123
  const popupRef = React.useRef<HTMLDivElement>(null);
113
124
  const popperRef = React.useRef<Instance>();
114
125
  const optionsRef = React.useRef<Partial<Options>>(getPopupOptions());
126
+ const hasTextRef = React.useRef(false);
115
127
 
116
128
  const apiHost = useSelector(getApiHost);
117
129
  const fileId = useSelector(getFileId);
@@ -142,10 +154,7 @@ const PopupV2 = ({ annotationId, onSubmit, popupPortalEl, reference }: Props): J
142
154
  const capturedToken = token;
143
155
  const url = await fetchAvatarBlob(capturedApiHost, capturedToken, fileId, userId);
144
156
  if (!url) return null;
145
- if (
146
- credentialsRef.current.apiHost !== capturedApiHost ||
147
- credentialsRef.current.token !== capturedToken
148
- ) {
157
+ if (credentialsRef.current.apiHost !== capturedApiHost || credentialsRef.current.token !== capturedToken) {
149
158
  URL.revokeObjectURL(url);
150
159
  return null;
151
160
  }
@@ -188,6 +197,21 @@ const PopupV2 = ({ annotationId, onSubmit, popupPortalEl, reference }: Props): J
188
197
  event.stopPropagation();
189
198
  }, []);
190
199
 
200
+ const handleEditorChange = React.useCallback(
201
+ (content: JSONContent | null): void => {
202
+ hasTextRef.current = !isEmptyEditorContent(content, isRichTextEnabled);
203
+ },
204
+ [isRichTextEnabled],
205
+ );
206
+
207
+ const handleClickAway = React.useCallback((): void => {
208
+ if (!hasTextRef.current) {
209
+ onCancel?.();
210
+ }
211
+ }, [onCancel]);
212
+
213
+ useOutsideEvent('mousedown', popupRef, onCancel ? handleClickAway : undefined);
214
+
191
215
  React.useEffect(() => {
192
216
  if (!annotation) return undefined;
193
217
 
@@ -225,11 +249,9 @@ const PopupV2 = ({ annotationId, onSubmit, popupPortalEl, reference }: Props): J
225
249
 
226
250
  const isResolved = annotation?.status === 'resolved';
227
251
  const resolvedBy = isResolved
228
- ? annotation?.resolution?.resolved_by?.name ?? annotation?.modified_by?.name
229
- : undefined;
230
- const resolvedAtSource = isResolved
231
- ? annotation?.resolution?.resolved_at ?? annotation?.modified_at
252
+ ? (annotation?.resolution?.resolved_by?.name ?? annotation?.modified_by?.name)
232
253
  : undefined;
254
+ const resolvedAtSource = isResolved ? (annotation?.resolution?.resolved_at ?? annotation?.modified_at) : undefined;
233
255
  const resolvedAt = resolvedAtSource ? new Date(resolvedAtSource).getTime() : undefined;
234
256
 
235
257
  const userSelectorProps = React.useMemo(
@@ -288,14 +310,11 @@ const PopupV2 = ({ annotationId, onSubmit, popupPortalEl, reference }: Props): J
288
310
  [annotationId, dispatch, isRichTextEnabled],
289
311
  );
290
312
 
291
- const handleThreadDelete = React.useCallback(
292
- async (): Promise<void> => {
293
- if (annotationId) {
294
- await dispatch(deleteAnnotationAction(annotationId));
295
- }
296
- },
297
- [annotationId, dispatch],
298
- );
313
+ const handleThreadDelete = React.useCallback(async (): Promise<void> => {
314
+ if (annotationId) {
315
+ await dispatch(deleteAnnotationAction(annotationId));
316
+ }
317
+ }, [annotationId, dispatch]);
299
318
 
300
319
  const handleDelete = React.useCallback(
301
320
  async (id: string): Promise<void> => {
@@ -305,24 +324,21 @@ const PopupV2 = ({ annotationId, onSubmit, popupPortalEl, reference }: Props): J
305
324
  [annotationId, dispatch],
306
325
  );
307
326
 
308
- const handleResolve = React.useCallback(
309
- async (): Promise<void> => {
310
- if (!annotationId) return;
311
- const result = await dispatch(updateAnnotationAction({ annotationId, payload: { status: 'resolved' } }));
312
- if (updateAnnotationAction.fulfilled.match(result)) {
313
- dispatch(setActiveAnnotationIdAction(null));
314
- }
315
- },
316
- [annotationId, dispatch],
317
- );
327
+ const handleResolve = React.useCallback(async (): Promise<void> => {
328
+ if (!annotationId) return;
329
+ const result = await dispatch(updateAnnotationAction({ annotationId, payload: { status: 'resolved' } }));
330
+ if (updateAnnotationAction.fulfilled.match(result)) {
331
+ dispatch(setActiveAnnotationIdAction(null));
332
+ }
333
+ }, [annotationId, dispatch]);
318
334
 
319
- const handleUnresolve = React.useCallback(
320
- async (): Promise<void> => {
321
- if (!annotationId) return;
322
- await dispatch(updateAnnotationAction({ annotationId, payload: { status: 'open' } }));
323
- },
324
- [annotationId, dispatch],
325
- );
335
+ const handleUnresolve = React.useCallback(async (): Promise<void> => {
336
+ if (!annotationId) return;
337
+ await dispatch(updateAnnotationAction({ annotationId, payload: { status: 'open' } }));
338
+ }, [annotationId, dispatch]);
339
+
340
+ // No messages means this pane does not own the thread; do not render an empty composer.
341
+ if (annotationId && !threadMessages.length) return null;
326
342
 
327
343
  if (!popupPortalEl) return null;
328
344
 
@@ -331,25 +347,26 @@ const PopupV2 = ({ annotationId, onSubmit, popupPortalEl, reference }: Props): J
331
347
  <div
332
348
  ref={popupRef}
333
349
  aria-label={intl.formatMessage(messages.ariaLabelComment)}
334
- className="ba-PopupV2"
350
+ className="ba-PopupAnnotation"
335
351
  data-resin-component={annotationId ? 'popupThreadV2' : 'popupReplyV2'}
336
- onClick={handleEvent}
352
+ data-resin-feature="annotations"
337
353
  onMouseDown={handleEvent}
338
354
  onMouseMove={handleEvent}
339
- onMouseUp={handleEvent}
340
355
  onTouchMove={handleEvent}
341
356
  onTouchStart={handleEvent}
342
357
  role="presentation"
343
358
  >
344
359
  <BlueprintModernizationProvider enableModernizedComponents>
345
360
  <TooltipProvider>
346
- <MentionContextProvider value={{
347
- collaborationPopoverProps: {
348
- onSubmit: () => Promise.resolve(),
349
- },
350
- // Mentions are restricted to file collaborators; the invite popover is intentionally never shown.
351
- fetchCollaboratorState: async () => true,
352
- }}>
361
+ <MentionContextProvider
362
+ value={{
363
+ collaborationPopoverProps: {
364
+ onSubmit: () => Promise.resolve(),
365
+ },
366
+ // Mentions are restricted to file collaborators; the invite popover is intentionally never shown.
367
+ fetchCollaboratorState: async () => true,
368
+ }}
369
+ >
353
370
  {annotationId ? (
354
371
  <ThreadedAnnotationsV2
355
372
  isAnnotations
@@ -372,6 +389,7 @@ const PopupV2 = ({ annotationId, onSubmit, popupPortalEl, reference }: Props): J
372
389
  <MessageEditorV2
373
390
  isFirstAnnotation
374
391
  isRichTextEnabled={isRichTextEnabled}
392
+ onChange={handleEditorChange}
375
393
  onPost={handlePost}
376
394
  userSelectorProps={userSelectorProps}
377
395
  />
@@ -70,14 +70,7 @@ const ReplyForm = (props: Props): JSX.Element => {
70
70
  }, [formRef]); // eslint-disable-line react-hooks/exhaustive-deps
71
71
 
72
72
  return (
73
- <Form
74
- ref={formRef}
75
- className="ba-ReplyForm"
76
- data-testid="ba-ReplyForm"
77
- onPointerEnterCapture={undefined}
78
- onPointerLeaveCapture={undefined}
79
- placeholder={undefined}
80
- >
73
+ <Form ref={formRef} className="ba-ReplyForm" data-testid="ba-ReplyForm">
81
74
  <div className="ba-Popup-main">
82
75
  <ReplyField
83
76
  className="ba-Popup-field"
package/src/constants.ts CHANGED
@@ -9,14 +9,13 @@ export const MOUSE_PRIMARY = 1;
9
9
 
10
10
  /**
11
11
  * This is the default location index for video annotations.
12
- * Documents have locations that represent a page but that doesn't apply to
12
+ * Documents have locations that represent a page but that doesn't apply to
13
13
  * videos as they have no pages. The annnotations managers take locations as
14
- * parameters so we need to provide a default value that indicates that this is a
14
+ * parameters so we need to provide a default value that indicates that this is a
15
15
  * video annotation.
16
16
  */
17
17
  export const MEDIA_LOCATION_INDEX = -1;
18
18
 
19
-
20
19
  export enum TARGET_TYPE {
21
20
  FRAME = 'frame',
22
21
  PAGE = 'page',