@instructure/canvas-rce
Version:
A component wrapping Canvas's usage of Tinymce
144 lines (141 loc) • 4.48 kB
JavaScript
/*
* Copyright (C) 2019 - 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 React from 'react';
import ReactDOM from 'react-dom';
import bridge from '../../../../bridge';
import { asImageEmbed } from '../../shared/ContentSelection';
import { renderLink, updateImage } from '../../../contentRendering';
import ImageOptionsTray from '.';
export const CONTAINER_ID = 'instructure-image-options-tray-container';
export default class TrayController {
constructor() {
this._editor = null;
this._isOpen = false;
this._shouldOpen = false;
this._renderId = 0;
this._isIconMaker = false;
}
get $container() {
let $container = document.getElementById(CONTAINER_ID);
if ($container == null) {
$container = document.createElement('div');
$container.id = CONTAINER_ID;
document.body.appendChild($container);
}
return $container;
}
get isOpen() {
return this._isOpen;
}
// Tray may be called to edit an Icon Maker icon alt text
showTrayForEditor(editor, isIconMaker = false) {
this._editor = editor;
this.$img = editor.selection.getNode();
this._shouldOpen = true;
this._isIconMaker = isIconMaker;
if (bridge.focusedEditor) {
// Dismiss any content trays that may already be open
bridge.hideTrays();
}
this._renderTray();
}
hideTrayForEditor(editor) {
if (this._editor === editor) {
this._dismissTray();
}
}
_applyImageOptions(imageOptions) {
const editor = this._editor;
const {
$img
} = this;
if (this._isIconMaker) {
this._applyIconAltTextChanges($img, editor, imageOptions);
this._dismissTray();
editor.focus();
return;
}
if (imageOptions.displayAs === 'embed') {
updateImage(editor, $img, imageOptions);
// tell tinymce so the context toolbar resets
editor.fire('ObjectResized', {
target: $img,
width: imageOptions.appliedWidth,
height: imageOptions.appliedHeight
});
} else {
const link = renderLink({
href: $img.src,
text: imageOptions.altText || $img.src,
target: '_blank'
});
editor.selection.setContent(link);
}
this._dismissTray();
editor.focus();
}
_applyIconAltTextChanges($img, editor, imageOptions) {
// Workaround: When passing empty string to editor.dom.setAttribs it removes the attribute
$img.setAttribute('alt', imageOptions.altText);
editor.dom.setAttribs($img, {
role: imageOptions.isDecorativeImage ? 'presentation' : null
});
// tell tinymce so the context toolbar resets
editor.fire('ObjectResized', {
target: $img,
width: imageOptions.appliedWidth,
height: imageOptions.appliedHeight
});
}
_dismissTray() {
this._shouldOpen = false;
this._renderTray();
this.$img = null;
this._editor = null;
}
_renderTray() {
if (this._shouldOpen) {
/*
* When the tray is being opened again, it should be rendered fresh
* (clearing the internal state) so that the currently-selected image can
* be used for initial image options.
*/
this._renderId++;
}
const io = asImageEmbed(this.$img);
io.isLinked = this._editor?.selection?.getSel().anchorNode.tagName === 'A';
const element = /*#__PURE__*/React.createElement(ImageOptionsTray, {
key: this._renderId,
imageOptions: io,
onEntered: () => {
this._isOpen = true;
},
onExited: () => {
bridge.focusActiveEditor(false);
this._isOpen = false;
},
onSave: imageOptions => {
this._applyImageOptions(imageOptions);
},
onRequestClose: () => this._dismissTray(),
open: this._shouldOpen,
isIconMaker: this._isIconMaker
});
ReactDOM.render(element, this.$container);
}
}