@instructure/canvas-rce
Version:
A component wrapping Canvas's usage of Tinymce
201 lines (198 loc) • 9.13 kB
JavaScript
/*
* Copyright (C) 2015 - 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 { externalToolsEnvFor } from '../ExternalToolsEnv';
import { emptyAsNull } from '../../../../util/string-util';
import { addParentFrameContextToUrl } from '../util/addParentFrameContextToUrl';
import tinymce from 'tinymce';
import { isStudioContentItemCustomJson, studioAttributesFrom, displayStyleFrom } from '../../shared/StudioLtiSupportUtils';
function maybeAddPx(value) {
if (value == null) return undefined;
const strVal = String(value).trim();
if (/^\d+$/.test(strVal)) {
return strVal + 'px';
}
return strVal;
}
export class RceLti11ContentItem {
static fromJSON(contentItem, env = externalToolsEnvFor(tinymce.activeEditor)) {
return new RceLti11ContentItem(contentItem, env);
}
constructor(contentItem, env = externalToolsEnvFor(tinymce.activeEditor)) {
this.contentItem = void 0;
this.env = void 0;
this.contentItem = contentItem;
this.env = env;
}
get text() {
return this.contentItem.text;
}
get isLTI() {
var _this$contentItem$med;
return LTI_MIME_TYPES.includes((_this$contentItem$med = this.contentItem.mediaType) !== null && _this$contentItem$med !== void 0 ? _this$contentItem$med : '');
}
get isOverriddenForThumbnail() {
return this.isLTI && this.contentItem.thumbnail && this.contentItem.placementAdvice?.presentationDocumentTarget === 'iframe';
}
get isImage() {
return this.contentItem.mediaType?.startsWith?.('image') === true;
}
get linkClassName() {
return this.isOverriddenForThumbnail ? 'lti-thumbnail-launch' : '';
}
get url() {
return (this.isLTI ? this.contentItem.canvasURL : this.contentItem.url)?.replace(/^(data:text\/html|javascript:)/, '#$1');
}
get linkTarget() {
if (this.isOverriddenForThumbnail) {
return JSON.stringify(this.contentItem.placementAdvice);
}
return this.contentItem?.placementAdvice?.presentationDocumentTarget?.toLowerCase() === 'window' ? '_blank' : null;
}
get docTarget() {
if (this.contentItem?.placementAdvice?.presentationDocumentTarget === 'embed' && !this.isImage) {
return 'text';
} else if (this.isOverriddenForThumbnail) {
return 'link';
}
return this.contentItem?.placementAdvice?.presentationDocumentTarget?.toLowerCase();
}
get codePayload() {
switch (this.docTarget) {
case 'iframe':
return this.generateCodePayloadIframe();
case 'embed':
return this.generateCodePayloadEmbed();
case 'text':
return this.generateCodePayloadText();
default:
return this.generateCodePayloadLink();
}
}
get containingCanvasLtiToolId() {
return this.env.containingCanvasLtiToolId;
}
get currentTinyMceSelection() {
return this.env.editorSelection;
}
generateCodePayloadIframe() {
var _addParentFrameContex, _this$contentItem$tit, _this$contentItem$pla, _this$contentItem$pla2;
const iframe = document.createElement('iframe');
iframe.src = (_addParentFrameContex = addParentFrameContextToUrl(this.url, this.containingCanvasLtiToolId)) !== null && _addParentFrameContex !== void 0 ? _addParentFrameContex : '';
iframe.title = (_this$contentItem$tit = this.contentItem.title) !== null && _this$contentItem$tit !== void 0 ? _this$contentItem$tit : '';
iframe.setAttribute('allowfullscreen', 'true');
iframe.setAttribute('webkitallowfullscreen', 'true');
iframe.setAttribute('mozallowfullscreen', 'true');
if (this.env?.ltiIframeAllowPolicy !== undefined) {
iframe.setAttribute('allow', this.env.ltiIframeAllowPolicy);
} else if (this.isLTI) {
iframe.setAttribute('allow', 'microphone; camera; midi');
}
if (this.contentItem.class) {
iframe.className = this.contentItem.class;
}
const w = maybeAddPx((_this$contentItem$pla = this.contentItem.placementAdvice?.displayWidth) !== null && _this$contentItem$pla !== void 0 ? _this$contentItem$pla : undefined);
const h = maybeAddPx((_this$contentItem$pla2 = this.contentItem.placementAdvice?.displayHeight) !== null && _this$contentItem$pla2 !== void 0 ? _this$contentItem$pla2 : undefined);
if (w) {
iframe.style.width = w;
iframe.setAttribute('width', w.replace('px', ''));
}
if (h) {
iframe.style.height = h;
iframe.setAttribute('height', h.replace('px', ''));
}
if (isStudioContentItemCustomJson(this.contentItem.custom)) {
const studioAttributes = studioAttributesFrom(this.contentItem.custom);
const ds = displayStyleFrom(studioAttributes);
if (ds) iframe.style.display = ds;
for (const key in studioAttributes) {
const val = studioAttributes[key];
if (val !== undefined && val !== null) {
iframe.setAttribute(key, String(val));
}
}
}
const div = document.createElement('div');
div.appendChild(iframe);
return div.innerHTML;
}
generateCodePayloadEmbed() {
var _this$contentItem$pla3, _this$contentItem$pla4;
const img = document.createElement('img');
if (this.url) img.src = this.url;
if (this.text) img.alt = this.text;
const w = maybeAddPx((_this$contentItem$pla3 = this.contentItem.placementAdvice?.displayWidth) !== null && _this$contentItem$pla3 !== void 0 ? _this$contentItem$pla3 : undefined);
const h = maybeAddPx((_this$contentItem$pla4 = this.contentItem.placementAdvice?.displayHeight) !== null && _this$contentItem$pla4 !== void 0 ? _this$contentItem$pla4 : undefined);
if (w) img.style.width = w;
if (h) img.style.height = h;
const div = document.createElement('div');
div.appendChild(img);
return div.innerHTML;
}
generateCodePayloadText() {
var _this$text;
return (_this$text = this.text) !== null && _this$text !== void 0 ? _this$text : '';
}
generateCodePayloadLink() {
const div = document.createElement('div');
const a = document.createElement('a');
if (this.url) a.href = this.url;
if (this.contentItem.title) a.title = this.contentItem.title;
if (this.linkTarget) a.target = this.linkTarget;
if (this.linkClassName) a.className = this.linkClassName;
div.appendChild(a);
if (this.contentItem.thumbnail && this.contentItem.thumbnail['@id']) {
var _this$contentItem$thu, _this$contentItem$thu2;
const img = document.createElement('img');
img.src = this.contentItem.thumbnail['@id'];
const h = maybeAddPx((_this$contentItem$thu = this.contentItem.thumbnail.height) !== null && _this$contentItem$thu !== void 0 ? _this$contentItem$thu : 48);
const w = maybeAddPx((_this$contentItem$thu2 = this.contentItem.thumbnail.width) !== null && _this$contentItem$thu2 !== void 0 ? _this$contentItem$thu2 : 48);
if (h) img.style.height = h;
if (w) img.style.width = w;
if (this.text) img.alt = this.text;
a.appendChild(img);
} else if (emptyAsNull(this.currentTinyMceSelection) != null && a != null) {
var _this$currentTinyMceS;
a.innerHTML = (_this$currentTinyMceS = this.currentTinyMceSelection) !== null && _this$currentTinyMceS !== void 0 ? _this$currentTinyMceS : '';
} else {
// don't inject tool provided content into the page HTML
const linkHtml = this.generateLinkHtml();
if (linkHtml) a.textContent = linkHtml;
}
return div.innerHTML;
}
generateLinkHtml() {
var _ref, _emptyAsNull;
return (_ref = (_emptyAsNull = emptyAsNull(this.currentTinyMceSelection)) !== null && _emptyAsNull !== void 0 ? _emptyAsNull : emptyAsNull(this.contentItem.text?.trim())) !== null && _ref !== void 0 ? _ref : this.contentItem?.title?.trim();
}
}
const LTI_MIME_TYPES = ['application/vnd.ims.lti.v1.ltilink', 'application/vnd.ims.lti.v1.launch+json'];
/**
* Declare the global tinyMCE information used to pass editor context around in Canvas.
*
* Eventually, this should be moved into packages/canvas-rce.
*/
/**
* Interface for content items that come from external tool resource selection.
*
* Note that this interface may not be exhaustive, but provides types for the portion of ContentItem used by Canvas.
* Additionally, there are some extra properties present here used by canvas
*
* See https://www.imsglobal.org/spec/lti-dl/v2p0#content-item-types
* and https://www.imsglobal.org/lti/model/mediatype/application/vnd/ims/lti/v1/contentitems%2Bjson/index.html
* and https://www.imsglobal.org/lti/model/mediatype/application/vnd/ims/lti/v1/contentitems%2Bjson/context.json
*/