@dhis2/analytics 23.10.4 → 23.12.0

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 (80) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/build/cjs/components/Interpretations/InterpretationModal/Comment.js +64 -0
  3. package/build/cjs/components/Interpretations/InterpretationModal/CommentAddForm.js +93 -0
  4. package/build/cjs/components/Interpretations/InterpretationModal/CommentDeleteButton.js +62 -0
  5. package/build/cjs/components/Interpretations/InterpretationModal/CommentUpdateForm.js +95 -0
  6. package/build/cjs/components/Interpretations/InterpretationModal/InterpretationModal.js +187 -0
  7. package/build/cjs/components/Interpretations/InterpretationModal/InterpretationThread.js +100 -0
  8. package/build/cjs/components/Interpretations/InterpretationModal/index.js +13 -0
  9. package/build/cjs/components/Interpretations/InterpretationModal/useModalContentWidth.js +39 -0
  10. package/build/cjs/components/Interpretations/InterpretationsUnit/InterpretationForm.js +94 -0
  11. package/build/cjs/components/Interpretations/InterpretationsUnit/InterpretationList.js +94 -0
  12. package/build/cjs/components/Interpretations/InterpretationsUnit/InterpretationsUnit.js +135 -0
  13. package/build/cjs/components/Interpretations/InterpretationsUnit/index.js +13 -0
  14. package/build/cjs/components/Interpretations/common/Interpretation/Interpretation.js +110 -0
  15. package/build/cjs/components/Interpretations/common/Interpretation/InterpretationDeleteButton.js +58 -0
  16. package/build/cjs/components/Interpretations/common/Interpretation/InterpretationSharingLink.js +50 -0
  17. package/build/cjs/components/Interpretations/common/Interpretation/InterpretationUpdateForm.js +108 -0
  18. package/build/cjs/components/Interpretations/common/Interpretation/index.js +21 -0
  19. package/build/cjs/components/Interpretations/common/Interpretation/useLike.js +53 -0
  20. package/build/cjs/components/Interpretations/common/Message/Message.js +55 -0
  21. package/build/cjs/components/Interpretations/common/Message/MessageButtonStrip.js +33 -0
  22. package/build/cjs/components/Interpretations/common/Message/MessageEditorContainer.js +42 -0
  23. package/build/cjs/components/Interpretations/common/Message/MessageIconButton.js +67 -0
  24. package/build/cjs/components/Interpretations/common/Message/MessageInput.js +31 -0
  25. package/build/cjs/components/Interpretations/common/Message/MessageStatsBar.js +33 -0
  26. package/build/cjs/components/Interpretations/common/Message/index.js +53 -0
  27. package/build/cjs/components/Interpretations/common/RichTextEditor/RichTextEditor.js +262 -0
  28. package/build/cjs/components/Interpretations/common/RichTextEditor/index.js +13 -0
  29. package/build/cjs/components/Interpretations/common/RichTextEditor/markdownHandler.js +148 -0
  30. package/build/cjs/components/Interpretations/common/RichTextEditor/styles/RichTextEditor.style.js +21 -0
  31. package/build/cjs/components/Interpretations/common/UserMention/UserList.js +48 -0
  32. package/build/cjs/components/Interpretations/common/UserMention/UserMentionWrapper.js +226 -0
  33. package/build/cjs/components/Interpretations/common/UserMention/styles/UserMentionWrapper.style.js +30 -0
  34. package/build/cjs/components/Interpretations/common/UserMention/useUserSearchResults.js +78 -0
  35. package/build/cjs/components/Interpretations/common/index.js +44 -0
  36. package/build/cjs/components/LegendKey/styles/LegendKey.style.js +2 -2
  37. package/build/cjs/index.js +22 -0
  38. package/build/cjs/locales/en/translations.json +32 -1
  39. package/build/cjs/locales/ro/translations.json +6 -6
  40. package/build/cjs/modules/legends.js +6 -2
  41. package/build/es/components/Interpretations/InterpretationModal/Comment.js +45 -0
  42. package/build/es/components/Interpretations/InterpretationModal/CommentAddForm.js +70 -0
  43. package/build/es/components/Interpretations/InterpretationModal/CommentDeleteButton.js +47 -0
  44. package/build/es/components/Interpretations/InterpretationModal/CommentUpdateForm.js +73 -0
  45. package/build/es/components/Interpretations/InterpretationModal/InterpretationModal.js +165 -0
  46. package/build/es/components/Interpretations/InterpretationModal/InterpretationThread.js +79 -0
  47. package/build/es/components/Interpretations/InterpretationModal/index.js +1 -0
  48. package/build/es/components/Interpretations/InterpretationModal/useModalContentWidth.js +28 -0
  49. package/build/es/components/Interpretations/InterpretationsUnit/InterpretationForm.js +71 -0
  50. package/build/es/components/Interpretations/InterpretationsUnit/InterpretationList.js +78 -0
  51. package/build/es/components/Interpretations/InterpretationsUnit/InterpretationsUnit.js +112 -0
  52. package/build/es/components/Interpretations/InterpretationsUnit/index.js +1 -0
  53. package/build/es/components/Interpretations/common/Interpretation/Interpretation.js +87 -0
  54. package/build/es/components/Interpretations/common/Interpretation/InterpretationDeleteButton.js +43 -0
  55. package/build/es/components/Interpretations/common/Interpretation/InterpretationSharingLink.js +33 -0
  56. package/build/es/components/Interpretations/common/Interpretation/InterpretationUpdateForm.js +85 -0
  57. package/build/es/components/Interpretations/common/Interpretation/index.js +2 -0
  58. package/build/es/components/Interpretations/common/Interpretation/useLike.js +45 -0
  59. package/build/es/components/Interpretations/common/Message/Message.js +41 -0
  60. package/build/es/components/Interpretations/common/Message/MessageButtonStrip.js +21 -0
  61. package/build/es/components/Interpretations/common/Message/MessageEditorContainer.js +30 -0
  62. package/build/es/components/Interpretations/common/Message/MessageIconButton.js +54 -0
  63. package/build/es/components/Interpretations/common/Message/MessageInput.js +16 -0
  64. package/build/es/components/Interpretations/common/Message/MessageStatsBar.js +21 -0
  65. package/build/es/components/Interpretations/common/Message/index.js +6 -0
  66. package/build/es/components/Interpretations/common/RichTextEditor/RichTextEditor.js +240 -0
  67. package/build/es/components/Interpretations/common/RichTextEditor/index.js +1 -0
  68. package/build/es/components/Interpretations/common/RichTextEditor/markdownHandler.js +128 -0
  69. package/build/es/components/Interpretations/common/RichTextEditor/styles/RichTextEditor.style.js +9 -0
  70. package/build/es/components/Interpretations/common/UserMention/UserList.js +33 -0
  71. package/build/es/components/Interpretations/common/UserMention/UserMentionWrapper.js +202 -0
  72. package/build/es/components/Interpretations/common/UserMention/styles/UserMentionWrapper.style.js +17 -0
  73. package/build/es/components/Interpretations/common/UserMention/useUserSearchResults.js +63 -0
  74. package/build/es/components/Interpretations/common/index.js +3 -0
  75. package/build/es/components/LegendKey/styles/LegendKey.style.js +2 -2
  76. package/build/es/index.js +3 -1
  77. package/build/es/locales/en/translations.json +32 -1
  78. package/build/es/locales/ro/translations.json +6 -6
  79. package/build/es/modules/legends.js +6 -2
  80. package/package.json +3 -2
@@ -0,0 +1,240 @@
1
+ import _JSXStyle from "styled-jsx/style";
2
+ import i18n from '@dhis2/d2-i18n';
3
+ import { Parser as RichTextParser } from '@dhis2/d2-ui-rich-text';
4
+ import { Button, Popover, Tooltip, Field, IconAt24, IconFaceAdd24, IconLink24, IconTextBold24, IconTextItalic24, colors } from '@dhis2/ui';
5
+ import PropTypes from 'prop-types';
6
+ import React, { forwardRef, useRef, useEffect, useState } from 'react';
7
+ import { UserMentionWrapper } from '../UserMention/UserMentionWrapper.js';
8
+ import { convertCtrlKey, insertMarkdown, emojis, EMOJI_SMILEY_FACE, EMOJI_SAD_FACE, EMOJI_THUMBS_UP, EMOJI_THUMBS_DOWN, BOLD, ITALIC, LINK, MENTION } from './markdownHandler.js';
9
+ import { mainClasses, toolbarClasses, tooltipAnchorClasses, emojisPopoverClasses } from './styles/RichTextEditor.style.js';
10
+
11
+ const EmojisPopover = _ref => {
12
+ let {
13
+ onInsertMarkdown,
14
+ onClose,
15
+ reference
16
+ } = _ref;
17
+ return /*#__PURE__*/React.createElement(Popover, {
18
+ reference: reference,
19
+ onClickOutside: onClose
20
+ }, /*#__PURE__*/React.createElement("ul", {
21
+ className: "jsx-".concat(emojisPopoverClasses.__hash) + " " + "emojisList"
22
+ }, /*#__PURE__*/React.createElement("li", {
23
+ onClick: () => onInsertMarkdown(EMOJI_SMILEY_FACE),
24
+ className: "jsx-".concat(emojisPopoverClasses.__hash)
25
+ }, /*#__PURE__*/React.createElement(RichTextParser, null, emojis[EMOJI_SMILEY_FACE])), /*#__PURE__*/React.createElement("li", {
26
+ onClick: () => onInsertMarkdown(EMOJI_SAD_FACE),
27
+ className: "jsx-".concat(emojisPopoverClasses.__hash)
28
+ }, /*#__PURE__*/React.createElement(RichTextParser, null, emojis[EMOJI_SAD_FACE])), /*#__PURE__*/React.createElement("li", {
29
+ onClick: () => onInsertMarkdown(EMOJI_THUMBS_UP),
30
+ className: "jsx-".concat(emojisPopoverClasses.__hash)
31
+ }, /*#__PURE__*/React.createElement(RichTextParser, null, emojis[EMOJI_THUMBS_UP])), /*#__PURE__*/React.createElement("li", {
32
+ onClick: () => onInsertMarkdown(EMOJI_THUMBS_DOWN),
33
+ className: "jsx-".concat(emojisPopoverClasses.__hash)
34
+ }, /*#__PURE__*/React.createElement(RichTextParser, null, emojis[EMOJI_THUMBS_DOWN]))), /*#__PURE__*/React.createElement(_JSXStyle, {
35
+ id: emojisPopoverClasses.__hash
36
+ }, emojisPopoverClasses));
37
+ };
38
+
39
+ EmojisPopover.propTypes = {
40
+ onClose: PropTypes.func.isRequired,
41
+ onInsertMarkdown: PropTypes.func.isRequired,
42
+ reference: PropTypes.object
43
+ };
44
+
45
+ const IconButtonWithTooltip = _ref2 => {
46
+ let {
47
+ tooltipContent,
48
+ disabled,
49
+ icon,
50
+ onClick
51
+ } = _ref2;
52
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Tooltip, {
53
+ content: tooltipContent,
54
+ placement: "bottom",
55
+ closeDelay: 200
56
+ }, _ref3 => {
57
+ let {
58
+ ref,
59
+ onMouseOver,
60
+ onMouseOut
61
+ } = _ref3;
62
+ return /*#__PURE__*/React.createElement("span", {
63
+ ref: ref,
64
+ onMouseOver: onMouseOver,
65
+ onMouseOut: onMouseOut,
66
+ className: "jsx-".concat(tooltipAnchorClasses.__hash) + " " + "tooltip"
67
+ }, /*#__PURE__*/React.createElement(Button, {
68
+ secondary: true,
69
+ small: true,
70
+ disabled: disabled,
71
+ icon: icon,
72
+ onClick: onClick
73
+ }));
74
+ }), /*#__PURE__*/React.createElement(_JSXStyle, {
75
+ id: tooltipAnchorClasses.__hash
76
+ }, tooltipAnchorClasses));
77
+ };
78
+
79
+ IconButtonWithTooltip.propTypes = {
80
+ disabled: PropTypes.bool,
81
+ icon: PropTypes.node,
82
+ tooltipContent: PropTypes.string,
83
+ onClick: PropTypes.func
84
+ };
85
+
86
+ const Toolbar = _ref4 => {
87
+ let {
88
+ disabled,
89
+ onInsertMarkdown,
90
+ onTogglePreview,
91
+ previewButtonDisabled,
92
+ previewMode
93
+ } = _ref4;
94
+ const emojisButtonRef = useRef();
95
+ const [emojisPopoverIsOpen, setEmojisPopoverIsOpen] = useState(false);
96
+ const iconColor = disabled ? colors.grey600 : colors.grey700;
97
+ return /*#__PURE__*/React.createElement("div", {
98
+ className: "jsx-".concat(tooltipAnchorClasses.__hash, " jsx-").concat(toolbarClasses.__hash) + " " + "toolbar"
99
+ }, !previewMode ? /*#__PURE__*/React.createElement("div", {
100
+ className: "jsx-".concat(tooltipAnchorClasses.__hash, " jsx-").concat(toolbarClasses.__hash) + " " + "actionsWrapper"
101
+ }, /*#__PURE__*/React.createElement("div", {
102
+ className: "jsx-".concat(tooltipAnchorClasses.__hash, " jsx-").concat(toolbarClasses.__hash) + " " + "mainActions"
103
+ }, /*#__PURE__*/React.createElement(IconButtonWithTooltip, {
104
+ tooltipContent: i18n.t('Bold text'),
105
+ disabled: disabled,
106
+ icon: /*#__PURE__*/React.createElement(IconTextBold24, {
107
+ color: iconColor
108
+ }),
109
+ onClick: () => onInsertMarkdown(BOLD)
110
+ }), /*#__PURE__*/React.createElement(IconButtonWithTooltip, {
111
+ tooltipContent: i18n.t('Italic text'),
112
+ disabled: disabled,
113
+ icon: /*#__PURE__*/React.createElement(IconTextItalic24, {
114
+ color: iconColor
115
+ }),
116
+ onClick: () => onInsertMarkdown(ITALIC)
117
+ }), /*#__PURE__*/React.createElement(IconButtonWithTooltip, {
118
+ tooltipContent: i18n.t('Link to a URL'),
119
+ disabled: disabled,
120
+ icon: /*#__PURE__*/React.createElement(IconLink24, {
121
+ color: iconColor
122
+ }),
123
+ onClick: () => onInsertMarkdown(LINK)
124
+ }), /*#__PURE__*/React.createElement(IconButtonWithTooltip, {
125
+ tooltipContent: i18n.t('Mention a user'),
126
+ disabled: disabled,
127
+ icon: /*#__PURE__*/React.createElement(IconAt24, {
128
+ color: iconColor
129
+ }),
130
+ onClick: () => onInsertMarkdown(MENTION)
131
+ }), /*#__PURE__*/React.createElement("span", {
132
+ ref: emojisButtonRef,
133
+ className: "jsx-".concat(tooltipAnchorClasses.__hash, " jsx-").concat(toolbarClasses.__hash) + " " + "tooltip"
134
+ }, /*#__PURE__*/React.createElement(IconButtonWithTooltip, {
135
+ tooltipContent: i18n.t('Add emoji'),
136
+ disabled: disabled,
137
+ icon: /*#__PURE__*/React.createElement(IconFaceAdd24, {
138
+ color: iconColor
139
+ }),
140
+ onClick: () => setEmojisPopoverIsOpen(true)
141
+ })), emojisPopoverIsOpen && /*#__PURE__*/React.createElement(EmojisPopover, {
142
+ onClose: () => setEmojisPopoverIsOpen(false),
143
+ onInsertMarkdown: markup => {
144
+ onInsertMarkdown(markup);
145
+ setEmojisPopoverIsOpen(false);
146
+ },
147
+ reference: emojisButtonRef
148
+ })), /*#__PURE__*/React.createElement("div", {
149
+ className: "jsx-".concat(tooltipAnchorClasses.__hash, " jsx-").concat(toolbarClasses.__hash) + " " + "sideActions"
150
+ }, /*#__PURE__*/React.createElement(Button, {
151
+ secondary: true,
152
+ small: true,
153
+ disabled: previewButtonDisabled || disabled,
154
+ onClick: onTogglePreview
155
+ }, i18n.t('Preview')))) : /*#__PURE__*/React.createElement("div", {
156
+ className: "jsx-".concat(tooltipAnchorClasses.__hash, " jsx-").concat(toolbarClasses.__hash) + " " + "previewWrapper"
157
+ }, /*#__PURE__*/React.createElement(Button, {
158
+ secondary: true,
159
+ small: true,
160
+ onClick: onTogglePreview,
161
+ disabled: disabled
162
+ }, i18n.t('Back to write mode'))), /*#__PURE__*/React.createElement(_JSXStyle, {
163
+ id: tooltipAnchorClasses.__hash
164
+ }, tooltipAnchorClasses), /*#__PURE__*/React.createElement(_JSXStyle, {
165
+ id: toolbarClasses.__hash
166
+ }, toolbarClasses));
167
+ };
168
+
169
+ Toolbar.propTypes = {
170
+ previewButtonDisabled: PropTypes.bool.isRequired,
171
+ previewMode: PropTypes.bool.isRequired,
172
+ onInsertMarkdown: PropTypes.func.isRequired,
173
+ onTogglePreview: PropTypes.func.isRequired,
174
+ disabled: PropTypes.bool
175
+ };
176
+ export const RichTextEditor = /*#__PURE__*/forwardRef((_ref5, externalRef) => {
177
+ let {
178
+ value,
179
+ disabled,
180
+ inputPlaceholder,
181
+ onChange,
182
+ errorText
183
+ } = _ref5;
184
+ const [previewMode, setPreviewMode] = useState(false);
185
+ const internalRef = useRef();
186
+ const textareaRef = externalRef || internalRef;
187
+ useEffect(() => {
188
+ var _textareaRef$current;
189
+
190
+ return (_textareaRef$current = textareaRef.current) === null || _textareaRef$current === void 0 ? void 0 : _textareaRef$current.focus();
191
+ }, [textareaRef.current]);
192
+ return /*#__PURE__*/React.createElement("div", {
193
+ className: "jsx-".concat(mainClasses.__hash) + " " + "container"
194
+ }, /*#__PURE__*/React.createElement(Toolbar, {
195
+ onInsertMarkdown: markdown => {
196
+ insertMarkdown(markdown, textareaRef.current, (text, caretPos) => {
197
+ onChange(text);
198
+ textareaRef.current.focus();
199
+ textareaRef.current.selectionEnd = caretPos;
200
+ });
201
+
202
+ if (markdown === MENTION) {
203
+ textareaRef.current.dispatchEvent(new KeyboardEvent('keydown', {
204
+ key: '@',
205
+ bubbles: true
206
+ }));
207
+ }
208
+ },
209
+ onTogglePreview: () => setPreviewMode(!previewMode),
210
+ previewMode: previewMode,
211
+ previewButtonDisabled: !value,
212
+ disabled: disabled
213
+ }), previewMode ? /*#__PURE__*/React.createElement("div", {
214
+ className: "jsx-".concat(mainClasses.__hash) + " " + "preview"
215
+ }, /*#__PURE__*/React.createElement(RichTextParser, null, value)) : /*#__PURE__*/React.createElement(Field, {
216
+ error: !!errorText,
217
+ validationText: errorText
218
+ }, /*#__PURE__*/React.createElement(UserMentionWrapper, {
219
+ onUserSelect: onChange,
220
+ inputReference: textareaRef
221
+ }, /*#__PURE__*/React.createElement("textarea", {
222
+ ref: textareaRef,
223
+ placeholder: inputPlaceholder,
224
+ disabled: disabled,
225
+ value: value,
226
+ onChange: event => onChange(event.target.value),
227
+ onKeyDown: event => convertCtrlKey(event, onChange),
228
+ className: "jsx-".concat(mainClasses.__hash) + " " + "textarea"
229
+ }))), /*#__PURE__*/React.createElement(_JSXStyle, {
230
+ id: mainClasses.__hash
231
+ }, mainClasses));
232
+ });
233
+ RichTextEditor.displayName = 'RichTextEditor';
234
+ RichTextEditor.propTypes = {
235
+ value: PropTypes.string.isRequired,
236
+ onChange: PropTypes.func.isRequired,
237
+ disabled: PropTypes.bool,
238
+ errorText: PropTypes.string,
239
+ inputPlaceholder: PropTypes.string
240
+ };
@@ -0,0 +1 @@
1
+ export { RichTextEditor } from './RichTextEditor.js';
@@ -0,0 +1,128 @@
1
+ export const BOLD = 'bold';
2
+ export const ITALIC = 'italic';
3
+ export const LINK = 'link';
4
+ export const MENTION = 'mention';
5
+ export const EMOJI_SMILEY_FACE = 'smileyFace';
6
+ export const EMOJI_SAD_FACE = 'sadFace';
7
+ export const EMOJI_THUMBS_UP = 'thumbsUp';
8
+ export const EMOJI_THUMBS_DOWN = 'thumsDown';
9
+ export const emojis = {
10
+ [EMOJI_SMILEY_FACE]: ':-)',
11
+ [EMOJI_SAD_FACE]: ':-(',
12
+ [EMOJI_THUMBS_UP]: ':+1',
13
+ [EMOJI_THUMBS_DOWN]: ':-1'
14
+ };
15
+ const markdownMap = {
16
+ [ITALIC]: {
17
+ prefix: '_',
18
+ postfix: '_'
19
+ },
20
+ [BOLD]: {
21
+ prefix: '*',
22
+ postfix: '*'
23
+ },
24
+ [LINK]: {
25
+ prefix: '[',
26
+ postfix: '](https://link-url)'
27
+ },
28
+ [MENTION]: {
29
+ prefix: '@'
30
+ },
31
+ [EMOJI_SMILEY_FACE]: {
32
+ prefix: emojis[EMOJI_SMILEY_FACE]
33
+ },
34
+ [EMOJI_SAD_FACE]: {
35
+ prefix: emojis[EMOJI_SAD_FACE]
36
+ },
37
+ [EMOJI_THUMBS_UP]: {
38
+ prefix: emojis[EMOJI_THUMBS_UP]
39
+ },
40
+ [EMOJI_THUMBS_DOWN]: {
41
+ prefix: emojis[EMOJI_THUMBS_DOWN]
42
+ }
43
+ };
44
+
45
+ const trim = str => {
46
+ const leftSpaces = /^\s+/;
47
+ const rightSpaces = /\s+$/;
48
+ return str.replace(leftSpaces, '').replace(rightSpaces, '');
49
+ };
50
+
51
+ export const insertMarkdown = (markdown, target, cb) => {
52
+ const {
53
+ selectionStart: start,
54
+ selectionEnd: end,
55
+ value
56
+ } = target;
57
+ const marker = markdownMap[markdown] || null;
58
+
59
+ if (!marker || !cb || start < 0) {
60
+ return;
61
+ }
62
+
63
+ let newValue;
64
+ let caretPos = end + 1;
65
+
66
+ const padMarkers = text => {
67
+ // is caret between two markers (i.e., "**" or "__")? Then do not add padding
68
+ if (start === end && value.length && start > 0) {
69
+ if (value[start - 1] === markdownMap[BOLD].prefix && value[start] === markdownMap[BOLD].prefix || value[start - 1] === markdownMap[ITALIC].prefix && value[start] === markdownMap[ITALIC].prefix) {
70
+ return text;
71
+ }
72
+ }
73
+
74
+ if (value.length && start > 0 && value[start - 1] !== ' ') {
75
+ text = " ".concat(text);
76
+ ++caretPos;
77
+ }
78
+
79
+ if (value.length && end !== value.length && value[end] !== ' ') {
80
+ text = "".concat(text, " ");
81
+ }
82
+
83
+ return text;
84
+ };
85
+
86
+ if (start === end) {
87
+ //no text
88
+ const valueArr = value.split('');
89
+ let markdown = marker.prefix;
90
+
91
+ if (marker.postfix) {
92
+ markdown += marker.postfix;
93
+ }
94
+
95
+ valueArr.splice(start, 0, padMarkers(markdown));
96
+ newValue = valueArr.join('');
97
+ } else {
98
+ const text = value.slice(start, end);
99
+ const trimmedText = trim(text); // TODO really needed?
100
+ // adjust caretPos based on trimmed text selection
101
+
102
+ caretPos = caretPos - (text.length - trimmedText.length) + 1;
103
+ let markdown = "".concat(marker.prefix).concat(trimmedText);
104
+
105
+ if (marker.postfix) {
106
+ markdown += marker.postfix;
107
+ }
108
+
109
+ newValue = [value.slice(0, start), padMarkers(markdown), value.slice(end)].join('');
110
+ }
111
+
112
+ cb(newValue, caretPos);
113
+ };
114
+ export const convertCtrlKey = (event, cb) => {
115
+ const {
116
+ key,
117
+ ctrlKey,
118
+ metaKey
119
+ } = event;
120
+
121
+ if (key === 'b' && (ctrlKey || metaKey)) {
122
+ event.preventDefault();
123
+ insertMarkdown(BOLD, event.target, cb);
124
+ } else if (key === 'i' && (ctrlKey || metaKey)) {
125
+ event.preventDefault();
126
+ insertMarkdown(ITALIC, event.target, cb);
127
+ }
128
+ };
@@ -0,0 +1,9 @@
1
+ import { colors, spacers, theme } from '@dhis2/ui';
2
+ export const mainClasses = [".container.jsx-2278350860{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;width:100%;}", ".preview.jsx-2278350860{font-size:14px;line-height:19px;color:".concat(colors.grey900, ";}"), ".textarea.jsx-2278350860{width:100%;box-sizing:border-box;padding:".concat(spacers.dp8, " ").concat(spacers.dp12, ";color:").concat(colors.grey900, ";background-color:").concat(colors.white, ";border:1px solid ").concat(colors.grey500, ";border-radius:3px;box-shadow:inset 0 0 0 1px rgba(102,113,123,0.15), inset 0 1px 2px 0 rgba(102,113,123,0.1);outline:0;font-size:14px;line-height:").concat(spacers.dp16, ";-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text;}"), ".textarea.jsx-2278350860:focus{outline:none;box-shadow:0 0 0 3px ".concat(theme.focus, ";}"), ".textarea.jsx-2278350860:disabled{background-color:".concat(colors.grey100, ";border-color:").concat(colors.grey500, ";color:").concat(theme.disabled, ";cursor:not-allowed;}")];
3
+ mainClasses.__hash = "2278350860";
4
+ export const toolbarClasses = [".toolbar.jsx-1189800463{background:".concat(colors.grey050, ";border-radius:3px;border:1px solid ").concat(colors.grey300, ";margin-bottom:").concat(spacers.dp4, ";}"), ".actionsWrapper.jsx-1189800463{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;gap:".concat(spacers.dp4, ";-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:").concat(spacers.dp4, ";}"), ".mainActions.jsx-1189800463{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;gap:".concat(spacers.dp4, ";margin-top:").concat(spacers.dp2, ";}"), ".sideActions.jsx-1189800463{-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;margin-left:auto;}", ".previewWrapper.jsx-1189800463{margin:".concat(spacers.dp4, ";text-align:right;}")];
5
+ toolbarClasses.__hash = "1189800463";
6
+ export const tooltipAnchorClasses = [".tooltip.jsx-2182400256{display:-webkit-inline-box;display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;}"];
7
+ tooltipAnchorClasses.__hash = "2182400256";
8
+ export const emojisPopoverClasses = [".emojisList.jsx-2802175370{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;gap:".concat(spacers.dp8, ";list-style-type:none;margin:0 ").concat(spacers.dp4, " 0 ").concat(spacers.dp8, ";padding:0;}"), ".emojisList.jsx-2802175370 li.jsx-2802175370{cursor:pointer;}"];
9
+ emojisPopoverClasses.__hash = "2802175370";
@@ -0,0 +1,33 @@
1
+ import i18n from '@dhis2/d2-i18n';
2
+ import { MenuItem } from '@dhis2/ui';
3
+ import PropTypes from 'prop-types';
4
+ import React from 'react';
5
+ export const UserList = _ref => {
6
+ let {
7
+ users,
8
+ selectedUserIndex,
9
+ onUserClick,
10
+ pager
11
+ } = _ref;
12
+ return /*#__PURE__*/React.createElement(React.Fragment, null, users.map(u => {
13
+ var _users$selectedUserIn;
14
+
15
+ return /*#__PURE__*/React.createElement(MenuItem, {
16
+ dense: true,
17
+ key: u.id,
18
+ onClick: onUserClick(u),
19
+ label: "".concat(u.displayName, " (").concat(u.username, ")"),
20
+ active: ((_users$selectedUserIn = users[selectedUserIndex]) === null || _users$selectedUserIn === void 0 ? void 0 : _users$selectedUserIn.id) === u.id
21
+ });
22
+ }), pager.total > pager.pageSize && /*#__PURE__*/React.createElement(MenuItem, {
23
+ dense: true,
24
+ disabled: true,
25
+ label: i18n.t('Too many results. Try refining the search.')
26
+ }));
27
+ };
28
+ UserList.propTypes = {
29
+ pager: PropTypes.object.isRequired,
30
+ selectedUserIndex: PropTypes.number.isRequired,
31
+ users: PropTypes.array.isRequired,
32
+ onUserClick: PropTypes.func.isRequired
33
+ };
@@ -0,0 +1,202 @@
1
+ import _JSXStyle from "styled-jsx/style";
2
+ import i18n from '@dhis2/d2-i18n';
3
+ import { CenteredContent, CircularLoader, Menu, MenuSectionHeader, MenuItem, Popper, Card, Portal } from '@dhis2/ui';
4
+ import PropTypes from 'prop-types';
5
+ import React, { useState, useRef } from 'react';
6
+ import { resolvedHeaderStyle, userMentionWrapperClasses } from './styles/UserMentionWrapper.style.js';
7
+ import { UserList } from './UserList.js';
8
+ import { useUserSearchResults } from './useUserSearchResults.js';
9
+ const AT_SYMBOL_WIDTH = 14;
10
+
11
+ const getVirtualPopperReference = ref => {
12
+ const rects = ref.current.getClientRects();
13
+ const lastRect = rects[rects.length - 1];
14
+ const left = lastRect.left + lastRect.width - AT_SYMBOL_WIDTH;
15
+ return {
16
+ getBoundingClientRect: () => ({
17
+ top: lastRect.top,
18
+ right: lastRect.right,
19
+ bottom: lastRect.bottom,
20
+ left,
21
+ width: AT_SYMBOL_WIDTH,
22
+ height: lastRect.height,
23
+ x: left
24
+ })
25
+ };
26
+ };
27
+
28
+ export const UserMentionWrapper = _ref => {
29
+ let {
30
+ children,
31
+ inputReference,
32
+ onUserSelect
33
+ } = _ref;
34
+ const [captureText, setCaptureText] = useState(false);
35
+ const [capturedText, setCapturedText] = useState('');
36
+ const [cloneText, setCloneText] = useState('');
37
+ const cloneRef = useRef(null);
38
+ const [captureStartPosition, setCaptureStartPosition] = useState(null);
39
+ const [selectedUserIndex, setSelectedUserIndex] = useState(0);
40
+ const {
41
+ users,
42
+ pager,
43
+ fetching,
44
+ clear
45
+ } = useUserSearchResults({
46
+ searchText: capturedText
47
+ });
48
+
49
+ const reset = () => {
50
+ setCaptureText(false);
51
+ setCapturedText('');
52
+ setCloneText('');
53
+ setCaptureStartPosition(null);
54
+ setSelectedUserIndex(0);
55
+ clear();
56
+ }; // event bubbles up from the input/textarea
57
+
58
+
59
+ const onInput = _ref2 => {
60
+ let {
61
+ target
62
+ } = _ref2;
63
+ const {
64
+ selectionEnd,
65
+ value
66
+ } = target;
67
+
68
+ if (captureText) {
69
+ clear();
70
+ const spacePosition = value.indexOf(' ', captureStartPosition - 1);
71
+ const filterValue = value.substring(captureStartPosition, spacePosition > 0 ? spacePosition : selectionEnd + 1);
72
+
73
+ if (filterValue !== capturedText) {
74
+ setCapturedText(filterValue);
75
+ } else if (filterValue.length === 0) {
76
+ setCapturedText('');
77
+ clear();
78
+ }
79
+ }
80
+ }; // event bubbles up from the wrapped input/textarea
81
+
82
+
83
+ const onKeyDown = _ref3 => {
84
+ let {
85
+ key,
86
+ target
87
+ } = _ref3;
88
+ const {
89
+ selectionStart
90
+ } = target;
91
+
92
+ if (!captureText && key === '@') {
93
+ setCaptureText(true);
94
+ setCaptureStartPosition(selectionStart + 1);
95
+ setCloneText(target.value.substring(0, selectionStart) + '@');
96
+ } else if (captureText) {
97
+ if (key === ' ' || key === 'Backspace' && selectionStart <= captureStartPosition) {
98
+ reset();
99
+ } else if (users.length) {
100
+ switch (key) {
101
+ case 'Enter':
102
+ event.preventDefault();
103
+
104
+ if (selectedUserIndex >= 0) {
105
+ onSelect(users[selectedUserIndex]);
106
+ }
107
+
108
+ break;
109
+
110
+ case 'ArrowDown':
111
+ event.preventDefault();
112
+
113
+ if (selectedUserIndex < users.length - 1) {
114
+ setSelectedUserIndex(selectedUserIndex + 1);
115
+ }
116
+
117
+ break;
118
+
119
+ case 'ArrowUp':
120
+ event.preventDefault();
121
+
122
+ if (selectedUserIndex > 0) {
123
+ setSelectedUserIndex(selectedUserIndex - 1);
124
+ }
125
+
126
+ break;
127
+
128
+ default: // other key strokes, typically the text typed
129
+ // the onInput event handler set on the input element is triggering the user lookup
130
+
131
+ }
132
+ }
133
+ }
134
+ };
135
+
136
+ const onSelect = user => {
137
+ const originalValue = inputReference.current.value;
138
+ const newValue = "".concat(originalValue.slice(0, captureStartPosition - 1)).concat(originalValue.slice(captureStartPosition - 1).replace(/^@\w*/, "@".concat(user.username, " ")));
139
+ reset(); // typically for connected components we want the state to be updated too
140
+ // but the logic belongs to the wrapped component, so we just invoke the supplied callback
141
+
142
+ if (onUserSelect) {
143
+ onUserSelect(newValue);
144
+ } // need to refocus on the input/textarea
145
+
146
+
147
+ inputReference.current.focus(); // position the cursor at the end
148
+
149
+ requestAnimationFrame(() => inputReference.current.setSelectionRange(-1, -1), 0);
150
+ };
151
+
152
+ const onClick = user => () => onSelect(user);
153
+
154
+ return /*#__PURE__*/React.createElement("div", {
155
+ onKeyDown: onKeyDown,
156
+ onInput: onInput,
157
+ className: "jsx-".concat(userMentionWrapperClasses.__hash) + " " + "wrapper"
158
+ }, children, /*#__PURE__*/React.createElement("div", {
159
+ className: "jsx-".concat(userMentionWrapperClasses.__hash) + " " + "clone"
160
+ }, /*#__PURE__*/React.createElement("pre", {
161
+ ref: cloneRef,
162
+ className: "jsx-".concat(userMentionWrapperClasses.__hash)
163
+ }, cloneText)), captureText && /*#__PURE__*/React.createElement(Portal, null, /*#__PURE__*/React.createElement(Popper, {
164
+ reference: getVirtualPopperReference(cloneRef),
165
+ placement: "top-start"
166
+ }, /*#__PURE__*/React.createElement(Card, null, /*#__PURE__*/React.createElement("div", {
167
+ className: "jsx-".concat(userMentionWrapperClasses.__hash) + " " + "container"
168
+ }, /*#__PURE__*/React.createElement(Menu, {
169
+ dense: true
170
+ }, /*#__PURE__*/React.createElement(MenuSectionHeader, {
171
+ className: resolvedHeaderStyle.className,
172
+ dense: true,
173
+ hideDivider: true,
174
+ label: capturedText === '' ? i18n.t('Search for a user') : i18n.t('Searching for "{{searchText}}"', {
175
+ searchText: capturedText
176
+ })
177
+ }), fetching && /*#__PURE__*/React.createElement(MenuItem, {
178
+ label: /*#__PURE__*/React.createElement(CenteredContent, null, /*#__PURE__*/React.createElement(CircularLoader, {
179
+ small: true
180
+ }))
181
+ }), !fetching && users.length > 0 && /*#__PURE__*/React.createElement(UserList, {
182
+ users: users,
183
+ selectedUserIndex: selectedUserIndex,
184
+ onUserClick: onClick,
185
+ pager: pager
186
+ }), capturedText && !fetching && users.length === 0 && /*#__PURE__*/React.createElement(MenuItem, {
187
+ dense: true,
188
+ disabled: true,
189
+ label: i18n.t('No results found')
190
+ })))))), /*#__PURE__*/React.createElement(_JSXStyle, {
191
+ id: userMentionWrapperClasses.__hash
192
+ }, userMentionWrapperClasses), resolvedHeaderStyle.styles);
193
+ };
194
+ UserMentionWrapper.defaultProps = {
195
+ onUserSelect: Function.prototype
196
+ };
197
+ UserMentionWrapper.propTypes = {
198
+ inputReference: PropTypes.object.isRequired,
199
+ onUserSelect: PropTypes.func.isRequired,
200
+ children: PropTypes.node
201
+ };
202
+ export default UserMentionWrapper;
@@ -0,0 +1,17 @@
1
+ import _JSXStyle from "styled-jsx/style";
2
+ import React from "react";
3
+ import { colors, spacers } from '@dhis2/ui';
4
+
5
+ /*
6
+ * Note that the clone and clone > pre styles have been chosen
7
+ * to emulate the styles of the textarea. If we decide to make
8
+ * changes there, they should be refelcted here too.
9
+ */
10
+ export const userMentionWrapperClasses = [".wrapper.jsx-1289989717{position:relative;}", ".clone.jsx-1289989717{position:absolute;visibility:hidden;inset:0;box-sizing:border-box;padding:".concat(spacers.dp8, " ").concat(spacers.dp12, ";border:1px solid ").concat(colors.grey500, ";font-size:14px;line-height:").concat(spacers.dp16, ";z-index:1;pointer-events:none;}"), ".clone.jsx-1289989717>pre.jsx-1289989717{display:inline;word-wrap:break-word;overflow-wrap:break-word;font:inherit;margin:0;}", ".container.jsx-1289989717{background-color:".concat(colors.white, ";max-height:180px;overflow:auto;}")];
11
+ userMentionWrapperClasses.__hash = "1289989717";
12
+ export const resolvedHeaderStyle = {
13
+ styles: /*#__PURE__*/React.createElement(_JSXStyle, {
14
+ id: "4275958396"
15
+ }, [".jsx-4275958396{position:-webkit-sticky;position:sticky;top:0;}"]),
16
+ className: "jsx-4275958396"
17
+ };