@instructure/canvas-rce
Version:
A component wrapping Canvas's usage of Tinymce
226 lines (223 loc) • 6.82 kB
JavaScript
/*
* Copyright (C) 2018 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { K5Uploader } from '@instructure/k5uploader';
import { fileEmbed } from '../common/mimeClass';
import mediaTranslations from '../rce/plugins/instructure_record/mediaTranslations';
/* eslint no-console: 0 */
export default class Bridge {
constructor() {
this.insertLink = link => {
if (this.focusedEditor) {
const {
selection
} = this.focusedEditor.props.tinymce.get(this.focusedEditor.props.textareaId);
link.selectionDetails = {
node: selection.getNode(),
range: selection.getRng()
};
if (!link.text || link.text.trim().length === 0) {
link.text = link.title || link.href;
}
this.focusedEditor.insertLink(link);
this.controller(this.focusedEditor.id)?.hideTray();
} else {
console.warn('clicked sidebar link without a focused editor');
}
};
// insertFileLink is called from the FileBrowser when All files is chosen
// vs the above insertLink which is called from the other CanvasContentTray panels.
this.insertFileLink = link => {
const embedData = fileEmbed(link);
if (embedData.type === 'image') {
return this.insertImage(link);
} else if (embedData.type === 'video' || embedData.type === 'audio') {
link.embedded_iframe_url = link.embedded_iframe_url || link.href;
return this.embedMedia(link);
}
return this.insertLink(link);
};
this.embedImage = image => {
if (this.existingContentToLink() && !this.existingContentToLinkIsImg()) {
this.insertLink({
title: image.display_name,
href: image.href,
embed: {
type: 'image'
}
});
} else {
this.insertImage(image);
}
};
this.embedMedia = media => {
const embedData = fileEmbed(media);
if (embedData.type === 'video') {
this.insertVideo(media);
} else {
this.insertAudio(media);
}
};
this.insertEmbedCode = embedCode => {
this.focusedEditor.insertEmbedCode(embedCode);
};
this.insertVideo = video => {
if (this.focusedEditor) {
this.focusedEditor.insertVideo(video);
this.controller(this.focusedEditor.id)?.hideTray();
}
};
this.insertAudio = audio => {
if (this.focusedEditor) {
this.focusedEditor.insertAudio(audio);
this.controller(this.focusedEditor.id)?.hideTray();
}
};
this.focusedEditor = null; // the RCEWrapper, not tinymce
this.resolveEditorRendered = null;
this._editorRendered = new Promise(resolve => {
this.resolveEditorRendered = resolve;
});
this.trayProps = new WeakMap();
this.userLocale = 'en';
this._controller = {};
this._uploadMediaTranslations = null;
this._canvasOrigin = '';
}
get editorRendered() {
return this._editorRendered;
}
controller(editorId) {
return this._controller[editorId];
}
activeEditor() {
return this.focusedEditor;
}
focusEditor(editor) {
if (this.focusedEditor !== editor) {
this.hideTrays();
}
this.focusedEditor = editor;
}
blurEditor(editor) {
if (this.focusedEditor === editor) {
this.hideTrays();
this.focusedEditor = null;
}
}
focusActiveEditor(skipFocus = false) {
this.focusedEditor?.mceInstance?.()?.focus(skipFocus);
}
get mediaServerSession() {
return this._mediaServerSession;
}
get mediaServerUploader() {
return this._mediaServerUploader;
}
setMediaServerSession(session) {
this._mediaServerSession = session;
if (this._mediaServerUploader) {
this._mediaServerUploader.destroy();
this._mediaServerUploader = null;
}
this._mediaServerUploader = new K5Uploader(session);
}
get canvasOrigin() {
return this._canvasOrigin;
}
set canvasOrigin(origin) {
this._canvasOrigin = origin;
}
// we have to defer importing mediaTranslations until they are asked for
// or they get imported before the locale has been setup and all the strings
// are in English
get uploadMediaTranslations() {
if (!this._uploadMediaTranslations) {
this._uploadMediaTranslations = mediaTranslations;
}
return this._uploadMediaTranslations;
}
detachEditor(editor) {
if (editor === this.focusedEditor) {
this.focusedEditor = null;
}
}
getEditor() {
return this.focusedEditor;
}
renderEditor(editor) {
this.resolveEditorRendered();
if (this.focusedEditor === null) {
this.focusEditor(editor);
}
}
attachController(controller, editorId) {
this._controller[editorId] = controller;
}
detachController(editorId) {
delete this._controller[editorId];
}
showTrayForPlugin(plugin, editorId) {
this._controller[editorId]?.showTrayForPlugin(plugin);
}
hideTrays() {
Object.keys(this._controller).forEach(eid => {
this._controller[eid].hideTray(true);
});
}
existingContentToLink() {
if (this.focusedEditor) {
return this.focusedEditor.existingContentToLink();
}
return false;
}
existingContentToLinkIsImg() {
if (this.focusedEditor) {
return this.focusedEditor.existingContentToLinkIsImg();
}
return false;
}
insertImage(image) {
if (this.focusedEditor) {
const result = this.focusedEditor.insertImage(image);
this.controller(this.focusedEditor.id)?.hideTray();
return result;
}
}
insertImagePlaceholder(fileMetaProps) {
if (this.focusedEditor) {
// don't insert a placeholder if the user has selected content, because in some cases the selected
// content will be used as the content of a link
if (!this.existingContentToLink()) {
this.focusedEditor.insertImagePlaceholder(fileMetaProps);
}
}
}
removePlaceholders(name) {
if (this.focusedEditor) {
this.focusedEditor.removePlaceholders(name);
}
}
showError(err) {
if (this.focusedEditor) {
this.focusedEditor.addAlert({
text: err.toString(),
type: 'error'
});
}
}
}