@instructure/canvas-rce 6.0.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
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
7
 
8
+ ## 7.0.0 - 2025-03-31
9
+
10
+ ### Fixed
11
+
12
+ - Screen readers for RCE toolbar on mobile platform
13
+ - Axios CSRF vulnerability
14
+ - Mailto link insertion
15
+
16
+ ### Changed
17
+
18
+ - Upgraded Instui to v10
19
+ - Refactored deprecated plugins to prepare for tinymce upgrade
20
+ - Removed media_links_use_attachment_id feature flag
21
+
8
22
  ## 6.0.0 - 2025-03-20
9
23
 
10
24
  ### Changed
@@ -31,8 +31,8 @@ export function reset() {
31
31
  });
32
32
  }
33
33
  export function set(env) {
34
- isIE = !!env.ie;
35
- isEdge = isIE && !!(env.edge || env.ie == 12);
34
+ isIE = env.browser.isIE();
35
+ isEdge = env.browser.isEdge();
36
36
  }
37
37
  export function ie() {
38
38
  return isIE;
@@ -21,18 +21,28 @@
21
21
  // in mocha tests.
22
22
 
23
23
  import RCEGlobals from '../rce/RCEGlobals';
24
+ const CONTACT_PROTOCOLS = ['mailto:', 'tel:', 'skype:'];
25
+ function parseUrl(url, canvasOrigin = window.location.origin) {
26
+ try {
27
+ // If the URL is already absolute, use it as-is
28
+ return new URL(url);
29
+ } catch {
30
+ return new URL(`${canvasOrigin}${url.startsWith('/') ? '' : '/'}${url}`);
31
+ }
32
+ }
24
33
  function parseCanvasUrl(url, canvasOrigin = window.location.origin) {
25
34
  if (!url) {
26
35
  return null;
27
36
  }
28
37
  try {
29
- // For absolute URLs
30
- const fullUrl = url.startsWith('http') ? url : `${canvasOrigin}${url.startsWith('/') ? '' : '/'}${url}`;
31
- const parsed = new URL(fullUrl);
38
+ const parsed = parseUrl(url, canvasOrigin);
32
39
  const canvasUrl = new URL(canvasOrigin);
33
40
  if (parsed.host && canvasUrl.host !== parsed.host) {
34
41
  return null;
35
42
  }
43
+ if (CONTACT_PROTOCOLS.includes(parsed.protocol)) {
44
+ return null;
45
+ }
36
46
 
37
47
  // Convert URLSearchParams to query object
38
48
  const query = {};
@@ -1,12 +1,10 @@
1
1
  export default instance;
2
2
  export type Features = {
3
- media_links_use_attachment_id: boolean;
4
3
  file_verifiers_for_quiz_links: boolean;
5
4
  };
6
5
  declare const instance: RCEGlobals;
7
6
  /**
8
7
  * @typedef {Object} Features
9
- * @property {boolean} media_links_use_attachment_id
10
8
  * @property {boolean} file_verifiers_for_quiz_links
11
9
  */
12
10
  declare class RCEGlobals {
@@ -20,7 +20,6 @@ const isEmpty = obj => Object.keys(obj).length === 0;
20
20
 
21
21
  /**
22
22
  * @typedef {Object} Features
23
- * @property {boolean} media_links_use_attachment_id
24
23
  * @property {boolean} file_verifiers_for_quiz_links
25
24
  */
26
25
 
@@ -13,5 +13,5 @@ export type RCEVariant = (typeof RCEVariantValues)[number];
13
13
  export declare function getMenubarForVariant(variant: RCEVariant): MenuBarSpec;
14
14
  export declare function getMenuForVariant(variant: RCEVariant): MenusSpec;
15
15
  export declare function getToolbarForVariant(variant: RCEVariant, ltiToolFavorites?: string[]): ToolbarGroupSetting[];
16
- export declare function getStatusBarFeaturesForVariant(variant: RCEVariant, ai_text_tools?: boolean): StatusBarFeature[];
16
+ export declare function getStatusBarFeaturesForVariant(variant: RCEVariant, aiTextTools?: boolean, isDesktop?: boolean): StatusBarFeature[];
17
17
  export {};
@@ -121,16 +121,16 @@ export function getToolbarForVariant(variant, ltiToolFavorites = []) {
121
121
  items: ['removeformat', 'table', 'instructure_equation', 'instructure_media_embed']
122
122
  }];
123
123
  }
124
- export function getStatusBarFeaturesForVariant(variant, ai_text_tools = false) {
125
- if (variant === 'lite' || variant === 'text-only') {
126
- return ['keyboard_shortcuts', 'a11y_checker', 'word_count'];
127
- }
124
+ const DESKTOP_FEATURES = ['keyboard_shortcuts', 'a11y_checker', 'word_count'];
125
+ const MOBILE_FEATURES = ['a11y_checker', 'word_count'];
126
+ const EXTENDED_FEATURES = ['html_view', 'fullscreen', 'resize_handle'];
127
+ export function getStatusBarFeaturesForVariant(variant, aiTextTools = false, isDesktop = true) {
128
128
  if (variant === 'text-block') {
129
129
  return [];
130
130
  }
131
- const full_features = ['keyboard_shortcuts', 'a11y_checker', 'word_count', 'html_view', 'fullscreen', 'resize_handle'];
132
- if (ai_text_tools) {
133
- full_features.push('ai_tools');
131
+ const platformFeatures = isDesktop ? DESKTOP_FEATURES : MOBILE_FEATURES;
132
+ if (variant === 'lite' || variant === 'text-only') {
133
+ return platformFeatures;
134
134
  }
135
- return full_features;
135
+ return [...platformFeatures, ...EXTENDED_FEATURES, ...(aiTextTools ? ['ai_tools'] : [])];
136
136
  }
@@ -214,7 +214,6 @@ declare class RCEWrapper extends React.Component<RCEWrapperProps, RCEWrapperStat
214
214
  new_math_equation_handling: unknown;
215
215
  explicit_latex_typesetting: unknown;
216
216
  rce_transform_loaded_content: unknown;
217
- media_links_use_attachment_id: unknown;
218
217
  file_verifiers_for_quiz_links: unknown;
219
218
  rce_find_replace: unknown;
220
219
  consolidated_media_player: unknown;
@@ -348,7 +347,6 @@ declare class RCEWrapper extends React.Component<RCEWrapperProps, RCEWrapperStat
348
347
  type: string;
349
348
  content: string;
350
349
  };
351
- setFocusAbilityForHeader: (focusable: boolean) => void;
352
350
  componentWillUnmount(): void;
353
351
  wrapOptions(options?: {}): {
354
352
  readonly: boolean | undefined;
@@ -19,6 +19,7 @@ import _pt from "prop-types";
19
19
 
20
20
  import React, { Suspense } from 'react';
21
21
  import { Editor } from '@tinymce/tinymce-react';
22
+ import tinymce from 'tinymce';
22
23
  import _ from 'lodash';
23
24
  import { StoreProvider } from './plugins/shared/StoreContext';
24
25
  import { IconKeyboardShortcutsLine } from '@instructure/ui-icons';
@@ -268,13 +269,11 @@ class RCEWrapper extends React.Component {
268
269
  if (event.code === 'F9' && event.altKey) {
269
270
  event.preventDefault();
270
271
  event.stopPropagation();
271
- this.setFocusAbilityForHeader(true);
272
272
  // @ts-expect-error
273
273
  focusFirstMenuButton(this._elementRef.current);
274
274
  } else if (event.code === 'F10' && event.altKey) {
275
275
  event.preventDefault();
276
276
  event.stopPropagation();
277
- this.setFocusAbilityForHeader(true);
278
277
  // @ts-expect-error
279
278
  focusToolbar(this._elementRef.current);
280
279
  } else if (event.code === 'F8' && event.altKey) {
@@ -329,20 +328,6 @@ class RCEWrapper extends React.Component {
329
328
  tinyapp.setAttribute('tabIndex', '-1');
330
329
  }
331
330
 
332
- // Adds a focusout event listener for handling screen reader navigation focus
333
- const header = this._elementRef.current?.querySelector('.tox-editor-header');
334
- if (header) {
335
- // @ts-expect-error
336
- header.addEventListener('focusout', e => {
337
- // @ts-expect-error
338
- const leavingHeader = !header.contains(e.relatedTarget);
339
- if (leavingHeader) {
340
- this.setFocusAbilityForHeader(false);
341
- }
342
- });
343
- }
344
- this.setFocusAbilityForHeader(false);
345
-
346
331
  // Probably should do this in tinymce.scss, but we only want it in new rce
347
332
  textarea.style.resize = 'none';
348
333
  editor.on('keydown', this.handleKey);
@@ -705,13 +690,6 @@ class RCEWrapper extends React.Component {
705
690
  content: this.mceInstance().getContent()
706
691
  };
707
692
  };
708
- this.setFocusAbilityForHeader = focusable => {
709
- // Sets aria-hidden to prevent screen readers focus in RCE menus and toolbar
710
- const header = this._elementRef.current?.querySelector('.tox-editor-header');
711
- if (header) {
712
- header.setAttribute('aria-hidden', focusable ? 'false' : 'true');
713
- }
714
- };
715
693
  this.handleTextareaChange = () => {
716
694
  if (this.isHidden()) {
717
695
  this.setCode(this.textareaValue());
@@ -853,7 +831,6 @@ class RCEWrapper extends React.Component {
853
831
  new_math_equation_handling = false,
854
832
  explicit_latex_typesetting = false,
855
833
  rce_transform_loaded_content = false,
856
- media_links_use_attachment_id = false,
857
834
  rce_find_replace = false,
858
835
  file_verifiers_for_quiz_links = false,
859
836
  consolidated_media_player = false
@@ -862,7 +839,6 @@ class RCEWrapper extends React.Component {
862
839
  new_math_equation_handling,
863
840
  explicit_latex_typesetting,
864
841
  rce_transform_loaded_content,
865
- media_links_use_attachment_id,
866
842
  file_verifiers_for_quiz_links,
867
843
  rce_find_replace,
868
844
  consolidated_media_player
@@ -1732,7 +1708,7 @@ class RCEWrapper extends React.Component {
1732
1708
  }
1733
1709
  });
1734
1710
  }
1735
- const statusBarFeatures = getStatusBarFeaturesForVariant(this.variant, this.props.ai_text_tools);
1711
+ const statusBarFeatures = getStatusBarFeaturesForVariant(this.variant, this.props.ai_text_tools, tinymce.Env.deviceType.isDesktop());
1736
1712
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("style", null, this.style.css), /*#__PURE__*/React.createElement(StoreProvider, {
1737
1713
  jwt: this.props.trayProps?.jwt,
1738
1714
  refreshToken: this.props.trayProps?.refreshToken,
@@ -1754,7 +1730,7 @@ class RCEWrapper extends React.Component {
1754
1730
  } : undefined,
1755
1731
  onFocus: this.handleFocusRCE,
1756
1732
  onBlur: this.handleBlurRCE
1757
- }, this.state.shouldShowOnFocusButton && /*#__PURE__*/React.createElement(ShowOnFocusButton, {
1733
+ }, this.state.shouldShowOnFocusButton && tinymce.Env.deviceType.isDesktop() && /*#__PURE__*/React.createElement(ShowOnFocusButton, {
1758
1734
  id: `show-on-focus-btn-${this.id}`,
1759
1735
  onClick: this.openKBShortcutModal,
1760
1736
  margin: "xx-small",
@@ -13,7 +13,7 @@ declare class EquationEditorModal extends React.Component<any, any, any> {
13
13
  executeCommand: (cmd: any, advancedCmd: any) => void;
14
14
  handleModalCancel: () => void;
15
15
  handleModalDone: () => void;
16
- renderMathInAdvancedPreview: import("@instructure/debounce").Debounced;
16
+ renderMathInAdvancedPreview: import("@instructure/debounce").Debounced<() => void>;
17
17
  setPreviewElementContent(): void;
18
18
  toggleAdvanced: () => void;
19
19
  toggleAndUpdatePreference: () => void;
@@ -18,7 +18,6 @@
18
18
 
19
19
  import formatMessage from '../../../format-message';
20
20
  import { scaleForHeight, scaleForWidth } from '../shared/DimensionUtils';
21
- import RCEGlobals from '../../RCEGlobals';
22
21
  export const MIN_HEIGHT = 10;
23
22
  export const MIN_WIDTH = 10;
24
23
  export const MIN_WIDTH_VIDEO = 320;
@@ -117,12 +116,10 @@ export function fromVideoEmbed($element) {
117
116
  // bad json?
118
117
  }
119
118
  videoOptions.videoSize = imageSizeFromKnownOptions(videoOptions);
120
- if (RCEGlobals.getFeatures().media_links_use_attachment_id) {
121
- const source = $videoIframe.getAttribute('src');
122
- const matches = source?.match(/\/media_attachments_iframe\/(\d+)/);
123
- if (matches) {
124
- videoOptions.attachmentId = matches[1];
125
- }
119
+ const source = $videoIframe.getAttribute('src');
120
+ const matches = source?.match(/\/media_attachments_iframe\/(\d+)/);
121
+ if (matches) {
122
+ videoOptions.attachmentId = matches[1];
126
123
  }
127
124
  return videoOptions;
128
125
  }
@@ -21,7 +21,6 @@ import ReactDOM from 'react-dom';
21
21
  import bridge from '../../../../bridge';
22
22
  import { asAudioElement, findMediaPlayerIframe } from '../../shared/ContentSelection';
23
23
  import AudioOptionsTray from '.';
24
- import RCEGlobals from '../../../RCEGlobals';
25
24
  export const CONTAINER_ID = 'instructure-audio-options-tray-container';
26
25
  export default class TrayController {
27
26
  constructor() {
@@ -74,7 +73,7 @@ export default class TrayController {
74
73
  return elem.parentNode.removeChild(elem);
75
74
  }
76
75
  _applyAudioOptions(audioOptions) {
77
- const hasAttachmentId = RCEGlobals.getFeatures().media_links_use_attachment_id && audioOptions.attachment_id;
76
+ const hasAttachmentId = audioOptions.attachment_id;
78
77
  if (!hasAttachmentId && (!audioOptions.media_object_id || audioOptions.media_object_id === 'undefined')) {
79
78
  return;
80
79
  }
@@ -118,11 +118,9 @@ export default class TrayController {
118
118
  const data = {
119
119
  media_object_id: videoOptions.media_object_id,
120
120
  title: videoOptions.titleText,
121
- subtitles: videoOptions.subtitles
121
+ subtitles: videoOptions.subtitles,
122
+ attachment_id: videoOptions.attachment_id
122
123
  };
123
- if (RCEGlobals.getFeatures().media_links_use_attachment_id) {
124
- data.attachment_id = videoOptions.attachment_id;
125
- }
126
124
 
127
125
  // If the video just edited came from a file uploaded to canvas
128
126
  // and not notorious, we probably don't have a media_object_id.
@@ -20,7 +20,6 @@ import { fromImageEmbed, fromVideoEmbed } from '../instructure_image/ImageEmbedO
20
20
  import { isOnlyTextSelected } from '../../contentInsertionUtils';
21
21
  import formatMessage from '../../../format-message';
22
22
  import { isStudioEmbeddedMedia } from './StudioLtiSupportUtils';
23
- import RCEGlobals from '../../RCEGlobals';
24
23
  import { parseUrlPath } from '../../../util/url-util';
25
24
  const FILE_DOWNLOAD_PATH_REGEX = /^\/(courses\/\d+\/)?files\/\d+\/download$/;
26
25
  export const LINK_TYPE = 'link';
@@ -124,12 +123,10 @@ export function asAudioElement($element) {
124
123
  // eslint-disable-next-line no-empty
125
124
  } catch (e) {}
126
125
  }
127
- if (RCEGlobals.getFeatures().media_links_use_attachment_id) {
128
- const source = $audioIframe.getAttribute('src');
129
- const matches = source?.match(/\/media_attachments_iframe\/(\d+)/);
130
- if (matches) {
131
- audioOptions.attachmentId = matches[1];
132
- }
126
+ const source = $audioIframe.getAttribute('src');
127
+ const matches = source?.match(/\/media_attachments_iframe\/(\d+)/);
128
+ if (matches) {
129
+ audioOptions.attachmentId = matches[1];
133
130
  }
134
131
  return audioOptions;
135
132
  }
@@ -83,7 +83,7 @@ export function mediaPlayerURLFromFile(file, canvasOrigin) {
83
83
  if (typeof content_type !== 'string') throw new Error('Invalid content type');
84
84
  const type = content_type.replace(/\/.*$/, '');
85
85
  const baseOrigin = canvasOrigin !== null && canvasOrigin !== void 0 ? canvasOrigin : window.location.origin;
86
- if (RCEGlobals.getFeatures()?.media_links_use_attachment_id && isAudioOrVideo(content_type) && file.id) {
86
+ if (isAudioOrVideo(content_type) && file.id) {
87
87
  const url = new URL(`/media_attachments_iframe/${file.id}`, baseOrigin);
88
88
  url.searchParams.set('type', type);
89
89
  url.searchParams.set('embedded', 'true');
@@ -27,60 +27,58 @@ const pendingInstanceCallbacks = [];
27
27
  const container = document.createElement('div');
28
28
  container.className = 'tinymce-a11y-checker-container';
29
29
  document.body.appendChild(container);
30
- tinymce.create('tinymce.plugins.AccessibilityChecker', {
31
- init(ed) {
32
- ed.addCommand('openAccessibilityChecker', function (ui, {
33
- done,
34
- config,
35
- additionalRules,
36
- mountNode,
37
- triggerElementId,
38
- onFixError
39
- }) {
40
- if (!isCheckerOpen) {
41
- ReactDOM.render(/*#__PURE__*/React.createElement(Checker, {
42
- getBody: ed.getBody.bind(ed),
43
- editor: ed,
44
- additionalRules: additionalRules,
45
- mountNode: mountNode,
46
- onClose: () => {
47
- isCheckerOpen = false;
48
- if (triggerElementId) {
49
- const button = document.querySelectorAll(`[data-btn-id=${triggerElementId}]`);
50
- button[0]?.focus();
51
- }
52
- },
53
- onFixError: onFixError
54
- }), container, function () {
55
- // this is a workaround for react 16 since ReactDOM.render is not
56
- // guaranteed to return the instance synchronously (especially if called
57
- // within another component's lifecycle method eg: componentDidMount). see:
58
- // https://github.com/facebook/react/issues/10309#issuecomment-318434635
59
- instance = this;
60
- if (config) getInstance(instance => instance.setConfig(config));
61
- pendingInstanceCallbacks.forEach(cb => cb(instance));
62
- instance.check(done);
63
- });
64
- isCheckerOpen = true;
65
- }
66
- });
67
- ed.addCommand('checkAccessibility', function (ui, {
68
- done,
69
- config,
70
- additionalRules
71
- }) {
72
- checkNode(ed.getBody(), done, config, additionalRules);
73
- });
74
- ed.ui.registry.addButton('check_a11y', {
75
- title: formatMessage('Check Accessibility'),
76
- onAction: _ => ed.execCommand('openAccessibilityChecker'),
77
- icon: 'a11y'
78
- });
79
- }
80
- });
30
+ const AccessibilityChecker = function (ed) {
31
+ ed.addCommand('openAccessibilityChecker', function (ui, {
32
+ done,
33
+ config,
34
+ additionalRules,
35
+ mountNode,
36
+ triggerElementId,
37
+ onFixError
38
+ }) {
39
+ if (!isCheckerOpen) {
40
+ ReactDOM.render(/*#__PURE__*/React.createElement(Checker, {
41
+ getBody: ed.getBody.bind(ed),
42
+ editor: ed,
43
+ additionalRules: additionalRules,
44
+ mountNode: mountNode,
45
+ onClose: () => {
46
+ isCheckerOpen = false;
47
+ if (triggerElementId) {
48
+ const button = document.querySelectorAll(`[data-btn-id=${triggerElementId}]`);
49
+ button[0]?.focus();
50
+ }
51
+ },
52
+ onFixError: onFixError
53
+ }), container, function () {
54
+ // this is a workaround for react 16 since ReactDOM.render is not
55
+ // guaranteed to return the instance synchronously (especially if called
56
+ // within another component's lifecycle method eg: componentDidMount). see:
57
+ // https://github.com/facebook/react/issues/10309#issuecomment-318434635
58
+ instance = this;
59
+ if (config) getInstance(instance => instance.setConfig(config));
60
+ pendingInstanceCallbacks.forEach(cb => cb(instance));
61
+ instance.check(done);
62
+ });
63
+ isCheckerOpen = true;
64
+ }
65
+ });
66
+ ed.addCommand('checkAccessibility', function (ui, {
67
+ done,
68
+ config,
69
+ additionalRules
70
+ }) {
71
+ checkNode(ed.getBody(), done, config, additionalRules);
72
+ });
73
+ ed.ui.registry.addButton('check_a11y', {
74
+ title: formatMessage('Check Accessibility'),
75
+ onAction: _ => ed.execCommand('openAccessibilityChecker'),
76
+ icon: 'a11y'
77
+ });
78
+ };
81
79
 
82
80
  // Register plugin
83
- tinymce.PluginManager.add('a11y_checker', tinymce.plugins.AccessibilityChecker);
81
+ tinymce.PluginManager.add('a11y_checker', AccessibilityChecker);
84
82
  export function getInstance(cb) {
85
83
  if (instance != null) {
86
84
  return cb(instance);
package/es/rce/style.js CHANGED
@@ -62,44 +62,44 @@ export default function buildStyle() {
62
62
  break;
63
63
  case 'canvas-a11y':
64
64
  case 'canvas-high-contrast':
65
- themeCanvasButtonBackground = variables.colors.backgroundLight;
66
- themeCanvasSecondaryButtonBorderColor = variables.colors.borderMedium;
65
+ themeCanvasButtonBackground = variables.colors.contrasts.grey1111;
66
+ themeCanvasSecondaryButtonBorderColor = variables.colors.contrasts.grey1214;
67
67
  themeCanvasLinkDecoration = 'underline';
68
- themeCanvasFocusBorderColor = variables.colors.borderBrand;
68
+ themeCanvasFocusBorderColor = variables.colors.contrasts.blue4570;
69
69
  themeCanvasFocusBoxShadow = `0 0 0 2px ${variables.colors.brand}`;
70
70
  themeCanvasBrandColor = variables.colors.brand;
71
71
  break;
72
72
  default:
73
73
  themeCanvasLinkColor = variables.colors.link;
74
74
  themeCanvasLinkDecoration = 'none';
75
- themeCanvasTextColor = variables.colors.textDarkest;
75
+ themeCanvasTextColor = variables.colors.contrasts.grey125125;
76
76
  themeCanvasBrandColor = variables.colors.brand;
77
77
  themeCanvasFocusBorderColor = variables.borders.brand;
78
78
  themeCanvasFocusBoxShadow = `0 0 0 2px ${variables.colors.brand}`;
79
79
  themeCanvasEnabledColor = variables.borders.brand;
80
- themeCanvasPrimaryButtonBackground = variables.colors.backgroundBrand;
81
- themeCanvasPrimaryButtonColor = variables.colors.textLightest;
82
- themeCanvasPrimaryButtonHoverBackground = darken(variables.colors.backgroundBrand, 10);
83
- themeActiveMenuItemBackground = variables.colors.backgroundBrand;
84
- themeActiveMenuItemLabelColor = variables.colors.textLightest;
80
+ themeCanvasPrimaryButtonBackground = variables.colors.contrasts.blue4570;
81
+ themeCanvasPrimaryButtonColor = variables.colors.contrasts.white1010;
82
+ themeCanvasPrimaryButtonHoverBackground = darken(variables.colors.contrasts.blue4570, 10);
83
+ themeActiveMenuItemBackground = variables.colors.contrasts.blue4570;
84
+ themeActiveMenuItemLabelColor = variables.colors.contrasts.white1010;
85
85
  themeTableSelectorHighlightColor = alpha(lighten(variables.colors.brand, 10), 50);
86
- themeCanvasButtonBackground = variables.colors.backgroundLightest;
87
- themeCanvasSecondaryButtonBorderColor = darken(variables.colors.backgroundLight, 10);
86
+ themeCanvasButtonBackground = variables.colors.contrasts.white1010;
87
+ themeCanvasSecondaryButtonBorderColor = darken(variables.colors.contrasts.grey1111, 10);
88
88
  break;
89
89
  }
90
90
  const classNames = {
91
91
  root: 'canvas-rce__skins--root'
92
92
  };
93
- const toolbarButtonHoverBackgroundConst = darken(variables.colors.backgroundLightest, 5);
93
+ const toolbarButtonHoverBackgroundConst = darken(variables.colors.contrasts.white1010, 5);
94
94
  const tinySplitButtonChevronHoverBackgroundConst = darken(toolbarButtonHoverBackgroundConst, 10);
95
95
  const theme = {
96
96
  canvasBackgroundColor: variables.colors.white,
97
97
  canvasTextColor: themeCanvasTextColor,
98
- canvasErrorColor: variables.colors.textDanger,
99
- canvasWarningColor: variables.colors.textWarning,
100
- canvasInfoColor: variables.colors.textInfo,
101
- canvasSuccessColor: variables.colors.textSuccess,
102
- canvasBorderColor: variables.colors.borderMedium,
98
+ canvasErrorColor: variables.colors.contrasts.red4570,
99
+ canvasWarningColor: variables.colors.contrasts.orange4570,
100
+ canvasInfoColor: variables.colors.contrasts.blue4570,
101
+ canvasSuccessColor: variables.colors.contrasts.green4570,
102
+ canvasBorderColor: variables.colors.contrasts.grey1214,
103
103
  toolbarButtonHoverBackground: toolbarButtonHoverBackgroundConst,
104
104
  // copied from INSTUI "light" Button
105
105
  tinySplitButtonChevronHoverBackground: tinySplitButtonChevronHoverBackgroundConst,
@@ -112,10 +112,10 @@ export default function buildStyle() {
112
112
  // the instui default button
113
113
  canvasButtonBackground: themeCanvasButtonBackground,
114
114
  canvasButtonBorderColor: 'transparent',
115
- canvasButtonColor: variables.colors.textDarkest,
116
- canvasButtonHoverBackground: variables.colors.backgroundLightest,
115
+ canvasButtonColor: variables.colors.contrasts.grey125125,
116
+ canvasButtonHoverBackground: variables.colors.contrasts.white1010,
117
117
  canvasButtonHoverColor: variables.colors.brand,
118
- canvasButtonActiveBackground: variables.colors.backgroundLightest,
118
+ canvasButtonActiveBackground: variables.colors.contrasts.white1010,
119
119
  canvasButtonFontWeight: variables.typography.fontWeightNormal,
120
120
  canvasButtonFontSize: variables.typography.fontSizeMedium,
121
121
  canvasButtonLineHeight: variables.forms.inputHeightMedium,
@@ -125,19 +125,19 @@ export default function buildStyle() {
125
125
  canvasPrimaryButtonColor: themeCanvasPrimaryButtonColor,
126
126
  canvasPrimaryButtonBorderColor: 'transparent',
127
127
  canvasPrimaryButtonHoverBackground: themeCanvasPrimaryButtonHoverBackground,
128
- canvasPrimaryButtonHoverColor: variables.colors.textLightest,
128
+ canvasPrimaryButtonHoverColor: variables.colors.contrasts.white1010,
129
129
  // the instui secondary button
130
- canvasSecondaryButtonBackground: variables.colors.backgroundLight,
131
- canvasSecondaryButtonColor: variables.colors.textDarkest,
130
+ canvasSecondaryButtonBackground: variables.colors.contrasts.grey1111,
131
+ canvasSecondaryButtonColor: variables.colors.contrasts.grey125125,
132
132
  canvasSecondaryButtonBorderColor: themeCanvasSecondaryButtonBorderColor,
133
- canvasSecondaryButtonHoverBackground: darken(variables.colors.backgroundLight, 10),
134
- canvasSecondaryButtonHoverColor: variables.colors.textDarkest,
133
+ canvasSecondaryButtonHoverBackground: darken(variables.colors.contrasts.grey1111, 10),
134
+ canvasSecondaryButtonHoverColor: variables.colors.contrasts.grey125125,
135
135
  canvasFocusBorderColor: themeCanvasFocusBorderColor,
136
136
  canvasFocusBorderWidth: variables.borders.widthSmall,
137
137
  // canvas really uses widthMedium
138
138
  canvasFocusBoxShadow: themeCanvasFocusBoxShadow,
139
139
  canvasEnabledColor: themeCanvasEnabledColor,
140
- canvasEnabledBoxShadow: `inset 0 0 0.1875rem 0.0625rem ${darken(variables.colors.borderLightest, 25)}`,
140
+ canvasEnabledBoxShadow: `inset 0 0 0.1875rem 0.0625rem ${darken(variables.colors.contrasts.white1010, 25)}`,
141
141
  canvasFontFamily: variables.typography.fontFamily,
142
142
  canvasFontSize: '1rem',
143
143
  canvasFontSizeSmall: variables.typography.fontSizeSmall,
@@ -148,14 +148,14 @@ export default function buildStyle() {
148
148
  canvasModalHeadingFontWeight: variables.typography.fontWeightNormal,
149
149
  canvasModalBodyPadding: variables.spacing.medium,
150
150
  canvasModalFooterPadding: variables.spacing.small,
151
- canvasModalFooterBackground: variables.colors.backgroundLight,
151
+ canvasModalFooterBackground: variables.colors.contrasts.grey1111,
152
152
  canvasFormElementMargin: `0 0 ${variables.spacing.medium} 0`,
153
- canvasFormElementLabelColor: variables.colors.textDarkest,
153
+ canvasFormElementLabelColor: variables.colors.contrasts.grey125125,
154
154
  canvasFormElementLabelMargin: `0 0 ${variables.spacing.small} 0`,
155
155
  canvasFormElementLabelFontSize: variables.typography.fontSizeMedium,
156
156
  canvasFormElementLabelFontWeight: variables.typography.fontWeightBold,
157
157
  // a11y button badge
158
- canvasBadgeBackgroundColor: variables.colors.textInfo
158
+ canvasBadgeBackgroundColor: variables.colors.contrasts.blue4570
159
159
  };
160
160
  const css = `
161
161
  .${classNames.root} {
package/es/rcs/api.d.ts CHANGED
@@ -63,7 +63,10 @@ declare class RceApiSource {
63
63
  bookmark: any;
64
64
  files: any;
65
65
  }>;
66
- fetchMedia(props: any): Promise<any>;
66
+ fetchMedia(props: any): Promise<{
67
+ bookmark: any;
68
+ files: any;
69
+ }>;
67
70
  fetchFiles(uri: any): Promise<{
68
71
  bookmark: any;
69
72
  files: any;
package/es/rcs/api.js CHANGED
@@ -21,7 +21,6 @@ import { saveClosedCaptions, saveClosedCaptionsForAttachment, CONSTANTS } from '
21
21
  import { downloadToWrap, fixupFileUrl } from '../common/fileUrl';
22
22
  import alertHandler from '../rce/alertHandler';
23
23
  import buildError from './buildError';
24
- import RCEGlobals from '../rce/RCEGlobals';
25
24
  import { parseUrlPath } from '../util/url-util';
26
25
  export function headerFor(jwt) {
27
26
  return {
@@ -164,18 +163,15 @@ class RceApiSource {
164
163
  fetchMedia(props) {
165
164
  const media = props.media[props.contextType];
166
165
  const uri = media.bookmark || this.uriFor('media', props);
167
- if (RCEGlobals.getFeatures()?.media_links_use_attachment_id) {
168
- return this.apiFetch(uri, headerFor(this.jwt)).then(({
166
+ return this.apiFetch(uri, headerFor(this.jwt)).then(({
167
+ bookmark,
168
+ files
169
+ }) => {
170
+ return {
169
171
  bookmark,
170
- files
171
- }) => {
172
- return {
173
- bookmark,
174
- files: files.map(f => fixupFileUrl(props.contextType, props.contextId, f, this.canvasOrigin))
175
- };
176
- });
177
- }
178
- return this.apiFetch(uri, headerFor(this.jwt));
172
+ files: files.map(f => fixupFileUrl(props.contextType, props.contextId, f, this.canvasOrigin))
173
+ };
174
+ });
179
175
  }
180
176
  fetchFiles(uri) {
181
177
  return this.fetchPage(uri).then(({
@@ -219,7 +215,7 @@ class RceApiSource {
219
215
  title,
220
216
  attachment_id
221
217
  }) {
222
- const uri = RCEGlobals.getFeatures()?.media_links_use_attachment_id && attachment_id ? `${this.baseUri('media_attachments', apiProps.host)}/${attachment_id}?user_entered_title=${encodeURIComponent(title)}` : `${this.baseUri('media_objects', apiProps.host)}/${media_object_id}?user_entered_title=${encodeURIComponent(title)}`;
218
+ const uri = attachment_id ? `${this.baseUri('media_attachments', apiProps.host)}/${attachment_id}?user_entered_title=${encodeURIComponent(title)}` : `${this.baseUri('media_objects', apiProps.host)}/${media_object_id}?user_entered_title=${encodeURIComponent(title)}`;
223
219
  return this.apiPost(uri, headerFor(this.jwt), null, 'PUT');
224
220
  }
225
221
 
@@ -1123,6 +1123,9 @@ const locale = {
1123
1123
  "intersection_cd4590e4": {
1124
1124
  "message": "Intersection"
1125
1125
  },
1126
+ "invalid_description_991e23bb": {
1127
+ "message": "Invalid description"
1128
+ },
1126
1129
  "invalid_entry_f7d2a0f5": {
1127
1130
  "message": "Invalid entry."
1128
1131
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/canvas-rce",
3
- "version": "6.0.0",
3
+ "version": "7.0.0",
4
4
  "description": "A component wrapping Canvas's usage of Tinymce",
5
5
  "main": "es/index.js",
6
6
  "types": "es/index.d.ts",
@@ -36,7 +36,7 @@
36
36
  "build:canvas": "yarn run build",
37
37
  "build:watch": "yarn clean:es && yarn build:es --watch",
38
38
  "build:cafe": "wp --config webpack.testcafe.config.js",
39
- "prepublishOnly": "yarn build",
39
+ "prepublishOnly": "yarn build && yarn test",
40
40
  "fmt:check": "biome check",
41
41
  "fmt:fix": "biome format --write",
42
42
  "clean": "rm -rf lib && yarn clean:es && rm -rf coverage && yarn demo:clean",
@@ -71,66 +71,66 @@
71
71
  "instrument": false
72
72
  },
73
73
  "dependencies": {
74
- "@instructure/canvas-theme": "9.10.1",
74
+ "@instructure/canvas-theme": "10.11.0",
75
75
  "@instructure/canvas-media": "*",
76
- "@instructure/debounce": "9.10.1",
77
- "@instructure/emotion": "9.10.1",
76
+ "@instructure/debounce": "10.11.0",
77
+ "@instructure/emotion": "10.11.0",
78
78
  "@instructure/k5uploader": "*",
79
79
  "@instructure/media-capture": "^9.0.0",
80
- "@instructure/theme-registry": "9.10.1",
81
- "@instructure/ui-a11y-content": "9.10.1",
82
- "@instructure/ui-a11y-utils": "9.10.1",
83
- "@instructure/ui-alerts": "9.10.1",
84
- "@instructure/ui-avatar": "9.10.1",
85
- "@instructure/ui-badge": "9.10.1",
86
- "@instructure/ui-billboard": "9.10.1",
87
- "@instructure/ui-buttons": "9.10.1",
88
- "@instructure/ui-checkbox": "9.10.1",
89
- "@instructure/ui-source-code-editor": "9.10.1",
90
- "@instructure/ui-color-picker": "8.56.4",
91
- "@instructure/ui-color-utils": "9.10.1",
92
- "@instructure/ui-file-drop": "9.10.1",
93
- "@instructure/ui-flex": "9.10.1",
94
- "@instructure/ui-focusable": "9.10.1",
95
- "@instructure/ui-form-field": "9.10.1",
96
- "@instructure/ui-grid": "9.10.1",
97
- "@instructure/ui-heading": "9.10.1",
98
- "@instructure/ui-icons": "9.10.1",
99
- "@instructure/ui-img": "9.10.1",
100
- "@instructure/ui-link": "9.10.1",
101
- "@instructure/ui-list": "9.10.1",
80
+ "@instructure/theme-registry": "10.11.0",
81
+ "@instructure/ui-a11y-content": "10.11.0",
82
+ "@instructure/ui-a11y-utils": "10.11.0",
83
+ "@instructure/ui-alerts": "10.11.0",
84
+ "@instructure/ui-avatar": "10.11.0",
85
+ "@instructure/ui-badge": "10.11.0",
86
+ "@instructure/ui-billboard": "10.11.0",
87
+ "@instructure/ui-buttons": "10.11.0",
88
+ "@instructure/ui-checkbox": "10.11.0",
89
+ "@instructure/ui-source-code-editor": "10.11.0",
90
+ "@instructure/ui-color-picker": "10.11.0",
91
+ "@instructure/ui-color-utils": "10.11.0",
92
+ "@instructure/ui-file-drop": "10.11.0",
93
+ "@instructure/ui-flex": "10.11.0",
94
+ "@instructure/ui-focusable": "10.11.0",
95
+ "@instructure/ui-form-field": "10.11.0",
96
+ "@instructure/ui-grid": "10.11.0",
97
+ "@instructure/ui-heading": "10.11.0",
98
+ "@instructure/ui-icons": "10.11.0",
99
+ "@instructure/ui-img": "10.11.0",
100
+ "@instructure/ui-link": "10.11.0",
101
+ "@instructure/ui-list": "10.11.0",
102
102
  "@instructure/ui-media-player": "^9.0.0",
103
- "@instructure/ui-menu": "9.10.1",
104
- "@instructure/ui-modal": "9.10.1",
105
- "@instructure/ui-motion": "9.10.1",
106
- "@instructure/ui-number-input": "9.10.1",
107
- "@instructure/ui-overlays": "9.10.1",
108
- "@instructure/ui-pagination": "9.10.1",
109
- "@instructure/ui-pill": "9.10.1",
110
- "@instructure/ui-popover": "9.10.1",
111
- "@instructure/ui-radio-input": "9.10.1",
112
- "@instructure/ui-simple-select": "9.10.1",
113
- "@instructure/ui-spinner": "9.10.1",
114
- "@instructure/ui-svg-images": "9.10.1",
115
- "@instructure/ui-table": "9.10.1",
116
- "@instructure/ui-tabs": "9.10.1",
117
- "@instructure/ui-text-area": "9.10.1",
118
- "@instructure/ui-text-input": "9.10.1",
119
- "@instructure/ui-text": "9.10.1",
120
- "@instructure/ui-themes": "9.10.1",
121
- "@instructure/ui-toggle-details": "9.10.1",
122
- "@instructure/ui-tooltip": "9.10.1",
123
- "@instructure/ui-tray": "9.10.1",
124
- "@instructure/ui-tree-browser": "9.10.1",
125
- "@instructure/ui-truncate-text": "9.10.1",
126
- "@instructure/ui-utils": "9.10.1",
127
- "@instructure/ui-view": "9.10.1",
128
- "@instructure/uid": "9.10.1",
103
+ "@instructure/ui-menu": "10.11.0",
104
+ "@instructure/ui-modal": "10.11.0",
105
+ "@instructure/ui-motion": "10.11.0",
106
+ "@instructure/ui-number-input": "10.11.0",
107
+ "@instructure/ui-overlays": "10.11.0",
108
+ "@instructure/ui-pagination": "10.11.0",
109
+ "@instructure/ui-pill": "10.11.0",
110
+ "@instructure/ui-popover": "10.11.0",
111
+ "@instructure/ui-radio-input": "10.11.0",
112
+ "@instructure/ui-simple-select": "10.11.0",
113
+ "@instructure/ui-spinner": "10.11.0",
114
+ "@instructure/ui-svg-images": "10.11.0",
115
+ "@instructure/ui-table": "10.11.0",
116
+ "@instructure/ui-tabs": "10.11.0",
117
+ "@instructure/ui-text-area": "10.11.0",
118
+ "@instructure/ui-text-input": "10.11.0",
119
+ "@instructure/ui-text": "10.11.0",
120
+ "@instructure/ui-themes": "10.11.0",
121
+ "@instructure/ui-toggle-details": "10.11.0",
122
+ "@instructure/ui-tooltip": "10.11.0",
123
+ "@instructure/ui-tray": "10.11.0",
124
+ "@instructure/ui-tree-browser": "10.11.0",
125
+ "@instructure/ui-truncate-text": "10.11.0",
126
+ "@instructure/ui-utils": "10.11.0",
127
+ "@instructure/ui-view": "10.11.0",
128
+ "@instructure/uid": "10.11.0",
129
129
  "@sheerun/mutationobserver-shim": "^0.3.2",
130
130
  "@types/tinycolor2": "^1.4.6",
131
131
  "@tinymce/tinymce-react": "~3.8.4",
132
132
  "aphrodite": "^2",
133
- "axios": "^0.21.1",
133
+ "axios": "^0.28.0",
134
134
  "bloody-offset": "0.0.0",
135
135
  "crypto-es": "^2.0.4",
136
136
  "classnames": "^2.2.5",