UNPKG

@instructure/canvas-rce

Version:

A component wrapping Canvas's usage of Tinymce

192 lines (191 loc) 7.31 kB
/* * Copyright (C) 2022 - 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 formatMessage from '../format-message'; import { showFlashAlert } from '../common/FlashAlert'; import { isPreviewable, loadDocPreview, removeLoadingImage, showLoadingImage } from './doc_previews'; import { show } from './jqueryish_funcs'; import { parseUrlOrNull } from '../util/url-util'; import psl from 'psl'; const youTubeRegEx = /^https?:\/\/(www\.youtube\.com\/watch.*v(=|\/)|youtu\.be\/)([^&#]*)/; export function youTubeID(path) { const match = path.match(youTubeRegEx); if (match && match[match.length - 1]) { return match[match.length - 1]; } return null; } export function getTld(hostname) { hostname = (hostname || '').split(':')[0]; if (hostname.includes('inseng.test')) { const parts = hostname.split('.'); return parts.slice(-3).join('.'); } const parsed = psl.parse(hostname); return parsed.domain || hostname; } export function isExternalLink(element, canvasOrigin = window.location.origin) { let canvasHost; try { canvasHost = new URL(canvasOrigin).hostname; } catch (_ex) { canvasHost = window.location.hostname; } const href = element.getAttribute('href'); // if a browser doesnt support <a>.hostname then just dont mark anything as external, better to not get false positives. return !!(href && href.length && !href.match(/^(mailto\:|javascript\:)/) && element.hostname && getTld(element.hostname) !== getTld(canvasHost)); } export function showFilePreview(event, opts = {}) { event.stopPropagation(); const { canvasOrigin, disableGooglePreviews } = { ...opts }; let target = null; if (event.target?.href) { target = event.target; } else if (event.currentTarget?.href) { target = event.currentTarget; } else { return; } if (target.classList.contains('no_preview')) { return; } if (target.classList.contains('inline_disabled') || target.classList.contains('preview_in_overlay')) { showFilePreviewInOverlay(event, canvasOrigin); } else { showFilePreviewInline(event, canvasOrigin, disableGooglePreviews); } } export function showFilePreviewInOverlay(event, canvasOrigin) { let target = null; if (event.target?.href) { target = event.target; } else if (event.currentTarget?.href) { target = event.currentTarget; } const matches = target?.href.match(/\/files\/(\d+~\d+|\d+)/); if (matches) { if (event.ctrlKey || event.altKey || event.metaKey || event.shiftKey) { // if any modifier keys are pressed, do the browser default thing return; } event.preventDefault(); const url = new URL(target.href); const verifier = url?.searchParams.get('verifier'); const access_token = url?.searchParams.get('access_token'); const instfs_id = url?.searchParams.get('instfs_id'); const file_id = matches[1]; const params = { subject: 'preview_file', file_id }; if (verifier) params.verifier = verifier; if (access_token && instfs_id) { params.access_token = access_token; params.instfs_id = instfs_id; } // TODO: // 1. what window should we be using // 2. is that the right origin? // 3. this is temporary until we can decouple the file previewer from canvas window.top.postMessage(params, canvasOrigin); } } export function showFilePreviewInline(event, canvasOrigin, disableGooglePreviews) { if (event.ctrlKey || event.altKey || event.metaKey || event.shiftKey) { // if any modifier keys are pressed, do the browser default thing return; } event.preventDefault(); const $link = event.currentTarget || event.target; if ($link.getAttribute('aria-expanded') === 'true') { // close the preview by clicking the "Minimize File Preview" link const $preview = document.getElementById($link.getAttribute('aria-controls')); $preview.querySelector('.hide_file_preview_link').click(); return; } showLoadingImage($link); fetch($link.getAttribute('href').replace(/\/(download|preview)/, '') // download as part of the path .replace(/wrap=1&?/, '') // wrap=1 as part of the query_string .replace(/[?&]$/, ''), // any trailing chars if wrap=1 was at the end { method: 'GET', headers: { Accept: 'application/json' }, credentials: 'include' }).then(response => { if (!response.ok) throw new Error(`${response.status}: ${response.statusText}`); return response; }).then(response => response.json()).then(data => { const attachment = data && data.attachment; removeLoadingImage($link); let canvadoc_session_url = attachment.canvadoc_session_url; if (attachment && (!disableGooglePreviews && isPreviewable(attachment.content_type) || canvadoc_session_url)) { $link.setAttribute('aria-expanded', 'true'); if (canvasOrigin && canvadoc_session_url !== null) { canvadoc_session_url = parseUrlOrNull(canvadoc_session_url, canvasOrigin)?.toString(); } const $div = document.querySelector(`[id="${$link.getAttribute('aria-controls')}"]`); $div.style.display = 'block'; loadDocPreview($div, { canvadoc_session_url, mimeType: attachment.content_type, public_url: attachment.public_url, attachment_preview_processing: attachment.workflow_state === 'pending_upload' || attachment.workflow_state === 'processing', disableGooglePreviews }); const $minimizeLink = document.createElement('a'); $minimizeLink.setAttribute('href', '#'); $minimizeLink.setAttribute('style', 'font-size: 0.8em;'); $minimizeLink.setAttribute('class', 'hide_file_preview_link'); $minimizeLink.textContent = formatMessage('Minimize File Preview'); $minimizeLink.addEventListener('click', event2 => { event2.preventDefault(); resetInlinePreview($link, $div); }); $div.prepend($minimizeLink); if (Object.prototype.hasOwnProperty.call(event, 'originalEvent')) { // Only focus this link if the open preview link was initiated by a real browser event // If it was triggered by our auto_open stuff it shouldn't focus here. $minimizeLink.focus(); } } }).catch(ex => { showFlashAlert({ message: formatMessage('Failed getting file contents'), type: 'error' }); console.error(ex); resetInlinePreview($link); removeLoadingImage($link); }); } function resetInlinePreview($link, $previewContainer) { $link.setAttribute('aria-expanded', 'false'); show($link); $link.focus(); if ($previewContainer) { $previewContainer.innerHTML = ''; $previewContainer.style.display = 'none'; } }