box-annotations 4.0.0-beta.8 → 4.0.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.
- package/README.md +7 -57
- package/dist/annotations.css +1 -1
- package/dist/annotations.js +1 -1
- package/i18n/bn-IN.js +6 -0
- package/i18n/bn-IN.properties +12 -0
- package/i18n/da-DK.js +6 -0
- package/i18n/da-DK.properties +12 -0
- package/i18n/de-DE.js +6 -0
- package/i18n/de-DE.properties +12 -0
- package/i18n/en-AU.js +6 -0
- package/i18n/en-AU.properties +12 -0
- package/i18n/en-CA.js +6 -0
- package/i18n/en-CA.properties +12 -0
- package/i18n/en-GB.js +6 -0
- package/i18n/en-GB.properties +12 -0
- package/i18n/en-US.js +6 -0
- package/i18n/en-US.properties +12 -0
- package/i18n/en-x-pseudo.js +16 -9
- package/i18n/en-x-pseudo.properties +23 -9
- package/i18n/es-419.js +6 -0
- package/i18n/es-419.properties +12 -0
- package/i18n/es-ES.js +6 -0
- package/i18n/es-ES.properties +12 -0
- package/i18n/fi-FI.js +6 -0
- package/i18n/fi-FI.properties +12 -0
- package/i18n/fr-CA.js +6 -0
- package/i18n/fr-CA.properties +12 -0
- package/i18n/fr-FR.js +6 -0
- package/i18n/fr-FR.properties +12 -0
- package/i18n/hi-IN.js +6 -0
- package/i18n/hi-IN.properties +12 -0
- package/i18n/it-IT.js +6 -0
- package/i18n/it-IT.properties +12 -0
- package/i18n/ja-JP.js +6 -0
- package/i18n/ja-JP.properties +12 -0
- package/i18n/json/src/components/Popups/messages.json +1 -1
- package/i18n/ko-KR.js +6 -0
- package/i18n/ko-KR.properties +12 -0
- package/i18n/nb-NO.js +6 -0
- package/i18n/nb-NO.properties +12 -0
- package/i18n/nl-NL.js +6 -0
- package/i18n/nl-NL.properties +12 -0
- package/i18n/pl-PL.js +6 -0
- package/i18n/pl-PL.properties +12 -0
- package/i18n/pt-BR.js +6 -0
- package/i18n/pt-BR.properties +12 -0
- package/i18n/ru-RU.js +6 -0
- package/i18n/ru-RU.properties +12 -0
- package/i18n/sv-SE.js +6 -0
- package/i18n/sv-SE.properties +12 -0
- package/i18n/tr-TR.js +6 -0
- package/i18n/tr-TR.properties +12 -0
- package/i18n/zh-CN.js +6 -0
- package/i18n/zh-CN.properties +12 -0
- package/i18n/zh-TW.js +6 -0
- package/i18n/zh-TW.properties +12 -0
- package/package.json +29 -29
- package/src/@types/events.ts +8 -2
- package/src/@types/global.ts +1 -0
- package/src/@types/model.ts +20 -7
- package/src/BoxAnnotations.ts +16 -2
- package/src/common/BaseAnnotator.scss +0 -1
- package/src/common/BaseAnnotator.ts +125 -19
- package/src/common/BaseManager.ts +67 -4
- package/src/common/DeselectListener.tsx +21 -0
- package/src/common/DeselectManager.tsx +30 -0
- package/src/common/useIsListInteractive.ts +10 -0
- package/src/common/useMountId.ts +14 -0
- package/src/common/useOutsideEvent.ts +9 -4
- package/src/common/usePrevious.ts +13 -0
- package/src/components/ItemList/ItemList.scss +7 -2
- package/src/components/ItemList/ItemList.tsx +45 -26
- package/src/components/ItemList/ItemRow.scss +19 -7
- package/src/components/ItemList/ItemRow.tsx +33 -12
- package/src/components/PointerCapture/PointerCapture.tsx +116 -0
- package/src/components/PointerCapture/index.ts +2 -0
- package/src/components/Popups/Popper.ts +1 -0
- package/src/components/Popups/PopupArrow.scss +91 -0
- package/src/components/Popups/PopupBase.scss +12 -81
- package/src/components/Popups/PopupBase.tsx +0 -1
- package/src/components/Popups/PopupCursor.tsx +1 -1
- package/src/components/Popups/PopupDrawingToolbar.scss +76 -0
- package/src/components/Popups/PopupDrawingToolbar.tsx +109 -0
- package/src/components/Popups/PopupHighlight.scss +39 -0
- package/src/components/Popups/PopupHighlight.tsx +97 -0
- package/src/components/Popups/PopupHighlightError.scss +23 -0
- package/src/components/Popups/PopupHighlightError.tsx +74 -0
- package/src/components/Popups/PopupList.scss +1 -4
- package/src/components/Popups/PopupList.tsx +2 -2
- package/src/components/Popups/PopupReply.tsx +51 -6
- package/src/components/Popups/messages.ts +30 -0
- package/src/components/ReplyField/ReplyField.scss +6 -1
- package/src/components/ReplyField/ReplyField.tsx +43 -42
- package/src/components/ReplyField/ReplyFieldContainer.tsx +2 -1
- package/src/components/ReplyForm/ReplyButton.tsx +38 -0
- package/src/components/ReplyForm/ReplyForm.scss +3 -0
- package/src/components/ReplyForm/ReplyForm.tsx +49 -12
- package/src/components/ReplyForm/ReplyFormContainer.tsx +6 -4
- package/src/constants.js +1 -8
- package/src/document/DocumentAnnotator.scss +15 -0
- package/src/document/DocumentAnnotator.ts +110 -53
- package/src/document/docUtil.ts +61 -0
- package/src/drawing/DecoratedDrawingPath.scss +9 -0
- package/src/drawing/DecoratedDrawingPath.tsx +43 -0
- package/src/drawing/DrawingAnnotations.scss +39 -0
- package/src/drawing/DrawingAnnotations.tsx +157 -0
- package/src/drawing/DrawingAnnotationsContainer.tsx +66 -0
- package/src/drawing/DrawingCreator.scss +9 -0
- package/src/drawing/DrawingCreator.tsx +183 -0
- package/src/drawing/DrawingCursor.ts +23 -0
- package/src/drawing/DrawingList.tsx +60 -0
- package/src/drawing/DrawingManager.tsx +15 -0
- package/src/drawing/DrawingPath.tsx +17 -0
- package/src/drawing/DrawingPathGroup.tsx +52 -0
- package/src/drawing/DrawingSVG.tsx +34 -0
- package/src/drawing/DrawingSVGGroup.tsx +23 -0
- package/src/drawing/DrawingTarget.scss +11 -0
- package/src/drawing/DrawingTarget.tsx +100 -0
- package/src/drawing/SVGFilterContext.js +5 -0
- package/src/drawing/actions.ts +48 -0
- package/src/drawing/drawingUtil.ts +106 -0
- package/src/drawing/index.ts +2 -0
- package/src/highlight/HighlightAnnotations.scss +12 -0
- package/src/highlight/HighlightAnnotations.tsx +144 -0
- package/src/highlight/HighlightCanvas.scss +9 -0
- package/src/highlight/HighlightCanvas.tsx +109 -0
- package/src/highlight/HighlightContainer.tsx +64 -0
- package/src/highlight/HighlightCreator.scss +12 -0
- package/src/highlight/HighlightCreatorManager.ts +79 -0
- package/src/highlight/HighlightList.scss +13 -0
- package/src/highlight/HighlightList.tsx +92 -0
- package/src/highlight/HighlightListener.ts +37 -0
- package/src/highlight/HighlightManager.tsx +15 -0
- package/src/highlight/HighlightSvg.scss +7 -0
- package/src/highlight/HighlightSvg.tsx +18 -0
- package/src/highlight/HighlightTarget.scss +5 -0
- package/src/highlight/HighlightTarget.tsx +106 -0
- package/src/highlight/actions.ts +36 -0
- package/src/highlight/highlightUtil.ts +143 -0
- package/src/highlight/index.ts +4 -0
- package/src/image/ImageAnnotator.scss +20 -0
- package/src/image/ImageAnnotator.ts +137 -0
- package/src/polyfill.ts +19 -0
- package/src/popup/PopupContainer.tsx +50 -0
- package/src/popup/PopupLayer.scss +3 -0
- package/src/popup/PopupLayer.tsx +103 -0
- package/src/popup/PopupManager.tsx +15 -0
- package/src/region/RegionAnnotation.scss +4 -0
- package/src/region/RegionAnnotation.tsx +15 -16
- package/src/region/RegionAnnotations.scss +1 -15
- package/src/region/RegionAnnotations.tsx +14 -115
- package/src/region/RegionAnnotationsContainer.tsx +24 -0
- package/src/region/RegionCreation.scss +17 -0
- package/src/region/RegionCreation.tsx +79 -0
- package/src/region/RegionCreationContainer.tsx +43 -0
- package/src/region/RegionCreationManager.tsx +15 -0
- package/src/region/RegionCreator.scss +9 -2
- package/src/region/RegionCreator.tsx +50 -71
- package/src/region/RegionList.tsx +8 -12
- package/src/region/RegionManager.tsx +7 -39
- package/src/region/RegionRect.scss +41 -9
- package/src/region/RegionRect.tsx +9 -3
- package/src/region/index.ts +1 -0
- package/src/region/regionUtil.ts +1 -8
- package/src/region/transformUtil.ts +28 -0
- package/src/store/annotations/actions.ts +1 -0
- package/src/store/annotations/reducer.ts +9 -2
- package/src/store/annotations/selectors.ts +1 -0
- package/src/store/annotations/types.ts +1 -0
- package/src/store/common/actions.ts +1 -0
- package/src/store/common/reducer.ts +16 -10
- package/src/store/common/selectors.ts +1 -0
- package/src/store/common/types.ts +3 -0
- package/src/store/createRootReducer.ts +4 -0
- package/src/store/creator/actions.ts +2 -0
- package/src/store/creator/reducer.ts +22 -3
- package/src/store/creator/selectors.ts +9 -1
- package/src/store/creator/types.ts +18 -3
- package/src/store/drawing/actions.ts +10 -0
- package/src/store/drawing/index.ts +4 -0
- package/src/store/drawing/reducer.ts +50 -0
- package/src/store/drawing/selectors.ts +9 -0
- package/src/store/drawing/types.ts +7 -0
- package/src/store/eventing/active.ts +4 -2
- package/src/store/eventing/fetch.ts +8 -0
- package/src/store/eventing/init.ts +14 -0
- package/src/store/eventing/middleware.ts +21 -3
- package/src/store/eventing/mode.ts +7 -0
- package/src/store/eventing/staged.ts +56 -0
- package/src/store/eventing/status.ts +19 -0
- package/src/store/highlight/actions.ts +106 -0
- package/src/store/highlight/index.ts +4 -0
- package/src/store/highlight/reducer.ts +37 -0
- package/src/store/highlight/selectors.ts +12 -0
- package/src/store/highlight/types.ts +14 -0
- package/src/store/index.ts +2 -0
- package/src/store/options/actions.ts +2 -0
- package/src/store/options/reducer.ts +17 -1
- package/src/store/options/selectors.ts +8 -0
- package/src/store/options/types.ts +5 -0
- package/src/store/types.ts +5 -1
- package/src/store/users/actions.ts +3 -2
- package/src/utils/resin.ts +22 -0
- package/src/utils/rotate.ts +131 -0
- package/src/utils/scroll.ts +45 -0
- package/src/utils/util.ts +3 -0
- package/src/region/RegionContainer.tsx +0 -49
- package/src/utils/polyfill.js +0 -161
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import IconHighlightTextAnnotation from 'box-ui-elements/es/icon/fill/AnnotationsHighlight16';
|
|
3
|
+
import noop from 'lodash/noop';
|
|
4
|
+
import { FormattedMessage } from 'react-intl';
|
|
5
|
+
import messages from './messages';
|
|
6
|
+
import PopupBase from './PopupBase';
|
|
7
|
+
import useOutsideEvent from '../../common/useOutsideEvent';
|
|
8
|
+
import { Options } from './Popper';
|
|
9
|
+
import { Shape } from '../../@types/model';
|
|
10
|
+
import './PopupHighlight.scss';
|
|
11
|
+
|
|
12
|
+
export type Props = {
|
|
13
|
+
onCancel?: () => void;
|
|
14
|
+
onSubmit?: () => void;
|
|
15
|
+
shape: Shape;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const options: Partial<Options> = {
|
|
19
|
+
modifiers: [
|
|
20
|
+
{
|
|
21
|
+
name: 'arrow',
|
|
22
|
+
options: {
|
|
23
|
+
element: '.ba-Popup-arrow',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'eventListeners',
|
|
28
|
+
options: {
|
|
29
|
+
scroll: false,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'offset',
|
|
34
|
+
options: {
|
|
35
|
+
offset: [0, 8],
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'preventOverflow',
|
|
40
|
+
options: {
|
|
41
|
+
padding: 5,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
placement: 'bottom',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export default function PopupHighlight({ onCancel = noop, onSubmit = noop, shape }: Props): JSX.Element {
|
|
49
|
+
const buttonRef = React.useRef<HTMLButtonElement>(null);
|
|
50
|
+
const { height, width, x, y } = shape;
|
|
51
|
+
|
|
52
|
+
const reference = {
|
|
53
|
+
getBoundingClientRect: () => ({
|
|
54
|
+
bottom: y + height,
|
|
55
|
+
height,
|
|
56
|
+
left: x,
|
|
57
|
+
right: x + width,
|
|
58
|
+
top: y,
|
|
59
|
+
width,
|
|
60
|
+
}),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const handleKeydownOutside = (event: Event): void => {
|
|
64
|
+
if (!(event instanceof KeyboardEvent) || event.key !== 'Enter' || event.ctrlKey || event.metaKey) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
event.preventDefault(); // Consume any enter keydown event if the promoter is open
|
|
69
|
+
event.stopPropagation(); // Prevent the enter keydown event from adding a new line to the textarea
|
|
70
|
+
|
|
71
|
+
onSubmit();
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
useOutsideEvent('keydown', buttonRef, handleKeydownOutside);
|
|
75
|
+
useOutsideEvent('mousedown', buttonRef, onCancel);
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<PopupBase
|
|
79
|
+
aria-live="polite"
|
|
80
|
+
className="ba-PopupHighlight"
|
|
81
|
+
options={options}
|
|
82
|
+
reference={reference}
|
|
83
|
+
role="dialog"
|
|
84
|
+
>
|
|
85
|
+
<button
|
|
86
|
+
ref={buttonRef}
|
|
87
|
+
className="ba-PopupHighlight-button"
|
|
88
|
+
data-testid="ba-PopupHighlight-button"
|
|
89
|
+
onClick={onSubmit}
|
|
90
|
+
type="button"
|
|
91
|
+
>
|
|
92
|
+
<IconHighlightTextAnnotation className="ba-PopupHighlight-icon" />
|
|
93
|
+
<FormattedMessage {...messages.popupHighlightPromoter} />
|
|
94
|
+
</button>
|
|
95
|
+
</PopupBase>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
@import '~box-ui-elements/es/styles/variables';
|
|
2
|
+
@import './PopupArrow';
|
|
3
|
+
|
|
4
|
+
.ba {
|
|
5
|
+
$popup-content-bg: $bdl-gray-50;
|
|
6
|
+
|
|
7
|
+
.ba-PopupHighlightError {
|
|
8
|
+
@include ba-PopupArrow(4px, $popup-content-bg, $popup-content-bg, $popup-content-bg, $popup-content-bg, $popup-content-bg);
|
|
9
|
+
@include common-typography;
|
|
10
|
+
|
|
11
|
+
.ba-Popup-content {
|
|
12
|
+
display: flex;
|
|
13
|
+
flex-direction: row;
|
|
14
|
+
align-items: center;
|
|
15
|
+
padding: 5px 10px;
|
|
16
|
+
color: $white;
|
|
17
|
+
background-color: $popup-content-bg;
|
|
18
|
+
border: none;
|
|
19
|
+
border-radius: $bdl-border-radius-size;
|
|
20
|
+
box-shadow: none;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import noop from 'lodash/noop';
|
|
3
|
+
import { FormattedMessage } from 'react-intl';
|
|
4
|
+
import messages from './messages';
|
|
5
|
+
import PopupBase from './PopupBase';
|
|
6
|
+
import useOutsideEvent from '../../common/useOutsideEvent';
|
|
7
|
+
import { Options } from './Popper';
|
|
8
|
+
import { Shape } from '../../@types/model';
|
|
9
|
+
import './PopupHighlightError.scss';
|
|
10
|
+
|
|
11
|
+
export type Props = {
|
|
12
|
+
onCancel?: () => void;
|
|
13
|
+
shape: Shape;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const options: Partial<Options> = {
|
|
17
|
+
modifiers: [
|
|
18
|
+
{
|
|
19
|
+
name: 'arrow',
|
|
20
|
+
options: {
|
|
21
|
+
element: '.ba-Popup-arrow',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'eventListeners',
|
|
26
|
+
options: {
|
|
27
|
+
scroll: false,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'offset',
|
|
32
|
+
options: {
|
|
33
|
+
offset: [0, 8],
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'preventOverflow',
|
|
38
|
+
options: {
|
|
39
|
+
padding: 5,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
placement: 'bottom',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export default function PopupHighlightError({ onCancel = noop, shape }: Props): JSX.Element {
|
|
47
|
+
const popupRef = React.useRef<PopupBase>(null);
|
|
48
|
+
const { height, width, x, y } = shape;
|
|
49
|
+
|
|
50
|
+
const reference = {
|
|
51
|
+
getBoundingClientRect: () => ({
|
|
52
|
+
bottom: y + height,
|
|
53
|
+
height,
|
|
54
|
+
left: x,
|
|
55
|
+
right: x + width,
|
|
56
|
+
top: y,
|
|
57
|
+
width,
|
|
58
|
+
}),
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
useOutsideEvent('mousedown', popupRef.current?.popupRef, onCancel);
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<PopupBase
|
|
65
|
+
ref={popupRef}
|
|
66
|
+
className="ba-PopupHighlightError"
|
|
67
|
+
data-testid="ba-PopupHighlightError"
|
|
68
|
+
options={options}
|
|
69
|
+
reference={reference}
|
|
70
|
+
>
|
|
71
|
+
<FormattedMessage {...messages.popupHighlightRestrictedPrompt} />
|
|
72
|
+
</PopupBase>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -6,15 +6,12 @@
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
.ba-Popup-content {
|
|
9
|
-
padding-top: 10px;
|
|
10
|
-
padding-bottom: 10px;
|
|
11
9
|
border: solid 1px $bdl-gray-30;
|
|
12
10
|
border-radius: $bdl-border-radius-size;
|
|
13
11
|
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .05);
|
|
14
12
|
}
|
|
15
13
|
|
|
16
14
|
.ba-PopupList-prompt {
|
|
17
|
-
padding
|
|
18
|
-
padding-left: 10px;
|
|
15
|
+
padding: 10px;
|
|
19
16
|
}
|
|
20
17
|
}
|
|
@@ -9,11 +9,11 @@ import './PopupList.scss';
|
|
|
9
9
|
|
|
10
10
|
export type Props<T extends { id: string }> = {
|
|
11
11
|
activeItemIndex?: number;
|
|
12
|
-
|
|
12
|
+
autoScroll?: boolean;
|
|
13
13
|
items: T[];
|
|
14
14
|
onActivate?: (index: number) => void;
|
|
15
|
+
onMouseDown?: (event: React.SyntheticEvent) => void;
|
|
15
16
|
onSelect: (index: number, event: React.SyntheticEvent) => void;
|
|
16
|
-
options?: Partial<Options>;
|
|
17
17
|
reference: PopupReference;
|
|
18
18
|
};
|
|
19
19
|
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import * as Popper from '@popperjs/core';
|
|
3
|
+
import * as ReactRedux from 'react-redux';
|
|
2
4
|
import PopupBase from './PopupBase';
|
|
3
5
|
import ReplyForm from '../ReplyForm';
|
|
4
|
-
|
|
6
|
+
import usePrevious from '../../common/usePrevious';
|
|
7
|
+
import { getScale, getRotation } from '../../store/options';
|
|
8
|
+
import { PopupReference } from './Popper';
|
|
5
9
|
import './PopupReply.scss';
|
|
6
10
|
|
|
7
11
|
export type Props = {
|
|
@@ -10,17 +14,22 @@ export type Props = {
|
|
|
10
14
|
onCancel: (text?: string) => void;
|
|
11
15
|
onChange: (text?: string) => void;
|
|
12
16
|
onSubmit: (text: string) => void;
|
|
13
|
-
reference:
|
|
17
|
+
reference: PopupReference;
|
|
14
18
|
value?: string;
|
|
15
19
|
};
|
|
16
20
|
|
|
17
|
-
|
|
21
|
+
const isIE = (): boolean => {
|
|
22
|
+
const { userAgent } = navigator;
|
|
23
|
+
return userAgent.indexOf('Trident/') > 0;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const options: Pick<Popper.Options, 'modifiers'> = {
|
|
18
27
|
modifiers: [
|
|
19
28
|
{
|
|
20
29
|
name: 'arrow',
|
|
21
30
|
options: {
|
|
22
31
|
element: '.ba-Popup-arrow',
|
|
23
|
-
padding:
|
|
32
|
+
padding: 10,
|
|
24
33
|
},
|
|
25
34
|
},
|
|
26
35
|
{
|
|
@@ -45,6 +54,27 @@ export const options = {
|
|
|
45
54
|
],
|
|
46
55
|
};
|
|
47
56
|
|
|
57
|
+
const getOptions = (): Partial<Popper.Options> => {
|
|
58
|
+
const { modifiers: defaultModifiers } = options;
|
|
59
|
+
const placement = isIE() ? 'top' : 'bottom';
|
|
60
|
+
const modifiers = isIE()
|
|
61
|
+
? [
|
|
62
|
+
...defaultModifiers,
|
|
63
|
+
{
|
|
64
|
+
name: 'eventListeners',
|
|
65
|
+
options: {
|
|
66
|
+
scroll: false,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
]
|
|
70
|
+
: defaultModifiers;
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
modifiers,
|
|
74
|
+
placement,
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
|
|
48
78
|
export default function PopupReply({
|
|
49
79
|
isPending,
|
|
50
80
|
onCancel,
|
|
@@ -53,8 +83,23 @@ export default function PopupReply({
|
|
|
53
83
|
value = '',
|
|
54
84
|
...rest
|
|
55
85
|
}: Props): JSX.Element {
|
|
86
|
+
const popupRef = React.useRef<PopupBase>(null);
|
|
87
|
+
const popupOptions = React.useRef<Partial<Popper.Options>>(getOptions()); // Keep the options reference the same between renders
|
|
88
|
+
const rotation = ReactRedux.useSelector(getRotation);
|
|
89
|
+
const prevRotation = usePrevious(rotation);
|
|
90
|
+
const scale = ReactRedux.useSelector(getScale);
|
|
91
|
+
const prevScale = usePrevious(scale);
|
|
92
|
+
|
|
93
|
+
React.useEffect(() => {
|
|
94
|
+
const { current: popup } = popupRef;
|
|
95
|
+
|
|
96
|
+
if (popup?.popper && (rotation !== prevRotation || scale !== prevScale)) {
|
|
97
|
+
popup.popper.update();
|
|
98
|
+
}
|
|
99
|
+
}, [popupRef, prevRotation, prevScale, rotation, scale]);
|
|
100
|
+
|
|
56
101
|
return (
|
|
57
|
-
<PopupBase options={
|
|
102
|
+
<PopupBase ref={popupRef} data-resin-component="popupReply" options={popupOptions.current} {...rest}>
|
|
58
103
|
<ReplyForm
|
|
59
104
|
isPending={isPending}
|
|
60
105
|
onCancel={onCancel}
|
|
@@ -11,11 +11,41 @@ export default defineMessages({
|
|
|
11
11
|
description: 'Button label for creating a description, comment, or reply',
|
|
12
12
|
defaultMessage: 'Post',
|
|
13
13
|
},
|
|
14
|
+
drawingButtonAddComment: {
|
|
15
|
+
id: 'ba.popups.drawing.addComment',
|
|
16
|
+
description: 'Button label for adding a comment in the drawing toolbar',
|
|
17
|
+
defaultMessage: 'Add Comment',
|
|
18
|
+
},
|
|
19
|
+
drawingButtonDelete: {
|
|
20
|
+
id: 'ba.popups.drawing.delete',
|
|
21
|
+
description: 'Button title for deleting a staged drawing',
|
|
22
|
+
defaultMessage: 'Delete',
|
|
23
|
+
},
|
|
24
|
+
drawingButtonRedo: {
|
|
25
|
+
id: 'ba.popups.drawing.redo',
|
|
26
|
+
description: 'Button title for redoing a staged drawing',
|
|
27
|
+
defaultMessage: 'Redo',
|
|
28
|
+
},
|
|
29
|
+
drawingButtonUndo: {
|
|
30
|
+
id: 'ba.popups.drawing.undo',
|
|
31
|
+
description: 'Button title for undoing a staged drawing',
|
|
32
|
+
defaultMessage: 'Undo',
|
|
33
|
+
},
|
|
14
34
|
fieldPlaceholder: {
|
|
15
35
|
id: 'ba.popups.replyField.placeholder',
|
|
16
36
|
description: 'Placeholder for reply field editor',
|
|
17
37
|
defaultMessage: 'Type a comment...',
|
|
18
38
|
},
|
|
39
|
+
popupHighlightPromoter: {
|
|
40
|
+
id: 'ba.popups.popupHighlight.promoter',
|
|
41
|
+
description: 'Popup message for highlight promoter',
|
|
42
|
+
defaultMessage: 'Highlight and Comment',
|
|
43
|
+
},
|
|
44
|
+
popupHighlightRestrictedPrompt: {
|
|
45
|
+
id: 'ba.popups.popupHighlight.restrictedPrompt',
|
|
46
|
+
description: 'Prompt message when selection crosses multiple pages',
|
|
47
|
+
defaultMessage: 'Comments restricted to single page',
|
|
48
|
+
},
|
|
19
49
|
popupListPrompt: {
|
|
20
50
|
id: 'ba.popups.popupList.prompt',
|
|
21
51
|
description: 'Prompt message for empty popup list',
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
@import '~box-ui-elements/es/styles/variables';
|
|
2
|
-
@import '~draft-js/dist/Draft.css';
|
|
3
2
|
|
|
4
3
|
.ba-ReplyField {
|
|
4
|
+
@import '~draft-js/dist/Draft';
|
|
5
|
+
|
|
6
|
+
.DraftEditor-editorContainer {
|
|
7
|
+
border-left: none;
|
|
8
|
+
}
|
|
9
|
+
|
|
5
10
|
.public-DraftEditorPlaceholder-hasFocus,
|
|
6
11
|
.public-DraftEditorPlaceholder-root {
|
|
7
12
|
margin: 0;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import classnames from 'classnames';
|
|
3
|
+
import debounce from 'lodash/debounce';
|
|
3
4
|
import {
|
|
4
5
|
addMention,
|
|
5
6
|
getActiveMentionForEditorState,
|
|
6
|
-
} from 'box-ui-elements/es/components/form-elements/draft-js-mention-selector';
|
|
7
|
-
import fuzzySearch from 'box-ui-elements/es/utils/fuzzySearch';
|
|
7
|
+
} from 'box-ui-elements/es/components/form-elements/draft-js-mention-selector/utils';
|
|
8
8
|
import { DraftHandleValue, Editor, EditorState } from 'draft-js';
|
|
9
9
|
import PopupList from '../Popups/PopupList';
|
|
10
10
|
import { Collaborator } from '../../@types';
|
|
@@ -24,8 +24,8 @@ export type Props = {
|
|
|
24
24
|
collaborators: Collaborator[];
|
|
25
25
|
cursorPosition: number;
|
|
26
26
|
editorState: EditorState;
|
|
27
|
+
fetchCollaborators: (searchString: string) => void;
|
|
27
28
|
isDisabled?: boolean;
|
|
28
|
-
itemRowAs?: JSX.Element;
|
|
29
29
|
onChange: (editorState: EditorState) => void;
|
|
30
30
|
placeholder?: string;
|
|
31
31
|
setCursorPosition: (cursorPosition: number) => void;
|
|
@@ -33,15 +33,37 @@ export type Props = {
|
|
|
33
33
|
|
|
34
34
|
export type State = {
|
|
35
35
|
activeItemIndex: number;
|
|
36
|
+
popupAutoScroll: boolean;
|
|
36
37
|
popupReference: VirtualElement | null;
|
|
37
38
|
};
|
|
38
39
|
|
|
40
|
+
export const DEFAULT_COLLAB_DEBOUNCE = 500;
|
|
41
|
+
|
|
39
42
|
export default class ReplyField extends React.Component<Props, State> {
|
|
40
43
|
static defaultProps = {
|
|
41
44
|
isDisabled: false,
|
|
42
45
|
};
|
|
43
46
|
|
|
44
|
-
state: State = { activeItemIndex: 0, popupReference: null };
|
|
47
|
+
state: State = { activeItemIndex: 0, popupAutoScroll: false, popupReference: null };
|
|
48
|
+
|
|
49
|
+
fetchCollaborators = debounce((editorState: EditorState): void => {
|
|
50
|
+
const { fetchCollaborators } = this.props;
|
|
51
|
+
|
|
52
|
+
const activeMention = getActiveMentionForEditorState(editorState);
|
|
53
|
+
const trimmedQuery = activeMention?.mentionString.trim();
|
|
54
|
+
if (!trimmedQuery) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
fetchCollaborators(trimmedQuery);
|
|
59
|
+
}, DEFAULT_COLLAB_DEBOUNCE);
|
|
60
|
+
|
|
61
|
+
handleResize = debounce(() => this.updatePopupReference(), 100);
|
|
62
|
+
|
|
63
|
+
componentDidMount(): void {
|
|
64
|
+
// The browser’s native zoom doesn’t always trigger Preview’s resize
|
|
65
|
+
window.addEventListener('resize', this.handleResize);
|
|
66
|
+
}
|
|
45
67
|
|
|
46
68
|
componentDidUpdate({ editorState: prevEditorState }: Props): void {
|
|
47
69
|
const { editorState } = this.props;
|
|
@@ -52,36 +74,10 @@ export default class ReplyField extends React.Component<Props, State> {
|
|
|
52
74
|
}
|
|
53
75
|
|
|
54
76
|
componentWillUnmount(): void {
|
|
77
|
+
window.removeEventListener('resize', this.handleResize);
|
|
55
78
|
this.saveCursorPosition();
|
|
56
79
|
}
|
|
57
80
|
|
|
58
|
-
getCollaborators = (): Collaborator[] => {
|
|
59
|
-
const { collaborators, editorState } = this.props;
|
|
60
|
-
|
|
61
|
-
const activeMention = getActiveMentionForEditorState(editorState);
|
|
62
|
-
if (!activeMention) {
|
|
63
|
-
return [];
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const trimmedQuery = activeMention.mentionString.trim();
|
|
67
|
-
// fuzzySearch doesn't match anything if query length is less than 2
|
|
68
|
-
// Compared to empty list, full list has a better user experience
|
|
69
|
-
if (trimmedQuery.length < 2) {
|
|
70
|
-
return collaborators;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return collaborators.filter(({ item }) => {
|
|
74
|
-
if (!item) {
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const isNameMatch = fuzzySearch(trimmedQuery, item.name, 0);
|
|
79
|
-
const isEmailMatch = 'email' in item && fuzzySearch(trimmedQuery, item.email, 0);
|
|
80
|
-
|
|
81
|
-
return isNameMatch || isEmailMatch;
|
|
82
|
-
});
|
|
83
|
-
};
|
|
84
|
-
|
|
85
81
|
getVirtualElement = (activeMention: Mention): VirtualElement | null => {
|
|
86
82
|
const selection = window.getSelection();
|
|
87
83
|
if (!selection?.focusNode) {
|
|
@@ -94,7 +90,7 @@ export default class ReplyField extends React.Component<Props, State> {
|
|
|
94
90
|
|
|
95
91
|
const { mentionString, mentionTrigger } = activeMention;
|
|
96
92
|
const offset = mentionString.length + mentionTrigger.length;
|
|
97
|
-
const mentionStart = currentCursor - offset;
|
|
93
|
+
const mentionStart = Math.max(0, currentCursor - offset);
|
|
98
94
|
|
|
99
95
|
range.setStart(textNode, mentionStart);
|
|
100
96
|
range.setEnd(textNode, mentionStart);
|
|
@@ -129,19 +125,20 @@ export default class ReplyField extends React.Component<Props, State> {
|
|
|
129
125
|
event.stopPropagation();
|
|
130
126
|
};
|
|
131
127
|
|
|
132
|
-
setPopupListActiveItem = (index: number): void =>
|
|
128
|
+
setPopupListActiveItem = (index: number, autoScroll = false): void =>
|
|
129
|
+
this.setState({ activeItemIndex: index, popupAutoScroll: autoScroll });
|
|
133
130
|
|
|
134
131
|
handleChange = (nextEditorState: EditorState): void => {
|
|
135
132
|
const { onChange } = this.props;
|
|
136
133
|
|
|
134
|
+
this.fetchCollaborators(nextEditorState);
|
|
137
135
|
onChange(nextEditorState);
|
|
138
136
|
};
|
|
139
137
|
|
|
140
138
|
handleSelect = (index: number): void => {
|
|
141
|
-
const { editorState } = this.props;
|
|
139
|
+
const { collaborators, editorState } = this.props;
|
|
142
140
|
|
|
143
141
|
const activeMention = getActiveMentionForEditorState(editorState);
|
|
144
|
-
const collaborators = this.getCollaborators();
|
|
145
142
|
const editorStateWithLink = addMention(editorState, activeMention, collaborators[index]);
|
|
146
143
|
|
|
147
144
|
this.handleChange(editorStateWithLink);
|
|
@@ -162,8 +159,9 @@ export default class ReplyField extends React.Component<Props, State> {
|
|
|
162
159
|
};
|
|
163
160
|
|
|
164
161
|
handleArrow = (event: React.KeyboardEvent): number => {
|
|
162
|
+
const { collaborators } = this.props;
|
|
165
163
|
const { popupReference } = this.state;
|
|
166
|
-
const { length } =
|
|
164
|
+
const { length } = collaborators;
|
|
167
165
|
|
|
168
166
|
if (!popupReference || !length) {
|
|
169
167
|
return 0;
|
|
@@ -182,7 +180,7 @@ export default class ReplyField extends React.Component<Props, State> {
|
|
|
182
180
|
return;
|
|
183
181
|
}
|
|
184
182
|
|
|
185
|
-
this.setPopupListActiveItem(activeItemIndex === 0 ? length - 1 : activeItemIndex - 1);
|
|
183
|
+
this.setPopupListActiveItem(activeItemIndex === 0 ? length - 1 : activeItemIndex - 1, true);
|
|
186
184
|
};
|
|
187
185
|
|
|
188
186
|
handleDownArrow = (event: React.KeyboardEvent): void => {
|
|
@@ -193,12 +191,14 @@ export default class ReplyField extends React.Component<Props, State> {
|
|
|
193
191
|
return;
|
|
194
192
|
}
|
|
195
193
|
|
|
196
|
-
this.setPopupListActiveItem(activeItemIndex === length - 1 ? 0 : activeItemIndex + 1);
|
|
194
|
+
this.setPopupListActiveItem(activeItemIndex === length - 1 ? 0 : activeItemIndex + 1, true);
|
|
197
195
|
};
|
|
198
196
|
|
|
197
|
+
handleMouseDown = (event: React.SyntheticEvent): void => event.preventDefault(); // prevent the blur event so that Editor can remain focus
|
|
198
|
+
|
|
199
199
|
render(): JSX.Element {
|
|
200
|
-
const { className, editorState, isDisabled,
|
|
201
|
-
const { activeItemIndex, popupReference } = this.state;
|
|
200
|
+
const { className, collaborators, editorState, isDisabled, placeholder, ...rest } = this.props;
|
|
201
|
+
const { activeItemIndex, popupAutoScroll, popupReference } = this.state;
|
|
202
202
|
|
|
203
203
|
return (
|
|
204
204
|
<div className={classnames(className, 'ba-ReplyField')}>
|
|
@@ -218,9 +218,10 @@ export default class ReplyField extends React.Component<Props, State> {
|
|
|
218
218
|
{popupReference && (
|
|
219
219
|
<PopupList
|
|
220
220
|
activeItemIndex={activeItemIndex}
|
|
221
|
-
|
|
222
|
-
items={
|
|
221
|
+
autoScroll={popupAutoScroll}
|
|
222
|
+
items={collaborators}
|
|
223
223
|
onActivate={this.setPopupListActiveItem}
|
|
224
|
+
onMouseDown={this.handleMouseDown}
|
|
224
225
|
onSelect={this.handleSelect}
|
|
225
226
|
reference={popupReference}
|
|
226
227
|
/>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { connect } from 'react-redux';
|
|
2
|
-
import { AppState, getCollaborators, getCreatorCursor, setCursorAction } from '../../store';
|
|
2
|
+
import { AppState, fetchCollaboratorsAction, getCollaborators, getCreatorCursor, setCursorAction } from '../../store';
|
|
3
3
|
import ReplyField from './ReplyField';
|
|
4
4
|
import { Collaborator } from '../../@types';
|
|
5
5
|
|
|
@@ -14,6 +14,7 @@ export const mapStateToProps = (state: AppState): Props => ({
|
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
export const mapDispatchToProps = {
|
|
17
|
+
fetchCollaborators: fetchCollaboratorsAction,
|
|
17
18
|
setCursorPosition: setCursorAction,
|
|
18
19
|
};
|
|
19
20
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React, { HTMLAttributes } from 'react';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
import noop from 'lodash/noop';
|
|
4
|
+
|
|
5
|
+
export type Props = {
|
|
6
|
+
children?: React.ReactNode;
|
|
7
|
+
isDisabled?: boolean;
|
|
8
|
+
isPrimary?: boolean;
|
|
9
|
+
onClick?: (event: React.MouseEvent) => void;
|
|
10
|
+
type: 'button' | 'submit' | 'reset' | undefined;
|
|
11
|
+
} & HTMLAttributes<HTMLButtonElement>;
|
|
12
|
+
|
|
13
|
+
export default function ReplyButton({ children, isDisabled, isPrimary, onClick = noop, ...rest }: Props): JSX.Element {
|
|
14
|
+
const handleClick = (event: React.MouseEvent): void => {
|
|
15
|
+
if (isDisabled) {
|
|
16
|
+
event.preventDefault();
|
|
17
|
+
event.stopPropagation();
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
onClick(event);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<button
|
|
26
|
+
aria-disabled={isDisabled ? true : undefined}
|
|
27
|
+
className={classNames('btn', {
|
|
28
|
+
'btn-primary': isPrimary,
|
|
29
|
+
'is-disabled': isDisabled,
|
|
30
|
+
})}
|
|
31
|
+
onClick={handleClick}
|
|
32
|
+
type="button"
|
|
33
|
+
{...rest}
|
|
34
|
+
>
|
|
35
|
+
{children}
|
|
36
|
+
</button>
|
|
37
|
+
);
|
|
38
|
+
}
|