@dhis2/analytics 26.6.14 → 26.7.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/build/cjs/components/AboutAOUnit/AboutAOUnit.js +5 -3
- package/build/cjs/components/Interpretations/InterpretationModal/CommentAddForm.js +5 -4
- package/build/cjs/components/Interpretations/InterpretationModal/CommentUpdateForm.js +4 -3
- package/build/cjs/components/Interpretations/InterpretationsUnit/InterpretationForm.js +5 -4
- package/build/cjs/components/Interpretations/common/Interpretation/InterpretationUpdateForm.js +5 -4
- package/build/cjs/components/Interpretations/common/Message/Message.js +2 -2
- package/build/cjs/components/Interpretations/common/Message/MessageEditorContainer.js +5 -5
- package/build/cjs/components/Interpretations/common/index.js +0 -11
- package/build/cjs/components/{Interpretations/common/RichTextEditor/RichTextEditor.js → RichText/Editor/Editor.js} +76 -53
- package/build/cjs/components/RichText/Editor/__tests__/Editor.spec.js +38 -0
- package/build/cjs/components/RichText/Editor/__tests__/convertCtrlKey.spec.js +204 -0
- package/build/cjs/components/{Interpretations/common/RichTextEditor → RichText/Editor}/markdownHandler.js +12 -6
- package/build/cjs/components/{Interpretations/common/RichTextEditor/styles/RichTextEditor.style.js → RichText/Editor/styles/Editor.style.js} +2 -2
- package/build/cjs/components/RichText/Parser/MdParser.js +106 -0
- package/build/cjs/components/RichText/Parser/Parser.js +35 -0
- package/build/cjs/components/RichText/Parser/__tests__/MdParser.spec.js +42 -0
- package/build/cjs/components/RichText/Parser/__tests__/Parser.spec.js +41 -0
- package/build/cjs/components/RichText/index.js +26 -0
- package/build/cjs/components/{Interpretations/common/UserMention → UserMention}/UserMentionWrapper.js +19 -9
- package/build/cjs/components/{Interpretations/common/UserMention → UserMention}/styles/UserMentionWrapper.style.js +2 -2
- package/build/cjs/components/{Interpretations/common/UserMention → UserMention}/useUserSearchResults.js +2 -2
- package/build/cjs/index.js +58 -46
- package/build/cjs/locales/en/translations.json +11 -11
- package/build/cjs/locales/ne/translations.json +15 -15
- package/build/es/components/AboutAOUnit/AboutAOUnit.js +5 -3
- package/build/es/components/Interpretations/InterpretationModal/CommentAddForm.js +2 -1
- package/build/es/components/Interpretations/InterpretationModal/CommentUpdateForm.js +2 -1
- package/build/es/components/Interpretations/InterpretationsUnit/InterpretationForm.js +3 -2
- package/build/es/components/Interpretations/common/Interpretation/InterpretationUpdateForm.js +2 -1
- package/build/es/components/Interpretations/common/Message/Message.js +1 -1
- package/build/es/components/Interpretations/common/Message/MessageEditorContainer.js +5 -5
- package/build/es/components/Interpretations/common/index.js +0 -1
- package/build/es/components/{Interpretations/common/RichTextEditor/RichTextEditor.js → RichText/Editor/Editor.js} +51 -28
- package/build/es/components/RichText/Editor/__tests__/Editor.spec.js +35 -0
- package/build/es/components/RichText/Editor/__tests__/convertCtrlKey.spec.js +202 -0
- package/build/es/components/{Interpretations/common/RichTextEditor → RichText/Editor}/markdownHandler.js +12 -6
- package/build/es/components/{Interpretations/common/RichTextEditor/styles/RichTextEditor.style.js → RichText/Editor/styles/Editor.style.js} +2 -2
- package/build/es/components/RichText/Parser/MdParser.js +98 -0
- package/build/es/components/RichText/Parser/Parser.js +25 -0
- package/build/es/components/RichText/Parser/__tests__/MdParser.spec.js +40 -0
- package/build/es/components/RichText/Parser/__tests__/Parser.spec.js +38 -0
- package/build/es/components/RichText/index.js +3 -0
- package/build/es/components/{Interpretations/common/UserMention → UserMention}/UserMentionWrapper.js +19 -8
- package/build/es/components/UserMention/styles/UserMentionWrapper.style.js +16 -0
- package/build/es/components/{Interpretations/common/UserMention → UserMention}/useUserSearchResults.js +2 -2
- package/build/es/index.js +1 -0
- package/build/es/locales/en/translations.json +11 -11
- package/build/es/locales/ne/translations.json +15 -15
- package/package.json +2 -2
- package/build/cjs/components/Interpretations/common/RichTextEditor/index.js +0 -12
- package/build/es/components/Interpretations/common/RichTextEditor/index.js +0 -1
- package/build/es/components/Interpretations/common/UserMention/styles/UserMentionWrapper.style.js +0 -16
- /package/build/cjs/components/{Interpretations/common/UserMention → UserMention}/UserList.js +0 -0
- /package/build/es/components/{Interpretations/common/UserMention → UserMention}/UserList.js +0 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _markdownHandler = require("../markdownHandler.js");
|
|
4
|
+
describe('convertCtrlKey', () => {
|
|
5
|
+
it('does not trigger callback if no ctrl key', () => {
|
|
6
|
+
const cb = jest.fn();
|
|
7
|
+
const e = {
|
|
8
|
+
key: 'j',
|
|
9
|
+
preventDefault: () => {}
|
|
10
|
+
};
|
|
11
|
+
(0, _markdownHandler.convertCtrlKey)(e, cb);
|
|
12
|
+
expect(cb).not.toHaveBeenCalled();
|
|
13
|
+
});
|
|
14
|
+
describe('when ctrl key + "b" pressed', () => {
|
|
15
|
+
it('triggers callback with open/close markers and caret pos in between', () => {
|
|
16
|
+
const cb = jest.fn();
|
|
17
|
+
const e = {
|
|
18
|
+
key: 'b',
|
|
19
|
+
ctrlKey: true,
|
|
20
|
+
target: {
|
|
21
|
+
selectionStart: 0,
|
|
22
|
+
selectionEnd: 0,
|
|
23
|
+
value: 'rainbow dash'
|
|
24
|
+
},
|
|
25
|
+
preventDefault: () => {}
|
|
26
|
+
};
|
|
27
|
+
(0, _markdownHandler.convertCtrlKey)(e, cb);
|
|
28
|
+
expect(cb).toHaveBeenCalled();
|
|
29
|
+
expect(cb).toHaveBeenCalledWith('** rainbow dash', 1);
|
|
30
|
+
});
|
|
31
|
+
it('triggers callback with open/close markers and caret pos in between (end of text)', () => {
|
|
32
|
+
const cb = jest.fn();
|
|
33
|
+
const e = {
|
|
34
|
+
key: 'b',
|
|
35
|
+
ctrlKey: true,
|
|
36
|
+
target: {
|
|
37
|
+
selectionStart: 22,
|
|
38
|
+
selectionEnd: 22,
|
|
39
|
+
value: 'rainbow dash is purple'
|
|
40
|
+
},
|
|
41
|
+
preventDefault: () => {}
|
|
42
|
+
};
|
|
43
|
+
(0, _markdownHandler.convertCtrlKey)(e, cb);
|
|
44
|
+
expect(cb).toHaveBeenCalled();
|
|
45
|
+
expect(cb).toHaveBeenCalledWith('rainbow dash is purple **', 24);
|
|
46
|
+
});
|
|
47
|
+
it('triggers callback with open/close markers mid-text with surrounding spaces (1)', () => {
|
|
48
|
+
const cb = jest.fn();
|
|
49
|
+
const e = {
|
|
50
|
+
key: 'b',
|
|
51
|
+
metaKey: true,
|
|
52
|
+
target: {
|
|
53
|
+
selectionStart: 4,
|
|
54
|
+
// caret located just before "quick"
|
|
55
|
+
selectionEnd: 4,
|
|
56
|
+
value: 'the quick brown fox'
|
|
57
|
+
},
|
|
58
|
+
preventDefault: () => {}
|
|
59
|
+
};
|
|
60
|
+
(0, _markdownHandler.convertCtrlKey)(e, cb);
|
|
61
|
+
expect(cb).toHaveBeenCalled();
|
|
62
|
+
expect(cb).toHaveBeenCalledWith('the ** quick brown fox', 5);
|
|
63
|
+
});
|
|
64
|
+
it('triggers callback with open/close markers mid-text with surrounding spaces (2)', () => {
|
|
65
|
+
const cb = jest.fn();
|
|
66
|
+
const e = {
|
|
67
|
+
key: 'b',
|
|
68
|
+
metaKey: true,
|
|
69
|
+
target: {
|
|
70
|
+
selectionStart: 3,
|
|
71
|
+
// caret located just after "the"
|
|
72
|
+
selectionEnd: 3,
|
|
73
|
+
value: 'the quick brown fox'
|
|
74
|
+
},
|
|
75
|
+
preventDefault: () => {}
|
|
76
|
+
};
|
|
77
|
+
(0, _markdownHandler.convertCtrlKey)(e, cb);
|
|
78
|
+
expect(cb).toHaveBeenCalled();
|
|
79
|
+
expect(cb).toHaveBeenCalledWith('the ** quick brown fox', 5);
|
|
80
|
+
});
|
|
81
|
+
it('triggers callback with correct double markers and padding', () => {
|
|
82
|
+
const cb = jest.fn();
|
|
83
|
+
const e = {
|
|
84
|
+
key: 'b',
|
|
85
|
+
metaKey: true,
|
|
86
|
+
target: {
|
|
87
|
+
selectionStart: 9,
|
|
88
|
+
// between the underscores
|
|
89
|
+
selectionEnd: 9,
|
|
90
|
+
value: 'rainbow __'
|
|
91
|
+
},
|
|
92
|
+
preventDefault: () => {}
|
|
93
|
+
};
|
|
94
|
+
(0, _markdownHandler.convertCtrlKey)(e, cb);
|
|
95
|
+
expect(cb).toHaveBeenCalled();
|
|
96
|
+
expect(cb).toHaveBeenCalledWith('rainbow _**_', 10);
|
|
97
|
+
});
|
|
98
|
+
describe('selected text', () => {
|
|
99
|
+
it('triggers callback with open/close markers around text and caret pos after closing marker', () => {
|
|
100
|
+
const cb = jest.fn();
|
|
101
|
+
const e = {
|
|
102
|
+
key: 'b',
|
|
103
|
+
metaKey: true,
|
|
104
|
+
target: {
|
|
105
|
+
selectionStart: 5,
|
|
106
|
+
// "ow da" is selected
|
|
107
|
+
selectionEnd: 10,
|
|
108
|
+
value: 'rainbow dash is purple'
|
|
109
|
+
},
|
|
110
|
+
preventDefault: () => {}
|
|
111
|
+
};
|
|
112
|
+
(0, _markdownHandler.convertCtrlKey)(e, cb);
|
|
113
|
+
expect(cb).toHaveBeenCalled();
|
|
114
|
+
expect(cb).toHaveBeenCalledWith('rainb *ow da* sh is purple', 13);
|
|
115
|
+
});
|
|
116
|
+
it('triggers callback with open/close markers around text when starting at beginning of line', () => {
|
|
117
|
+
const cb = jest.fn();
|
|
118
|
+
const e = {
|
|
119
|
+
key: 'b',
|
|
120
|
+
metaKey: true,
|
|
121
|
+
target: {
|
|
122
|
+
selectionStart: 0,
|
|
123
|
+
// "rainbow" is selected
|
|
124
|
+
selectionEnd: 7,
|
|
125
|
+
value: 'rainbow dash is purple'
|
|
126
|
+
},
|
|
127
|
+
preventDefault: () => {}
|
|
128
|
+
};
|
|
129
|
+
(0, _markdownHandler.convertCtrlKey)(e, cb);
|
|
130
|
+
expect(cb).toHaveBeenCalled();
|
|
131
|
+
expect(cb).toHaveBeenCalledWith('*rainbow* dash is purple', 9);
|
|
132
|
+
});
|
|
133
|
+
it('triggers callback with open/close markers around text when ending at end of line', () => {
|
|
134
|
+
const cb = jest.fn();
|
|
135
|
+
const e = {
|
|
136
|
+
key: 'b',
|
|
137
|
+
metaKey: true,
|
|
138
|
+
target: {
|
|
139
|
+
selectionStart: 16,
|
|
140
|
+
// "purple" is selected
|
|
141
|
+
selectionEnd: 22,
|
|
142
|
+
value: 'rainbow dash is purple'
|
|
143
|
+
},
|
|
144
|
+
preventDefault: () => {}
|
|
145
|
+
};
|
|
146
|
+
(0, _markdownHandler.convertCtrlKey)(e, cb);
|
|
147
|
+
expect(cb).toHaveBeenCalled();
|
|
148
|
+
expect(cb).toHaveBeenCalledWith('rainbow dash is *purple*', 24);
|
|
149
|
+
});
|
|
150
|
+
it('triggers callback with open/close markers around word', () => {
|
|
151
|
+
const cb = jest.fn();
|
|
152
|
+
const e = {
|
|
153
|
+
key: 'b',
|
|
154
|
+
metaKey: true,
|
|
155
|
+
target: {
|
|
156
|
+
selectionStart: 8,
|
|
157
|
+
// "dash" is selected
|
|
158
|
+
selectionEnd: 12,
|
|
159
|
+
value: 'rainbow dash is purple'
|
|
160
|
+
},
|
|
161
|
+
preventDefault: () => {}
|
|
162
|
+
};
|
|
163
|
+
(0, _markdownHandler.convertCtrlKey)(e, cb);
|
|
164
|
+
expect(cb).toHaveBeenCalled();
|
|
165
|
+
expect(cb).toHaveBeenCalledWith('rainbow *dash* is purple', 14);
|
|
166
|
+
});
|
|
167
|
+
it('triggers callback with leading/trailing spaces trimmed from selection', () => {
|
|
168
|
+
const cb = jest.fn();
|
|
169
|
+
const e = {
|
|
170
|
+
key: 'b',
|
|
171
|
+
metaKey: true,
|
|
172
|
+
target: {
|
|
173
|
+
selectionStart: 8,
|
|
174
|
+
// " dash " is selected (note leading and trailing space)
|
|
175
|
+
selectionEnd: 13,
|
|
176
|
+
value: 'rainbow dash is purple'
|
|
177
|
+
},
|
|
178
|
+
preventDefault: () => {}
|
|
179
|
+
};
|
|
180
|
+
(0, _markdownHandler.convertCtrlKey)(e, cb);
|
|
181
|
+
expect(cb).toHaveBeenCalled();
|
|
182
|
+
expect(cb).toHaveBeenCalledWith('rainbow *dash* is purple', 14);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
describe('when ctrl key + "i" pressed', () => {
|
|
187
|
+
it('triggers callback with open/close italics markers and caret pos in between', () => {
|
|
188
|
+
const cb = jest.fn();
|
|
189
|
+
const e = {
|
|
190
|
+
key: 'i',
|
|
191
|
+
ctrlKey: true,
|
|
192
|
+
target: {
|
|
193
|
+
selectionStart: 0,
|
|
194
|
+
selectionEnd: 0,
|
|
195
|
+
value: ''
|
|
196
|
+
},
|
|
197
|
+
preventDefault: () => {}
|
|
198
|
+
};
|
|
199
|
+
(0, _markdownHandler.convertCtrlKey)(e, cb);
|
|
200
|
+
expect(cb).toHaveBeenCalled();
|
|
201
|
+
expect(cb).toHaveBeenCalledWith('__', 1);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
});
|
|
@@ -92,23 +92,29 @@ const insertMarkdown = (markdown, target, cb) => {
|
|
|
92
92
|
if (start === end) {
|
|
93
93
|
//no text
|
|
94
94
|
const valueArr = value.split('');
|
|
95
|
-
let
|
|
95
|
+
let markdownString = marker.prefix;
|
|
96
96
|
if (marker.postfix) {
|
|
97
|
-
|
|
97
|
+
markdownString += marker.postfix;
|
|
98
98
|
}
|
|
99
|
-
valueArr.splice(start, 0, padMarkers(
|
|
99
|
+
valueArr.splice(start, 0, padMarkers(markdownString));
|
|
100
100
|
newValue = valueArr.join('');
|
|
101
|
+
|
|
102
|
+
// for smileys, put the caret after a space
|
|
103
|
+
if (Object.keys(emojis).includes(markdown)) {
|
|
104
|
+
newValue += ' ';
|
|
105
|
+
caretPos = caretPos + newValue.length - 1;
|
|
106
|
+
}
|
|
101
107
|
} else {
|
|
102
108
|
const text = value.slice(start, end);
|
|
103
109
|
const trimmedText = trim(text); // TODO really needed?
|
|
104
110
|
|
|
105
111
|
// adjust caretPos based on trimmed text selection
|
|
106
112
|
caretPos = caretPos - (text.length - trimmedText.length) + 1;
|
|
107
|
-
let
|
|
113
|
+
let markdownString = `${marker.prefix}${trimmedText}`;
|
|
108
114
|
if (marker.postfix) {
|
|
109
|
-
|
|
115
|
+
markdownString += marker.postfix;
|
|
110
116
|
}
|
|
111
|
-
newValue = [value.slice(0, start), padMarkers(
|
|
117
|
+
newValue = [value.slice(0, start), padMarkers(markdownString), value.slice(end)].join('');
|
|
112
118
|
}
|
|
113
119
|
cb(newValue, caretPos);
|
|
114
120
|
};
|
|
@@ -5,9 +5,9 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.tooltipAnchorClasses = exports.toolbarClasses = exports.mainClasses = exports.emojisPopoverClasses = void 0;
|
|
7
7
|
var _ui = require("@dhis2/ui");
|
|
8
|
-
const mainClasses = [".container.jsx-
|
|
8
|
+
const mainClasses = [".container.jsx-185829738{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;width:100%;height:100%;}", `.preview.jsx-185829738{padding:${_ui.spacers.dp8} ${_ui.spacers.dp12};font-size:14px;line-height:${_ui.spacers.dp16};color:${_ui.colors.grey900};overflow-y:auto;-webkit-scroll-behavior:smooth;-moz-scroll-behavior:smooth;-ms-scroll-behavior:smooth;scroll-behavior:smooth;}`, ".edit.jsx-185829738{width:100%;height:100%;-webkit-scroll-behavior:smooth;-moz-scroll-behavior:smooth;-ms-scroll-behavior:smooth;scroll-behavior:smooth;}", `.textarea.jsx-185829738{width:100%;height:100%;box-sizing:border-box;padding:${_ui.spacers.dp8} 15px;color:${_ui.colors.grey900};background-color:${_ui.colors.white};border:1px solid ${_ui.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:${_ui.spacers.dp16};-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text;resize:none;}`, ".textarea.resizable.jsx-185829738{resize:vertical;}", `.textarea.jsx-185829738:focus{outline:none;box-shadow:0 0 0 3px ${_ui.theme.focus};width:calc(100% - 6px);height:calc(100% - 3px);padding:${_ui.spacers.dp8} ${_ui.spacers.dp12};margin-left:3px;}`, `.textarea.jsx-185829738:disabled{background-color:${_ui.colors.grey100};border-color:${_ui.colors.grey500};color:${_ui.theme.disabled};cursor:not-allowed;}`];
|
|
9
9
|
exports.mainClasses = mainClasses;
|
|
10
|
-
mainClasses.__hash = "
|
|
10
|
+
mainClasses.__hash = "185829738";
|
|
11
11
|
const toolbarClasses = [`.toolbar.jsx-2267496677{background:${_ui.colors.grey050};border-radius:3px;border:1px solid ${_ui.colors.grey300};margin-bottom:${_ui.spacers.dp4};}`, `.actionsWrapper.jsx-2267496677{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;gap:${_ui.spacers.dp4};-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;padding:${_ui.spacers.dp4};}`, `.mainActions.jsx-2267496677{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;gap:${_ui.spacers.dp4};margin-top:${_ui.spacers.dp2};}`, ".sideActions.jsx-2267496677{-webkit-flex-shrink:0;-ms-flex-negative:0;flex-shrink:0;}", `.previewWrapper.jsx-2267496677{margin:${_ui.spacers.dp4};text-align:right;}`];
|
|
12
12
|
exports.toolbarClasses = toolbarClasses;
|
|
13
13
|
toolbarClasses.__hash = "2267496677";
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.MdParser = void 0;
|
|
7
|
+
var _markdownIt = _interopRequireDefault(require("markdown-it"));
|
|
8
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
9
|
+
const emojiDb = {
|
|
10
|
+
':-)': '\u{1F642}',
|
|
11
|
+
':)': '\u{1F642}',
|
|
12
|
+
':-(': '\u{1F641}',
|
|
13
|
+
':(': '\u{1F641}',
|
|
14
|
+
':+1': '\u{1F44D}',
|
|
15
|
+
':-1': '\u{1F44E}'
|
|
16
|
+
};
|
|
17
|
+
const codes = {
|
|
18
|
+
bold: {
|
|
19
|
+
name: 'bold',
|
|
20
|
+
char: '*',
|
|
21
|
+
domEl: 'strong',
|
|
22
|
+
encodedChar: 0x2a,
|
|
23
|
+
// see https://regex101.com/r/evswdV/8 for explanation of regexp
|
|
24
|
+
regexString: '\\B\\*((?!\\s)[^*]+(?:\\b|[^*\\s]))\\*\\B',
|
|
25
|
+
contentFn: val => val
|
|
26
|
+
},
|
|
27
|
+
italic: {
|
|
28
|
+
name: 'italic',
|
|
29
|
+
char: '_',
|
|
30
|
+
domEl: 'em',
|
|
31
|
+
encodedChar: 0x5f,
|
|
32
|
+
// see https://regex101.com/r/p6LpjK/6 for explanation of regexp
|
|
33
|
+
regexString: '\\b_((?!\\s)[^_]+(?:\\B|[^_\\s]))_\\b',
|
|
34
|
+
contentFn: val => val
|
|
35
|
+
},
|
|
36
|
+
emoji: {
|
|
37
|
+
name: 'emoji',
|
|
38
|
+
char: ':',
|
|
39
|
+
domEl: 'span',
|
|
40
|
+
encodedChar: 0x3a,
|
|
41
|
+
regexString: '^(:-\\)|:\\)|:\\(|:-\\(|:\\+1|:-1)',
|
|
42
|
+
contentFn: val => emojiDb[val]
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
let linksInText;
|
|
46
|
+
const markerIsInLinkText = pos => linksInText.some(link => pos >= link.index && pos <= link.lastIndex);
|
|
47
|
+
const parse = code => (state, silent) => {
|
|
48
|
+
if (silent) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
const start = state.pos;
|
|
52
|
+
|
|
53
|
+
// skip parsing emphasis if marker is within a link
|
|
54
|
+
if (markerIsInLinkText(start)) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const marker = state.src.charCodeAt(start);
|
|
58
|
+
|
|
59
|
+
// marker character: "_", "*", ":"
|
|
60
|
+
if (marker !== codes[code].encodedChar) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
const MARKER_REGEX = new RegExp(codes[code].regexString);
|
|
64
|
+
const token = state.src.slice(start);
|
|
65
|
+
if (MARKER_REGEX.test(token)) {
|
|
66
|
+
const markerMatch = token.match(MARKER_REGEX);
|
|
67
|
+
|
|
68
|
+
// skip parsing sections where the marker is not at the start of the token
|
|
69
|
+
if (markerMatch.index !== 0) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
const text = markerMatch[1];
|
|
73
|
+
state.push(`${codes[code].domEl}_open`, codes[code].domEl, 1);
|
|
74
|
+
const t = state.push('text', '', 0);
|
|
75
|
+
t.content = codes[code].contentFn(text);
|
|
76
|
+
state.push(`${codes.bold.domEl}_close`, codes[code].domEl, -1);
|
|
77
|
+
state.pos += markerMatch[0].length;
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
};
|
|
82
|
+
class MdParser {
|
|
83
|
+
constructor() {
|
|
84
|
+
// disable all rules, enable autolink for URLs and email addresses
|
|
85
|
+
const md = new _markdownIt.default('zero', {
|
|
86
|
+
linkify: true,
|
|
87
|
+
breaks: true
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// *bold* -> <strong>bold</strong>
|
|
91
|
+
md.inline.ruler.push('strong', parse(codes.bold.name));
|
|
92
|
+
|
|
93
|
+
// _italic_ -> <em>italic</em>
|
|
94
|
+
md.inline.ruler.push('italic', parse(codes.italic.name));
|
|
95
|
+
|
|
96
|
+
// :-) :) :-( :( :+1 :-1 -> <span>[unicode]</span>
|
|
97
|
+
md.inline.ruler.push('emoji', parse(codes.emoji.name));
|
|
98
|
+
md.enable(['heading', 'link', 'linkify', 'list', 'newline', 'strong', 'italic', 'emoji']);
|
|
99
|
+
this.md = md;
|
|
100
|
+
}
|
|
101
|
+
render(text) {
|
|
102
|
+
linksInText = this.md.linkify.match(text) || [];
|
|
103
|
+
return this.md.render(text);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
exports.MdParser = MdParser;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.Parser = void 0;
|
|
7
|
+
var _propTypes = _interopRequireDefault(require("prop-types"));
|
|
8
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
9
|
+
var _MdParser = require("./MdParser.js");
|
|
10
|
+
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
|
11
|
+
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
|
12
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
13
|
+
const Parser = _ref => {
|
|
14
|
+
let {
|
|
15
|
+
children,
|
|
16
|
+
style
|
|
17
|
+
} = _ref;
|
|
18
|
+
const MdParserInstance = (0, _react.useMemo)(() => new _MdParser.MdParser(), []);
|
|
19
|
+
return children ? /*#__PURE__*/_react.default.createElement("div", {
|
|
20
|
+
style: {
|
|
21
|
+
...style
|
|
22
|
+
},
|
|
23
|
+
dangerouslySetInnerHTML: {
|
|
24
|
+
__html: MdParserInstance.render(children)
|
|
25
|
+
}
|
|
26
|
+
}) : null;
|
|
27
|
+
};
|
|
28
|
+
exports.Parser = Parser;
|
|
29
|
+
Parser.defaultProps = {
|
|
30
|
+
style: null
|
|
31
|
+
};
|
|
32
|
+
Parser.propTypes = {
|
|
33
|
+
children: _propTypes.default.oneOfType([_propTypes.default.arrayOf(_propTypes.default.node), _propTypes.default.node]),
|
|
34
|
+
style: _propTypes.default.object
|
|
35
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _MdParser = require("../MdParser.js");
|
|
4
|
+
const Parser = new _MdParser.MdParser();
|
|
5
|
+
describe('MdParser class', () => {
|
|
6
|
+
it('converts text into HTML', () => {
|
|
7
|
+
const inlineTests = [['_italic_', '<em>italic</em>'], ['*bold*', '<strong>bold</strong>'], ['_ not italic because there is a space _', '_ not italic because there is a space _'], [':-)', '<span>\u{1F642}</span>'], [':)', '<span>\u{1F642}</span>'], [':-(', '<span>\u{1F641}</span>'], [':(', '<span>\u{1F641}</span>'], [':+1', '<span>\u{1F44D}</span>'], [':-1', '<span>\u{1F44E}</span>'], ['mixed _italic_ *bold* and :+1', 'mixed <em>italic</em> <strong>bold</strong> and <span>\u{1F44D}</span>'], ['_italic with * inside_', '<em>italic with * inside</em>'], ['*bold with _ inside*', '<strong>bold with _ inside</strong>'],
|
|
8
|
+
// italic marker followed by : should work
|
|
9
|
+
['_italic_:', '<em>italic</em>:'], ['_italic_: some text, *bold*: some other text', '<em>italic</em>: some text, <strong>bold</strong>: some other text'],
|
|
10
|
+
// bold marker followed by : should work
|
|
11
|
+
['*bold*:', '<strong>bold</strong>:'], ['*bold*: some text, _italic_: some other text', '<strong>bold</strong>: some text, <em>italic</em>: some other text'],
|
|
12
|
+
// italic marker inside an italic string not allowed
|
|
13
|
+
['_italic with _ inside_', '_italic with _ inside_'],
|
|
14
|
+
// bold marker inside a bold string not allowed
|
|
15
|
+
['*bold with * inside*', '*bold with * inside*'], ['_multiple_ italic in the _same line_', '<em>multiple</em> italic in the <em>same line</em>'],
|
|
16
|
+
// nested italic/bold combinations not allowed
|
|
17
|
+
['_italic with *bold* inside_', '<em>italic with *bold* inside</em>'], ['*bold with _italic_ inside*', '<strong>bold with _italic_ inside</strong>'], ['text with : and :)', 'text with : and <span>\u{1F642}</span>'], ['(parenthesis and :))', '(parenthesis and <span>\u{1F642}</span>)'], [':((parenthesis:))', '<span>\u{1F641}</span>(parenthesis<span>\u{1F642}</span>)'], [':+1+1', '<span>\u{1F44D}</span>+1'], ['-1:-1', '-1<span>\u{1F44E}</span>'],
|
|
18
|
+
// links
|
|
19
|
+
['example.com/path', '<a href="http://example.com/path">example.com/path</a>'],
|
|
20
|
+
// not recognized links with italic marker inside not converted
|
|
21
|
+
['example_with_underscore.com/path', 'example_with_underscore.com/path'], ['example_with_underscore.com/path_with_underscore', 'example_with_underscore.com/path_with_underscore'],
|
|
22
|
+
// markers around non-recognized links
|
|
23
|
+
['link example_with_underscore.com/path should _not_ be converted', 'link example_with_underscore.com/path should <em>not</em> be converted'], ['link example_with_underscore.com/path should *not* be converted', 'link example_with_underscore.com/path should <strong>not</strong> be converted'],
|
|
24
|
+
// italic marker inside links not converted
|
|
25
|
+
['example.com/path_with_underscore', '<a href="http://example.com/path_with_underscore">example.com/path_with_underscore</a>'], ['_italic_ and *bold* with a example.com/link_with_underscore', '<em>italic</em> and <strong>bold</strong> with a <a href="http://example.com/link_with_underscore">example.com/link_with_underscore</a>'], ['example.com/path with *bold* after :)', '<a href="http://example.com/path">example.com/path</a> with <strong>bold</strong> after <span>\u{1F642}</span>'], ['_before_ example.com/path_with_underscore *after* :)', '<em>before</em> <a href="http://example.com/path_with_underscore">example.com/path_with_underscore</a> <strong>after</strong> <span>\u{1F642}</span>'],
|
|
26
|
+
// italic/bold markers right after non-word characters
|
|
27
|
+
['_If % of ART retention rate after 12 months >90(%)_: Sustain the efforts.', '<em>If % of ART retention rate after 12 months >90(%)</em>: Sustain the efforts.'], ['*If % of ART retention rate after 12 months >90(%)*: Sustain the efforts.', '<strong>If % of ART retention rate after 12 months >90(%)</strong>: Sustain the efforts.']];
|
|
28
|
+
inlineTests.forEach(test => {
|
|
29
|
+
const renderedText = Parser.render(test[0]);
|
|
30
|
+
expect(renderedText).toEqual(`<p>${test[1]}</p>\n`);
|
|
31
|
+
});
|
|
32
|
+
const blockTests = [
|
|
33
|
+
// heading
|
|
34
|
+
['# Heading 1', '<h1>Heading 1</h1>'], ['## Heading 2', '<h2>Heading 2</h2>'], ['### Heading 3', '<h3>Heading 3</h3>'], ['#### Heading 4', '<h4>Heading 4</h4>'], ['##### Heading 5', '<h5>Heading 5</h5>'], ['###### Heading 6', '<h6>Heading 6</h6>'], ['# *Bold head*', '<h1><strong>Bold head</strong></h1>'], ['## _Italic title_', '<h2><em>Italic title</em></h2>'], ['### *Bold* and _italic_ title', '<h3><strong>Bold</strong> and <em>italic</em> title</h3>'],
|
|
35
|
+
// lists
|
|
36
|
+
['* first\n* second\n* third', '<ul>\n<li>first</li>\n<li>second</li>\n<li>third</li>\n</ul>'], ['1. one\n1. two\n1. three\n', '<ol>\n<li>one</li>\n<li>two</li>\n<li>three</li>\n</ol>'], ['* *first*\n* second\n* _third_', '<ul>\n<li><strong>first</strong></li>\n<li>second</li>\n<li><em>third</em></li>\n</ul>']];
|
|
37
|
+
blockTests.forEach(test => {
|
|
38
|
+
const renderedText = Parser.render(test[0]);
|
|
39
|
+
expect(renderedText).toEqual(`${test[1]}\n`);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _enzyme = require("enzyme");
|
|
4
|
+
var _react = _interopRequireDefault(require("react"));
|
|
5
|
+
var _Parser = require("../Parser.js");
|
|
6
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
7
|
+
jest.mock('../MdParser.js', () => ({
|
|
8
|
+
MdParser: jest.fn().mockImplementation(() => {
|
|
9
|
+
return {
|
|
10
|
+
render: () => 'converted text'
|
|
11
|
+
};
|
|
12
|
+
})
|
|
13
|
+
}));
|
|
14
|
+
describe('RichText: Parser component', () => {
|
|
15
|
+
let richTextParser;
|
|
16
|
+
const defaultProps = {
|
|
17
|
+
style: {
|
|
18
|
+
color: 'blue',
|
|
19
|
+
whiteSpace: 'pre-line'
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const renderComponent = (props, text) => {
|
|
23
|
+
return (0, _enzyme.shallow)( /*#__PURE__*/_react.default.createElement(_Parser.Parser, props, text));
|
|
24
|
+
};
|
|
25
|
+
it('should have rendered a result', () => {
|
|
26
|
+
richTextParser = renderComponent({}, 'test');
|
|
27
|
+
expect(richTextParser).toHaveLength(1);
|
|
28
|
+
});
|
|
29
|
+
it('should have rendered a result with the style prop', () => {
|
|
30
|
+
richTextParser = renderComponent(defaultProps, 'test prop');
|
|
31
|
+
expect(richTextParser.props().style).toEqual(defaultProps.style);
|
|
32
|
+
});
|
|
33
|
+
it('should have rendered content', () => {
|
|
34
|
+
richTextParser = renderComponent({}, 'plain text');
|
|
35
|
+
expect(richTextParser.html()).toEqual('<div>converted text</div>');
|
|
36
|
+
});
|
|
37
|
+
it('should return null if no children is passed', () => {
|
|
38
|
+
richTextParser = renderComponent({}, undefined);
|
|
39
|
+
expect(richTextParser.html()).toBe(null);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
Object.defineProperty(exports, "RichTextEditor", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function () {
|
|
9
|
+
return _Editor.Editor;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
Object.defineProperty(exports, "RichTextMdParser", {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: function () {
|
|
15
|
+
return _MdParser.MdParser;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
Object.defineProperty(exports, "RichTextParser", {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
get: function () {
|
|
21
|
+
return _Parser.Parser;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
var _Editor = require("./Editor/Editor.js");
|
|
25
|
+
var _Parser = require("./Parser/Parser.js");
|
|
26
|
+
var _MdParser = require("./Parser/MdParser.js");
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.
|
|
6
|
+
exports.UserMentionWrapper = void 0;
|
|
7
7
|
var _style = _interopRequireDefault(require("styled-jsx/style"));
|
|
8
8
|
var _d2I18n = _interopRequireDefault(require("@dhis2/d2-i18n"));
|
|
9
9
|
var _ui = require("@dhis2/ui");
|
|
@@ -38,6 +38,7 @@ const UserMentionWrapper = _ref => {
|
|
|
38
38
|
inputReference,
|
|
39
39
|
onUserSelect
|
|
40
40
|
} = _ref;
|
|
41
|
+
const [listIsOpen, setListIsOpen] = (0, _react.useState)(false);
|
|
41
42
|
const [captureText, setCaptureText] = (0, _react.useState)(false);
|
|
42
43
|
const [capturedText, setCapturedText] = (0, _react.useState)('');
|
|
43
44
|
const [cloneText, setCloneText] = (0, _react.useState)('');
|
|
@@ -53,6 +54,7 @@ const UserMentionWrapper = _ref => {
|
|
|
53
54
|
searchText: capturedText
|
|
54
55
|
});
|
|
55
56
|
const reset = () => {
|
|
57
|
+
setListIsOpen(false);
|
|
56
58
|
setCaptureText(false);
|
|
57
59
|
setCapturedText('');
|
|
58
60
|
setCloneText('');
|
|
@@ -61,6 +63,12 @@ const UserMentionWrapper = _ref => {
|
|
|
61
63
|
clear();
|
|
62
64
|
};
|
|
63
65
|
|
|
66
|
+
// focus the input/textarea when the user list is closed by clicking above the input/textarea
|
|
67
|
+
const onClick = () => inputReference.current.focus();
|
|
68
|
+
|
|
69
|
+
// close the user list when clicking in the input/textarea or outside of it (input/textarea blur)
|
|
70
|
+
const onUserListClose = () => reset();
|
|
71
|
+
|
|
64
72
|
// event bubbles up from the input/textarea
|
|
65
73
|
const onInput = _ref2 => {
|
|
66
74
|
let {
|
|
@@ -73,7 +81,7 @@ const UserMentionWrapper = _ref => {
|
|
|
73
81
|
if (captureText) {
|
|
74
82
|
clear();
|
|
75
83
|
const spacePosition = value.indexOf(' ', captureStartPosition - 1);
|
|
76
|
-
const filterValue = value.substring(captureStartPosition, spacePosition > 0 ? spacePosition : selectionEnd + 1);
|
|
84
|
+
const filterValue = value.substring(captureStartPosition, spacePosition > 0 ? spacePosition : selectionEnd + 1).replace(/\n+/, '');
|
|
77
85
|
if (filterValue !== capturedText) {
|
|
78
86
|
setCapturedText(filterValue);
|
|
79
87
|
} else if (filterValue.length === 0) {
|
|
@@ -93,6 +101,7 @@ const UserMentionWrapper = _ref => {
|
|
|
93
101
|
selectionStart
|
|
94
102
|
} = target;
|
|
95
103
|
if (!captureText && key === '@') {
|
|
104
|
+
setListIsOpen(true);
|
|
96
105
|
setCaptureText(true);
|
|
97
106
|
setCaptureStartPosition(selectionStart + 1);
|
|
98
107
|
setCloneText(target.value.substring(0, selectionStart) + '@');
|
|
@@ -144,17 +153,20 @@ const UserMentionWrapper = _ref => {
|
|
|
144
153
|
// position the cursor at the end
|
|
145
154
|
requestAnimationFrame(() => inputReference.current.setSelectionRange(-1, -1), 0);
|
|
146
155
|
};
|
|
147
|
-
const
|
|
156
|
+
const onUserClick = user => () => onSelect(user);
|
|
148
157
|
return /*#__PURE__*/_react.default.createElement("div", {
|
|
149
158
|
onKeyDown: onKeyDown,
|
|
150
159
|
onInput: onInput,
|
|
160
|
+
onClick: onClick,
|
|
151
161
|
className: `jsx-${_UserMentionWrapperStyle.userMentionWrapperClasses.__hash}` + " " + "wrapper"
|
|
152
162
|
}, children, /*#__PURE__*/_react.default.createElement("div", {
|
|
153
163
|
className: `jsx-${_UserMentionWrapperStyle.userMentionWrapperClasses.__hash}` + " " + "clone"
|
|
154
|
-
}, /*#__PURE__*/_react.default.createElement("
|
|
164
|
+
}, /*#__PURE__*/_react.default.createElement("p", {
|
|
155
165
|
ref: cloneRef,
|
|
156
166
|
className: `jsx-${_UserMentionWrapperStyle.userMentionWrapperClasses.__hash}`
|
|
157
|
-
}, cloneText)),
|
|
167
|
+
}, cloneText)), listIsOpen && /*#__PURE__*/_react.default.createElement(_ui.Layer, {
|
|
168
|
+
onBackdropClick: onUserListClose
|
|
169
|
+
}, /*#__PURE__*/_react.default.createElement(_ui.Popper, {
|
|
158
170
|
reference: getVirtualPopperReference(cloneRef),
|
|
159
171
|
placement: "top-start"
|
|
160
172
|
}, /*#__PURE__*/_react.default.createElement(_ui.Card, null, /*#__PURE__*/_react.default.createElement("div", {
|
|
@@ -175,7 +187,7 @@ const UserMentionWrapper = _ref => {
|
|
|
175
187
|
}), !fetching && users.length > 0 && /*#__PURE__*/_react.default.createElement(_UserList.UserList, {
|
|
176
188
|
users: users,
|
|
177
189
|
selectedUserIndex: selectedUserIndex,
|
|
178
|
-
onUserClick:
|
|
190
|
+
onUserClick: onUserClick,
|
|
179
191
|
pager: pager
|
|
180
192
|
}), capturedText && !fetching && users.length === 0 && /*#__PURE__*/_react.default.createElement(_ui.MenuItem, {
|
|
181
193
|
dense: true,
|
|
@@ -193,6 +205,4 @@ UserMentionWrapper.propTypes = {
|
|
|
193
205
|
inputReference: _propTypes.default.object.isRequired,
|
|
194
206
|
onUserSelect: _propTypes.default.func.isRequired,
|
|
195
207
|
children: _propTypes.default.node
|
|
196
|
-
};
|
|
197
|
-
var _default = UserMentionWrapper;
|
|
198
|
-
exports.default = _default;
|
|
208
|
+
};
|
|
@@ -13,9 +13,9 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
|
|
13
13
|
* to emulate the styles of the textarea. If we decide to make
|
|
14
14
|
* changes there, they should be refelcted here too.
|
|
15
15
|
*/
|
|
16
|
-
const userMentionWrapperClasses = [".wrapper.jsx-
|
|
16
|
+
const userMentionWrapperClasses = [".wrapper.jsx-2386066169{width:100%;height:100%;position:relative;}", `.clone.jsx-2386066169{position:absolute;visibility:hidden;inset:0;box-sizing:border-box;padding:${_ui.spacers.dp8} 15px;border:1px solid ${_ui.colors.grey500};font-size:14px;line-height:${_ui.spacers.dp16};z-index:1;pointer-events:none;}`, ".clone.jsx-2386066169>p.jsx-2386066169{display:inline;word-wrap:break-word;overflow-wrap:break-word;font:inherit;margin:0;white-space:break-spaces;}", `.container.jsx-2386066169{background-color:${_ui.colors.white};max-height:180px;overflow:auto;}`];
|
|
17
17
|
exports.userMentionWrapperClasses = userMentionWrapperClasses;
|
|
18
|
-
userMentionWrapperClasses.__hash = "
|
|
18
|
+
userMentionWrapperClasses.__hash = "2386066169";
|
|
19
19
|
const resolvedHeaderStyle = {
|
|
20
20
|
styles: /*#__PURE__*/_react.default.createElement(_style.default, {
|
|
21
21
|
id: "4275958396"
|
|
@@ -53,10 +53,10 @@ const useUserSearchResults = _ref2 => {
|
|
|
53
53
|
return () => debouncedRefetch.cancel();
|
|
54
54
|
}, [searchText, debouncedRefetch]);
|
|
55
55
|
(0, _react.useEffect)(() => {
|
|
56
|
-
if (data) {
|
|
56
|
+
if (fetching === false && data) {
|
|
57
57
|
setData(data.users);
|
|
58
58
|
}
|
|
59
|
-
}, [data]);
|
|
59
|
+
}, [data, fetching]);
|
|
60
60
|
return {
|
|
61
61
|
users,
|
|
62
62
|
pager,
|