@dhis2/analytics 23.11.0 → 23.12.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 (136) 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/index.js +16 -0
  37. package/build/cjs/locales/ar/translations.json +32 -1
  38. package/build/cjs/locales/ar_EG/translations.json +32 -1
  39. package/build/cjs/locales/ar_IQ/translations.json +32 -1
  40. package/build/cjs/locales/ckb/translations.json +32 -1
  41. package/build/cjs/locales/cs/translations.json +32 -1
  42. package/build/cjs/locales/da/translations.json +32 -1
  43. package/build/cjs/locales/en/translations.json +32 -1
  44. package/build/cjs/locales/es/translations.json +32 -1
  45. package/build/cjs/locales/fr/translations.json +32 -1
  46. package/build/cjs/locales/id/translations.json +32 -1
  47. package/build/cjs/locales/km/translations.json +32 -1
  48. package/build/cjs/locales/lo/translations.json +32 -1
  49. package/build/cjs/locales/my/translations.json +32 -1
  50. package/build/cjs/locales/nb/translations.json +32 -1
  51. package/build/cjs/locales/nl/translations.json +32 -1
  52. package/build/cjs/locales/or/translations.json +32 -1
  53. package/build/cjs/locales/prs/translations.json +32 -1
  54. package/build/cjs/locales/ps/translations.json +32 -1
  55. package/build/cjs/locales/pt/translations.json +32 -1
  56. package/build/cjs/locales/pt_BR/translations.json +32 -1
  57. package/build/cjs/locales/ro/translations.json +38 -7
  58. package/build/cjs/locales/ru/translations.json +32 -1
  59. package/build/cjs/locales/sv/translations.json +32 -1
  60. package/build/cjs/locales/tet/translations.json +32 -1
  61. package/build/cjs/locales/tg/translations.json +32 -1
  62. package/build/cjs/locales/uk/translations.json +32 -1
  63. package/build/cjs/locales/ur/translations.json +32 -1
  64. package/build/cjs/locales/uz/translations.json +32 -1
  65. package/build/cjs/locales/uz_Latn/translations.json +32 -1
  66. package/build/cjs/locales/vi/translations.json +32 -1
  67. package/build/cjs/locales/zh/translations.json +32 -1
  68. package/build/cjs/locales/zh_CN/translations.json +32 -1
  69. package/build/es/components/Interpretations/InterpretationModal/Comment.js +45 -0
  70. package/build/es/components/Interpretations/InterpretationModal/CommentAddForm.js +70 -0
  71. package/build/es/components/Interpretations/InterpretationModal/CommentDeleteButton.js +47 -0
  72. package/build/es/components/Interpretations/InterpretationModal/CommentUpdateForm.js +73 -0
  73. package/build/es/components/Interpretations/InterpretationModal/InterpretationModal.js +165 -0
  74. package/build/es/components/Interpretations/InterpretationModal/InterpretationThread.js +79 -0
  75. package/build/es/components/Interpretations/InterpretationModal/index.js +1 -0
  76. package/build/es/components/Interpretations/InterpretationModal/useModalContentWidth.js +28 -0
  77. package/build/es/components/Interpretations/InterpretationsUnit/InterpretationForm.js +71 -0
  78. package/build/es/components/Interpretations/InterpretationsUnit/InterpretationList.js +78 -0
  79. package/build/es/components/Interpretations/InterpretationsUnit/InterpretationsUnit.js +112 -0
  80. package/build/es/components/Interpretations/InterpretationsUnit/index.js +1 -0
  81. package/build/es/components/Interpretations/common/Interpretation/Interpretation.js +87 -0
  82. package/build/es/components/Interpretations/common/Interpretation/InterpretationDeleteButton.js +43 -0
  83. package/build/es/components/Interpretations/common/Interpretation/InterpretationSharingLink.js +33 -0
  84. package/build/es/components/Interpretations/common/Interpretation/InterpretationUpdateForm.js +85 -0
  85. package/build/es/components/Interpretations/common/Interpretation/index.js +2 -0
  86. package/build/es/components/Interpretations/common/Interpretation/useLike.js +45 -0
  87. package/build/es/components/Interpretations/common/Message/Message.js +41 -0
  88. package/build/es/components/Interpretations/common/Message/MessageButtonStrip.js +21 -0
  89. package/build/es/components/Interpretations/common/Message/MessageEditorContainer.js +30 -0
  90. package/build/es/components/Interpretations/common/Message/MessageIconButton.js +54 -0
  91. package/build/es/components/Interpretations/common/Message/MessageInput.js +16 -0
  92. package/build/es/components/Interpretations/common/Message/MessageStatsBar.js +21 -0
  93. package/build/es/components/Interpretations/common/Message/index.js +6 -0
  94. package/build/es/components/Interpretations/common/RichTextEditor/RichTextEditor.js +240 -0
  95. package/build/es/components/Interpretations/common/RichTextEditor/index.js +1 -0
  96. package/build/es/components/Interpretations/common/RichTextEditor/markdownHandler.js +128 -0
  97. package/build/es/components/Interpretations/common/RichTextEditor/styles/RichTextEditor.style.js +9 -0
  98. package/build/es/components/Interpretations/common/UserMention/UserList.js +33 -0
  99. package/build/es/components/Interpretations/common/UserMention/UserMentionWrapper.js +202 -0
  100. package/build/es/components/Interpretations/common/UserMention/styles/UserMentionWrapper.style.js +17 -0
  101. package/build/es/components/Interpretations/common/UserMention/useUserSearchResults.js +63 -0
  102. package/build/es/components/Interpretations/common/index.js +3 -0
  103. package/build/es/index.js +2 -0
  104. package/build/es/locales/ar/translations.json +32 -1
  105. package/build/es/locales/ar_EG/translations.json +32 -1
  106. package/build/es/locales/ar_IQ/translations.json +32 -1
  107. package/build/es/locales/ckb/translations.json +32 -1
  108. package/build/es/locales/cs/translations.json +32 -1
  109. package/build/es/locales/da/translations.json +32 -1
  110. package/build/es/locales/en/translations.json +32 -1
  111. package/build/es/locales/es/translations.json +32 -1
  112. package/build/es/locales/fr/translations.json +32 -1
  113. package/build/es/locales/id/translations.json +32 -1
  114. package/build/es/locales/km/translations.json +32 -1
  115. package/build/es/locales/lo/translations.json +32 -1
  116. package/build/es/locales/my/translations.json +32 -1
  117. package/build/es/locales/nb/translations.json +32 -1
  118. package/build/es/locales/nl/translations.json +32 -1
  119. package/build/es/locales/or/translations.json +32 -1
  120. package/build/es/locales/prs/translations.json +32 -1
  121. package/build/es/locales/ps/translations.json +32 -1
  122. package/build/es/locales/pt/translations.json +32 -1
  123. package/build/es/locales/pt_BR/translations.json +32 -1
  124. package/build/es/locales/ro/translations.json +38 -7
  125. package/build/es/locales/ru/translations.json +32 -1
  126. package/build/es/locales/sv/translations.json +32 -1
  127. package/build/es/locales/tet/translations.json +32 -1
  128. package/build/es/locales/tg/translations.json +32 -1
  129. package/build/es/locales/uk/translations.json +32 -1
  130. package/build/es/locales/ur/translations.json +32 -1
  131. package/build/es/locales/uz/translations.json +32 -1
  132. package/build/es/locales/uz_Latn/translations.json +32 -1
  133. package/build/es/locales/vi/translations.json +32 -1
  134. package/build/es/locales/zh/translations.json +32 -1
  135. package/build/es/locales/zh_CN/translations.json +32 -1
  136. package/package.json +2 -1
@@ -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
+ };