![]() Server : Apache System : Linux server2.corals.io 4.18.0-348.2.1.el8_5.x86_64 #1 SMP Mon Nov 15 09:17:08 EST 2021 x86_64 User : corals ( 1002) PHP Version : 7.4.33 Disable Function : exec,passthru,shell_exec,system Directory : /home/corals/old/vendor/magento/module-page-builder/view/adminhtml/web/ts/js/utils/ |
/** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ import $ from "jquery"; import "mage/adminhtml/tools"; import $t from "mage/translate"; import mageUtils from "mageUtils"; import {Editor} from "tinymce"; import Config from "../config"; /** * Is the inline WYSIWYG supported? */ export function isWysiwygSupported() { return Config.getConfig("can_use_inline_editing_on_stage"); } /** * Encode content for TinyMCE * * @param content */ export function encodeContent(content: string) { if (isWysiwygSupported()) { return convertVariablesToHtmlPreview( convertWidgetsToHtmlPreview( unescapeDoubleQuoteWithinWidgetDirective( removeInvalidPlaceholders(content), ), ), ); } return content; } /** * Prior to parsing the content remove any invalid placeholders within the content * * @param content */ function removeInvalidPlaceholders(content: string) { if (content.indexOf("magento-placeholder") !== -1) { const html = new DOMParser().parseFromString(content, "text/html"); const placeholders = html.querySelectorAll("span.magento-placeholder"); if (placeholders.length > 0) { [].slice.call(placeholders).forEach((placeholder: HTMLSpanElement) => { // If the invalid placeholder contains a directive, let's insert it back where it belongs if (placeholder.innerText.indexOf("{{") !== -1) { placeholder.parentNode.insertBefore(document.createTextNode(placeholder.innerText), placeholder); } placeholder.remove(); }); } return html.body.innerHTML; } return content; } /** * Convert all variables to their HTML preview counterparts * * Re-implements logic from lib/web/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentovariable/editor_plugin.js to parse * and replace the variables within the content. * * @param content */ function convertVariablesToHtmlPreview(content: string) { const config = Config.getConfig("tinymce").variables; const magentoVariables = JSON.parse(config.placeholders); return content.replace(/{\{\s?(?:customVar code=|config path=\")([^\}\"]+)[\"]?\s?\}\}/ig, (match, path) => { const id = btoa(path).replace(/\+/g, ":").replace(/\//g, "_").replace(/=/g, "-"); const placeholder = $("<span />") .addClass("magento-variable") .addClass("magento-placeholder") .addClass("mceNonEditable") .prop("id", id) .prop("contentEditable", "false"); if (magentoVariables[path].variable_type === "custom") { placeholder.addClass("magento-custom-var"); } const variableType = magentoVariables[path].variable_type; if (magentoVariables[path] && (variableType === "default" || variableType === "custom")) { placeholder.text(magentoVariables[path].variable_name); } else { // If we're unable to find the placeholder we need to attach an error class placeholder.addClass("magento-placeholder-error"); placeholder.text((variableType === "custom" ? path : $t("Not Found"))); } return placeholder[0].outerHTML; }); } /** * Convert widgets within content to their HTML counterparts * * @param content */ function convertWidgetsToHtmlPreview(content: string) { const config = Config.getConfig("tinymce").widgets; return content.replace(/\{\{widget([\S\s]*?)\}\}/ig, (match, widgetBody) => { const attributes = parseAttributesString(widgetBody); let imageSrc; if (attributes.type) { const placeholder = $("<span />") .addClass("magento-placeholder") .addClass("magento-widget") .addClass("mceNonEditable") .prop("id", mageUtils.uniqueid()) .prop("contentEditable", "false"); attributes.type = attributes.type.replace(/\\\\/g, "\\"); imageSrc = config.placeholders[attributes.type]; if (!imageSrc) { imageSrc = config.error_image_url; placeholder.addClass("magento-placeholder-error"); } const image = $("<img />") .prop("id", window.Base64.idEncode(match)) .prop("src", imageSrc); placeholder.append(image); let widgetType = ""; if (config.types[attributes.type]) { widgetType += config.types[attributes.type]; } placeholder.append($(document.createTextNode(widgetType))); return placeholder[0].outerHTML; } }); } /** * Parse attributes into a string * * @param attributes */ export function parseAttributesString(attributes: string): { [key: string]: string; } { const result: { [key: string]: string; } = {}; attributes.replace( /(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g, (match: string, key: string, value: string) => { result[key] = value ? value.replace(/"e;/g, "\"") : value; return ""; }, ); return result; } /** * Lock all image sizes before initializing TinyMCE to avoid content jumps * * @param element */ export function lockImageSize(element: HTMLElement) { [].slice.call(element.querySelectorAll("img")).forEach((image: HTMLImageElement) => { if (image.style.width.length === 0) { image.style.width = /^\d+$/.test(image.getAttribute("width")) ? image.getAttribute("width") + "px" : image.getAttribute("width"); image.setAttribute("data-width-locked", "true"); } if (image.style.height.length === 0) { image.style.height = /^\d+$/.test(image.getAttribute("height")) ? image.getAttribute("height") + "px" : image.getAttribute("height"); image.setAttribute("data-height-locked", "true"); } }); } /** * Reverse forced image size after TinyMCE is finished initializing * * @param element */ export function unlockImageSize(element: HTMLElement) { [].slice.call(element.querySelectorAll("img")).forEach((image: HTMLImageElement) => { if (image.getAttribute("data-width-locked")) { image.style.width = null; image.removeAttribute("data-width-locked"); } if (image.getAttribute("data-height-locked")) { image.style.height = null; image.removeAttribute("data-height-locked"); } }); } /** * Create a bookmark within the content to be restored later */ export function createBookmark(event: JQueryEventObject): Bookmark { const wrapperElement = $(event.target).parents(".inline-wysiwyg"); /** * Create an element bookmark * * @param element */ const createElementBookmark = (element: Element) => { return { name: element.nodeName, index: findNodeIndex( wrapperElement[0], element.nodeName, element, ), }; }; // Handle direct clicks onto an IMG if (event.target.nodeName === "IMG") { return createElementBookmark(event.target); } if (window.getSelection) { const selection = window.getSelection(); const id = mageUtils.uniqueid(); if (selection.getRangeAt && selection.rangeCount) { const range = normalizeTableCellSelection(selection.getRangeAt(0).cloneRange()); // Determine if the current node is an image or span that we want to select instead of text const currentNode = range.startContainer as HTMLElement; if (currentNode.nodeType === Node.ELEMENT_NODE && (currentNode.nodeName === "IMG" || (currentNode.nodeName === "SPAN" && currentNode.classList.contains("magento-placeholder"))) ) { return createElementBookmark(currentNode); } // Also check if the direct parent is either of these const parentNode = range.startContainer.parentNode as HTMLElement; if (parentNode.nodeName === "IMG" || (parentNode.nodeName === "SPAN" && parentNode.classList.contains("magento-placeholder")) ) { return createElementBookmark(parentNode); } if (!range.collapsed) { range.collapse(false); const endBookmarkNode = createBookmarkSpan(id + "_end"); range.insertNode(endBookmarkNode); } const range2 = normalizeTableCellSelection(selection.getRangeAt(0)); range2.collapse(true); const startBookmarkNode = createBookmarkSpan(id + "_start"); range2.insertNode(startBookmarkNode); return {id}; } } return null; } /** * Move the cursor to our new bookmark * * @param bookmark */ export function moveToBookmark(bookmark: Bookmark) { ((window as any).tinymce.activeEditor as Editor).selection.moveToBookmark(bookmark); } /** * Retrieve active editor from TinyMCE */ export function getActiveEditor(): Editor { return ((window as any).tinymce.activeEditor as Editor); } /** * Create a bookmark span for the selection * * @param id */ function createBookmarkSpan(id: string) { const bookmark = document.createElement("span"); bookmark.setAttribute("data-mce-type", "bookmark"); bookmark.id = id; bookmark.style.overflow = "hidden"; bookmark.style.lineHeight = "0px"; return bookmark; } /** * Find the index of an element within a wrapper * * @param wrapperElement * @param name * @param element */ export function findNodeIndex(wrapperElement: HTMLElement, name: string, element: Element) { const selector = name.toLowerCase() + ':not([data-mce-bogus="all"])'; // If there is no ID on the element add a unique ID so we can efficiently find it if (!element.id) { element.id = mageUtils.uniqueid(); } return $(wrapperElement).find(selector).toArray().findIndex((node) => { return node.id === element.id; }); } /** * Get a node by index within a wrapper * * @param wrapperElement * @param name * @param index */ export function getNodeByIndex(wrapperElement: HTMLElement, name: string, index: number): HTMLElement { const selector = name.toLowerCase() + ':not([data-mce-bogus="all"])'; return $(wrapperElement).find(selector).get(index); } /** * Create a double click event that works in all browsers */ export function createDoubleClickEvent(): Event { try { return new MouseEvent("dblclick", { view: window, bubbles: true, cancelable: true, }); } catch (e) { const dblClickEvent = document.createEvent("MouseEvent"); dblClickEvent.initMouseEvent( "dblclick", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null, ); return dblClickEvent; } } /** * Replace all desktop styles that left unprocessed back to style element to prevent data corruption. */ export function processInlineStyles(html: string) { const name = Config.getConfig("defaultViewport"); const searchPattern = new RegExp(`data-${name}-style=`, "g"); return html.replace(searchPattern, "style="); } /** * Move the end point of a range to handle tables * * @param range * @param start */ function moveEndPoint(range: Range, start: boolean) { let container; let offset; let childNodes; if (start) { container = range.startContainer; offset = range.startOffset; } else { container = range.endContainer; offset = range.endOffset; } if (container.nodeType === Node.ELEMENT_NODE && container.nodeName === "TR") { childNodes = container.childNodes; container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)]; if (container) { offset = start ? 0 : container.childNodes.length; if (start) { range.setStart(container, offset); } else { range.setEnd(container, offset); } } } } /** * Normalize the table sell selection within a range to better handle selections being inside of tables * * @param range */ function normalizeTableCellSelection(range: Range): Range { moveEndPoint(range, true); moveEndPoint(range, false); return range; } /** * Convert HTML encoded double quote to double quote with backslash within widget directives * * @param {string} content * @returns {string} */ export function escapeDoubleQuoteWithinWidgetDirective(content: string): string { return content.replace(/\{\{widget[\S\s]*?\}\}/ig, (match: string) => { return match.replace(/"/g, "\\\""); }); } /** * Convert double quote with backslash to HTML encoded double quote within widget directives * * @param {string} content * @returns {string} */ export function unescapeDoubleQuoteWithinWidgetDirective(content: string): string { return content.replace(/\{\{widget[\S\s]*?\}\}/ig, (match: string) => { return match.replace(/\\+"/g, """); }); } /** * Convert double quote to single quote within magento variable directives * * @param {string} content * @returns {string} */ export function replaceDoubleQuoteWithSingleQuoteWithinVariableDirective(content: string): string { // Find html elements which attributes contain magento variables directives return content.replace( /<([a-z0-9\-\_]+)([^>]+?[a-z0-9\-\_]+="[^"]*?\{\{.+?\}\}.*?".*?)>/gi, (match1: string, tag: string, attributes: string) => { // Replace double quote with single quote within magento variable directive const sanitizedAttributes = attributes.replace( /\{\{[^\{\}]+\}\}/gi, (match2: string) => match2.replace(/"/g, "'"), ); return "<" + tag + sanitizedAttributes + ">"; }, ); } interface IdBookmark { id: string; keep?: boolean; } interface IndexBookmark { name: string; index: number; } export type Bookmark = IdBookmark | IndexBookmark;