Ir a la última revisión | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |
// This file is part of Moodle - http://moodle.org///// Moodle is free software: you can redistribute it and/or modify// it under the terms of the GNU General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// Moodle 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 General Public License for more details.//// You should have received a copy of the GNU General Public License// along with Moodle. If not, see <http://www.gnu.org/licenses/>./*** Helper for Tiny noautolink plugin.** @module tiny_noautolink/noautolink* @copyright 2023 Meirza <meirza.arson@moodle.com>* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/import Pending from 'core/pending';const noautolinkClassName = 'nolink';const noautolinkTagHTML = 'span';const notificationTimeout = 2000;/*** Handle action.** @param {TinyMCE} editor* @param {object} messages*/export const handleAction = (editor, messages) => {const toggleState = isInAnchor(editor, editor.selection.getNode());const urlString = getSelectedContent(editor);if (!toggleState && urlString !== '') {setNoAutoLink(editor, messages, urlString);} else if (toggleState) {unsetNoAutoLink(editor, messages, urlString);} else {editor.notificationManager.open({text: messages.infoEmptySelection, type: 'info', timeout: notificationTimeout});}};/*** Display notification feedback when applying the noautolink to the selected text.** @param {TinyMCE} editor* @param {object} messages* @param {String} urlString*/const setNoAutoLink = (editor, messages, urlString) => {// Check whether the string is a URL. Otherwise, show an error notification.if (isValidUrl(urlString)) {const pendingPromise = new Pending('tiny_noautolink/setNoautolink');// Applying the auto-link prevention.setNoautolinkOnSelection(editor, urlString).catch(error => {editor.notificationManager.open({text: error, type: 'error', timeout: notificationTimeout});}).finally(() => {editor.notificationManager.open({text: messages.infoAddSuccess, type: 'success', timeout: notificationTimeout});pendingPromise.resolve();});} else {editor.notificationManager.open({text: messages.errorInvalidURL, type: 'error', timeout: notificationTimeout});}};/*** Display notification feedback when removing the noautolink to the selected text.** @param {TinyMCE} editor* @param {object} messages*/const unsetNoAutoLink = (editor, messages) => {const nodeString = editor.selection.getNode().outerHTML.trim();// Convert HTML string to DOM element to get nolink class.const wrapper = document.createElement('div');wrapper.innerHTML = nodeString;const tempElement = wrapper.firstChild;if (tempElement.classList.contains('nolink')) {const pendingPromise = new Pending('tiny_noautolink/setNoautolink');// Removing the auto-link prevention.unsetNoautolinkOnSelection(editor, nodeString).catch(error => {editor.notificationManager.open({text: error, type: 'error', timeout: notificationTimeout});pendingPromise.reject(error); // Handle the error as needed.}).finally(() => {editor.notificationManager.open({text: messages.infoRemoveSuccess, type: 'success', timeout: notificationTimeout});pendingPromise.resolve();});}};/*** Return the full string based on the position of the cursor within the string.** @param {TinyMCE} editor* @returns {String}*/const getSelectedContent = (editor) => {const selection = editor.selection; // Get the selection object.let content = selection.getContent({format: 'text'}).trim();if (content == '') {const range = selection.getRng(); // Get the range object.// Check if the cursor is within a text node.if (range.startContainer.nodeType === Node.TEXT_NODE) {const textContent = range.startContainer.textContent;const cursorOffset = range.startOffset;// Find the word boundaries around the cursor.let wordStart = cursorOffset;while (wordStart > 0 && /\S/.test(textContent[wordStart - 1])) {wordStart--;}let wordEnd = cursorOffset;while (wordEnd < textContent.length && /\S/.test(textContent[wordEnd])) {wordEnd++;}// Set the selection range to the word.selection.setRng({startContainer: range.startContainer,startOffset: wordStart,endContainer: range.startContainer,endOffset: wordEnd,});content = selection.getContent({format: 'text'}).trim();}}return content;};/*** Wrap the selection with the nolink class.** @param {TinyMCE} editor* @param {String} url URL the link will point to.*/const setNoautolinkOnSelection = async(editor, url) => {const newContent = `<${noautolinkTagHTML} class="${noautolinkClassName}">${url}</${noautolinkTagHTML}>`;editor.selection.setContent(newContent);// Select the new content.const currentNode = editor.selection.getNode();const currentDOM = editor.dom.select(`${noautolinkTagHTML}.${noautolinkClassName}`, currentNode);currentDOM.forEach(function(value, index) {if (value.outerHTML == newContent) {editor.selection.select(currentDOM[index]);return;}});};/*** Remove the nolink on the selection.** @param {TinyMCE} editor* @param {String} url URL the link will point to.*/const unsetNoautolinkOnSelection = async(editor, url) => {const regex = new RegExp(`</?${noautolinkTagHTML}[^>]*>`, "g");url = url.replace(regex, "");const currentSpan = editor.dom.getParent(editor.selection.getNode(), noautolinkTagHTML);currentSpan.outerHTML = url;};/*** Check if given string is a valid URL.** @param {String} urlString URL the link will point to.* @returns {boolean} True is valid, otherwise false.*/const isValidUrl = urlString => {const urlPattern = new RegExp('^((http|https):\\/\\/|www\\.)' + // A URL must have one of these https/https/www.'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // Validate domain name.'((\\d{1,3}\\.){3}\\d{1,3}))' + // Validate ip (v4) address.'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // Validate port and path.'(\\?[;&a-z\\d%_.~+=-]*)?' + // Validate query string.'(\\#[-a-z\\d_]*)?$', 'i'); // Validate fragment locator.return !!urlPattern.test(urlString);};/*** Get anchor element.** @param {TinyMCE} editor* @param {Element} selectedElm* @returns {Element}*/const getAnchorElement = (editor, selectedElm) => {selectedElm = selectedElm || editor.selection.getNode();return editor.dom.getParent(selectedElm, `${noautolinkTagHTML}.${noautolinkClassName}`);};/*** Check the current selected element is an anchor or not.** @param {TinyMCE} editor* @param {Element} selectedElm* @returns {boolean}*/const isInAnchor = (editor, selectedElm) => getAnchorElement(editor, selectedElm) !== null;/*** Change state of button.** @param {TinyMCE} editor* @param {function()} toggler* @returns {function()}*/const toggleState = (editor, toggler) => {editor.on('NodeChange', toggler);return () => editor.off('NodeChange', toggler);};/*** Change the active state of button.** @param {TinyMCE} editor* @returns {function(*): function(): *}*/export const toggleActiveState = (editor) => (api) => {const updateState = () => api.setActive(!editor.mode.isReadOnly() && isInAnchor(editor, editor.selection.getNode()));updateState();return toggleState(editor, updateState);};