@instructure/canvas-rce 5.13.2 → 5.13.5

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 (128) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/es/bridge/Bridge.js +0 -4
  3. package/es/defaultTinymceConfig.js +1 -1
  4. package/es/index.js +2 -0
  5. package/es/rce/RCE.js +3 -1
  6. package/es/rce/RCEVariants.js +121 -0
  7. package/es/rce/RCEWrapper.js +96 -47
  8. package/es/rce/RCEWrapperProps.js +5 -2
  9. package/es/rce/StatusBar.js +67 -17
  10. package/es/rce/plugins/instructure_icon_maker/components/CreateIconMakerForm/Footer.js +1 -0
  11. package/es/rce/plugins/instructure_icon_maker/components/IconMakerTray.js +6 -1
  12. package/es/rce/plugins/instructure_rce_external_tools/RceToolWrapper.js +9 -9
  13. package/es/rce/plugins/instructure_rce_external_tools/components/ExternalToolDialog/ExternalToolDialog.js +25 -3
  14. package/es/rce/plugins/instructure_rce_external_tools/plugin.js +4 -1
  15. package/es/rce/plugins/shared/CanvasContentTray.js +6 -158
  16. package/es/rce/plugins/shared/Filter.js +0 -17
  17. package/es/rce/plugins/shared/FixedContentTray.js +7 -4
  18. package/es/rce/plugins/shared/ImageOptionsForm.js +3 -2
  19. package/es/rce/plugins/shared/Upload/CanvasContentPanel.js +160 -0
  20. package/es/rce/plugins/shared/Upload/ComputerPanel.js +1 -0
  21. package/es/rce/plugins/shared/Upload/PanelFilter.js +144 -0
  22. package/es/rce/plugins/shared/Upload/UploadFile.js +10 -2
  23. package/es/rce/plugins/shared/Upload/UploadFileModal.js +47 -11
  24. package/es/rce/plugins/shared/Upload/UsageRightsSelectBox.js +20 -20
  25. package/es/rce/plugins/shared/Upload/index.js +19 -0
  26. package/es/rce/plugins/shared/ai_tools/AIResponseModal.js +70 -0
  27. package/es/rce/plugins/shared/ai_tools/AIToolsTray.js +510 -0
  28. package/es/rce/plugins/shared/ai_tools/aiicons.js +59 -0
  29. package/es/rce/plugins/shared/ai_tools/index.js +20 -0
  30. package/es/rce/plugins/shared/canvasContentUtils.js +190 -0
  31. package/es/rce/plugins/shared/do-fetch-api-effect/defaultFetchOptions.js +31 -0
  32. package/es/rce/plugins/shared/do-fetch-api-effect/doFetchApi.js +85 -0
  33. package/es/rce/plugins/shared/do-fetch-api-effect/get-cookie.js +29 -0
  34. package/es/rce/plugins/shared/do-fetch-api-effect/index.js +22 -0
  35. package/es/rce/plugins/shared/do-fetch-api-effect/parse-link-header.js +116 -0
  36. package/es/rce/plugins/shared/do-fetch-api-effect/query-string-encoding.js +51 -0
  37. package/es/rce/plugins/shared/useFilterSettings.js +35 -0
  38. package/es/sidebar/actions/upload.js +5 -2
  39. package/es/translations/locales/ar.js +64 -1
  40. package/es/translations/locales/ca.js +65 -2
  41. package/es/translations/locales/cy.js +64 -1
  42. package/es/translations/locales/da-x-k12.js +65 -2
  43. package/es/translations/locales/da.js +65 -2
  44. package/es/translations/locales/de.js +65 -2
  45. package/es/translations/locales/el.js +9 -0
  46. package/es/translations/locales/en-AU-x-unimelb.js +65 -2
  47. package/es/translations/locales/en-GB-x-ukhe.js +65 -2
  48. package/es/translations/locales/en.js +65 -2
  49. package/es/translations/locales/en_AU.js +65 -2
  50. package/es/translations/locales/en_CA.js +65 -2
  51. package/es/translations/locales/en_CY.js +65 -2
  52. package/es/translations/locales/en_GB.js +65 -2
  53. package/es/translations/locales/es.js +64 -1
  54. package/es/translations/locales/es_ES.js +64 -1
  55. package/es/translations/locales/fa_IR.js +9 -0
  56. package/es/translations/locales/fi.js +64 -1
  57. package/es/translations/locales/fr.js +65 -2
  58. package/es/translations/locales/fr_CA.js +65 -2
  59. package/es/translations/locales/ga.js +62 -2
  60. package/es/translations/locales/he.js +9 -0
  61. package/es/translations/locales/hi.js +119 -2
  62. package/es/translations/locales/ht.js +65 -2
  63. package/es/translations/locales/hu.js +15 -3
  64. package/es/translations/locales/hy.js +9 -0
  65. package/es/translations/locales/id.js +65 -2
  66. package/es/translations/locales/is.js +65 -2
  67. package/es/translations/locales/it.js +65 -2
  68. package/es/translations/locales/ja.js +65 -2
  69. package/es/translations/locales/ko.js +3 -0
  70. package/es/translations/locales/mi.js +65 -2
  71. package/es/translations/locales/ms.js +64 -1
  72. package/es/translations/locales/nb-x-k12.js +65 -2
  73. package/es/translations/locales/nb.js +65 -2
  74. package/es/translations/locales/nl.js +65 -2
  75. package/es/translations/locales/nn.js +15 -3
  76. package/es/translations/locales/pl.js +64 -1
  77. package/es/translations/locales/pt.js +64 -1
  78. package/es/translations/locales/pt_BR.js +65 -2
  79. package/es/translations/locales/ru.js +64 -1
  80. package/es/translations/locales/sl.js +65 -2
  81. package/es/translations/locales/sv-x-k12.js +65 -2
  82. package/es/translations/locales/sv.js +65 -2
  83. package/es/translations/locales/th.js +64 -1
  84. package/es/translations/locales/tr.js +9 -0
  85. package/es/translations/locales/uk_UA.js +9 -0
  86. package/es/translations/locales/vi.js +64 -1
  87. package/es/translations/locales/zh-Hans.js +64 -1
  88. package/es/translations/locales/zh-Hant.js +65 -2
  89. package/es/translations/locales/zh.js +64 -1
  90. package/es/translations/locales/zh_HK.js +65 -2
  91. package/es/translations/tinymce/ar_SA.js +4 -0
  92. package/es/translations/tinymce/bg_BG.js +4 -0
  93. package/es/translations/tinymce/ca.js +4 -0
  94. package/es/translations/tinymce/cs.js +4 -0
  95. package/es/translations/tinymce/cy.js +4 -0
  96. package/es/translations/tinymce/da.js +4 -0
  97. package/es/translations/tinymce/de.js +4 -0
  98. package/es/translations/tinymce/el.js +4 -0
  99. package/es/translations/tinymce/es.js +4 -0
  100. package/es/translations/tinymce/fa_IR.js +4 -0
  101. package/es/translations/tinymce/fr_FR.js +4 -0
  102. package/es/translations/tinymce/ga.js +4 -0
  103. package/es/translations/tinymce/he_IL.js +4 -0
  104. package/es/translations/tinymce/hu_HU.js +4 -0
  105. package/es/translations/tinymce/hy.js +4 -0
  106. package/es/translations/tinymce/id.js +4 -0
  107. package/es/translations/tinymce/it.js +4 -0
  108. package/es/translations/tinymce/ja.js +4 -0
  109. package/es/translations/tinymce/ko_KR.js +4 -0
  110. package/es/translations/tinymce/nb_NO.js +4 -0
  111. package/es/translations/tinymce/nl.js +4 -0
  112. package/es/translations/tinymce/pl.js +4 -0
  113. package/es/translations/tinymce/pt_BR.js +4 -0
  114. package/es/translations/tinymce/pt_PT.js +4 -0
  115. package/es/translations/tinymce/ro.js +4 -0
  116. package/es/translations/tinymce/ru.js +4 -0
  117. package/es/translations/tinymce/ru_RU.js +5 -1
  118. package/es/translations/tinymce/sl.js +4 -0
  119. package/es/translations/tinymce/sr.js +4 -0
  120. package/es/translations/tinymce/sv_SE.js +4 -0
  121. package/es/translations/tinymce/th.js +5 -1
  122. package/es/translations/tinymce/tr_TR.js +4 -0
  123. package/es/translations/tinymce/uk_UA.js +4 -0
  124. package/es/translations/tinymce/vi_VN.js +4 -0
  125. package/es/translations/tinymce/zh_CN.js +4 -0
  126. package/es/translations/tinymce/zh_TW.js +4 -0
  127. package/jest/jest-setup.js +1 -0
  128. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,30 @@ 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
+ ## 5.13.4 - 2024-08-12
9
+
10
+ ### Fixed
11
+
12
+ - RCE "Lato Extended" now properly uses the "Lato Extended" font
13
+
14
+ ## 5.13.4 - 2024-08-12
15
+
16
+ ### Changed
17
+
18
+ - Icon Maker tray now stays open until the user closes it with the close button
19
+
20
+ ## 5.13.3 - 2024-07-22
21
+
22
+ ### Fixed
23
+
24
+ - Icon Maker tray now stays open while an image upload modal is present
25
+
26
+ ## 5.13.2 - 2024-06-26
27
+
28
+ ### Changed
29
+
30
+ - Removed polyfill.io reference from README
31
+
8
32
  ## 5.13.1 - 2024-06-03
9
33
 
10
34
  ### Changed
@@ -252,8 +252,6 @@ export default class Bridge {
252
252
  const result = this.focusedEditor.insertImage(image);
253
253
  (_this$controller4 = this.controller(this.focusedEditor.id)) === null || _this$controller4 === void 0 ? void 0 : _this$controller4.hideTray();
254
254
  return result;
255
- } else {
256
- console.warn('clicked sidebar image without a focused editor');
257
255
  }
258
256
  }
259
257
 
@@ -264,8 +262,6 @@ export default class Bridge {
264
262
  if (!this.existingContentToLink()) {
265
263
  this.focusedEditor.insertImagePlaceholder(fileMetaProps);
266
264
  }
267
- } else {
268
- console.warn('clicked sidebar image without a focused editor');
269
265
  }
270
266
  }
271
267
 
@@ -51,7 +51,7 @@ const defaultTinymceConfig = {
51
51
  // this will always be provided by the RCE
52
52
  convert_urls: false,
53
53
  // fonts specified here need to either be web-safe or self-hosted and loaded in app/stylesheets/bundles/fonts.scss
54
- font_formats: "Lato=lato,Helvetica Neue,Helvetica,Arial,sans-serif; Balsamiq Sans=Balsamiq Sans,lato,Helvetica Neue,Helvetica,Arial,sans-serif; Architect's Daughter=Architects Daughter,lato,Helvetica Neue,Helvetica,Arial,sans-serif; Arial=arial,helvetica,sans-serif; Arial Black=arial black,avant garde; Courier New=courier new,courier; Georgia=georgia,palatino; Tahoma=tahoma,arial,helvetica,sans-serif; Times New Roman=times new roman,times; Trebuchet MS=trebuchet ms,geneva; Verdana=verdana,geneva",
54
+ font_formats: "Lato Extended=Lato Extended,Helvetica Neue,Helvetica,Arial,sans-serif; Balsamiq Sans=Balsamiq Sans,Lato Extended,Helvetica Neue,Helvetica,Arial,sans-serif; Architect's Daughter=Architects Daughter,Lato Extended,Helvetica Neue,Helvetica,Arial,sans-serif; Arial=arial,helvetica,sans-serif; Arial Black=arial black,avant garde; Courier New=courier new,courier; Georgia=georgia,palatino; Tahoma=tahoma,arial,helvetica,sans-serif; Times New Roman=times new roman,times; Trebuchet MS=trebuchet ms,geneva; Verdana=verdana,geneva",
55
55
  language_load: false,
56
56
  language_url: 'none',
57
57
  toolbar_mode: 'floating',
package/es/index.js CHANGED
@@ -26,6 +26,8 @@ export * from './enhance-user-content/index';
26
26
  export const defaultConfiguration = defaultTinymceConfig;
27
27
  export { instuiPopupMountNode } from './util/fullscreenHelpers';
28
28
  export { Mathml };
29
+ export { RCEVariantValues } from './rce/RCEVariants';
30
+ export { UploadFilePanelIds, handleSubmit, UploadFile } from './rce/plugins/shared/Upload';
29
31
  export function renderIntoDiv(editorEl, props, cb) {
30
32
  const language = normalizeLocale(props.language);
31
33
  setLocale(language);
package/es/rce/RCE.js CHANGED
@@ -58,6 +58,7 @@ const RCE = /*#__PURE__*/forwardRef(function RCE(props, rceRef) {
58
58
  rcsProps,
59
59
  use_rce_icon_maker,
60
60
  features,
61
+ variant,
61
62
  onFocus,
62
63
  onBlur,
63
64
  onInit,
@@ -104,7 +105,8 @@ const RCE = /*#__PURE__*/forwardRef(function RCE(props, rceRef) {
104
105
  selector: (editorOptions === null || editorOptions === void 0 ? void 0 : editorOptions.selector) || `#${textareaId}`,
105
106
  height,
106
107
  language: editorLanguage(props.language)
107
- }
108
+ },
109
+ variant
108
110
  };
109
111
  wrapInitCb(mirroredAttrs, iProps.editorOptions);
110
112
  return iProps;
@@ -0,0 +1,121 @@
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
+ import formatMessage from '../format-message';
19
+ export const RCEVariantValues = ['full', 'lite', 'text-only'];
20
+ export function getMenubarForVariant(variant) {
21
+ if (variant === 'full') {
22
+ return 'edit view insert format tools table';
23
+ }
24
+
25
+ return '';
26
+ }
27
+ export function getMenuForVariant(variant) {
28
+ if (variant === 'full') {
29
+ return {
30
+ edit: {
31
+ title: formatMessage('Edit'),
32
+ items: `undo redo | cut copy paste | selectall`
33
+ },
34
+ format: {
35
+ title: formatMessage('Format'),
36
+ items: 'bold italic underline strikethrough superscript subscript codeformat | formats blockformats fontformats fontsizes align directionality | forecolor backcolor | removeformat'
37
+ },
38
+ insert: {
39
+ title: formatMessage('Insert'),
40
+ items: 'instructure_links instructure_image instructure_media instructure_document instructure_icon_maker | instructure_equation inserttable instructure_media_embed | hr'
41
+ },
42
+ tools: {
43
+ title: formatMessage('Tools'),
44
+ items: 'instructure_wordcount lti_tools_menuitem instructure_search_and_replace'
45
+ },
46
+ view: {
47
+ title: formatMessage('View'),
48
+ items: 'instructure_fullscreen instructure_exit_fullscreen instructure_html_view'
49
+ }
50
+ };
51
+ }
52
+
53
+ return {};
54
+ }
55
+ export function getToolbarForVariant(variant) {
56
+ let ltiToolFavorites = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
57
+
58
+ if (variant === 'lite') {
59
+ return [{
60
+ name: formatMessage('Styles'),
61
+ items: ['formatselect']
62
+ }, {
63
+ name: formatMessage('Formatting'),
64
+ items: ['bold', 'italic', 'underline', 'forecolor']
65
+ }, {
66
+ name: formatMessage('Content'),
67
+ items: ['instructure_links', 'instructure_image']
68
+ }, {
69
+ name: formatMessage('Lists'),
70
+ items: ['bullist']
71
+ }, {
72
+ name: formatMessage('Miscellaneous'),
73
+ items: ['instructure_equation']
74
+ }];
75
+ }
76
+
77
+ if (variant === 'text-only') {
78
+ return [{
79
+ name: formatMessage('Formatting'),
80
+ items: ['bold', 'italic', 'underline']
81
+ }, {
82
+ name: formatMessage('Content'),
83
+ items: ['instructure_links']
84
+ }];
85
+ }
86
+
87
+ return [{
88
+ name: formatMessage('Styles'),
89
+ items: ['fontsizeselect', 'formatselect']
90
+ }, {
91
+ name: formatMessage('Formatting'),
92
+ items: ['bold', 'italic', 'underline', 'forecolor', 'backcolor', 'inst_subscript', 'inst_superscript']
93
+ }, {
94
+ name: formatMessage('Content'),
95
+ items: ['instructure_links', 'instructure_image', 'instructure_record', 'instructure_documents', 'instructure_icon_maker']
96
+ }, {
97
+ name: formatMessage('External Tools'),
98
+ items: [...ltiToolFavorites, 'lti_tool_dropdown', 'lti_mru_button']
99
+ }, {
100
+ name: formatMessage('Alignment and Lists'),
101
+ items: ['align', 'bullist', 'inst_indent', 'inst_outdent']
102
+ }, {
103
+ name: formatMessage('Miscellaneous'),
104
+ items: ['removeformat', 'table', 'instructure_equation', 'instructure_media_embed']
105
+ }];
106
+ }
107
+ export function getStatusBarFeaturesForVariant(variant) {
108
+ let ai_text_tools = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
109
+
110
+ if (variant === 'lite' || variant === 'text-only') {
111
+ return ['keyboard_shortcuts', 'a11y_checker', 'word_count'];
112
+ }
113
+
114
+ const full_features = ['keyboard_shortcuts', 'a11y_checker', 'word_count', 'html_view', 'fullscreen', 'resize_handle'];
115
+
116
+ if (ai_text_tools) {
117
+ full_features.push('ai_tools');
118
+ }
119
+
120
+ return full_features;
121
+ }
@@ -60,6 +60,7 @@ import { IconMoreSolid } from '@instructure/ui-icons/es/svg';
60
60
  import EncryptedStorage from '../util/encrypted-storage';
61
61
  import buildStyle from './style';
62
62
  import { externalToolsForToolbar } from './plugins/instructure_rce_external_tools/RceToolWrapper';
63
+ import { getMenubarForVariant, getMenuForVariant, getToolbarForVariant, getStatusBarFeaturesForVariant } from './RCEVariants';
63
64
  const RestoreAutoSaveModal = /*#__PURE__*/React.lazy(() => import('./RestoreAutoSaveModal'));
64
65
  const RceHtmlEditor = /*#__PURE__*/React.lazy(() => import('./RceHtmlEditor'));
65
66
  const ASYNC_FOCUS_TIMEOUT = 250;
@@ -693,6 +694,72 @@ class RCEWrapper extends React.Component {
693
694
  }
694
695
  };
695
696
 
697
+ this.handleAIClick = () => {
698
+ import('./plugins/shared/ai_tools').then(module => {
699
+ this.AIToolsTray = module.AIToolsTray;
700
+ this.setState({
701
+ AIToolsOpen: true,
702
+ AITToolsFocusReturn: document.activeElement
703
+ });
704
+ }).catch(ex => {
705
+ // eslint-disable-next-line no-console
706
+ console.error('Failed loading the AIToolsTray', ex);
707
+ });
708
+ };
709
+
710
+ this.closeAITools = () => {
711
+ this.setState({
712
+ AIToolsOpen: false
713
+ });
714
+ };
715
+
716
+ this.AIToolsExited = () => {
717
+ if (this.state.AITToolsFocusReturn === this.iframe) {
718
+ // launched using a kb shortcut
719
+ // the iframe has focus so we need to forward it on to tinymce editor
720
+ this.editor.focus(false);
721
+ } else if (this.state.AITToolsFocusReturn === document.getElementById(`show-on-focus-btn-${this.id}`)) {
722
+ var _this$_showOnFocusBut2;
723
+
724
+ // launched from showOnFocus button
725
+ // edge case where focusing KBShortcutFocusReturn doesn't work
726
+ (_this$_showOnFocusBut2 = this._showOnFocusButton) === null || _this$_showOnFocusBut2 === void 0 ? void 0 : _this$_showOnFocusBut2.focus();
727
+ } else {
728
+ var _this$state$AITToolsF;
729
+
730
+ // launched from kb shortcut button on status bar
731
+ (_this$state$AITToolsF = this.state.AITToolsFocusReturn) === null || _this$state$AITToolsF === void 0 ? void 0 : _this$state$AITToolsF.focus();
732
+ }
733
+ };
734
+
735
+ this.handleInsertAIContent = content => {
736
+ const editor = this.mceInstance();
737
+ contentInsertion.insertContent(editor, content);
738
+ };
739
+
740
+ this.handleReplaceAIContent = content => {
741
+ const ed = this.mceInstance();
742
+ const selection = ed.selection;
743
+
744
+ if (selection.getContent().length > 0) {
745
+ selection.setContent(content);
746
+ } else {
747
+ ed.selection.select(ed.getBody(), true);
748
+ selection.setContent(content);
749
+ }
750
+ };
751
+
752
+ this.getCurrentContentForAI = () => {
753
+ const selected = this.mceInstance().selection.getContent();
754
+ return selected ? {
755
+ type: 'selection',
756
+ content: selected
757
+ } : {
758
+ type: 'full',
759
+ content: this.mceInstance().getContent()
760
+ };
761
+ };
762
+
696
763
  this.setFocusAbilityForHeader = focusable => {
697
764
  // Sets aria-hidden to prevent screen readers focus in RCE menus and toolbar
698
765
  const header = this._elementRef.current.querySelector('.tox-editor-header');
@@ -788,7 +855,8 @@ class RCEWrapper extends React.Component {
788
855
  prevHeight: _ht
789
856
  },
790
857
  a11yErrorsCount: 0,
791
- shouldShowEditor: typeof IntersectionObserver === 'undefined' || maxInitRenderedRCEs <= 0 || currentRCECount < maxInitRenderedRCEs
858
+ shouldShowEditor: typeof IntersectionObserver === 'undefined' || maxInitRenderedRCEs <= 0 || currentRCECount < maxInitRenderedRCEs,
859
+ AIToolsOpen: false
792
860
  };
793
861
  this._statusBarId = `${this.state.id}_statusbar`;
794
862
  this.pendingEventHandlers = [];
@@ -796,12 +864,15 @@ class RCEWrapper extends React.Component {
796
864
  this.pluginsToExclude = parsePluginsToExclude(((_props$editorOptions2 = props.editorOptions) === null || _props$editorOptions2 === void 0 ? void 0 : _props$editorOptions2.plugins) || []);
797
865
  this.resourceType = props.resourceType;
798
866
  this.resourceId = props.resourceId;
867
+ this.variant = window.RCE_VARIANT || props.variant; // to facilitate testing
868
+
799
869
  this.tinymceInitOptions = this.wrapOptions(props.editorOptions);
800
870
  alertHandler.alertFunc = this.addAlert;
801
871
  this.handleContentTrayClosing = this.handleContentTrayClosing.bind(this);
802
872
  this.resizeObserver = new ResizeObserver(_entries => {
803
873
  this._handleFullscreenResize();
804
874
  });
875
+ this.AIToolsTray = undefined;
805
876
  } // when the RCE is put into fullscreen we need to move the div
806
877
  // tinymce mounts popup menus into from the body to the rce-wrapper
807
878
  // or the menus wind up behind the RCE. I can't find a way to
@@ -979,7 +1050,7 @@ class RCEWrapper extends React.Component {
979
1050
  }
980
1051
 
981
1052
  replaceCode(code) {
982
- if (code !== "" && window.confirm(formatMessage('Content in the editor will be changed. Press Cancel to keep the original content.'))) {
1053
+ if (code !== '' && window.confirm(formatMessage('Content in the editor will be changed. Press Cancel to keep the original content.'))) {
983
1054
  this.mceInstance().setContent(code);
984
1055
  }
985
1056
  }
@@ -1551,54 +1622,15 @@ class RCEWrapper extends React.Component {
1551
1622
  // things like table resizing and stuff.
1552
1623
  content_css: options.content_css || [],
1553
1624
  content_style: contentCSS,
1554
- menubar: mergeMenuItems('edit view insert format tools table', possibleNewMenubarItems),
1625
+ menubar: mergeMenuItems(getMenubarForVariant(this.variant), possibleNewMenubarItems),
1555
1626
  // default menu options listed at https://www.tiny.cloud/docs/configure/editor-appearance/#menu
1556
1627
  // tinymce's default edit and table menus are fine
1557
1628
  // note: the tinymce paste command is used here instead of instructure_paste
1558
1629
  // since we currently can't effectively paste using the clipboard api anyway.
1559
1630
  // we include all the canvas specific items in the menu and toolbar
1560
1631
  // and rely on tinymce only showing them if the plugin is provided.
1561
- menu: mergeMenu({
1562
- edit: {
1563
- title: formatMessage('Edit'),
1564
- items: `undo redo | cut copy paste | selectall`
1565
- },
1566
- format: {
1567
- title: formatMessage('Format'),
1568
- items: 'bold italic underline strikethrough superscript subscript codeformat | formats blockformats fontformats fontsizes align directionality | forecolor backcolor | removeformat'
1569
- },
1570
- insert: {
1571
- title: formatMessage('Insert'),
1572
- items: 'instructure_links instructure_image instructure_media instructure_document instructure_icon_maker | instructure_equation inserttable instructure_media_embed | hr'
1573
- },
1574
- tools: {
1575
- title: formatMessage('Tools'),
1576
- items: 'instructure_wordcount lti_tools_menuitem instructure_search_and_replace'
1577
- },
1578
- view: {
1579
- title: formatMessage('View'),
1580
- items: 'instructure_fullscreen instructure_exit_fullscreen instructure_html_view'
1581
- }
1582
- }, options.menu),
1583
- toolbar: mergeToolbar([{
1584
- name: formatMessage('Styles'),
1585
- items: ['fontsizeselect', 'formatselect']
1586
- }, {
1587
- name: formatMessage('Formatting'),
1588
- items: ['bold', 'italic', 'underline', 'forecolor', 'backcolor', 'inst_subscript', 'inst_superscript']
1589
- }, {
1590
- name: formatMessage('Content'),
1591
- items: ['instructure_links', 'instructure_image', 'instructure_record', 'instructure_documents', 'instructure_icon_maker']
1592
- }, {
1593
- name: formatMessage('External Tools'),
1594
- items: [...this.ltiToolFavorites, 'lti_tool_dropdown', 'lti_mru_button']
1595
- }, {
1596
- name: formatMessage('Alignment and Lists'),
1597
- items: ['align', 'bullist', 'inst_indent', 'inst_outdent']
1598
- }, {
1599
- name: formatMessage('Miscellaneous'),
1600
- items: ['removeformat', 'table', 'instructure_equation', 'instructure_media_embed']
1601
- }], options.toolbar),
1632
+ menu: mergeMenu(getMenuForVariant(this.variant), options.menu),
1633
+ toolbar: mergeToolbar(getToolbarForVariant(this.variant, this.ltiToolFavorites), options.toolbar),
1602
1634
  contextmenu: '',
1603
1635
  // show the browser's native context menu
1604
1636
  toolbar_mode: 'sliding',
@@ -1797,6 +1829,7 @@ class RCEWrapper extends React.Component {
1797
1829
  });
1798
1830
  }
1799
1831
 
1832
+ const statusBarFeatures = getStatusBarFeaturesForVariant(this.variant, this.props.ai_text_tools);
1800
1833
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("style", null, this.style.css), /*#__PURE__*/React.createElement(StoreProvider, {
1801
1834
  jwt: (_this$props$trayProps5 = this.props.trayProps) === null || _this$props$trayProps5 === void 0 ? void 0 : _this$props$trayProps5.jwt,
1802
1835
  refreshToken: (_this$props$trayProps6 = this.props.trayProps) === null || _this$props$trayProps6 === void 0 ? void 0 : _this$props$trayProps6.refreshToken,
@@ -1811,6 +1844,9 @@ class RCEWrapper extends React.Component {
1811
1844
  key: this.id,
1812
1845
  className: `${this.style.classNames.root} rce-wrapper`,
1813
1846
  ref: this._elementRef,
1847
+ style: this.variant === 'full' ? {
1848
+ marginBottom: '.5rem'
1849
+ } : undefined,
1814
1850
  onFocus: this.handleFocusRCE,
1815
1851
  onBlur: this.handleBlurRCE
1816
1852
  }, this.state.shouldShowOnFocusButton && /*#__PURE__*/React.createElement(ShowOnFocusButton, {
@@ -1842,7 +1878,7 @@ class RCEWrapper extends React.Component {
1842
1878
  onNodeChange: this.onNodeChange,
1843
1879
  onEditorChange: this.onEditorChange,
1844
1880
  liveRegion: this.props.liveRegion
1845
- })), /*#__PURE__*/React.createElement(StatusBar, {
1881
+ })), statusBarFeatures.length > 0 && /*#__PURE__*/React.createElement(StatusBar, {
1846
1882
  id: this._statusBarId,
1847
1883
  rceIsFullscreen: this._isFullscreen(),
1848
1884
  readOnly: this.props.readOnly,
@@ -1860,7 +1896,9 @@ class RCEWrapper extends React.Component {
1860
1896
  onWordcountModalOpen: () => launchWordcountModal(this.mceInstance(), document, {
1861
1897
  skipEditorFocus: true
1862
1898
  }),
1863
- disabledPlugins: this.pluginsToExclude
1899
+ disabledPlugins: this.pluginsToExclude,
1900
+ features: statusBarFeatures,
1901
+ onAI: this.handleAIClick
1864
1902
  }), ((_this$props$trayProps10 = this.props.trayProps) === null || _this$props$trayProps10 === void 0 ? void 0 : _this$props$trayProps10.containingContext) && /*#__PURE__*/React.createElement(CanvasContentTray, Object.assign({
1865
1903
  mountNode: instuiPopupMountNode,
1866
1904
  key: this.id,
@@ -1875,6 +1913,16 @@ class RCEWrapper extends React.Component {
1875
1913
  onExited: this.KBShortcutModalExited,
1876
1914
  onDismiss: this.closeKBShortcutModal,
1877
1915
  open: this.state.KBShortcutModalOpen
1916
+ }), this.props.ai_text_tools && this.AIToolsTray && /*#__PURE__*/React.createElement(this.AIToolsTray, {
1917
+ open: this.state.AIToolsOpen,
1918
+ container: document.querySelector('[role="main"]'),
1919
+ mountNode: instuiPopupMountNode,
1920
+ contextId: trayProps.contextId,
1921
+ contextType: trayProps.contextId,
1922
+ currentContent: this.getCurrentContentForAI(),
1923
+ onClose: this.closeAITools,
1924
+ onInsertContent: this.handleInsertAIContent,
1925
+ onReplaceContent: this.handleReplaceAIContent
1878
1926
  }), this.state.confirmAutoSave ? /*#__PURE__*/React.createElement(Suspense, {
1879
1927
  fallback: /*#__PURE__*/React.createElement(Spinner, {
1880
1928
  renderTitle: renderLoading,
@@ -1909,7 +1957,8 @@ RCEWrapper.defaultProps = {
1909
1957
  maxInitRenderedRCEs: -1,
1910
1958
  features: {},
1911
1959
  timezone: (_Intl = Intl) === null || _Intl === void 0 ? void 0 : (_Intl$DateTimeFormat = _Intl.DateTimeFormat()) === null || _Intl$DateTimeFormat === void 0 ? void 0 : (_Intl$DateTimeFormat$ = _Intl$DateTimeFormat.resolvedOptions()) === null || _Intl$DateTimeFormat$ === void 0 ? void 0 : _Intl$DateTimeFormat$.timeZone,
1912
- canvasOrigin: ''
1960
+ canvasOrigin: '',
1961
+ variant: 'full'
1913
1962
  };
1914
1963
  RCEWrapper.skinCssInjected = false;
1915
1964
 
@@ -17,6 +17,7 @@
17
17
  */
18
18
  import PropTypes from 'prop-types';
19
19
  import { trayPropTypes } from './plugins/shared/CanvasContentTray';
20
+ import { RCEVariantValues } from './RCEVariants';
20
21
  import { PRETTY_HTML_EDITOR_VIEW, RAW_HTML_EDITOR_VIEW, WYSIWYG_VIEW } from './StatusBar'; // This file contains the prop types for the RCEWrapper component, so that types can be shared without having
21
22
  // to refactor RCEWrapper.js into typescript.
22
23
 
@@ -49,7 +50,7 @@ export const ltiToolsPropType = PropTypes.arrayOf(PropTypes.shape({
49
50
  id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
50
51
  // is this a favorite tool?
51
52
  favorite: PropTypes.bool,
52
- always_on: PropTypes.bool,
53
+ on_by_default: PropTypes.bool,
53
54
  name: PropTypes.string,
54
55
  description: PropTypes.string,
55
56
  icon_url: PropTypes.string,
@@ -127,5 +128,7 @@ export const rceWrapperPropTypes = {
127
128
  flashAlertTimeout: PropTypes.number,
128
129
  timezone: PropTypes.string,
129
130
  userCacheKey: PropTypes.string,
130
- externalToolsConfig: externalToolsConfigPropType
131
+ externalToolsConfig: externalToolsConfigPropType,
132
+ ai_text_tools: PropTypes.bool,
133
+ variant: PropTypes.oneOf(RCEVariantValues)
131
134
  };
@@ -31,6 +31,7 @@ import { IconA11yLine, IconKeyboardShortcutsLine, IconMiniArrowEndLine, IconFull
31
31
  import formatMessage from '../format-message';
32
32
  import ResizeHandle from './ResizeHandle';
33
33
  import { FS_ENABLED } from '../util/fullscreenHelpers';
34
+ import { AIWandSVG } from './plugins/shared/ai_tools';
34
35
  export const WYSIWYG_VIEW = 'WYSIWYG';
35
36
  export const PRETTY_HTML_EDITOR_VIEW = 'PRETTY';
36
37
  export const RAW_HTML_EDITOR_VIEW = 'RAW'; // I don't know why eslint is reporting this, the props are all used
@@ -54,7 +55,10 @@ StatusBar.propTypes = {
54
55
  a11yBadgeColor: string,
55
56
  a11yErrorsCount: number,
56
57
  onWordcountModalOpen: func.isRequired,
57
- disabledPlugins: arrayOf(string)
58
+ disabledPlugins: arrayOf(string),
59
+ features: arrayOf(string),
60
+ // StatusBarFeature[]
61
+ onAI: func
58
62
  };
59
63
  StatusBar.defaultProps = {
60
64
  a11yBadgeColor: '#0374B5',
@@ -127,6 +131,10 @@ export default function StatusBar(props) {
127
131
  return !props.disabledPlugins.includes(plugin);
128
132
  }
129
133
 
134
+ function isFeature(feature_name) {
135
+ return props.features.includes(feature_name);
136
+ }
137
+
130
138
  function preferredHtmlEditor() {
131
139
  if (props.preferredHtmlEditor) return props.preferredHtmlEditor;
132
140
  return PRETTY_HTML_EDITOR_VIEW;
@@ -177,7 +185,7 @@ export default function StatusBar(props) {
177
185
  const a11yButtonId = 'rce-a11y-btn';
178
186
  const button = /*#__PURE__*/React.createElement(IconButton, {
179
187
  "data-btn-id": a11yButtonId,
180
- color: "primary",
188
+ color: "secondary",
181
189
  title: a11y,
182
190
  tabIndex: tabIndexForBtn(a11yButtonId),
183
191
  onClick: event => {
@@ -233,13 +241,39 @@ export default function StatusBar(props) {
233
241
 
234
242
  function renderIconButtons() {
235
243
  if (isHtmlView()) return null;
244
+ const ai_tools = isFeature('ai_tools');
245
+ const kb_shortcuts = isFeature('keyboard_shortcuts');
246
+ const a11y_checker = isFeature('a11y_checker');
247
+ if (!(ai_tools || kb_shortcuts || a11y_checker)) return null;
236
248
  const kbshortcut = formatMessage('View keyboard shortcuts');
237
249
  return /*#__PURE__*/React.createElement(View, {
238
250
  display: "inline-block",
239
251
  padding: "0 x-small"
240
- }, /*#__PURE__*/React.createElement(IconButton, {
252
+ }, ai_tools && props.onAI && /*#__PURE__*/React.createElement(IconButton, {
253
+ "data-btn-id": "rce-ai-btn",
254
+ color: "secondary",
255
+ "aria-haspopup": "dialog",
256
+ title: formatMessage('AI Tools'),
257
+ tabIndex: tabIndexForBtn('rce-ai-btn'),
258
+ onClick: event => {
259
+ event.target.focus(); // FF doesn't focus buttons on click
260
+
261
+ props.onAI();
262
+ },
263
+ onFocus: () => setFocusedBtnId('rce-ai-btn'),
264
+ screenReaderLabel: formatMessage('AI Tools'),
265
+ withBackground: false,
266
+ withBorder: false
267
+ }, /*#__PURE__*/React.createElement("span", {
268
+ style: {
269
+ color: 'dodgerBlue'
270
+ }
271
+ }, /*#__PURE__*/React.createElement(SVGIcon, {
272
+ src: AIWandSVG,
273
+ size: "x-small"
274
+ }))), kb_shortcuts && /*#__PURE__*/React.createElement(IconButton, {
241
275
  "data-btn-id": "rce-kbshortcut-btn",
242
- color: "primary",
276
+ color: "secondary",
243
277
  "aria-haspopup": "dialog",
244
278
  title: kbshortcut,
245
279
  tabIndex: tabIndexForBtn('rce-kbshortcut-btn'),
@@ -252,7 +286,7 @@ export default function StatusBar(props) {
252
286
  screenReaderLabel: kbshortcut,
253
287
  withBackground: false,
254
288
  withBorder: false
255
- }, /*#__PURE__*/React.createElement(IconKeyboardShortcutsLine, null)), !props.readOnly && isAvailable('ally_checker') && renderA11yButton());
289
+ }, /*#__PURE__*/React.createElement(IconKeyboardShortcutsLine, null)), a11y_checker && !props.readOnly && isAvailable('ally_checker') && renderA11yButton());
256
290
  }
257
291
 
258
292
  function renderWordCount() {
@@ -264,17 +298,25 @@ export default function StatusBar(props) {
264
298
  }`, {
265
299
  count: props.wordCount
266
300
  });
267
- return /*#__PURE__*/React.createElement(View, {
301
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
302
+ className: css(styles.separator)
303
+ }), /*#__PURE__*/React.createElement(View, {
268
304
  display: "inline-block",
269
305
  padding: "0 small",
270
306
  "data-testid": "status-bar-word-count"
271
307
  }, /*#__PURE__*/React.createElement(CondensedButton, {
272
308
  "data-btn-id": "rce-wordcount-btn",
273
- color: "primary",
309
+ color: "secondary",
274
310
  onClick: props.onWordcountModalOpen,
275
311
  tabIndex: tabIndexForBtn('rce-wordcount-btn'),
276
312
  title: formatMessage('View word and character counts')
277
- }, wordCount));
313
+ }, wordCount)));
314
+ }
315
+
316
+ function renderSection3(html_view, fullscreen, resize_handle) {
317
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
318
+ className: css(styles.separator)
319
+ }), html_view && renderToggleHtml(), fullscreen && renderFullscreen(), resize_handle && renderResizeHandle());
278
320
  }
279
321
 
280
322
  function descMsg() {
@@ -292,7 +334,7 @@ export default function StatusBar(props) {
292
334
  padding: "0 0 0 x-small"
293
335
  }, !props.readOnly && /*#__PURE__*/React.createElement(IconButton, {
294
336
  "data-btn-id": "rce-edit-btn",
295
- color: "primary",
337
+ color: "secondary",
296
338
  onClick: event => {
297
339
  props.onChangeView(isHtmlView() ? WYSIWYG_VIEW : getHtmlEditorView(event));
298
340
  },
@@ -329,7 +371,7 @@ export default function StatusBar(props) {
329
371
  const fullscreen = props.rceIsFullscreen ? formatMessage('Exit Fullscreen') : formatMessage('Fullscreen');
330
372
  return /*#__PURE__*/React.createElement(IconButton, {
331
373
  "data-btn-id": "rce-fullscreen-btn",
332
- color: "primary",
374
+ color: "secondary",
333
375
  title: fullscreen,
334
376
  tabIndex: tabIndexForBtn('rce-fullscreen-btn'),
335
377
  onClick: event => {
@@ -356,7 +398,19 @@ export default function StatusBar(props) {
356
398
  }
357
399
 
358
400
  const flexJustify = isHtmlView() ? 'end' : 'start';
359
- return /*#__PURE__*/React.createElement(Flex, {
401
+ const html_view = isFeature('html_view') && isAvailable('instructure_html_view');
402
+ const fullscreen = isFeature('fullscreen') && isAvailable('instructure_fullscreen');
403
+ const resize_handle = isFeature('resize_handle');
404
+ return /*#__PURE__*/React.createElement(InstUISettingsProvider, {
405
+ theme: {
406
+ componentOverrides: {
407
+ IconButton: {
408
+ secondaryGhostColor: 'rgb(34, 47, 62)' // to match tinymce's button color
409
+
410
+ }
411
+ }
412
+ }
413
+ }, /*#__PURE__*/React.createElement(Flex, {
360
414
  id: props.id,
361
415
  padding: "x-small 0 x-small x-small",
362
416
  "data-testid": "RCEStatusBar",
@@ -367,12 +421,8 @@ export default function StatusBar(props) {
367
421
  shouldGrow: true
368
422
  }, isHtmlView() ? renderHtmlEditorMessage() : renderPath()), /*#__PURE__*/React.createElement(Flex.Item, {
369
423
  role: "toolbar",
370
- title: formatMessage('Editor Statusbar')
371
- }, renderIconButtons(), /*#__PURE__*/React.createElement("div", {
372
- className: css(styles.separator)
373
- }), isAvailable('instructure_wordcount') && renderWordCount(), /*#__PURE__*/React.createElement("div", {
374
- className: css(styles.separator)
375
- }), isAvailable('instructure_html_view') && renderToggleHtml(), isAvailable('instructure_fullscreen') && renderFullscreen(), renderResizeHandle()));
424
+ title: formatMessage('Editor Status Bar')
425
+ }, renderIconButtons(), isFeature('word_count') && isAvailable('instructure_wordcount') && renderWordCount(), (html_view || fullscreen || resize_handle) && renderSection3(html_view, fullscreen, resize_handle))));
376
426
  }
377
427
  const styles = StyleSheet.create({
378
428
  separator: {
@@ -66,6 +66,7 @@ export const Footer = _ref => {
66
66
  margin: "0 0 0 x-small",
67
67
  "data-testid": "icon-maker-save"
68
68
  }, replaceAll ? formatMessage('Save') : formatMessage('Save Copy'))) : /*#__PURE__*/React.createElement(Button, {
69
+ id: "create-icon-button",
69
70
  disabled: disabled,
70
71
  margin: "0 0 0 x-small",
71
72
  color: "primary",