@instructure/canvas-rce 5.13.7 → 5.14.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/CHANGELOG.md CHANGED
@@ -4,6 +4,25 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+ ## 5.14.1 - 2024-10-28
8
+
9
+ ### Changed
10
+
11
+ - Forward along access token and inst_ui parameters from file URLs.
12
+
13
+ ## 5.14.0 - 2024-10-18
14
+
15
+ ### Added
16
+
17
+ - New optional media player for upload previews
18
+
19
+ ### Fixed
20
+
21
+ - Keyboard trap when switching to the HTML Editor
22
+
23
+ ### Changed
24
+
25
+ - Upgraded React to 18
7
26
 
8
27
  ## 5.13.7 - 2024-10-28
9
28
 
@@ -45,7 +64,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
45
64
  - Added IDs to multiple objects missing IDs
46
65
  - Add loading spinners to image uploads
47
66
 
48
- ## 5.13.4 - 2024-08-12
67
+ ## 5.13.5 - 2024-08-12
49
68
 
50
69
  ### Fixed
51
70
 
@@ -0,0 +1,21 @@
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
+
19
+ module.exports = {
20
+ StudioPlayer: () => null
21
+ };
package/build.sh CHANGED
@@ -1,23 +1,23 @@
1
1
  #!/bin/bash
2
2
  export COMPOSE_FILE=./docker-compose.yml
3
3
 
4
- docker-compose build
5
- docker-compose up -d
4
+ docker compose build
5
+ docker compose up -d
6
6
 
7
7
  # run unit tests
8
- docker-compose exec -T module npm run test-cov
8
+ docker compose exec -T module npm run test-cov
9
9
  unit_status=$?
10
- docker cp $(docker-compose ps -q module):/usr/src/app/coverage coverage
10
+ docker cp $(docker compose ps -q module):/usr/src/app/coverage coverage
11
11
 
12
12
  # check code formatting
13
- docker-compose exec -T module npm run fmt:check
13
+ docker compose exec -T module npm run fmt:check
14
14
  fmt_status=$?
15
15
 
16
16
  # lint all the things
17
- docker-compose exec -T module npm run lint
17
+ docker compose exec -T module npm run lint
18
18
  lint_status=$?
19
19
 
20
- docker-compose stop
20
+ docker compose stop
21
21
 
22
22
  # jenkins uses the exit code to decide whether you passed or not
23
23
  ((unit_status)) && exit $unit_status
@@ -272,10 +272,6 @@ class RCEWrapper extends React.Component {
272
272
  this.handleFocus(event);
273
273
  };
274
274
 
275
- this.handleFocusHtmlEditor = event => {
276
- this.handleFocus(event);
277
- };
278
-
279
275
  this.handleBlurEditor = (event, _editor) => {
280
276
  const ifr = this.iframe;
281
277
  ifr && ifr.parentElement.classList.remove('active');
@@ -906,7 +902,8 @@ class RCEWrapper extends React.Component {
906
902
  rce_transform_loaded_content = false,
907
903
  media_links_use_attachment_id = false,
908
904
  rce_find_replace = false,
909
- file_verifiers_for_quiz_links = false
905
+ file_verifiers_for_quiz_links = false,
906
+ consolidated_media_player = false
910
907
  } = this.props.features;
911
908
  return {
912
909
  new_math_equation_handling,
@@ -914,7 +911,8 @@ class RCEWrapper extends React.Component {
914
911
  rce_transform_loaded_content,
915
912
  media_links_use_attachment_id,
916
913
  file_verifiers_for_quiz_links,
917
- rce_find_replace
914
+ rce_find_replace,
915
+ consolidated_media_player
918
916
  };
919
917
  }
920
918
 
@@ -1314,19 +1312,6 @@ class RCEWrapper extends React.Component {
1314
1312
  break;
1315
1313
 
1316
1314
  case PRETTY_HTML_EDITOR_VIEW:
1317
- {
1318
- const cmta = this._elementRef.current.querySelector('.CodeMirror textarea');
1319
-
1320
- if (cmta) {
1321
- cmta.focus();
1322
- } else {
1323
- window.setTimeout(() => {
1324
- var _this$_elementRef$cur3;
1325
-
1326
- (_this$_elementRef$cur3 = this._elementRef.current.querySelector('.CodeMirror textarea')) === null || _this$_elementRef$cur3 === void 0 ? void 0 : _this$_elementRef$cur3.focus();
1327
- }, 200);
1328
- }
1329
- }
1330
1315
  break;
1331
1316
 
1332
1317
  case RAW_HTML_EDITOR_VIEW:
@@ -1400,7 +1385,7 @@ class RCEWrapper extends React.Component {
1400
1385
  // we often need a moment to see if focus comes back
1401
1386
  event && event.persist && event.persist();
1402
1387
  this.blurTimer = window.setTimeout(() => {
1403
- var _this$_elementRef$cur4, _event$focusedEditor, _event$relatedTarget, _event$relatedTarget$;
1388
+ var _this$_elementRef$cur3, _event$focusedEditor, _event$relatedTarget, _event$relatedTarget$;
1404
1389
 
1405
1390
  this.blurTimer = 0;
1406
1391
 
@@ -1410,7 +1395,7 @@ class RCEWrapper extends React.Component {
1410
1395
  return;
1411
1396
  }
1412
1397
 
1413
- if ((_this$_elementRef$cur4 = this._elementRef.current) !== null && _this$_elementRef$cur4 !== void 0 && _this$_elementRef$cur4.contains(document.activeElement)) {
1398
+ if ((_this$_elementRef$cur3 = this._elementRef.current) !== null && _this$_elementRef$cur3 !== void 0 && _this$_elementRef$cur3.contains(document.activeElement)) {
1414
1399
  // focus is still somewhere w/in me
1415
1400
  return;
1416
1401
  }
@@ -1763,21 +1748,14 @@ class RCEWrapper extends React.Component {
1763
1748
  }
1764
1749
 
1765
1750
  setEditorView(view) {
1766
- var _this$_elementRef$cur5;
1767
-
1768
1751
  switch (view) {
1769
- case RAW_HTML_EDITOR_VIEW:
1770
- this.mceInstance().hide();
1771
- break;
1772
-
1773
- case PRETTY_HTML_EDITOR_VIEW:
1774
- this.mceInstance().hide();
1775
- (_this$_elementRef$cur5 = this._elementRef.current.querySelector('.CodeMirror')) === null || _this$_elementRef$cur5 === void 0 ? void 0 : _this$_elementRef$cur5.CodeMirror.setCursor(0, 0);
1776
- break;
1777
-
1778
1752
  case WYSIWYG_VIEW:
1779
1753
  this.setCode(this.textareaValue());
1780
1754
  this.mceInstance().show();
1755
+ break;
1756
+
1757
+ default:
1758
+ this.mceInstance().hide();
1781
1759
  }
1782
1760
  }
1783
1761
 
@@ -1806,8 +1784,7 @@ class RCEWrapper extends React.Component {
1806
1784
  onChange: value => {
1807
1785
  this.getTextarea().value = value;
1808
1786
  this.handleTextareaChange();
1809
- },
1810
- onFocus: this.handleFocusHtmlEditor
1787
+ }
1811
1788
  })));
1812
1789
  }
1813
1790
 
@@ -18,38 +18,12 @@
18
18
  import React, { useCallback, useEffect, useRef, useState } from 'react';
19
19
  import { func, string } from 'prop-types';
20
20
  import formatMessage from '../format-message';
21
- import { CodeEditor } from '@instructure/ui-code-editor';
21
+ import { SourceCodeEditor } from '@instructure/ui-source-code-editor';
22
22
  import beautify from 'js-beautify';
23
- const RceHtmlEditor = /*#__PURE__*/React.forwardRef((_ref, editorRef) => {
24
- let {
25
- onFocus,
26
- ...props
27
- } = _ref;
23
+ const RceHtmlEditor = /*#__PURE__*/React.forwardRef((props, editorRef) => {
28
24
  const [code, setCode] = useState(props.code);
29
25
  const label = formatMessage('html code editor');
30
26
  const [dir, setDir] = useState(getComputedStyle(document.body, null).direction);
31
- const [codeMirrorEditorDiv, setCodeMirrorEditorDiv] = useState(null);
32
- useEffect(() => {
33
- ;
34
-
35
- (async () => {
36
- const p = new Promise(resolve => {
37
- const timerid = setInterval(() => {
38
- // scoping querySelector to the container div makes sure we're targeting this CodeEditor
39
- // The CodeMirror docs (https://codemirror.net/doc/manual.html#styling)
40
- // say this is the element we use to set the editor's height
41
- const editor = editorRef.current.querySelector('.CodeMirror');
42
-
43
- if (editor) {
44
- clearInterval(timerid);
45
- setCodeMirrorEditorDiv(editor);
46
- resolve();
47
- }
48
- }, 60);
49
- });
50
- await p;
51
- })();
52
- }, [editorRef]);
53
27
  useEffect(() => {
54
28
  setCode(beautify.html(props.code)); // eslint-disable-next-line react-hooks/exhaustive-deps
55
29
  }, []);
@@ -78,23 +52,7 @@ const RceHtmlEditor = /*#__PURE__*/React.forwardRef((_ref, editorRef) => {
78
52
 
79
53
  setDir(getComputedStyle(editorRef.current || document.body, null).direction);
80
54
  }, [dir, editorRef]);
81
- useEffect(() => {
82
- if (codeMirrorEditorDiv) {
83
- codeMirrorEditorDiv.CodeMirror.setSize(null, props.height);
84
- codeMirrorEditorDiv.style.margin = '0';
85
- codeMirrorEditorDiv.style.border = '0';
86
- }
87
- }, [codeMirrorEditorDiv, props.height]);
88
- const isFocused = useRef(false); // move cursor to the top of the html code when the editor is focused for the first time
89
-
90
- const handleFocus = useCallback((editor, event) => {
91
- if (!isFocused.current) {
92
- editor.setCursor(0, 0);
93
- isFocused.current = true;
94
- }
95
-
96
- onFocus(event);
97
- }, [onFocus]); // setting height on the container keeps the RCE toolbar from jumping
55
+ const direction = ['ltr', 'rtl'].includes(dir) ? dir : 'ltr'; // setting height on the container keeps the RCE toolbar from jumping
98
56
 
99
57
  return /*#__PURE__*/React.createElement("div", {
100
58
  ref: editorRef,
@@ -104,39 +62,30 @@ const RceHtmlEditor = /*#__PURE__*/React.forwardRef((_ref, editorRef) => {
104
62
  overflow: 'hidden',
105
63
  textAlign: 'start'
106
64
  }
107
- }, /*#__PURE__*/React.createElement(CodeEditor, {
65
+ }, /*#__PURE__*/React.createElement(SourceCodeEditor, {
108
66
  label: label,
109
67
  language: "html",
110
- options: {
111
- lineNumbers: true,
112
- lineWrapping: true,
113
- autofocus: false,
114
- spellcheck: true,
115
- extraKeys: {
116
- Tab: false,
117
- 'Shift-Tab': false
118
- },
119
- screenReaderLabel: label,
120
- direction: dir,
121
- rtlMoveVisually: true
122
- },
68
+ lineNumbers: true,
69
+ lineWrapping: true,
70
+ autofocus: true,
71
+ spellcheck: true,
72
+ direction: direction,
73
+ rtlMoveVisually: true,
74
+ height: props.height,
123
75
  value: code,
124
76
  onChange: value => {
125
77
  setCode(value);
126
78
  props.onChange(value);
127
- },
128
- onFocus: handleFocus
79
+ }
129
80
  }));
130
81
  });
131
82
  RceHtmlEditor.propTypes = {
132
83
  code: string.isRequired,
133
84
  height: string,
134
- onChange: func,
135
- onFocus: func
85
+ onChange: func
136
86
  };
137
87
  RceHtmlEditor.defaultProps = {
138
88
  height: 'auto',
139
- onChange: _value => {},
140
- onFocus: () => {}
89
+ onChange: _value => {}
141
90
  };
142
91
  export default RceHtmlEditor;
@@ -0,0 +1,20 @@
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
+ module.exports = {
19
+ StudioPlayer: () => null
20
+ };
@@ -77,7 +77,7 @@ export default class EquationEditorModal extends Component {
77
77
  onModalDismiss,
78
78
  onEquationSubmit
79
79
  } = this.props;
80
- const output = this.state.advanced ? this.state.workingFormula : this.mathField.getValue();
80
+ const output = this.state.advanced ? this.state.workingFormula : this.getMathFiled();
81
81
 
82
82
  if (output) {
83
83
  onEquationSubmit(output);
@@ -99,7 +99,7 @@ export default class EquationEditorModal extends Component {
99
99
  this.toggleAdvanced = () => {
100
100
  this.setState(state => {
101
101
  if (state.advanced) {
102
- this.mathField.setValue(state.workingFormula || '');
102
+ this.setMathField(state.workingFormula || '');
103
103
  return {
104
104
  advanced: false,
105
105
  workingFormula: ''
@@ -107,7 +107,7 @@ export default class EquationEditorModal extends Component {
107
107
  } else {
108
108
  return {
109
109
  advanced: true,
110
- workingFormula: this.mathField.getValue()
110
+ workingFormula: this.getMathFiled()
111
111
  };
112
112
  }
113
113
  });
@@ -281,7 +281,7 @@ export default class EquationEditorModal extends Component {
281
281
  this.registerBasicEditorListener();
282
282
  this.setPreviewElementContent();
283
283
  this.stubMacros();
284
- if (!this.state.advanced) this.mathField.setValue(this.state.workingFormula);
284
+ if (!this.state.advanced) this.setMathField(this.state.workingFormula);
285
285
  this.insertNewRange();
286
286
  }
287
287
 
@@ -303,6 +303,14 @@ export default class EquationEditorModal extends Component {
303
303
  });
304
304
  }
305
305
 
306
+ setMathField(formula) {
307
+ this.mathField.setValue(formula);
308
+ }
309
+
310
+ getMathFiled() {
311
+ return this.mathField.getValue();
312
+ }
313
+
306
314
  }
307
315
  EquationEditorModal.debounceRate = 1000;
308
316
  EquationEditorModal.propTypes = {
@@ -20,6 +20,7 @@ import ReactDOM from 'react-dom';
20
20
  import bridge from '../../../../bridge';
21
21
  import { asAudioElement, findMediaPlayerIframe } from '../../shared/ContentSelection';
22
22
  import AudioOptionsTray from '.';
23
+ import RCEGlobals from '../../../RCEGlobals';
23
24
  export const CONTAINER_ID = 'instructure-audio-options-tray-container';
24
25
  export default class TrayController {
25
26
  constructor() {
@@ -87,14 +88,17 @@ export default class TrayController {
87
88
  }
88
89
 
89
90
  _applyAudioOptions(audioOptions) {
90
- if (!audioOptions.media_object_id || audioOptions.media_object_id === 'undefined') {
91
+ const hasAttachmentId = RCEGlobals.getFeatures().media_links_use_attachment_id && audioOptions.attachment_id;
92
+
93
+ if (!hasAttachmentId && (!audioOptions.media_object_id || audioOptions.media_object_id === 'undefined')) {
91
94
  return;
92
95
  }
93
96
 
94
97
  const container = this._audioContainer;
95
98
  return audioOptions.updateMediaObject({
96
99
  media_object_id: audioOptions.media_object_id,
97
- subtitles: audioOptions.subtitles
100
+ subtitles: audioOptions.subtitles,
101
+ attachment_id: audioOptions.attachment_id
98
102
  }).then(() => container === null || container === void 0 ? void 0 : container.contentWindow.location.reload()).catch(ex => {
99
103
  // eslint-disable-next-line no-console
100
104
  console.error('Failed updating audio captions', ex);
@@ -109,7 +113,7 @@ export default class TrayController {
109
113
  window.addEventListener('message', event => {
110
114
  var _event$data;
111
115
 
112
- if ((event === null || event === void 0 ? void 0 : (_event$data = event.data) === null || _event$data === void 0 ? void 0 : _event$data.subject) === "media_tracks_response") {
116
+ if ((event === null || event === void 0 ? void 0 : (_event$data = event.data) === null || _event$data === void 0 ? void 0 : _event$data.subject) === 'media_tracks_response') {
113
117
  var _event$data2;
114
118
 
115
119
  cb(event === null || event === void 0 ? void 0 : (_event$data2 = event.data) === null || _event$data2 === void 0 ? void 0 : _event$data2.payload);
@@ -51,6 +51,7 @@ export default function AudioOptionsTray(_ref) {
51
51
  onSave({
52
52
  media_object_id: audioOptions.id,
53
53
  subtitles,
54
+ attachment_id: audioOptions.attachmentId,
54
55
  updateMediaObject: contentProps.updateMediaObject
55
56
  });
56
57
  };
@@ -146,12 +146,15 @@ export default class TrayController {
146
146
  // If not, we can't update the MediaObject in the canvas db.
147
147
 
148
148
 
149
- if (videoOptions.media_object_id && videoOptions.media_object_id !== 'undefined' && !videoOptions.editLocked) {
149
+ const hasMediaId = videoOptions.media_object_id && videoOptions.media_object_id !== 'undefined' || data.attachment_id && data.attachment_id !== 'undefined';
150
+
151
+ if (hasMediaId && !videoOptions.editLocked) {
150
152
  videoOptions.updateMediaObject(data).then(_r => {
151
153
  if (this.$videoContainer && videoOptions.displayAs === 'embed') {
152
154
  this.$videoContainer.contentWindow.postMessage({
153
155
  subject: 'reload_media',
154
- media_object_id: videoOptions.media_object_id
156
+ media_object_id: videoOptions.media_object_id,
157
+ attachment_id: data.attachment_id
155
158
  }, bridge.canvasOrigin);
156
159
  }
157
160
  }).catch(ex => {
@@ -185,7 +188,7 @@ export default class TrayController {
185
188
  window.addEventListener('message', event => {
186
189
  var _event$data;
187
190
 
188
- if ((event === null || event === void 0 ? void 0 : (_event$data = event.data) === null || _event$data === void 0 ? void 0 : _event$data.subject) === "media_tracks_response") {
191
+ if ((event === null || event === void 0 ? void 0 : (_event$data = event.data) === null || _event$data === void 0 ? void 0 : _event$data.subject) === 'media_tracks_response') {
189
192
  var _event$data2;
190
193
 
191
194
  cb(event === null || event === void 0 ? void 0 : (_event$data2 = event.data) === null || _event$data2 === void 0 ? void 0 : _event$data2.payload);
@@ -79,27 +79,32 @@ export default function (ed, document) {
79
79
  };
80
80
 
81
81
  const trayProps = Bridge.trayProps.get(ed);
82
- ReactDOM.render( /*#__PURE__*/React.createElement(StoreProvider, trayProps, contentProps => /*#__PURE__*/React.createElement(UploadMedia, {
83
- "data-mce-component": true,
84
- rcsConfig: {
85
- contextType: ed.settings.canvas_rce_user_context.type,
86
- contextId: ed.settings.canvas_rce_user_context.id,
87
- origin: originFromHost(contentProps.host),
88
- headers: headerFor(contentProps.jwt)
89
- },
90
- userLocale: Bridge.userLocale,
91
- mountNode: instuiPopupMountNode,
92
- open: true,
93
- liveRegion: () => document.getElementById('flash_screenreader_holder'),
94
- onStartUpload: fileProps => handleStartUpload(fileProps),
95
- onUploadComplete: (err, data) => handleUpload(err, data, contentProps.mediaUploadComplete, uploadBookmark),
96
- onDismiss: handleDismiss,
97
- tabs: {
98
- record: true,
99
- upload: true
100
- },
101
- uploadMediaTranslations: Bridge.uploadMediaTranslations,
102
- media_links_use_attachment_id: RCEGlobals.getFeatures().media_links_use_attachment_id
103
- })), container);
82
+ ReactDOM.render( /*#__PURE__*/React.createElement(StoreProvider, trayProps, contentProps => {
83
+ var _RCEGlobals$getFeatur;
84
+
85
+ return /*#__PURE__*/React.createElement(UploadMedia, {
86
+ "data-mce-component": true,
87
+ rcsConfig: {
88
+ contextType: ed.settings.canvas_rce_user_context.type,
89
+ contextId: ed.settings.canvas_rce_user_context.id,
90
+ origin: originFromHost(contentProps.host),
91
+ headers: headerFor(contentProps.jwt)
92
+ },
93
+ userLocale: Bridge.userLocale,
94
+ mountNode: instuiPopupMountNode,
95
+ open: true,
96
+ liveRegion: () => document.getElementById('flash_screenreader_holder'),
97
+ onStartUpload: fileProps => handleStartUpload(fileProps),
98
+ onUploadComplete: (err, data) => handleUpload(err, data, contentProps.mediaUploadComplete, uploadBookmark),
99
+ onDismiss: handleDismiss,
100
+ tabs: {
101
+ record: true,
102
+ upload: true
103
+ },
104
+ uploadMediaTranslations: Bridge.uploadMediaTranslations,
105
+ media_links_use_attachment_id: RCEGlobals.getFeatures().media_links_use_attachment_id,
106
+ useStudioPlayer: (_RCEGlobals$getFeatur = RCEGlobals.getFeatures()) === null || _RCEGlobals$getFeatur === void 0 ? void 0 : _RCEGlobals$getFeatur.consolidated_media_player
107
+ });
108
+ }), container);
104
109
  });
105
110
  }
@@ -88,7 +88,7 @@ export default function CanvasContentTray(props) {
88
88
  const [hidingTrayOnAction, setHidingTrayOnAction] = useState(true);
89
89
  const trayRef = useRef(null);
90
90
  const scrollingAreaRef = useRef(null);
91
- const closeButtonRef = useRef(null);
91
+ const [closeButtonRef, setCloseButtonRef] = useState(null);
92
92
  const [filterSettings, setFilterSettings] = useFilterSettings();
93
93
  const [isEditTray, setIsEditTray] = useState(false);
94
94
  const [link, setLink] = useState(null);
@@ -111,12 +111,17 @@ export default function CanvasContentTray(props) {
111
111
  onTrayClosing && onTrayClosing(CanvasContentTray.globalOpenCount); // tell RCEWrapper we're closing if we're open
112
112
 
113
113
  setIsOpen(false);
114
- }, [bridge, onTrayClosing]);
114
+ }, [bridge, onTrayClosing]); // this shouldn't be necessary, but INSTUI isn't focusing the close button
115
+ // like it should.
116
+
117
+ useEffect(() => {
118
+ if (isOpen && closeButtonRef) {
119
+ closeButtonRef.focus();
120
+ }
121
+ }, [closeButtonRef, isOpen]);
115
122
  useEffect(() => {
116
123
  const controller = {
117
124
  showTrayForPlugin(plugin) {
118
- var _closeButtonRef$curre;
119
-
120
125
  // increment a counter that's used as the key when rendering
121
126
  // this gets us a new instance everytime, which is necessary
122
127
  // to get the queries run so we have up to date data.
@@ -144,8 +149,6 @@ export default function CanvasContentTray(props) {
144
149
  } else {
145
150
  setIsEditTray(false);
146
151
  }
147
-
148
- (_closeButtonRef$curre = closeButtonRef.current) === null || _closeButtonRef$curre === void 0 ? void 0 : _closeButtonRef$curre.focus();
149
152
  },
150
153
 
151
154
  hideTray(forceClose) {
@@ -290,6 +293,17 @@ export default function CanvasContentTray(props) {
290
293
  return isEditTray ? formatMessage('Edit Course Link') : formatMessage('Add');
291
294
  }
292
295
 
296
+ function renderLinkDisplay() {
297
+ return isEditTray && /*#__PURE__*/React.createElement(LinkDisplay, {
298
+ linkText: linkText,
299
+ placeholderText: (link === null || link === void 0 ? void 0 : link.title) || placeholderText,
300
+ linkFileName: (link === null || link === void 0 ? void 0 : link.title) || '',
301
+ published: (link === null || link === void 0 ? void 0 : link.published) || false,
302
+ handleTextChange: setLinkText,
303
+ linkType: link === null || link === void 0 ? void 0 : link.type
304
+ });
305
+ }
306
+
293
307
  return /*#__PURE__*/React.createElement(Tray, {
294
308
  "data-mce-component": true,
295
309
  "data-testid": "CanvasContentTray",
@@ -306,7 +320,7 @@ export default function CanvasContentTray(props) {
306
320
  onExit: handleExitTray,
307
321
  onOpen: handleOpenTray,
308
322
  contentRef: el => trayRef.current = el
309
- }, isOpen && hasOpened ? /*#__PURE__*/React.createElement(Flex, {
323
+ }, /*#__PURE__*/React.createElement(Flex, {
310
324
  direction: "column",
311
325
  as: "div",
312
326
  height: getTrayHeight(),
@@ -327,15 +341,8 @@ export default function CanvasContentTray(props) {
327
341
  onClick: handleDismissTray,
328
342
  "data-testid": "CloseButton_ContentTray",
329
343
  screenReaderLabel: formatMessage('Close'),
330
- elementRef: el => closeButtonRef.current = el
331
- })), isEditTray && /*#__PURE__*/React.createElement(LinkDisplay, {
332
- linkText: linkText,
333
- placeholderText: (link === null || link === void 0 ? void 0 : link.title) || placeholderText,
334
- linkFileName: (link === null || link === void 0 ? void 0 : link.title) || '',
335
- published: (link === null || link === void 0 ? void 0 : link.published) || false,
336
- handleTextChange: setLinkText,
337
- linkType: link === null || link === void 0 ? void 0 : link.type
338
- }), /*#__PURE__*/React.createElement(Filter, Object.assign({}, filterSettings, {
344
+ elementRef: el => setCloseButtonRef(el)
345
+ })), renderLinkDisplay(), /*#__PURE__*/React.createElement(Filter, Object.assign({}, filterSettings, {
339
346
  mountNode: props.mountNode,
340
347
  userContextType: props.contextType,
341
348
  containingContextType: props.containingContext.contextType,
@@ -344,7 +351,7 @@ export default function CanvasContentTray(props) {
344
351
  },
345
352
  isContentLoading: isLoading(storeProps),
346
353
  use_rce_icon_maker: props.use_rce_icon_maker
347
- }))), /*#__PURE__*/React.createElement(Flex.Item, {
354
+ }))), isOpen && hasOpened ? /*#__PURE__*/React.createElement(Flex.Item, {
348
355
  shouldGrow: true,
349
356
  shouldShrink: true,
350
357
  margin: "xx-small xxx-small 0",
@@ -372,7 +379,7 @@ export default function CanvasContentTray(props) {
372
379
  editing: isEditTray,
373
380
  onEditClick: setLink,
374
381
  selectedLink: link
375
- }, storeProps)))), isEditTray && renderFooter()))) : null);
382
+ }, storeProps)))), isEditTray && renderFooter())) : null));
376
383
  }
377
384
  CanvasContentTray.globalOpenCount = 0;
378
385
 
@@ -16,10 +16,12 @@
16
16
  * with this program. If not, see <http://www.gnu.org/licenses/>.
17
17
  */
18
18
  import { fromImageEmbed, fromVideoEmbed } from '../instructure_image/ImageEmbedOptions';
19
- import { isOnlyTextSelected } from '../../contentInsertionUtils';
19
+ import { isOnlyTextSelected } from '../../contentInsertionUtils'; // eslint-disable-next-line import/no-nodejs-modules
20
+
20
21
  import * as url from 'url';
21
22
  import formatMessage from '../../../format-message';
22
23
  import { isStudioEmbeddedMedia } from './StudioLtiSupportUtils';
24
+ import RCEGlobals from '../../RCEGlobals';
23
25
  const FILE_DOWNLOAD_PATH_REGEX = /^\/(courses\/\d+\/)?files\/\d+\/download$/;
24
26
  export const LINK_TYPE = 'link';
25
27
  export const FILE_LINK_TYPE = 'file-link';
@@ -143,6 +145,15 @@ export function asAudioElement($element) {
143
145
  } catch (e) {}
144
146
  }
145
147
 
148
+ if (RCEGlobals.getFeatures().media_links_use_attachment_id) {
149
+ const source = $audioIframe.getAttribute('src');
150
+ const matches = source === null || source === void 0 ? void 0 : source.match(/\/media_attachments_iframe\/(\d+)/);
151
+
152
+ if (matches) {
153
+ audioOptions.attachmentId = matches[1];
154
+ }
155
+ }
156
+
146
157
  return audioOptions;
147
158
  }
148
159
 
@@ -211,7 +222,7 @@ export function isImageEmbed($element) {
211
222
  }
212
223
 
213
224
  function isMediaElement($element, mediaType) {
214
- var _tinymceIframeShim$fi;
225
+ var _tinymceIframeShim$fi, _tinymceIframeShim$ge;
215
226
 
216
227
  // the video is hosted in an iframe, but tinymce
217
228
  // wraps it in a span with swizzled attribute names
@@ -226,8 +237,9 @@ function isMediaElement($element, mediaType) {
226
237
  }
227
238
 
228
239
  const media_obj_id = tinymceIframeShim.getAttribute('data-mce-p-data-media-id');
240
+ const is_media_attachment_iframe = (_tinymceIframeShim$ge = tinymceIframeShim.getAttribute('data-mce-p-src')) === null || _tinymceIframeShim$ge === void 0 ? void 0 : _tinymceIframeShim$ge.includes('media_attachments_iframe');
229
241
 
230
- if (!media_obj_id) {
242
+ if (!media_obj_id && !is_media_attachment_iframe) {
231
243
  return false;
232
244
  }
233
245
 
@@ -775,7 +775,7 @@ const locale = {
775
775
  "message": "Cerca"
776
776
  },
777
777
  "find_and_replace_6e345933": {
778
- "message": "Cerca i substitueix"
778
+ "message": "Cerca i substituir"
779
779
  },
780
780
  "finish_bc343002": {
781
781
  "message": "Finalitza"
@@ -2037,6 +2037,9 @@ const locale = {
2037
2037
  "submit_a3cc6859": {
2038
2038
  "message": "Cuir isteach"
2039
2039
  },
2040
+ "submitting_b90fac62": {
2041
+ "message": "Á chur isteach..."
2042
+ },
2040
2043
  "subscript_59744f96": {
2041
2044
  "message": "Foscript"
2042
2045
  },
@@ -19,6 +19,11 @@
19
19
  // Several components use aphrodite, which tries to manipulate the dom
20
20
  // on a timer which expires after the test completes and the document no longer exists
21
21
  import {StyleSheetTestUtils} from 'aphrodite'
22
+ // eslint-disable-next-line import/no-nodejs-modules
23
+ import {TextDecoder, TextEncoder} from 'util'
24
+
25
+ global.TextEncoder = TextEncoder
26
+ global.TextDecoder = TextDecoder
22
27
 
23
28
  /**
24
29
  * We want to ensure errors and warnings get appropriate eyes. If
@@ -45,6 +50,15 @@ const ignoredErrors = [
45
50
  /You seem to have overlapping act\(\) calls/,
46
51
  /A theme registry has already been initialized/,
47
52
  /Warning: Failed prop type: Invalid prop `color` of value `secondary` supplied to `CondensedButton`, expected one of \["primary","primary-inverse"\]./,
53
+ /ReactDOM.render is no longer supported in React 18/,
54
+ /Warning: Failed %s type: %s%s/,
55
+ /Warning: %s: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.%s/,
56
+ /Warning: `ReactDOMTestUtils.act` is deprecated in favor of `React.act`. Import `act` from `react` instead of `react-dom\/test-utils`. See https:\/\/react.dev\/warnings\/react-dom-test-utils for more info./,
57
+ /Warning: `ReactDOMTestUtils.act` is deprecated in favor of `React.act`. Import `act` from `react` instead of `react-dom\/test-utils`./,
58
+ /Warning: unmountComponentAtNode is deprecated and will be removed in the next major release. Switch to the createRoot API. Learn more: https:\/\/reactjs.org\/link\/switch-to-createroot/,
59
+ /Warning: findDOMNode is deprecated and will be removed in the next major release. Instead, add a ref directly to the element you want to reference. Learn more about using refs safely here: https:\/\/reactjs.org\/link\/strict-mode-find-node/,
60
+ /Warning: %s uses the legacy childContextTypes API which is no longer supported and will be removed in the next major release. Use React.createContext\(\) instead/,
61
+ /Warning: %s uses the legacy contextTypes API which is no longer supported and will be removed in the next major release. Use React.createContext\(\) with static contextType instead./,
48
62
  ]
49
63
  const globalWarn = global.console.warn
50
64
  const ignoredWarnings = [
package/jest.config.js CHANGED
@@ -34,6 +34,7 @@ module.exports = {
34
34
  ],
35
35
  ],
36
36
  setupFilesAfterEnv: [
37
+ '@testing-library/jest-dom',
37
38
  '<rootDir>/jest/jest-setup-framework.js',
38
39
  '<rootDir>/../../jest/stubInstUi.js',
39
40
  ],
@@ -48,6 +49,8 @@ module.exports = {
48
49
  // mock the tinymce-react Editor component
49
50
  '@tinymce/tinymce-react': '<rootDir>/src/rce/__mocks__/tinymceReact.jsx',
50
51
  'crypto-es': '<rootDir>/src/rce/__mocks__/_mockCryptoEs.ts',
52
+ '@instructure/studio-player':
53
+ '<rootDir>/__mocks__/@instructure/studio-player/_mockStudioPlayer.js',
51
54
  },
52
55
 
53
56
  transform: {
@@ -60,9 +63,7 @@ module.exports = {
60
63
  ['@babel/preset-react', {}],
61
64
  ['@babel/preset-typescript', {}],
62
65
  ],
63
- plugins: [
64
- ['@babel/plugin-proposal-decorators', {legacy: true}],
65
- ],
66
+ plugins: [['@babel/plugin-proposal-decorators', {legacy: true}]],
66
67
  },
67
68
  ],
68
69
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/canvas-rce",
3
- "version": "5.13.7",
3
+ "version": "5.14.1",
4
4
  "description": "A component wrapping Canvas's usage of Tinymce",
5
5
  "main": "es/index.js",
6
6
  "owner": "LF",
@@ -32,7 +32,7 @@
32
32
  "demo": "scripts/demo.sh",
33
33
  "demo:clean": "rm -f github-pages/dist/*",
34
34
  "demo:build": "wp -c ./webpack.demo.config.js",
35
- "demo:dev": "yarn demo:clean && mkdir -p ./github-pages/dist && cp ./github-pages/index.html ./github-pages/dist && wp -c ./webpack.dev.config.js",
35
+ "demo:dev": "yarn demo:clean && mkdir -p ./github-pages/dist && cp ./github-pages/index.html ./github-pages/dist && wp -c ./webpack.dev.config.js",
36
36
  "installTranslations": "scripts/installTranslations.js",
37
37
  "commitTranslations": "scripts/commitTranslations.sh",
38
38
  "build:all": "scripts/build.js",
@@ -76,60 +76,60 @@
76
76
  "instrument": false
77
77
  },
78
78
  "dependencies": {
79
- "@instructure/canvas-theme": "^8",
79
+ "@instructure/canvas-theme": "8.56.4",
80
80
  "@instructure/canvas-media": "*",
81
- "@instructure/debounce": "^8",
82
- "@instructure/emotion": "^8.39",
81
+ "@instructure/debounce": "8.56.4",
82
+ "@instructure/emotion": "8.56.4",
83
83
  "@instructure/k5uploader": "*",
84
84
  "@instructure/media-capture": "^9.0.0",
85
- "@instructure/theme-registry": "^8",
86
- "@instructure/ui-a11y-content": "^8",
87
- "@instructure/ui-a11y-utils": "^8",
88
- "@instructure/ui-alerts": "^8",
89
- "@instructure/ui-avatar": "^8",
90
- "@instructure/ui-badge": "^8",
91
- "@instructure/ui-billboard": "^8",
92
- "@instructure/ui-buttons": "^8",
93
- "@instructure/ui-checkbox": "^8",
94
- "@instructure/ui-code-editor": "^8",
95
- "@instructure/ui-color-utils": "^8",
96
- "@instructure/ui-file-drop": "^8",
97
- "@instructure/ui-flex": "^8",
98
- "@instructure/ui-focusable": "^8",
99
- "@instructure/ui-form-field": "^8",
100
- "@instructure/ui-grid": "^8",
101
- "@instructure/ui-heading": "^8",
102
- "@instructure/ui-icons": "^8",
103
- "@instructure/ui-img": "^8",
104
- "@instructure/ui-link": "^8",
105
- "@instructure/ui-list": "^8",
85
+ "@instructure/theme-registry": "8.56.4",
86
+ "@instructure/ui-a11y-content": "8.56.4",
87
+ "@instructure/ui-a11y-utils": "8.56.4",
88
+ "@instructure/ui-alerts": "8.56.4",
89
+ "@instructure/ui-avatar": "8.56.4",
90
+ "@instructure/ui-badge": "8.56.4",
91
+ "@instructure/ui-billboard": "8.56.4",
92
+ "@instructure/ui-buttons": "8.56.4",
93
+ "@instructure/ui-checkbox": "8.56.4",
94
+ "@instructure/ui-source-code-editor": "8.56.4",
95
+ "@instructure/ui-color-utils": "8.56.4",
96
+ "@instructure/ui-file-drop": "8.56.4",
97
+ "@instructure/ui-flex": "8.56.4",
98
+ "@instructure/ui-focusable": "8.56.4",
99
+ "@instructure/ui-form-field": "8.56.4",
100
+ "@instructure/ui-grid": "8.56.4",
101
+ "@instructure/ui-heading": "8.56.4",
102
+ "@instructure/ui-icons": "8.56.4",
103
+ "@instructure/ui-img": "8.56.4",
104
+ "@instructure/ui-link": "8.56.4",
105
+ "@instructure/ui-list": "8.56.4",
106
106
  "@instructure/ui-media-player": "^9.0.0",
107
- "@instructure/ui-menu": "^8",
108
- "@instructure/ui-modal": "^8",
109
- "@instructure/ui-motion": "^8",
110
- "@instructure/ui-number-input": "^8",
111
- "@instructure/ui-overlays": "^8",
112
- "@instructure/ui-pagination": "^8",
113
- "@instructure/ui-popover": "^8",
114
- "@instructure/ui-radio-input": "^8",
115
- "@instructure/ui-react-utils": "^8",
116
- "@instructure/ui-simple-select": "^8",
117
- "@instructure/ui-spinner": "^8",
118
- "@instructure/ui-svg-images": "^8",
119
- "@instructure/ui-table": "^8",
120
- "@instructure/ui-tabs": "^8",
121
- "@instructure/ui-text-area": "^8",
122
- "@instructure/ui-text-input": "^8",
123
- "@instructure/ui-text": "^8",
124
- "@instructure/ui-themes": "^8",
125
- "@instructure/ui-toggle-details": "^8",
126
- "@instructure/ui-tooltip": "^8",
127
- "@instructure/ui-tray": "^8",
128
- "@instructure/ui-tree-browser": "^8",
129
- "@instructure/ui-truncate-text": "^8",
130
- "@instructure/ui-utils": "^8",
131
- "@instructure/ui-view": "^8",
132
- "@instructure/uid": "^8",
107
+ "@instructure/ui-menu": "8.56.4",
108
+ "@instructure/ui-modal": "8.56.4",
109
+ "@instructure/ui-motion": "8.56.4",
110
+ "@instructure/ui-number-input": "8.56.4",
111
+ "@instructure/ui-overlays": "8.56.4",
112
+ "@instructure/ui-pagination": "8.56.4",
113
+ "@instructure/ui-popover": "8.56.4",
114
+ "@instructure/ui-radio-input": "8.56.4",
115
+ "@instructure/ui-react-utils": "8.56.4",
116
+ "@instructure/ui-simple-select": "8.56.4",
117
+ "@instructure/ui-spinner": "8.56.4",
118
+ "@instructure/ui-svg-images": "8.56.4",
119
+ "@instructure/ui-table": "8.56.4",
120
+ "@instructure/ui-tabs": "8.56.4",
121
+ "@instructure/ui-text-area": "8.56.4",
122
+ "@instructure/ui-text-input": "8.56.4",
123
+ "@instructure/ui-text": "8.56.4",
124
+ "@instructure/ui-themes": "8.56.4",
125
+ "@instructure/ui-toggle-details": "8.56.4",
126
+ "@instructure/ui-tooltip": "8.56.4",
127
+ "@instructure/ui-tray": "8.56.4",
128
+ "@instructure/ui-tree-browser": "8.56.4",
129
+ "@instructure/ui-truncate-text": "8.56.4",
130
+ "@instructure/ui-utils": "8.56.4",
131
+ "@instructure/ui-view": "8.56.4",
132
+ "@instructure/uid": "8.56.4",
133
133
  "@sheerun/mutationobserver-shim": "^0.3.2",
134
134
  "@tinymce/tinymce-react": "~3.8.4",
135
135
  "aphrodite": "^2",
@@ -149,10 +149,10 @@
149
149
  "minimatch": "~3.0.4",
150
150
  "moment-timezone": "^0.5.45",
151
151
  "prop-types": "^15",
152
- "react": "^0.14.8 || ^15.0.0 || ^16",
152
+ "react": "^18",
153
153
  "react-aria-live": "^2",
154
154
  "react-color": "^2.13.4",
155
- "react-dom": "^0.14.8 || ^15.0.0 || ^16",
155
+ "react-dom": "^18",
156
156
  "react-draggable": "^3.3.0",
157
157
  "react-redux": "^5",
158
158
  "react-transition-group": "^1",
@@ -178,6 +178,7 @@
178
178
  "@testing-library/react": "^12",
179
179
  "@testing-library/react-hooks": "^5",
180
180
  "@testing-library/user-event": "^14",
181
+ "@types/testing-library__jest-dom": "^5.0.0",
181
182
  "axe-testcafe": "^3",
182
183
  "babel-loader": "^9.1.3",
183
184
  "babel-plugin-dynamic-import-node": "^2.2.0",
@@ -1,64 +0,0 @@
1
- /*
2
- * Copyright (C) 2018 - 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 React, { Component } from 'react';
19
- import { number, string, shape, func } from 'prop-types';
20
- import { css } from 'aphrodite';
21
- import styles from './styles';
22
- import { IconDocumentLine } from '@instructure/ui-icons';
23
- export default class File extends Component {
24
- constructor() {
25
- super(...arguments);
26
-
27
- this.handleSelect = () => {
28
- const {
29
- onSelect,
30
- file
31
- } = this.props;
32
-
33
- if (onSelect) {
34
- onSelect(file.id);
35
- }
36
- };
37
- }
38
-
39
- icon() {
40
- switch (this.props.file.type) {
41
- default:
42
- return /*#__PURE__*/React.createElement(IconDocumentLine, null);
43
- }
44
- }
45
-
46
- render() {
47
- const {
48
- name
49
- } = this.props.file;
50
- return /*#__PURE__*/React.createElement("button", {
51
- className: css(styles.button, styles.file),
52
- onClick: this.handleSelect
53
- }, this.icon(), " ", name);
54
- }
55
-
56
- }
57
- File.propTypes = {
58
- file: shape({
59
- id: number,
60
- name: string,
61
- type: string
62
- }).isRequired,
63
- onSelect: func
64
- };
@@ -1,110 +0,0 @@
1
- /*
2
- * Copyright (C) 2018 - 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 PropTypes from 'prop-types';
19
- import React, { Component } from 'react';
20
- import File from './File';
21
- import Loading from '../Loading';
22
- import { css } from 'aphrodite';
23
- import styles from './styles';
24
- import { IconMiniArrowDownLine, IconMiniArrowEndLine, IconFolderLine } from '@instructure/ui-icons';
25
- export default class Folder extends Component {
26
- constructor() {
27
- super(...arguments);
28
-
29
- this.handleToggle = () => {
30
- const {
31
- onToggle,
32
- folder
33
- } = this.props;
34
-
35
- if (onToggle) {
36
- onToggle(folder.id);
37
- }
38
- };
39
- }
40
-
41
- files() {
42
- return this.props.folder.fileIds.map(id => this.props.files[id]).filter(file => file != null);
43
- }
44
-
45
- subFolders() {
46
- return this.props.folder.folderIds.map(id => this.props.folders[id]).filter(folder => folder != null);
47
- }
48
-
49
- toggleIcon() {
50
- const {
51
- expanded
52
- } = this.props.folder;
53
- return expanded ? /*#__PURE__*/React.createElement(IconMiniArrowDownLine, null) : /*#__PURE__*/React.createElement(IconMiniArrowEndLine, null);
54
- }
55
-
56
- render() {
57
- const {
58
- folders,
59
- folder,
60
- files,
61
- onSelect,
62
- onToggle
63
- } = this.props;
64
- return /*#__PURE__*/React.createElement("div", {
65
- className: css(styles.node)
66
- }, /*#__PURE__*/React.createElement("button", {
67
- className: css(styles.button),
68
- onClick: this.handleToggle,
69
- "aria-expanded": !!folder.expanded
70
- }, this.toggleIcon(), " ", /*#__PURE__*/React.createElement(IconFolderLine, null), " ", folder.name), folder.expanded && /*#__PURE__*/React.createElement("ul", {
71
- className: css(styles.list)
72
- }, this.subFolders().map(folder => /*#__PURE__*/React.createElement("li", {
73
- key: `folder-${folder.id}`,
74
- className: css(styles.node)
75
- }, /*#__PURE__*/React.createElement(Folder, {
76
- folders: folders,
77
- files: files,
78
- folder: folder,
79
- onToggle: onToggle,
80
- onSelect: onSelect
81
- }))), this.files().map(file => /*#__PURE__*/React.createElement("li", {
82
- key: `file-${file.id}`,
83
- className: css(styles.node)
84
- }, /*#__PURE__*/React.createElement(File, {
85
- onSelect: onSelect,
86
- file: file
87
- })))), folder.expanded && folder.loading && /*#__PURE__*/React.createElement(Loading, {
88
- className: css(styles.loading)
89
- }));
90
- }
91
-
92
- }
93
- const folderPropType = PropTypes.shape({
94
- id: PropTypes.number,
95
- name: PropTypes.string,
96
- loading: PropTypes.bool,
97
- fileIds: PropTypes.arrayOf(PropTypes.number),
98
- folderIds: PropTypes.arrayOf(PropTypes.number)
99
- });
100
- Folder.propTypes = {
101
- folders: PropTypes.objectOf(folderPropType),
102
- files: PropTypes.objectOf(File.propTypes.file),
103
- folder: folderPropType.isRequired,
104
- onToggle: PropTypes.func,
105
- onSelect: File.propTypes.onSelect
106
- };
107
- Folder.defaultProps = {
108
- files: [],
109
- folders: []
110
- };
@@ -1,84 +0,0 @@
1
- /*
2
- * Copyright (C) 2018 - 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 PropTypes from 'prop-types';
19
- import React, { Component } from 'react';
20
- import Folder from './Folder';
21
- import { css } from 'aphrodite';
22
- import styles from './styles';
23
- const DOWN_KEY = 40;
24
- const UP_KEY = 38;
25
- const J_KEY = 74;
26
- const K_KEY = 75;
27
- export default class FileTree extends Component {
28
- constructor() {
29
- super(...arguments);
30
-
31
- this.handleKeyDown = event => {
32
- switch (event.keyCode) {
33
- case DOWN_KEY:
34
- case J_KEY:
35
- this.moveFocus(1);
36
- break;
37
-
38
- case UP_KEY:
39
- case K_KEY:
40
- this.moveFocus(-1);
41
- break;
42
-
43
- default:
44
- return;
45
- }
46
-
47
- event.stopPropagation();
48
- };
49
- }
50
-
51
- navigableNodes() {
52
- return Array.from(this.containerNode.querySelectorAll('button'));
53
- }
54
-
55
- moveFocus(delta) {
56
- const nodes = this.navigableNodes();
57
- const active = nodes.indexOf(window.document.activeElement);
58
- let next = active + delta;
59
-
60
- if (next < 0) {
61
- next = 0;
62
- } else if (next >= nodes.length) {
63
- next = nodes.length - 1;
64
- }
65
-
66
- nodes[next].focus();
67
- }
68
-
69
- render() {
70
- const inlineStyles = {
71
- maxHeight: this.props.maxHeight
72
- };
73
- return /*#__PURE__*/React.createElement("div", {
74
- className: css(styles.container),
75
- ref: c => this.containerNode = c,
76
- onKeyDown: this.handleKeyDown,
77
- style: inlineStyles
78
- }, /*#__PURE__*/React.createElement(Folder, this.props));
79
- }
80
-
81
- }
82
- FileTree.propTypes = { ...Folder.propTypes,
83
- maxHeight: PropTypes.string
84
- };
@@ -1,72 +0,0 @@
1
- /*
2
- * Copyright (C) 2018 - 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 { StyleSheet } from 'aphrodite';
19
- export default StyleSheet.create({
20
- container: {
21
- marginBottom: '1em',
22
- overflow: 'auto'
23
- },
24
- list: {
25
- margin: '0 0 0 .8em',
26
- padding: '0 0 0 .2em',
27
- borderLeft: '1px dotted #ccc',
28
- listStyle: 'none outside',
29
- flex: 1
30
- },
31
- node: {
32
- margin: 0,
33
- padding: 0,
34
- display: 'block'
35
- },
36
- loading: {
37
- marginLeft: '.8em',
38
- borderLeft: '1px dotted #ccc',
39
- padding: '.5em .7em'
40
- },
41
- button: {
42
- display: 'block',
43
- padding: '.3em',
44
- borderRadius: '.3em',
45
- backgroundColor: 'transparent',
46
- textAlign: 'left',
47
- margin: 0,
48
- fontFamily: 'inherit',
49
- fontSize: 'inherit',
50
- flex: 1,
51
- width: '100%',
52
- boxSizing: 'border-box',
53
- border: '1px solid transparent',
54
- transition: 'background-color 0.3s',
55
- wordBreak: 'break-all',
56
- ':hover': {
57
- backgroundColor: '#eee'
58
- },
59
- ':focus': {
60
- border: '1px solid #000',
61
- outline: 0
62
- },
63
- ':active': {
64
- backgroundColor: '#ddd'
65
- }
66
- },
67
- file: {
68
- ':active': {
69
- backgroundColor: '#008a14'
70
- }
71
- }
72
- });
@@ -1,83 +0,0 @@
1
- /*
2
- * Copyright (C) 2018 - 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 PropTypes from 'prop-types';
19
- import React, { Component } from 'react';
20
- import formatMessage from '../../format-message';
21
- import { ScreenReaderContent } from '@instructure/ui-a11y-content';
22
- import { StyleSheet, css } from 'aphrodite';
23
-
24
- function Loading(props) {
25
- const className = (css(styles.loading) + ' ' + props.className).trim();
26
- return /*#__PURE__*/React.createElement("span", {
27
- className: className
28
- }, /*#__PURE__*/React.createElement(ScreenReaderContent, null, formatMessage('Loading...')), /*#__PURE__*/React.createElement("span", {
29
- className: css(styles.dot, styles.dot0)
30
- }), /*#__PURE__*/React.createElement("span", {
31
- className: css(styles.dot, styles.dot1)
32
- }), /*#__PURE__*/React.createElement("span", {
33
- className: css(styles.dot, styles.dot2)
34
- }));
35
- }
36
-
37
- Loading.propTypes = {
38
- className: PropTypes.string
39
- };
40
- Loading.defaultProps = {
41
- className: ''
42
- };
43
- const opacityKeyframes = {
44
- '0%': {
45
- opacity: 0
46
- },
47
- '50%': {
48
- opacity: 1
49
- },
50
- '100%': {
51
- opacity: 0
52
- }
53
- };
54
- const styles = StyleSheet.create({
55
- loading: {
56
- display: 'inline-flex',
57
- alignItems: 'center',
58
- justifyContent: 'space-around',
59
- width: '48px',
60
- height: '10px'
61
- },
62
- dot: {
63
- animationName: [opacityKeyframes],
64
- animationDuration: '1.95s',
65
- animationIterationCount: 'infinite',
66
- animationDirection: 'linear',
67
- background: '#666',
68
- borderRadius: '8px',
69
- width: '10px',
70
- height: '10px',
71
- flex: 'none'
72
- },
73
- dot0: {
74
- animationDelay: '-1.8s'
75
- },
76
- dot1: {
77
- animationDelay: '-1.6s'
78
- },
79
- dot2: {
80
- animationDelay: '-1.4s'
81
- }
82
- });
83
- export default Loading;