@instructure/canvas-rce 5.12.2 → 5.13.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 (84) hide show
  1. package/CHANGELOG.md +33 -1
  2. package/es/common/fileUrl.js +5 -1
  3. package/es/defaultTinymceConfig.js +2 -1
  4. package/es/enhance-user-content/enhance_user_content.js +30 -1
  5. package/es/getTranslations.js +5 -1
  6. package/es/rce/RCEWrapper.js +63 -22
  7. package/es/rce/RCEWrapperProps.js +1 -0
  8. package/es/rce/StatusBar.js +5 -4
  9. package/es/rce/editorLanguage.js +2 -0
  10. package/es/rce/plugins/instructure_image/ImageEmbedOptions.js +1 -1
  11. package/es/rce/plugins/instructure_rce_external_tools/ExternalToolsEnv.js +6 -0
  12. package/es/rce/plugins/instructure_rce_external_tools/RceToolWrapper.js +30 -1
  13. package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolDialog/ExternalToolDialog.js +14 -1
  14. package/es/rce/plugins/instructure_rce_external_tools/lti13-content-items/processEditorContentItems.js +9 -3
  15. package/es/rce/plugins/instructure_rce_external_tools/plugin.js +3 -2
  16. package/es/rce/plugins/instructure_search_and_replace/clickCallback.js +55 -0
  17. package/es/rce/plugins/instructure_search_and_replace/components/FindReplaceTray.js +360 -0
  18. package/es/rce/plugins/instructure_search_and_replace/components/FindReplaceTrayController.js +139 -0
  19. package/es/rce/plugins/instructure_search_and_replace/getSelectionContext.js +68 -0
  20. package/es/rce/plugins/instructure_search_and_replace/plugin.js +39 -0
  21. package/es/rce/plugins/instructure_search_and_replace/types.d.js +1 -0
  22. package/es/rce/plugins/shared/fileTypeUtils.js +3 -1
  23. package/es/rce/plugins/tinymce-a11y-checker/plugin.js +11 -1
  24. package/es/rce/plugins/tinymce-a11y-checker/utils/indicate.js +1 -1
  25. package/es/rce/tinyRCE.js +3 -1
  26. package/es/translations/locales/ar.js +54 -0
  27. package/es/translations/locales/ca.js +54 -0
  28. package/es/translations/locales/cy.js +54 -0
  29. package/es/translations/locales/da-x-k12.js +54 -0
  30. package/es/translations/locales/da.js +54 -0
  31. package/es/translations/locales/de.js +54 -0
  32. package/es/translations/locales/el.js +9 -0
  33. package/es/translations/locales/en-AU-x-unimelb.js +54 -0
  34. package/es/translations/locales/en-GB-x-ukhe.js +54 -0
  35. package/es/translations/locales/en.js +54 -0
  36. package/es/translations/locales/en_AU.js +54 -0
  37. package/es/translations/locales/en_CA.js +54 -0
  38. package/es/translations/locales/en_CY.js +54 -0
  39. package/es/translations/locales/en_GB.js +54 -0
  40. package/es/translations/locales/es.js +54 -0
  41. package/es/translations/locales/es_ES.js +54 -0
  42. package/es/translations/locales/fa_IR.js +9 -0
  43. package/es/translations/locales/fi.js +54 -0
  44. package/es/translations/locales/fr.js +54 -0
  45. package/es/translations/locales/fr_CA.js +54 -0
  46. package/es/translations/locales/ga.js +2427 -0
  47. package/es/translations/locales/he.js +10 -1
  48. package/es/translations/locales/ht.js +54 -0
  49. package/es/translations/locales/hu.js +15 -0
  50. package/es/translations/locales/hy.js +9 -0
  51. package/es/translations/locales/id.js +55 -0
  52. package/es/translations/locales/id_ID.js +1 -0
  53. package/es/translations/locales/is.js +54 -0
  54. package/es/translations/locales/it.js +54 -0
  55. package/es/translations/locales/ja.js +61 -7
  56. package/es/translations/locales/ko.js +9 -0
  57. package/es/translations/locales/mi.js +54 -0
  58. package/es/translations/locales/ms.js +54 -0
  59. package/es/translations/locales/nb-x-k12.js +54 -0
  60. package/es/translations/locales/nb.js +54 -0
  61. package/es/translations/locales/nl.js +54 -0
  62. package/es/translations/locales/nn.js +9 -0
  63. package/es/translations/locales/pl.js +54 -0
  64. package/es/translations/locales/pt.js +54 -0
  65. package/es/translations/locales/pt_BR.js +54 -0
  66. package/es/translations/locales/ru.js +54 -0
  67. package/es/translations/locales/sl.js +54 -0
  68. package/es/translations/locales/sv-x-k12.js +54 -0
  69. package/es/translations/locales/sv.js +54 -0
  70. package/es/translations/locales/th.js +54 -0
  71. package/es/translations/locales/tr.js +9 -0
  72. package/es/translations/locales/uk_UA.js +9 -0
  73. package/es/translations/locales/vi.js +54 -0
  74. package/es/translations/locales/zh-Hans.js +54 -0
  75. package/es/translations/locales/zh-Hant.js +54 -0
  76. package/es/translations/locales/zh.js +54 -0
  77. package/es/translations/locales/zh_HK.js +54 -0
  78. package/es/translations/tinymce/ga.js +423 -0
  79. package/es/translations/tinymce/id.js +423 -0
  80. package/es/translations/tinymce/ja.js +1 -1
  81. package/package.json +3 -3
  82. package/scripts/commitTranslations.sh +2 -2
  83. package/scripts/publish_to_npm.sh +1 -1
  84. package/bin/jira_tickets.sh +0 -14
@@ -0,0 +1,360 @@
1
+ import _pt from "prop-types";
2
+
3
+ /*
4
+ * Copyright (C) 2019 - present Instructure, Inc.
5
+ *
6
+ * This file is part of Canvas.
7
+ *
8
+ * Canvas is free software: you can redistribute it and/or modify it under
9
+ * the terms of the GNU Affero General Public License as published by the Free
10
+ * Software Foundation, version 3 of the License.
11
+ *
12
+ * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
13
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
+ * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
15
+ * details.
16
+ *
17
+ * You should have received a copy of the GNU Affero General Public License along
18
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
19
+ */
20
+ import React, { useEffect, useRef, useState } from 'react';
21
+ import { Button, CloseButton } from '@instructure/ui-buttons';
22
+ import { Flex } from '@instructure/ui-flex';
23
+ import { Heading } from '@instructure/ui-heading';
24
+ import { getTrayHeight } from '../../shared/trayUtils';
25
+ import { View } from '@instructure/ui-view';
26
+ import { instuiPopupMountNode } from '../../../../util/fullscreenHelpers';
27
+ import formatMessage from '../../../../format-message';
28
+ import { Tray } from '@instructure/ui-tray';
29
+ import { TextInput } from '@instructure/ui-text-input';
30
+ import { Text } from '@instructure/ui-text';
31
+ import { ScreenReaderContent } from '@instructure/ui-a11y-content';
32
+ import { Alert } from '@instructure/ui-alerts';
33
+ export default function FindReplaceTray(_ref) {
34
+ let {
35
+ onNext,
36
+ onPrevious,
37
+ onFind,
38
+ onRequestClose,
39
+ onReplace,
40
+ index,
41
+ max,
42
+ initialText = '',
43
+ selectionContext = ['', '']
44
+ } = _ref;
45
+ const [findText, setFindText] = useState(initialText);
46
+ const [replaceText, setReplaceText] = useState('');
47
+ const [hasOpened, setHasOpened] = useState(false);
48
+ const [showReplaceAlert, setShowReplaceAlert] = useState('');
49
+ const [alertFindText, setAlertFindText] = useState('');
50
+ const [alertReplaceText, setAlertReplaceText] = useState('');
51
+ const trayRef = useRef(null);
52
+ const liveRegionKey = useRef(0);
53
+ const srDupKey = useRef(0); // moves RCE when tray opens/closes, copied from CanvasContentTray
54
+
55
+ useEffect(() => {
56
+ var _trayRef$current;
57
+
58
+ if (!hasOpened) return;
59
+ let c = document.querySelector('[role="main"]');
60
+ let target_w = 0;
61
+ if (!c) return;
62
+ const margin = window.getComputedStyle(c).direction === 'ltr' ? document.body.getBoundingClientRect().right - c.getBoundingClientRect().right : c.getBoundingClientRect().left;
63
+ target_w = c.offsetWidth - ((_trayRef$current = trayRef.current) === null || _trayRef$current === void 0 ? void 0 : _trayRef$current.offsetWidth) + margin;
64
+
65
+ if (target_w >= 320 && target_w < c.offsetWidth) {
66
+ c.style.boxSizing = 'border-box';
67
+ c.style.width = `${target_w}px`;
68
+ }
69
+
70
+ return () => {
71
+ c = document.querySelector('[role="main"]');
72
+ if (!c) return;
73
+ c.style.width = '';
74
+ };
75
+ }, [hasOpened]);
76
+ useEffect(() => {
77
+ if (initialText) {
78
+ onFind(initialText);
79
+ } // eslint-disable-next-line react-hooks/exhaustive-deps
80
+
81
+ }, []);
82
+
83
+ function usePrevious(value) {
84
+ const ref = useRef();
85
+ useEffect(() => {
86
+ ref.current = value;
87
+ }, [value]);
88
+ return ref.current;
89
+ }
90
+
91
+ const prepend = selectionContext[0];
92
+ const append = selectionContext[1];
93
+ const srContextMsg = formatMessage('{prepend}{findText}{append}', {
94
+ prepend,
95
+ findText,
96
+ append
97
+ });
98
+ const previousSrMsg = usePrevious(srContextMsg);
99
+ const previousFindText = usePrevious(findText);
100
+
101
+ const handleTextChange = e => {
102
+ setFindText(e.target.value);
103
+ onFind(e.target.value);
104
+ };
105
+
106
+ const handleFindKeyDown = e => {
107
+ if (e.key !== 'Enter') return;
108
+
109
+ if (e.shiftKey) {
110
+ if (isButtonDisabled('previous')) return;
111
+ onPrevious();
112
+ } else {
113
+ // try to search when there are currently no results and user presses enter
114
+ if (index === 0 && max === 0 && findText) {
115
+ onFind(findText);
116
+ return;
117
+ }
118
+
119
+ if (isButtonDisabled('next')) return;
120
+ onNext();
121
+ }
122
+ };
123
+
124
+ const handleReplaceKeyDown = e => {
125
+ if (e.key !== 'Enter') return;
126
+ if (isButtonDisabled('replace')) return;
127
+ const forward = !e.shiftKey;
128
+ replace(replaceText, forward, false);
129
+ };
130
+
131
+ const replace = (newText, forward, all) => {
132
+ onReplace(newText, forward, all);
133
+
134
+ if (all) {
135
+ setShowReplaceAlert('replaceAll');
136
+ } else {
137
+ setShowReplaceAlert('replace');
138
+ }
139
+
140
+ setAlertFindText(findText);
141
+ setAlertReplaceText(newText);
142
+ liveRegionKey.current++;
143
+ };
144
+
145
+ const alertText = showReplaceAlert === '' ? '' : showReplaceAlert === 'replace' ? formatMessage('Replaced {alertFindText} with {alertReplaceText}', {
146
+ alertFindText,
147
+ alertReplaceText
148
+ }) : formatMessage('Replaced all {alertFindText} with {alertReplaceText}', {
149
+ alertFindText,
150
+ alertReplaceText
151
+ });
152
+
153
+ const renderReplaceAlert = () => {
154
+ if (!showReplaceAlert) {
155
+ liveRegionKey.current = 0;
156
+ return /*#__PURE__*/React.createElement(React.Fragment, null);
157
+ }
158
+
159
+ return /*#__PURE__*/React.createElement(Alert, {
160
+ variant: "success",
161
+ renderCloseButtonLabel: "Close Alert",
162
+ margin: "small",
163
+ transition: "fade",
164
+ onDismiss: () => setShowReplaceAlert(''),
165
+ timeout: 3000
166
+ }, alertText);
167
+ };
168
+
169
+ const renderScreenReaderAlert = () => {
170
+ return /*#__PURE__*/React.createElement(ScreenReaderContent, null, alertText);
171
+ };
172
+
173
+ const errMsg = formatMessage('No results found');
174
+ const messages = findText && max === 0 ? [{
175
+ text: errMsg,
176
+ type: 'error'
177
+ }] : [];
178
+
179
+ const resultText = () => {
180
+ const srErrMsg = messages.length === 0 ? '' : errMsg; // resolves issue where focus is lost when this (dis)appears
181
+
182
+ if (max === 0) {
183
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(ScreenReaderContent, {
184
+ "aria-live": "polite"
185
+ }, srErrMsg), /*#__PURE__*/React.createElement(Text, null));
186
+ }
187
+
188
+ const msg = formatMessage('{index} of {max}', {
189
+ index,
190
+ max
191
+ });
192
+ const srResultMsg = formatMessage('Result {index} of {max}.', {
193
+ index,
194
+ max
195
+ }); // necessary to force screen reader to read the same message while typing
196
+
197
+ if (srContextMsg === previousSrMsg && previousFindText != findText) {
198
+ srDupKey.current++;
199
+ } else srDupKey.current = 0;
200
+
201
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(View, {
202
+ as: "span",
203
+ key: srDupKey.current,
204
+ "aria-live": "polite",
205
+ "aria-atomic": "true",
206
+ role: "alert"
207
+ }, /*#__PURE__*/React.createElement(ScreenReaderContent, null, srResultMsg, " ", srContextMsg)), /*#__PURE__*/React.createElement(Text, {
208
+ "aria-hidden": "true"
209
+ }, msg));
210
+ };
211
+
212
+ const isButtonDisabled = button => {
213
+ switch (button) {
214
+ case 'next':
215
+ case 'previous':
216
+ return max < 2;
217
+
218
+ case 'replace':
219
+ return !replaceText || index === 0;
220
+
221
+ case 'replaceAll':
222
+ return !replaceText || max < 2 || index === 0;
223
+
224
+ default:
225
+ return false;
226
+ }
227
+ };
228
+
229
+ return /*#__PURE__*/React.createElement(Tray, {
230
+ "data-mce-component": true,
231
+ label: formatMessage('Find and Replace'),
232
+ mountNode: instuiPopupMountNode,
233
+ onDismiss: onRequestClose,
234
+ open: true,
235
+ placement: "end",
236
+ size: "regular",
237
+ shouldContainFocus: true,
238
+ shouldReturnFocus: true,
239
+ shouldCloseOnDocumentClick: true,
240
+ onOpen: () => setHasOpened(true)
241
+ }, /*#__PURE__*/React.createElement(View, {
242
+ as: "div",
243
+ margin: "none none medium none",
244
+ key: liveRegionKey.current
245
+ }, renderReplaceAlert(), /*#__PURE__*/React.createElement(View, {
246
+ as: "div",
247
+ role: "alert",
248
+ "aria-live": "polite"
249
+ }, renderScreenReaderAlert())), /*#__PURE__*/React.createElement(Flex, {
250
+ direction: "column",
251
+ height: getTrayHeight()
252
+ }, /*#__PURE__*/React.createElement(Flex.Item, {
253
+ padding: "medium medium small"
254
+ }, /*#__PURE__*/React.createElement(Flex, {
255
+ direction: "row"
256
+ }, /*#__PURE__*/React.createElement(Flex.Item, {
257
+ shouldGrow: true,
258
+ shouldShrink: true
259
+ }, /*#__PURE__*/React.createElement(Heading, {
260
+ as: "h2"
261
+ }, formatMessage('Find and Replace'))), /*#__PURE__*/React.createElement(Flex.Item, null, /*#__PURE__*/React.createElement(CloseButton, {
262
+ placement: "static",
263
+ color: "primary",
264
+ "data-testid": "close-button",
265
+ screenReaderLabel: formatMessage('Close'),
266
+ onClick: onRequestClose
267
+ })))), /*#__PURE__*/React.createElement(Flex.Item, {
268
+ as: "div",
269
+ padding: "0 large large"
270
+ }, /*#__PURE__*/React.createElement(View, {
271
+ as: "div",
272
+ margin: "large 0 medium 0"
273
+ }, /*#__PURE__*/React.createElement(TextInput, {
274
+ renderLabel: formatMessage('Find'),
275
+ name: "findtext",
276
+ onChange: e => handleTextChange(e),
277
+ onKeyDown: e => handleFindKeyDown(e),
278
+ value: findText,
279
+ placeholder: formatMessage('enter search text'),
280
+ renderAfterInput: resultText(),
281
+ messages: messages,
282
+ "data-testid": "find-text-input"
283
+ })), /*#__PURE__*/React.createElement(View, {
284
+ as: "div"
285
+ }, /*#__PURE__*/React.createElement(Flex, null, /*#__PURE__*/React.createElement(Flex.Item, {
286
+ size: "6.125rem"
287
+ }, /*#__PURE__*/React.createElement(Button, {
288
+ color: "secondary",
289
+ margin: "0 medium 0 0",
290
+ onClick: onPrevious,
291
+ disabled: isButtonDisabled('previous'),
292
+ "data-testid": "previous-button",
293
+ "aria-label": formatMessage('Previous {findText}', {
294
+ findText
295
+ })
296
+ }, /*#__PURE__*/React.createElement("span", {
297
+ "aria-hidden": "true"
298
+ }, formatMessage('Previous')))), /*#__PURE__*/React.createElement(Flex.Item, null, /*#__PURE__*/React.createElement(Button, {
299
+ color: "secondary",
300
+ margin: "0 small 0 0",
301
+ onClick: onNext,
302
+ disabled: isButtonDisabled('next'),
303
+ "data-testid": "next-button",
304
+ "aria-label": formatMessage('Next {findText}', {
305
+ findText
306
+ })
307
+ }, /*#__PURE__*/React.createElement("span", {
308
+ "aria-hidden": "true"
309
+ }, formatMessage('Next')))))), /*#__PURE__*/React.createElement(View, {
310
+ as: "div",
311
+ margin: "large 0 medium 0"
312
+ }, /*#__PURE__*/React.createElement(TextInput, {
313
+ renderLabel: formatMessage('Replace with'),
314
+ name: "replacetext",
315
+ onChange: e => setReplaceText(e.target.value),
316
+ onKeyDown: e => handleReplaceKeyDown(e),
317
+ value: replaceText,
318
+ placeholder: formatMessage('enter replacement text'),
319
+ "data-testid": "replace-text-input"
320
+ })), /*#__PURE__*/React.createElement(View, {
321
+ as: "div"
322
+ }, /*#__PURE__*/React.createElement(Flex, null, /*#__PURE__*/React.createElement(Flex.Item, null, /*#__PURE__*/React.createElement(Button, {
323
+ color: "secondary",
324
+ margin: "0 small 0 0",
325
+ onClick: () => replace(replaceText, true, true),
326
+ disabled: isButtonDisabled('replaceAll'),
327
+ "data-testid": "replace-all-button",
328
+ "aria-label": formatMessage('Replace all {findText} with {replaceText}', {
329
+ findText,
330
+ replaceText
331
+ })
332
+ }, /*#__PURE__*/React.createElement("span", {
333
+ "aria-hidden": "true"
334
+ }, formatMessage('Replace All')))), /*#__PURE__*/React.createElement(Flex.Item, null, /*#__PURE__*/React.createElement(Button, {
335
+ color: "secondary",
336
+ margin: "0 small 0 0",
337
+ onClick: () => {
338
+ replace(replaceText, true, false);
339
+ },
340
+ disabled: isButtonDisabled('replace'),
341
+ "data-testid": "replace-button",
342
+ "aria-label": formatMessage('Replace {findText} with {replaceText}', {
343
+ findText,
344
+ replaceText
345
+ })
346
+ }, /*#__PURE__*/React.createElement("span", {
347
+ "aria-hidden": "true"
348
+ }, formatMessage('Replace')))))))));
349
+ }
350
+ FindReplaceTray.propTypes = {
351
+ onNext: _pt.func.isRequired,
352
+ onPrevious: _pt.func.isRequired,
353
+ onFind: _pt.func.isRequired,
354
+ onRequestClose: _pt.func.isRequired,
355
+ onReplace: _pt.func.isRequired,
356
+ index: _pt.number.isRequired,
357
+ max: _pt.number.isRequired,
358
+ initialText: _pt.string,
359
+ selectionContext: _pt.arrayOf(_pt.string)
360
+ };
@@ -0,0 +1,139 @@
1
+ import _pt from "prop-types";
2
+
3
+ /*
4
+ * Copyright (C) 2019 - present Instructure, Inc.
5
+ *
6
+ * This file is part of Canvas.
7
+ *
8
+ * Canvas is free software: you can redistribute it and/or modify it under
9
+ * the terms of the GNU Affero General Public License as published by the Free
10
+ * Software Foundation, version 3 of the License.
11
+ *
12
+ * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
13
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14
+ * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
15
+ * details.
16
+ *
17
+ * You should have received a copy of the GNU Affero General Public License along
18
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
19
+ */
20
+ import React, { useState } from 'react';
21
+ import FindReplaceTray from './FindReplaceTray';
22
+ export default function FindReplaceTrayController(_ref) {
23
+ let {
24
+ plugin,
25
+ onDismiss,
26
+ initialText = '',
27
+ undoManager,
28
+ getSelectionContext
29
+ } = _ref;
30
+ // this component really just exists to make the index easier to track
31
+ const [findIndex, setFindIndex] = useState(0);
32
+ const [findCount, setFindCount] = useState(0);
33
+ const [selectionContext, setSelectionContext] = useState(['', '']);
34
+
35
+ const updateSelectionContext = () => {
36
+ const srText = getSelectionContext();
37
+ setSelectionContext(srText);
38
+ };
39
+
40
+ const newFindIndex = direction => {
41
+ let newIndex = 0;
42
+
43
+ if (findCount === 0) {
44
+ newIndex = 0;
45
+ } else if (direction === 1) {
46
+ // at max count, wrap back to 1
47
+ if (findIndex === findCount) {
48
+ newIndex = 1;
49
+ } else newIndex = findIndex + 1;
50
+ } else if (direction === -1) {
51
+ // at 1, wrap back to max
52
+ if (findIndex === 1) {
53
+ newIndex = findCount;
54
+ } else newIndex = findIndex - 1;
55
+ }
56
+
57
+ return newIndex;
58
+ };
59
+
60
+ const done = () => {
61
+ plugin.done(false);
62
+ setFindCount(0);
63
+ setFindIndex(0);
64
+ setSelectionContext([]);
65
+ };
66
+
67
+ const handleDismiss = () => {
68
+ done();
69
+ onDismiss();
70
+ };
71
+
72
+ const handleNext = () => {
73
+ plugin.next();
74
+ setFindIndex(newFindIndex(1));
75
+ updateSelectionContext();
76
+ };
77
+
78
+ const handlePrevious = () => {
79
+ plugin.prev();
80
+ setFindIndex(newFindIndex(-1));
81
+ updateSelectionContext();
82
+ };
83
+
84
+ const handleFind = text => {
85
+ if (!text) {
86
+ done();
87
+ } else {
88
+ const count = plugin.find(text);
89
+ setFindCount(count);
90
+ const index = count ? 1 : 0;
91
+ setFindIndex(index);
92
+ updateSelectionContext();
93
+ }
94
+ };
95
+
96
+ const handleReplace = function (text) {
97
+ let forward = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
98
+ let all = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
99
+ if (!text) return;
100
+ undoManager === null || undoManager === void 0 ? void 0 : undoManager.add();
101
+ plugin.replace(text, forward, all);
102
+
103
+ if (findCount === 1 || all) {
104
+ done();
105
+ return;
106
+ } // we can't just find again, because that will reset index to 1
107
+
108
+
109
+ let newIndex;
110
+ const newFindCount = findCount - 1;
111
+
112
+ if (forward) {
113
+ newIndex = findIndex === findCount ? 1 : findIndex;
114
+ } else {
115
+ newIndex = findIndex === 1 ? newFindCount : findIndex - 1;
116
+ }
117
+
118
+ setFindCount(newFindCount);
119
+ setFindIndex(newIndex);
120
+ updateSelectionContext();
121
+ };
122
+
123
+ return /*#__PURE__*/React.createElement(FindReplaceTray, {
124
+ onRequestClose: handleDismiss,
125
+ onNext: handleNext,
126
+ onPrevious: handlePrevious,
127
+ onFind: handleFind,
128
+ onReplace: handleReplace,
129
+ index: findIndex,
130
+ max: findCount,
131
+ initialText: initialText,
132
+ selectionContext: selectionContext
133
+ });
134
+ }
135
+ FindReplaceTrayController.propTypes = {
136
+ onDismiss: _pt.func.isRequired,
137
+ initialText: _pt.string,
138
+ getSelectionContext: _pt.func.isRequired
139
+ };
@@ -0,0 +1,68 @@
1
+ /*
2
+ * Copyright (C) 2024 - present Instructure, Inc.
3
+ *
4
+ * This file is part of Canvas.
5
+ *
6
+ * Canvas is free software: you can redistribute it and/or modify it under
7
+ * the terms of the GNU Affero General Public License as published by the Free
8
+ * Software Foundation, version 3 of the License.
9
+ *
10
+ * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
11
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12
+ * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
13
+ * details.
14
+ *
15
+ * You should have received a copy of the GNU Affero General Public License along
16
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ */
18
+ const NUMBER_OF_WORDS = 10;
19
+
20
+ const countWords = text => {
21
+ // ignore text before the first whitespace because it is part of the selected text
22
+ const countText = text.split(/\s+/).slice(1).join(' ');
23
+ const count = countText.trim().split(/\s+/).length;
24
+ return count;
25
+ };
26
+
27
+ const getAfterText = startingElement => {
28
+ let text = '';
29
+ let element = startingElement.nextSibling;
30
+
31
+ while (element) {
32
+ text += element.textContent;
33
+ element = element.nextSibling;
34
+ }
35
+
36
+ if (text.includes('.')) {
37
+ const index = text.indexOf('.');
38
+ text = text.substring(0, index + 1);
39
+ }
40
+
41
+ if (countWords(text) > NUMBER_OF_WORDS) {
42
+ text = text.split(/\s+/).slice(0, NUMBER_OF_WORDS + 1).join(' ');
43
+ }
44
+
45
+ return text;
46
+ };
47
+
48
+ const getBeforeText = (startingElement, wordCount) => {
49
+ let text = '';
50
+ let element = startingElement.previousSibling;
51
+
52
+ while (element) {
53
+ text = element.textContent + text;
54
+ element = element.previousSibling;
55
+ }
56
+
57
+ text = text.split(/\s+/).slice(-wordCount - 1).join(' ');
58
+ return text;
59
+ };
60
+
61
+ export const getSelectionContext = elements => {
62
+ const firstSelected = elements[0];
63
+ const lastSelected = elements[elements.length - 1];
64
+ const afterText = getAfterText(lastSelected);
65
+ const remainingWordCount = Math.max(NUMBER_OF_WORDS - countWords(afterText), 0);
66
+ const beforeText = getBeforeText(firstSelected, remainingWordCount);
67
+ return [beforeText, afterText];
68
+ };
@@ -0,0 +1,39 @@
1
+ /*
2
+ * Copyright (C) 2023 - present Instructure, Inc.
3
+ *
4
+ * This file is part of Canvas.
5
+ *
6
+ * Canvas is free software: you can redistribute it and/or modify it under
7
+ * the terms of the GNU Affero General Public License as published by the Free
8
+ * Software Foundation, version 3 of the License.
9
+ *
10
+ * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
11
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12
+ * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
13
+ * details.
14
+ *
15
+ * You should have received a copy of the GNU Affero General Public License along
16
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ */
18
+ import tinymce from 'tinymce';
19
+ import formatMessage from '../../../format-message';
20
+ import clickCallback from './clickCallback';
21
+ tinymce.PluginManager.add('instructure_search_and_replace', function (editor) {
22
+ var _editor$plugins;
23
+
24
+ // We use the searchreplace plugins API
25
+ if (!((_editor$plugins = editor.plugins) !== null && _editor$plugins !== void 0 && _editor$plugins.searchreplace)) return;
26
+
27
+ const launchFindModal = ed => () => {
28
+ clickCallback(ed, document);
29
+ };
30
+
31
+ editor.addCommand('launch_instructure_search_and_replace', launchFindModal(editor));
32
+ editor.ui.registry.addMenuItem('instructure_search_and_replace', {
33
+ text: formatMessage('Find and Replace'),
34
+ icon: 'search',
35
+ shortcut: 'Meta+F',
36
+ onAction: () => editor.execCommand('launch_instructure_search_and_replace')
37
+ });
38
+ editor.shortcuts.add('Meta+F', '', launchFindModal(editor));
39
+ });
@@ -94,11 +94,13 @@ export function mediaPlayerURLFromFile(file, canvasOrigin) {
94
94
  const type = content_type.replace(/\/.*$/, '');
95
95
 
96
96
  if ((_RCEGlobals$getFeatur = RCEGlobals.getFeatures()) !== null && _RCEGlobals$getFeatur !== void 0 && _RCEGlobals$getFeatur.media_links_use_attachment_id && isAudioOrVideo(content_type) && file.id) {
97
+ var _RCEGlobals$getFeatur2;
98
+
97
99
  const url = parse(`/media_attachments_iframe/${file.id}`, true);
98
100
  url.query.type = type;
99
101
  url.query.embedded = true;
100
102
 
101
- if (file.uuid && file.contextType == 'User') {
103
+ if (file.uuid && (file.contextType == 'User' || !!canvasOrigin && canvasOrigin !== window.location.origin && (_RCEGlobals$getFeatur2 = RCEGlobals.getFeatures()) !== null && _RCEGlobals$getFeatur2 !== void 0 && _RCEGlobals$getFeatur2.file_verifiers_for_quiz_links)) {
102
104
  url.query.verifier = file.uuid;
103
105
  } else if (file.url || file.href) {
104
106
  const parsed_url = parse(file.url || file.href, true);
@@ -34,6 +34,7 @@ tinymce.create('tinymce.plugins.AccessibilityChecker', {
34
34
  config,
35
35
  additionalRules,
36
36
  mountNode,
37
+ triggerElementId,
37
38
  onFixError
38
39
  } = _ref;
39
40
 
@@ -43,7 +44,16 @@ tinymce.create('tinymce.plugins.AccessibilityChecker', {
43
44
  editor: ed,
44
45
  additionalRules: additionalRules,
45
46
  mountNode: mountNode,
46
- onClose: () => isCheckerOpen = false,
47
+ onClose: () => {
48
+ isCheckerOpen = false;
49
+
50
+ if (triggerElementId) {
51
+ var _button$;
52
+
53
+ const button = document.querySelectorAll(`[data-btn-id=${triggerElementId}]`);
54
+ (_button$ = button[0]) === null || _button$ === void 0 ? void 0 : _button$.focus();
55
+ }
56
+ },
47
57
  onFixError: onFixError
48
58
  }), container, function () {
49
59
  // this is a workaround for react 16 since ReactDOM.render is not
@@ -28,7 +28,7 @@ export const A11Y_CHECKER_STYLE_ELEM_ID = 'a11y-checker-style'; // Remove the cu
28
28
  // the style element
29
29
 
30
30
  export function clearIndicators(doc) {
31
- const checker_style = doc.getElementById(A11Y_CHECKER_STYLE_ELEM_ID);
31
+ const checker_style = doc === null || doc === void 0 ? void 0 : doc.getElementById(A11Y_CHECKER_STYLE_ELEM_ID);
32
32
 
33
33
  if (checker_style) {
34
34
  checker_style.textContent = '';
package/es/rce/tinyRCE.js CHANGED
@@ -34,7 +34,8 @@ import 'tinymce/plugins/textpattern/plugin';
34
34
  import 'tinymce/plugins/wordcount/plugin';
35
35
  import 'tinymce/plugins/paste/plugin';
36
36
  import 'tinymce/plugins/table/plugin';
37
- import 'tinymce/plugins/hr/plugin'; // add custom plugins
37
+ import 'tinymce/plugins/hr/plugin';
38
+ import 'tinymce/plugins/searchreplace/plugin'; // add custom plugins
38
39
 
39
40
  import './plugins/instructure-ui-icons/plugin';
40
41
  import './plugins/instructure_condensed_buttons/plugin';
@@ -51,6 +52,7 @@ import './plugins/instructure_wordcount/plugin';
51
52
  import './plugins/instructure_paste/plugin';
52
53
  import './plugins/instructure_fullscreen/plugin';
53
54
  import './plugins/instructure_studio_media_options/plugin';
55
+ import './plugins/instructure_search_and_replace/plugin';
54
56
  import './plugins/tinymce-a11y-checker/plugin'; // prevent tinymce from loading language scripts with explicit
55
57
  // language_url of 'none'
56
58