@instructure/canvas-rce 7.2.0 → 7.3.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 +41 -0
- package/es/enhance-user-content/doc_previews.js +6 -14
- package/es/enhance-user-content/enhance_user_content.js +1 -1
- package/es/enhance-user-content/instructure_helper.js +1 -0
- package/es/index.d.ts +1 -0
- package/es/index.js +2 -1
- package/es/rce/AlertMessageArea.js +1 -3
- package/es/rce/KeyboardShortcutModal.js +1 -1
- package/es/rce/RCEGlobals.d.ts +2 -0
- package/es/rce/RCEGlobals.js +1 -0
- package/es/rce/RCEVariants.js +3 -3
- package/es/rce/RCEWrapper.d.ts +3 -1
- package/es/rce/RCEWrapper.js +43 -11
- package/es/rce/plugins/instructure_keyboard_shortcuts_header/clickCallback.d.ts +2 -0
- package/es/rce/plugins/instructure_keyboard_shortcuts_header/clickCallback.js +45 -0
- package/es/rce/plugins/instructure_keyboard_shortcuts_header/plugin.d.ts +1 -0
- package/es/rce/plugins/instructure_keyboard_shortcuts_header/plugin.js +43 -0
- package/es/rce/plugins/instructure_record/AudioOptionsTray/TrayController.d.ts +1 -1
- package/es/rce/plugins/instructure_record/AudioOptionsTray/TrayController.js +2 -1
- package/es/rce/plugins/instructure_record/VideoOptionsTray/TrayController.d.ts +1 -1
- package/es/rce/plugins/instructure_record/VideoOptionsTray/TrayController.js +2 -1
- package/es/rce/plugins/instructure_record/mediaTranslations.js +1 -1
- package/es/rce/plugins/instructure_studio_media_options/plugin.js +111 -14
- package/es/rce/plugins/instructure_studio_media_options/studioToolbarIcons.d.ts +5 -0
- package/es/rce/plugins/instructure_studio_media_options/studioToolbarIcons.js +23 -0
- package/es/rce/plugins/instructure_wordcount/components/WordCountModal.js +1 -0
- package/es/rce/plugins/instructure_wordcount_header/plugin.d.ts +1 -0
- package/es/rce/plugins/instructure_wordcount_header/plugin.js +75 -0
- package/es/rce/plugins/shared/ContentSelection.d.ts +1 -2
- package/es/rce/plugins/shared/ContentSelection.js +1 -18
- package/es/rce/plugins/shared/StudioLtiSupportUtils.d.ts +10 -1
- package/es/rce/plugins/shared/StudioLtiSupportUtils.js +110 -1
- package/es/rce/plugins/shared/Upload/ComputerPanel.js +1 -1
- package/es/rce/plugins/shared/Upload/UploadFileModal.js +37 -4
- package/es/rce/plugins/shared/Upload/VideoUrlPanel.d.ts +15 -0
- package/es/rce/plugins/shared/Upload/VideoUrlPanel.js +51 -0
- package/es/rce/plugins/shared/Upload/videoValidationUtils.d.ts +7 -0
- package/es/rce/plugins/shared/Upload/videoValidationUtils.js +58 -0
- package/es/rce/plugins/shared/iframeUtils.d.ts +1 -0
- package/es/rce/plugins/shared/iframeUtils.js +37 -0
- package/es/rce/tinyRCE.js +2 -0
- package/es/sidebar/actions/upload.d.ts +1 -0
- package/es/sidebar/actions/upload.js +56 -0
- package/es/sidebar/containers/sidebarHandlers.d.ts +1 -0
- package/es/sidebar/containers/sidebarHandlers.js +2 -1
- package/es/translations/locales/ar.js +30 -6
- package/es/translations/locales/ca.js +30 -6
- package/es/translations/locales/cy.js +30 -6
- package/es/translations/locales/da-x-k12.js +30 -6
- package/es/translations/locales/da.js +30 -6
- package/es/translations/locales/de.js +30 -6
- package/es/translations/locales/el.js +6 -0
- package/es/translations/locales/en-AU-x-unimelb.js +30 -6
- package/es/translations/locales/en-GB-x-ukhe.js +30 -6
- package/es/translations/locales/en.js +36 -6
- package/es/translations/locales/en_AU.js +30 -6
- package/es/translations/locales/en_CA.js +30 -6
- package/es/translations/locales/en_CY.js +30 -6
- package/es/translations/locales/en_GB.js +30 -6
- package/es/translations/locales/es.js +30 -6
- package/es/translations/locales/es_ES.js +30 -6
- package/es/translations/locales/fa_IR.js +6 -3
- package/es/translations/locales/fi.js +30 -6
- package/es/translations/locales/fr.js +30 -6
- package/es/translations/locales/fr_CA.js +34 -10
- package/es/translations/locales/ga.js +30 -6
- package/es/translations/locales/he.js +6 -0
- package/es/translations/locales/hi.js +30 -6
- package/es/translations/locales/ht.js +30 -6
- package/es/translations/locales/hu.js +6 -6
- package/es/translations/locales/hy.js +6 -0
- package/es/translations/locales/id.js +30 -6
- package/es/translations/locales/is.js +36 -6
- package/es/translations/locales/it.js +30 -6
- package/es/translations/locales/ja.js +30 -6
- package/es/translations/locales/ko.js +6 -0
- package/es/translations/locales/mi.js +30 -6
- package/es/translations/locales/ms.js +30 -6
- package/es/translations/locales/nb-x-k12.js +30 -6
- package/es/translations/locales/nb.js +30 -6
- package/es/translations/locales/nl.js +30 -6
- package/es/translations/locales/nn.js +6 -6
- package/es/translations/locales/pl.js +30 -6
- package/es/translations/locales/pt.js +30 -6
- package/es/translations/locales/pt_BR.js +30 -6
- package/es/translations/locales/ru.js +30 -6
- package/es/translations/locales/sl.js +30 -6
- package/es/translations/locales/sv-x-k12.js +30 -6
- package/es/translations/locales/sv.js +30 -6
- package/es/translations/locales/th.js +30 -6
- package/es/translations/locales/tr.js +6 -3
- package/es/translations/locales/uk_UA.js +6 -3
- package/es/translations/locales/vi.js +30 -6
- package/es/translations/locales/zh-Hans.js +30 -6
- package/es/translations/locales/zh-Hant.js +30 -6
- package/es/translations/locales/zh.js +30 -6
- package/es/translations/locales/zh_HK.js +30 -6
- package/package.json +53 -53
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const thumbnailViewIcon = "<svg width=\"24\" height=\"17\" viewBox=\"0 0 24 17\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><g clip-path=\"url(#clip0_1209_167295)\"><path d=\"M24 17H0V0H24V17ZM1.10983 1.10569V15.8943H22.8902V1.10569H1.10983Z\" fill=\"currentColor\"/><path d=\"M21.7804 14.7887H2.21973V2.21143H21.7804V14.7887ZM9.84978 11.4716L14.5665 8.63826L9.84978 5.80492V11.4716Z\" fill=\"currentColor\"/></g><defs><clipPath id=\"clip0_1209_167295\"><rect width=\"24\" height=\"17\" fill=\"white\"/></clipPath></defs></svg>";
|
|
2
|
+
export declare const learnViewIcon = "<svg width=\"31\" height=\"22\" viewBox=\"0 0 31 22\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><g clip-path=\"url(#clip0_93_30328)\"><path d=\"M31 22H0V0H31V22ZM1.43353 1.43089V20.5691H29.5665V1.43089H1.43353Z\" fill=\"currentColor\"/><path d=\"M27.9537 3.04102H19.7109V18.9597H27.9537V3.04102Z\" fill=\"currentColor\"/><path d=\"M18.2781 18.9597H3.04688V3.04102H18.2781V18.9597ZM8.02537 14.7989L14.3165 11.0907L8.02537 7.38244V14.7989Z\" fill=\"currentColor\"/></g><defs><clipPath id=\"clip0_93_30328\"><rect width=\"31\" height=\"22\" fill=\"white\"/></clipPath></defs></svg>";
|
|
3
|
+
export declare const collabViewIcon = "<svg width=\"24\" height=\"17\" viewBox=\"0 0 24 17\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><g clip-path=\"url(#clip0_1209_167302)\"><path d=\"M24 17H0V0H24V17ZM1.10983 1.10569V15.8943H22.8902V1.10569H1.10983Z\" fill=\"currentColor\"/><path d=\"M21.6416 2.34961H17.3411V8.15449H21.6416V2.34961Z\" fill=\"currentColor\"/><path d=\"M16.2312 8.15449H2.3584V2.34961H16.2312V8.15449ZM8.60117 7.18701L11.7919 5.39026L8.60117 3.59351V7.18701Z\" fill=\"currentColor\"/><path d=\"M21.6415 12.5771H4.99414V13.4064H21.6415V12.5771Z\" fill=\"currentColor\"/><path d=\"M21.6415 13.8213H4.99414V14.6506H21.6415V13.8213Z\" fill=\"currentColor\"/><path d=\"M3.39886 14.6503C3.97349 14.6503 4.43932 14.1862 4.43932 13.6137C4.43932 13.0412 3.97349 12.5771 3.39886 12.5771C2.82423 12.5771 2.3584 13.0412 2.3584 13.6137C2.3584 14.1862 2.82423 14.6503 3.39886 14.6503Z\" fill=\"currentColor\"/><path d=\"M21.6415 9.39844H4.99414V10.2277H21.6415V9.39844Z\" fill=\"currentColor\"/><path d=\"M21.6415 10.6421H4.99414V11.4714H21.6415V10.6421Z\" fill=\"currentColor\"/><path d=\"M3.39886 11.4716C3.97349 11.4716 4.43932 11.0075 4.43932 10.435C4.43932 9.86253 3.97349 9.39844 3.39886 9.39844C2.82423 9.39844 2.3584 9.86253 2.3584 10.435C2.3584 11.0075 2.82423 11.4716 3.39886 11.4716Z\" fill=\"currentColor\"/></g><defs><clipPath id=\"clip0_1209_167302\"><rect width=\"24\" height=\"17\" fill=\"white\"/></clipPath></defs></svg>";
|
|
4
|
+
export declare const optionsIcon = "<svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><g clip-path=\"url(#clip0_1217_47603)\"><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M2.46326 3.85039H0V2.65039H2.46326C2.7388 1.36449 3.88181 0.400391 5.24999 0.400391C6.61818 0.400391 7.76119 1.36449 8.03673 2.65039H18V3.85039H8.03673C7.76119 5.13629 6.61818 6.10039 5.24999 6.10039C3.88181 6.10039 2.7388 5.13629 2.46326 3.85039ZM3.59999 3.25039C3.59999 2.33912 4.33872 1.60039 5.24999 1.60039C6.16126 1.60039 6.89999 2.33912 6.89999 3.25039C6.89999 4.16166 6.16126 4.90039 5.24999 4.90039C4.33872 4.90039 3.59999 4.16166 3.59999 3.25039Z\" fill=\"currentColor\"/><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M0 8.40039H9.46326C9.7388 7.11449 10.8818 6.15039 12.25 6.15039C13.6182 6.15039 14.7612 7.11449 15.0367 8.40039H18V9.60039H15.0367C14.7612 10.8863 13.6182 11.8504 12.25 11.8504C10.8818 11.8504 9.7388 10.8863 9.46326 9.60039H0V8.40039ZM12.25 7.35039C11.3387 7.35039 10.6 8.08912 10.6 9.00039C10.6 9.91166 11.3387 10.6504 12.25 10.6504C13.1613 10.6504 13.9 9.91166 13.9 9.00039C13.9 8.08912 13.1613 7.35039 12.25 7.35039Z\" fill=\"currentColor\"/><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M5.24999 17.6004C6.61818 17.6004 7.76119 16.6363 8.03673 15.3504H18V14.1504H8.03673C7.76119 12.8645 6.61818 11.9004 5.24999 11.9004C3.88181 11.9004 2.7388 12.8645 2.46326 14.1504H0V15.3504H2.46326C2.7388 16.6363 3.88181 17.6004 5.24999 17.6004ZM5.24999 13.1004C4.33872 13.1004 3.59999 13.8391 3.59999 14.7504C3.59999 15.6617 4.33872 16.4004 5.24999 16.4004C6.16126 16.4004 6.89999 15.6617 6.89999 14.7504C6.89999 13.8391 6.16126 13.1004 5.24999 13.1004Z\" fill=\"currentColor\"/></g><defs><clipPath id=\"clip0_1217_47603\"><rect width=\"18\" height=\"18\" fill=\"white\"/></clipPath></defs></svg>";
|
|
5
|
+
export declare const removeIcon = "<svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\"><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M14.8234 16.4118C14.8234 16.7029 14.5852 16.9412 14.294 16.9412H3.70577C3.41459 16.9412 3.17636 16.7029 3.17636 16.4118V5.29412H2.11753V16.4118C2.11753 17.2874 2.83012 18 3.70577 18H14.294C15.1697 18 15.8822 17.2874 15.8822 16.4118V5.29412H14.8234V16.4118ZM6.35284 14.8235H7.41166V6.35294H6.35284V14.8235ZM10.5881 14.8235H11.6469V6.35294H10.5881V14.8235ZM12.5682 3.17647L11.4099 0H6.51176L5.35553 3.17647H0V4.23529H18V3.17647H12.5682ZM6.48105 3.17647L7.25293 1.05882H10.6698L11.4416 3.17647H6.48105Z\" fill=\"currentColor\"/></svg>";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2023 - present Instructure, Inc.
|
|
3
|
+
*
|
|
4
|
+
* This file is part of Canvas.
|
|
5
|
+
*
|
|
6
|
+
* Canvas is free software: you can redistribute it and/or modify it under
|
|
7
|
+
* the terms of the GNU Affero General Public License as published by the Free
|
|
8
|
+
* Software Foundation, version 3 of the License.
|
|
9
|
+
*
|
|
10
|
+
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
11
|
+
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
12
|
+
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
13
|
+
* details.
|
|
14
|
+
*
|
|
15
|
+
* You should have received a copy of the GNU Affero General Public License along
|
|
16
|
+
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export const thumbnailViewIcon = '<svg width="24" height="17" viewBox="0 0 24 17" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0_1209_167295)"><path d="M24 17H0V0H24V17ZM1.10983 1.10569V15.8943H22.8902V1.10569H1.10983Z" fill="currentColor"/><path d="M21.7804 14.7887H2.21973V2.21143H21.7804V14.7887ZM9.84978 11.4716L14.5665 8.63826L9.84978 5.80492V11.4716Z" fill="currentColor"/></g><defs><clipPath id="clip0_1209_167295"><rect width="24" height="17" fill="white"/></clipPath></defs></svg>';
|
|
20
|
+
export const learnViewIcon = '<svg width="31" height="22" viewBox="0 0 31 22" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0_93_30328)"><path d="M31 22H0V0H31V22ZM1.43353 1.43089V20.5691H29.5665V1.43089H1.43353Z" fill="currentColor"/><path d="M27.9537 3.04102H19.7109V18.9597H27.9537V3.04102Z" fill="currentColor"/><path d="M18.2781 18.9597H3.04688V3.04102H18.2781V18.9597ZM8.02537 14.7989L14.3165 11.0907L8.02537 7.38244V14.7989Z" fill="currentColor"/></g><defs><clipPath id="clip0_93_30328"><rect width="31" height="22" fill="white"/></clipPath></defs></svg>';
|
|
21
|
+
export const collabViewIcon = '<svg width="24" height="17" viewBox="0 0 24 17" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0_1209_167302)"><path d="M24 17H0V0H24V17ZM1.10983 1.10569V15.8943H22.8902V1.10569H1.10983Z" fill="currentColor"/><path d="M21.6416 2.34961H17.3411V8.15449H21.6416V2.34961Z" fill="currentColor"/><path d="M16.2312 8.15449H2.3584V2.34961H16.2312V8.15449ZM8.60117 7.18701L11.7919 5.39026L8.60117 3.59351V7.18701Z" fill="currentColor"/><path d="M21.6415 12.5771H4.99414V13.4064H21.6415V12.5771Z" fill="currentColor"/><path d="M21.6415 13.8213H4.99414V14.6506H21.6415V13.8213Z" fill="currentColor"/><path d="M3.39886 14.6503C3.97349 14.6503 4.43932 14.1862 4.43932 13.6137C4.43932 13.0412 3.97349 12.5771 3.39886 12.5771C2.82423 12.5771 2.3584 13.0412 2.3584 13.6137C2.3584 14.1862 2.82423 14.6503 3.39886 14.6503Z" fill="currentColor"/><path d="M21.6415 9.39844H4.99414V10.2277H21.6415V9.39844Z" fill="currentColor"/><path d="M21.6415 10.6421H4.99414V11.4714H21.6415V10.6421Z" fill="currentColor"/><path d="M3.39886 11.4716C3.97349 11.4716 4.43932 11.0075 4.43932 10.435C4.43932 9.86253 3.97349 9.39844 3.39886 9.39844C2.82423 9.39844 2.3584 9.86253 2.3584 10.435C2.3584 11.0075 2.82423 11.4716 3.39886 11.4716Z" fill="currentColor"/></g><defs><clipPath id="clip0_1209_167302"><rect width="24" height="17" fill="white"/></clipPath></defs></svg>';
|
|
22
|
+
export const optionsIcon = '<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0_1217_47603)"><path fill-rule="evenodd" clip-rule="evenodd" d="M2.46326 3.85039H0V2.65039H2.46326C2.7388 1.36449 3.88181 0.400391 5.24999 0.400391C6.61818 0.400391 7.76119 1.36449 8.03673 2.65039H18V3.85039H8.03673C7.76119 5.13629 6.61818 6.10039 5.24999 6.10039C3.88181 6.10039 2.7388 5.13629 2.46326 3.85039ZM3.59999 3.25039C3.59999 2.33912 4.33872 1.60039 5.24999 1.60039C6.16126 1.60039 6.89999 2.33912 6.89999 3.25039C6.89999 4.16166 6.16126 4.90039 5.24999 4.90039C4.33872 4.90039 3.59999 4.16166 3.59999 3.25039Z" fill="currentColor"/><path fill-rule="evenodd" clip-rule="evenodd" d="M0 8.40039H9.46326C9.7388 7.11449 10.8818 6.15039 12.25 6.15039C13.6182 6.15039 14.7612 7.11449 15.0367 8.40039H18V9.60039H15.0367C14.7612 10.8863 13.6182 11.8504 12.25 11.8504C10.8818 11.8504 9.7388 10.8863 9.46326 9.60039H0V8.40039ZM12.25 7.35039C11.3387 7.35039 10.6 8.08912 10.6 9.00039C10.6 9.91166 11.3387 10.6504 12.25 10.6504C13.1613 10.6504 13.9 9.91166 13.9 9.00039C13.9 8.08912 13.1613 7.35039 12.25 7.35039Z" fill="currentColor"/><path fill-rule="evenodd" clip-rule="evenodd" d="M5.24999 17.6004C6.61818 17.6004 7.76119 16.6363 8.03673 15.3504H18V14.1504H8.03673C7.76119 12.8645 6.61818 11.9004 5.24999 11.9004C3.88181 11.9004 2.7388 12.8645 2.46326 14.1504H0V15.3504H2.46326C2.7388 16.6363 3.88181 17.6004 5.24999 17.6004ZM5.24999 13.1004C4.33872 13.1004 3.59999 13.8391 3.59999 14.7504C3.59999 15.6617 4.33872 16.4004 5.24999 16.4004C6.16126 16.4004 6.89999 15.6617 6.89999 14.7504C6.89999 13.8391 6.16126 13.1004 5.24999 13.1004Z" fill="currentColor"/></g><defs><clipPath id="clip0_1217_47603"><rect width="18" height="18" fill="white"/></clipPath></defs></svg>';
|
|
23
|
+
export const removeIcon = '<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M14.8234 16.4118C14.8234 16.7029 14.5852 16.9412 14.294 16.9412H3.70577C3.41459 16.9412 3.17636 16.7029 3.17636 16.4118V5.29412H2.11753V16.4118C2.11753 17.2874 2.83012 18 3.70577 18H14.294C15.1697 18 15.8822 17.2874 15.8822 16.4118V5.29412H14.8234V16.4118ZM6.35284 14.8235H7.41166V6.35294H6.35284V14.8235ZM10.5881 14.8235H11.6469V6.35294H10.5881V14.8235ZM12.5682 3.17647L11.4099 0H6.51176L5.35553 3.17647H0V4.23529H18V3.17647H12.5682ZM6.48105 3.17647L7.25293 1.05882H10.6698L11.4416 3.17647H6.48105Z" fill="currentColor"/></svg>';
|
|
@@ -56,6 +56,7 @@ export const WordCountModal = ({
|
|
|
56
56
|
label: formatMessage('Word Count'),
|
|
57
57
|
mountNode: instuiPopupMountNodeFn(),
|
|
58
58
|
open: true,
|
|
59
|
+
onDismiss: onDismiss,
|
|
59
60
|
"data-mce-component": true
|
|
60
61
|
}, /*#__PURE__*/React.createElement(Modal.Header, null, /*#__PURE__*/React.createElement(CloseButton, {
|
|
61
62
|
placement: "end",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
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
|
+
import tinymce from 'tinymce';
|
|
20
|
+
import formatMessage from '../../../format-message';
|
|
21
|
+
import { debounce } from '@instructure/debounce';
|
|
22
|
+
const clickCallbackPromise = import('../instructure_wordcount/clickCallback');
|
|
23
|
+
const TOOLTIP_MESSAGE = formatMessage('View word and character counts');
|
|
24
|
+
const UPDATE_DEBOUNCE_MS = 100;
|
|
25
|
+
function formatWordCount(count) {
|
|
26
|
+
return formatMessage(`{count, plural,
|
|
27
|
+
=0 {0 words}
|
|
28
|
+
one {1 word}
|
|
29
|
+
other {# words}
|
|
30
|
+
}`, {
|
|
31
|
+
count
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
tinymce.PluginManager.add('instructure_wordcount_header', function (ed) {
|
|
35
|
+
function updateWordCountDisplay() {
|
|
36
|
+
const count = ed.plugins.wordcount.body.getWordCount();
|
|
37
|
+
const button = ed.getContainer()?.querySelector(`[title*="${TOOLTIP_MESSAGE}"]`);
|
|
38
|
+
if (!button) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const textSpan = button.querySelector('.tox-tbtn__select-label');
|
|
42
|
+
if (textSpan) {
|
|
43
|
+
textSpan.textContent = formatWordCount(count);
|
|
44
|
+
}
|
|
45
|
+
const tooltip = `${TOOLTIP_MESSAGE} - ${formatWordCount(count)}`;
|
|
46
|
+
button.setAttribute('title', tooltip);
|
|
47
|
+
button.setAttribute('aria-label', tooltip);
|
|
48
|
+
}
|
|
49
|
+
ed.addCommand('instructureWordCountHeader', () => {
|
|
50
|
+
clickCallbackPromise.then(module => module.default(ed, document, {
|
|
51
|
+
skipEditorFocus: false
|
|
52
|
+
}));
|
|
53
|
+
});
|
|
54
|
+
ed.ui.registry.addButton('instructure_wordcount_header', {
|
|
55
|
+
text: formatWordCount(0),
|
|
56
|
+
tooltip: TOOLTIP_MESSAGE,
|
|
57
|
+
onAction: () => ed.execCommand('instructureWordCountHeader')
|
|
58
|
+
});
|
|
59
|
+
ed.on('PostRender', () => {
|
|
60
|
+
updateWordCountDisplay();
|
|
61
|
+
});
|
|
62
|
+
const debouncedUpdate = debounce(updateWordCountDisplay, UPDATE_DEBOUNCE_MS, {
|
|
63
|
+
trailing: true
|
|
64
|
+
});
|
|
65
|
+
ed.on('NodeChange', debouncedUpdate);
|
|
66
|
+
ed.on('KeyUp', debouncedUpdate);
|
|
67
|
+
ed.on('SetContent', debouncedUpdate);
|
|
68
|
+
ed.on('Change', debouncedUpdate);
|
|
69
|
+
ed.on('Undo', debouncedUpdate);
|
|
70
|
+
ed.on('Redo', debouncedUpdate);
|
|
71
|
+
ed.on('Paste', debouncedUpdate);
|
|
72
|
+
ed.on('init', () => {
|
|
73
|
+
updateWordCountDisplay();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -26,7 +26,7 @@ export function asLink($element: any, editor: any): {
|
|
|
26
26
|
export function asVideoElement($element: any): {
|
|
27
27
|
$element: any;
|
|
28
28
|
type: string;
|
|
29
|
-
id:
|
|
29
|
+
id: string | null;
|
|
30
30
|
titleText: any;
|
|
31
31
|
appliedHeight: any;
|
|
32
32
|
appliedWidth: any;
|
|
@@ -62,7 +62,6 @@ export function isFileLink($element: any, editor: any): boolean;
|
|
|
62
62
|
export function isImageEmbed($element: any): boolean;
|
|
63
63
|
export function isVideoElement($element: any): boolean;
|
|
64
64
|
export function isAudioElement($element: any): boolean;
|
|
65
|
-
export function findMediaPlayerIframe(elem: any): any;
|
|
66
65
|
export const LINK_TYPE: "link";
|
|
67
66
|
export const FILE_LINK_TYPE: "file-link";
|
|
68
67
|
export const IMAGE_EMBED_TYPE: "image-embed";
|
|
@@ -21,6 +21,7 @@ import { isOnlyTextSelected } from '../../contentInsertionUtils';
|
|
|
21
21
|
import formatMessage from '../../../format-message';
|
|
22
22
|
import { isStudioEmbeddedMedia } from './StudioLtiSupportUtils';
|
|
23
23
|
import { parseUrlPath } from '../../../util/url-util';
|
|
24
|
+
import { findMediaPlayerIframe } from './iframeUtils';
|
|
24
25
|
const FILE_DOWNLOAD_PATH_REGEX = /^\/(courses\/\d+\/)?files\/\d+\/download$/;
|
|
25
26
|
export const LINK_TYPE = 'link';
|
|
26
27
|
export const FILE_LINK_TYPE = 'file-link';
|
|
@@ -208,22 +209,4 @@ export function isVideoElement($element) {
|
|
|
208
209
|
}
|
|
209
210
|
export function isAudioElement($element) {
|
|
210
211
|
return isMediaElement($element, 'audio');
|
|
211
|
-
}
|
|
212
|
-
export function findMediaPlayerIframe(elem) {
|
|
213
|
-
if (!elem) return null;
|
|
214
|
-
if (elem.tagName === 'IFRAME') {
|
|
215
|
-
// we have the iframe
|
|
216
|
-
return elem;
|
|
217
|
-
}
|
|
218
|
-
if (elem.firstElementChild?.tagName === 'IFRAME') {
|
|
219
|
-
// we have the shim tinymce puts around the iframe
|
|
220
|
-
return elem.firstElementChild;
|
|
221
|
-
}
|
|
222
|
-
if (elem.classList.contains('mce-shim')) {
|
|
223
|
-
// tinymce puts a <span class='mce-shin'> after the iframe (since v5, I think)
|
|
224
|
-
if (elem.previousSibling?.tagName === 'IFRAME') {
|
|
225
|
-
return elem.previousSibling;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
return null;
|
|
229
212
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EditorEvent, Events } from 'tinymce';
|
|
1
|
+
import { Editor, EditorEvent, Events } from 'tinymce';
|
|
2
2
|
/**
|
|
3
3
|
* Interface for content item's 'custom' field, specifically for what is expected to come from Studio
|
|
4
4
|
*
|
|
@@ -35,3 +35,12 @@ export declare function parseStudioOptions(element: Element | null): ParsedStudi
|
|
|
35
35
|
* underlying iframe has a `data-studio-resizable='false'`
|
|
36
36
|
*/
|
|
37
37
|
export declare function handleBeforeObjectSelected(e: EditorEvent<Events.ObjectSelectedEvent>): void;
|
|
38
|
+
export declare function findStudioLtiIframeFromSelection(selectedNode: Node): HTMLIFrameElement | null;
|
|
39
|
+
export declare const notifyStudioEmbedTypeChange: (editor: Editor, embedType: "thumbnail_embed" | "learn_embed" | "collaboration_embed") => void;
|
|
40
|
+
export type EmbedType = 'thumbnail_embed' | 'learn_embed' | 'collaboration_embed';
|
|
41
|
+
export declare const updateStudioIframeDimensions: (editor: Editor, width: number, height: number, embedType: EmbedType, resizable?: boolean) => void;
|
|
42
|
+
type ValidStudioEmbedType = 'thumbnail_embed' | 'learn_embed' | 'collaboration_embed';
|
|
43
|
+
export declare const isValidEmbedType: (embedType: any) => embedType is ValidStudioEmbedType;
|
|
44
|
+
export declare const isValidDimension: (value: any) => value is number;
|
|
45
|
+
export declare const isValidResizable: (value: any) => value is boolean;
|
|
46
|
+
export {};
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import PropTypes, { bool, shape } from 'prop-types';
|
|
20
|
+
import { findMediaPlayerIframe } from './iframeUtils';
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Interface for content item's 'custom' field, specifically for what is expected to come from Studio
|
|
@@ -71,4 +72,112 @@ export function handleBeforeObjectSelected(e) {
|
|
|
71
72
|
if (targetElement.getAttribute('data-mce-p-data-studio-resizable') === 'false') {
|
|
72
73
|
targetElement.setAttribute('data-mce-resize', 'false');
|
|
73
74
|
}
|
|
74
|
-
}
|
|
75
|
+
}
|
|
76
|
+
export function findStudioLtiIframeFromSelection(selectedNode) {
|
|
77
|
+
let outerIframe = null;
|
|
78
|
+
|
|
79
|
+
// First, find the outer iframe
|
|
80
|
+
if (selectedNode.nodeName === 'IFRAME') {
|
|
81
|
+
outerIframe = selectedNode;
|
|
82
|
+
} else if (selectedNode.nodeType === Node.ELEMENT_NODE) {
|
|
83
|
+
// Look for iframe inside the selected element (the span)
|
|
84
|
+
outerIframe = selectedNode.querySelector('iframe');
|
|
85
|
+
}
|
|
86
|
+
if (!outerIframe) {
|
|
87
|
+
console.error('No outer iframe found');
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Now try to access the content document of the outer iframe
|
|
92
|
+
try {
|
|
93
|
+
const outerIframeDoc = outerIframe.contentDocument || outerIframe.contentWindow?.document;
|
|
94
|
+
if (!outerIframeDoc) {
|
|
95
|
+
return outerIframe; // Return outer iframe as fallback
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Search for nested iframe with data-lti-launch attribute
|
|
99
|
+
const nestedIframe = outerIframeDoc.querySelector('iframe[data-lti-launch="true"]');
|
|
100
|
+
if (nestedIframe) {
|
|
101
|
+
return nestedIframe;
|
|
102
|
+
} else {
|
|
103
|
+
// Try to find any iframe inside
|
|
104
|
+
const anyNestedIframe = outerIframeDoc.querySelector('iframe');
|
|
105
|
+
if (anyNestedIframe) {
|
|
106
|
+
return anyNestedIframe;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} catch (error) {
|
|
110
|
+
console.error('>> Cannot access outer iframe content (cross-origin):', error);
|
|
111
|
+
// Return the outer iframe as fallback since we can't access its contents
|
|
112
|
+
return outerIframe;
|
|
113
|
+
}
|
|
114
|
+
return outerIframe;
|
|
115
|
+
}
|
|
116
|
+
export const notifyStudioEmbedTypeChange = (editor, embedType) => {
|
|
117
|
+
const studioIframe = findStudioLtiIframeFromSelection(editor.selection.getNode());
|
|
118
|
+
if (studioIframe && studioIframe.contentWindow) {
|
|
119
|
+
studioIframe.contentWindow.postMessage({
|
|
120
|
+
subject: 'studio.embedTypeChanged',
|
|
121
|
+
embedType: embedType,
|
|
122
|
+
timestamp: Date.now()
|
|
123
|
+
}, '*');
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
export const updateStudioIframeDimensions = (editor, width, height, embedType, resizable) => {
|
|
127
|
+
const selectedNode = editor.selection.getNode();
|
|
128
|
+
const videoContainer = findMediaPlayerIframe(selectedNode);
|
|
129
|
+
if (videoContainer?.tagName !== 'IFRAME') {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const tinymceIframeShim = videoContainer.parentElement;
|
|
133
|
+
if (!tinymceIframeShim) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
editor.dom.setStyles(tinymceIframeShim, {
|
|
137
|
+
width: `${width}px`,
|
|
138
|
+
height: `${height}px`
|
|
139
|
+
});
|
|
140
|
+
editor.dom.setStyles(videoContainer, {
|
|
141
|
+
width: `${width}px`,
|
|
142
|
+
height: `${height}px`
|
|
143
|
+
});
|
|
144
|
+
if (resizable !== undefined) {
|
|
145
|
+
// Update both the actual attribute and the TinyMCE prefixed version
|
|
146
|
+
// This ensures they stay in sync when content is saved and reloaded
|
|
147
|
+
editor.dom.setAttrib(tinymceIframeShim, 'data-studio-resizable', String(resizable));
|
|
148
|
+
editor.dom.setAttrib(tinymceIframeShim, 'data-mce-p-data-studio-resizable', String(resizable));
|
|
149
|
+
|
|
150
|
+
// Force TinyMCE to update the overlay by setting/removing data-mce-resize
|
|
151
|
+
if (!resizable) {
|
|
152
|
+
tinymceIframeShim.setAttribute('data-mce-resize', 'false');
|
|
153
|
+
} else {
|
|
154
|
+
tinymceIframeShim.removeAttribute('data-mce-resize');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const href = editor.dom.getAttrib(tinymceIframeShim, 'data-mce-p-src');
|
|
158
|
+
if (href && embedType) {
|
|
159
|
+
if (embedType) {
|
|
160
|
+
// Replace thumbnail_embed, learn_embed, or collaboration_embed with the new embed type
|
|
161
|
+
const updatedHref = href.replace(/(thumbnail_embed|learn_embed|collaboration_embed)/g, embedType);
|
|
162
|
+
|
|
163
|
+
// updating only mce-p-src as in we only want to update the real src whenever we step out of the editor or save it
|
|
164
|
+
editor.dom.setAttrib(tinymceIframeShim, 'data-mce-p-src', updatedHref);
|
|
165
|
+
editor.nodeChanged();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
editor.fire('ObjectResized', {
|
|
169
|
+
// @ts-expect-error - needed for aligning tooltip with new iframe size
|
|
170
|
+
target: videoContainer,
|
|
171
|
+
width,
|
|
172
|
+
height
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
export const isValidEmbedType = embedType => {
|
|
176
|
+
return typeof embedType === 'string' && ['thumbnail_embed', 'learn_embed', 'collaboration_embed'].includes(embedType);
|
|
177
|
+
};
|
|
178
|
+
export const isValidDimension = value => {
|
|
179
|
+
return typeof value === 'number' && !isNaN(value) && isFinite(value) && value > 0;
|
|
180
|
+
};
|
|
181
|
+
export const isValidResizable = value => {
|
|
182
|
+
return typeof value === 'boolean';
|
|
183
|
+
};
|
|
@@ -238,7 +238,7 @@ export default function ComputerPanel({
|
|
|
238
238
|
});
|
|
239
239
|
},
|
|
240
240
|
renderIcon: IconTrashLine,
|
|
241
|
-
screenReaderLabel: formatMessage('
|
|
241
|
+
screenReaderLabel: formatMessage('Remove {filename}', {
|
|
242
242
|
filename
|
|
243
243
|
})
|
|
244
244
|
})), /*#__PURE__*/React.createElement(Flex.Item, {
|
|
@@ -31,9 +31,11 @@ import ImageOptionsForm from '../ImageOptionsForm';
|
|
|
31
31
|
import UsageRightsSelectBox from './UsageRightsSelectBox';
|
|
32
32
|
import { View } from '@instructure/ui-view';
|
|
33
33
|
import { UploadCanvasPanelIds, CanvasPanelTitles } from '../canvasContentUtils';
|
|
34
|
+
import { validateVideoUrl } from './videoValidationUtils';
|
|
34
35
|
const CanvasContentPanel = /*#__PURE__*/React.lazy(() => import('./CanvasContentPanel'));
|
|
35
36
|
const ComputerPanel = /*#__PURE__*/React.lazy(() => import('./ComputerPanel'));
|
|
36
37
|
const UrlPanel = /*#__PURE__*/React.lazy(() => import('./UrlPanel'));
|
|
38
|
+
const VideoUrlPanel = /*#__PURE__*/React.lazy(() => import('./VideoUrlPanel'));
|
|
37
39
|
function shouldBeDisabled({
|
|
38
40
|
fileUrl,
|
|
39
41
|
theFile,
|
|
@@ -46,6 +48,7 @@ function shouldBeDisabled({
|
|
|
46
48
|
case 'COMPUTER':
|
|
47
49
|
return !theFile || theFile.error;
|
|
48
50
|
case 'URL':
|
|
51
|
+
case 'VIDEO_URL':
|
|
49
52
|
return !fileUrl;
|
|
50
53
|
default:
|
|
51
54
|
if (UploadCanvasPanelIds.includes(selectedPanel)) return !fileUrl;
|
|
@@ -133,8 +136,17 @@ const UploadFileModal = /*#__PURE__*/React.forwardRef(({
|
|
|
133
136
|
if (submitDisabled || uploading) {
|
|
134
137
|
return false;
|
|
135
138
|
}
|
|
139
|
+
let finalFileUrl = fileUrl;
|
|
140
|
+
if (selectedPanel === 'VIDEO_URL' && finalFileUrl) {
|
|
141
|
+
const validation = validateVideoUrl(finalFileUrl);
|
|
142
|
+
if (!validation.isValid) {
|
|
143
|
+
setError('Invalid video URL');
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
finalFileUrl = validation.embedUrl;
|
|
147
|
+
}
|
|
136
148
|
onSubmit(editor, accept, selectedPanel, {
|
|
137
|
-
fileUrl,
|
|
149
|
+
fileUrl: finalFileUrl,
|
|
138
150
|
theFile,
|
|
139
151
|
imageOptions: {
|
|
140
152
|
altText,
|
|
@@ -155,7 +167,7 @@ const UploadFileModal = /*#__PURE__*/React.forwardRef(({
|
|
|
155
167
|
}), /*#__PURE__*/React.createElement(Heading, null, label)), /*#__PURE__*/React.createElement(Modal.Body, {
|
|
156
168
|
ref: ref
|
|
157
169
|
}, /*#__PURE__*/React.createElement(Tabs, {
|
|
158
|
-
onRequestTabChange: (
|
|
170
|
+
onRequestTabChange: (_event, {
|
|
159
171
|
index
|
|
160
172
|
}) => handleRequestTabChange(index)
|
|
161
173
|
}, panels.map(panel => {
|
|
@@ -197,7 +209,28 @@ const UploadFileModal = /*#__PURE__*/React.forwardRef(({
|
|
|
197
209
|
})
|
|
198
210
|
}, /*#__PURE__*/React.createElement(UrlPanel, {
|
|
199
211
|
fileUrl: fileUrl,
|
|
200
|
-
setFileUrl: setFileUrl
|
|
212
|
+
setFileUrl: setFileUrl,
|
|
213
|
+
urlHasError: !!error
|
|
214
|
+
})));
|
|
215
|
+
case 'VIDEO_URL':
|
|
216
|
+
return /*#__PURE__*/React.createElement(Tabs.Panel, {
|
|
217
|
+
key: panel,
|
|
218
|
+
renderTitle: function () {
|
|
219
|
+
return formatMessage('Video URL');
|
|
220
|
+
},
|
|
221
|
+
isSelected: selectedPanel === 'VIDEO_URL'
|
|
222
|
+
}, /*#__PURE__*/React.createElement(Suspense, {
|
|
223
|
+
fallback: /*#__PURE__*/React.createElement(Spinner, {
|
|
224
|
+
renderTitle: formatMessage('Loading'),
|
|
225
|
+
size: "large"
|
|
226
|
+
})
|
|
227
|
+
}, /*#__PURE__*/React.createElement(VideoUrlPanel, {
|
|
228
|
+
fileUrl: fileUrl,
|
|
229
|
+
setFileUrl: url => {
|
|
230
|
+
setError(null);
|
|
231
|
+
setFileUrl(url);
|
|
232
|
+
},
|
|
233
|
+
urlHasError: !!error
|
|
201
234
|
})));
|
|
202
235
|
default:
|
|
203
236
|
if (UploadCanvasPanelIds.includes(panel)) {
|
|
@@ -274,7 +307,7 @@ UploadFileModal.propTypes = {
|
|
|
274
307
|
canvasOrigin: string,
|
|
275
308
|
onSubmit: func,
|
|
276
309
|
onDismiss: func.isRequired,
|
|
277
|
-
panels: arrayOf(oneOf(['COMPUTER', 'URL', ...UploadCanvasPanelIds])),
|
|
310
|
+
panels: arrayOf(oneOf(['COMPUTER', 'URL', 'VIDEO_URL', ...UploadCanvasPanelIds])),
|
|
278
311
|
label: string.isRequired,
|
|
279
312
|
accept: oneOfType([arrayOf(string), string]),
|
|
280
313
|
modalBodyWidth: number,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
declare function VideoUrlPanel({ fileUrl, setFileUrl, urlHasError }: {
|
|
2
|
+
fileUrl: any;
|
|
3
|
+
setFileUrl: any;
|
|
4
|
+
urlHasError: any;
|
|
5
|
+
}): React.JSX.Element;
|
|
6
|
+
declare namespace VideoUrlPanel {
|
|
7
|
+
namespace propTypes {
|
|
8
|
+
export let fileUrl: import("prop-types").Validator<string>;
|
|
9
|
+
export let setFileUrl: import("prop-types").Validator<(...args: any[]) => any>;
|
|
10
|
+
export { bool as urlHasError };
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export default VideoUrlPanel;
|
|
14
|
+
import React from 'react';
|
|
15
|
+
import { bool } from 'prop-types';
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2025 - 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
|
+
import React from 'react';
|
|
20
|
+
import { string, func, bool } from 'prop-types';
|
|
21
|
+
import { TextInput } from '@instructure/ui-text-input';
|
|
22
|
+
import formatMessage from '../../../../format-message';
|
|
23
|
+
export default function VideoUrlPanel({
|
|
24
|
+
fileUrl,
|
|
25
|
+
setFileUrl,
|
|
26
|
+
urlHasError
|
|
27
|
+
}) {
|
|
28
|
+
const handleChange = (_e, val) => {
|
|
29
|
+
setFileUrl(val);
|
|
30
|
+
};
|
|
31
|
+
const getErrorMessage = () => {
|
|
32
|
+
if (!urlHasError) return [];
|
|
33
|
+
return [{
|
|
34
|
+
text: formatMessage('Please enter a valid video URL from a supported platform.'),
|
|
35
|
+
type: 'newError'
|
|
36
|
+
}];
|
|
37
|
+
};
|
|
38
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(TextInput, {
|
|
39
|
+
name: "video-url",
|
|
40
|
+
renderLabel: formatMessage('YouTube embed URL'),
|
|
41
|
+
type: "text",
|
|
42
|
+
value: fileUrl,
|
|
43
|
+
onChange: handleChange,
|
|
44
|
+
messages: getErrorMessage()
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
VideoUrlPanel.propTypes = {
|
|
48
|
+
fileUrl: string.isRequired,
|
|
49
|
+
setFileUrl: func.isRequired,
|
|
50
|
+
urlHasError: bool
|
|
51
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2020 - 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
|
+
function validateAndExtractYouTubeUrl(input) {
|
|
20
|
+
if (!input || typeof input !== 'string') {
|
|
21
|
+
return {
|
|
22
|
+
isValid: false,
|
|
23
|
+
embedUrl: null
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
const trimmedInput = input.trim();
|
|
27
|
+
const patterns = [/^(?:https?:\/\/)?(?:www\.|m\.)?(?:youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9_-]+)(?:[&?][^\s]*)?$/, /^(?:https?:\/\/)?(?:www\.)?youtube\.com\/embed\/([a-zA-Z0-9_-]+)(?:[?][^\s]*)?$/, /<iframe[^>]*src=["'](?:https?:\/\/)?(?:www\.)?youtube\.com\/embed\/([a-zA-Z0-9_-]+)[^"']*["'][^>]*>/];
|
|
28
|
+
for (const pattern of patterns) {
|
|
29
|
+
const match = trimmedInput.match(pattern);
|
|
30
|
+
if (match && match[1]) {
|
|
31
|
+
const videoId = match[1];
|
|
32
|
+
return {
|
|
33
|
+
isValid: true,
|
|
34
|
+
embedUrl: `https://www.youtube.com/embed/${videoId}`
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
isValid: false,
|
|
40
|
+
embedUrl: null
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function validateVideoUrl(input) {
|
|
44
|
+
if (!input || typeof input !== 'string') {
|
|
45
|
+
return {
|
|
46
|
+
isValid: false,
|
|
47
|
+
embedUrl: null
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const youTubeRegexp = /(?:https?:\/\/)?(?:www\.|m\.)?(?:youtube\.com|youtu\.be)/i;
|
|
51
|
+
if (youTubeRegexp.test(input)) {
|
|
52
|
+
return validateAndExtractYouTubeUrl(input);
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
isValid: false,
|
|
56
|
+
embedUrl: null
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function findMediaPlayerIframe(elem: Element | null): Element | null;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2025 - 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
|
+
export function findMediaPlayerIframe(elem) {
|
|
20
|
+
if (!elem) return null;
|
|
21
|
+
if (elem.tagName === 'IFRAME') {
|
|
22
|
+
// we have the iframe
|
|
23
|
+
return elem;
|
|
24
|
+
}
|
|
25
|
+
if (elem.firstElementChild?.tagName === 'IFRAME') {
|
|
26
|
+
// we have the shim tinymce puts around the iframe
|
|
27
|
+
return elem.firstElementChild;
|
|
28
|
+
}
|
|
29
|
+
if (elem.classList.contains('mce-shim')) {
|
|
30
|
+
// tinymce puts a <span class='mce-shin'> after the iframe (since v5, I think)
|
|
31
|
+
const prevSibling = elem.previousSibling;
|
|
32
|
+
if (prevSibling && 'tagName' in prevSibling && prevSibling.tagName === 'IFRAME') {
|
|
33
|
+
return prevSibling;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
package/es/rce/tinyRCE.js
CHANGED
|
@@ -55,6 +55,8 @@ import './plugins/instructure_html_view/plugin';
|
|
|
55
55
|
import './plugins/instructure_media_embed/plugin';
|
|
56
56
|
import './plugins/instructure_icon_maker/plugin';
|
|
57
57
|
import './plugins/instructure_wordcount/plugin';
|
|
58
|
+
import './plugins/instructure_wordcount_header/plugin';
|
|
59
|
+
import './plugins/instructure_keyboard_shortcuts_header/plugin';
|
|
58
60
|
import './plugins/instructure_paste/plugin';
|
|
59
61
|
import './plugins/instructure_fullscreen/plugin';
|
|
60
62
|
import './plugins/instructure_studio_media_options/plugin';
|
|
@@ -91,6 +91,7 @@ export function fetchFolders(bookmark: any): (dispatch: any, getState: any) => a
|
|
|
91
91
|
export function mediaUploadComplete(error: any, uploadData: any): (dispatch: any, _getState: any) => void;
|
|
92
92
|
export function createMediaServerSession(): (dispatch: any, getState: any) => any;
|
|
93
93
|
export function uploadToIconMakerFolder(svg: any, uploadSettings?: {}): (_dispatch: any, getState: any) => any;
|
|
94
|
+
export function uploadToMediaFolderWithoutEditor(fileMetaProps: any): (_: any, getState: any) => any;
|
|
94
95
|
export function uploadToMediaFolder(tabContext: any, fileMetaProps: any): (dispatch: any, getState: any) => any;
|
|
95
96
|
export function setUsageRights(source: any, fileMetaProps: any, results: any): any;
|
|
96
97
|
export function getFileUrlIfMissing(source: any, results: any): any;
|