diff --git a/src/shell/components/FieldTypeTinyMCE/index.tsx b/src/shell/components/FieldTypeTinyMCE/index.tsx index 75462a461a..2220d76b08 100644 --- a/src/shell/components/FieldTypeTinyMCE/index.tsx +++ b/src/shell/components/FieldTypeTinyMCE/index.tsx @@ -1,6 +1,6 @@ -import React, { useEffect, useState, useRef } from "react"; +import React, { useEffect, useState } from "react"; import { Editor } from "@tinymce/tinymce-react"; -import { Box, alpha, Skeleton } from "@mui/material"; +import { Box, alpha } from "@mui/material"; import { theme } from "@zesty-io/material"; // TinyMCE so the global var exists @@ -74,21 +74,12 @@ export const FieldTypeTinyMCE = React.memo(function FieldTypeTinyMCE({ // NOTE: controlled component const [initialValue, setInitialValue] = useState(value); const [isSkinLoaded, setIsSkinLoaded] = useState(false); - const editor = useRef(null); // NOTE: update if version changes useEffect(() => { setInitialValue(value); }, [version]); - /** - * NOTE: We're doing this instead of doing a ternary operator to render - * the skeleton and editor since that causes an infinite loop - */ - useEffect(() => { - editor?.current?.style?.visibility(isSkinLoaded ? "visible" : "hidden"); - }, [isSkinLoaded]); - return ( - { - onChange(content, name, datatype); - - const charCount = - editor.plugins?.wordcount?.body?.getCharacterCount() ?? 0; - - onCharacterCountChange(charCount); - }} - onInit={(_, editor) => { - const charCount = - editor.plugins?.wordcount?.body?.getCharacterCount() ?? 0; - - onCharacterCountChange(charCount); - }} - onKeyDown={(evt) => { - // Makes sure that when scrolling through a collection group, it - // autoscrolls highlighted items that are out of view - if (evt.code === "ArrowDown" || evt.code === "ArrowUp") { - const autocompleterEl = - document.getElementsByClassName("tox-autocompleter"); - - if (autocompleterEl.length) { - const activeAutocompleteItem = - autocompleterEl[0].getElementsByClassName( - "tox-collection__item--active" - ); - - // Needed to scroll to view the first and last item when scroll wrapping - setTimeout(() => { - activeAutocompleteItem?.[0]?.scrollIntoView({ - block: "center", - }); - }, 100); + + { + onChange(content, name, datatype); + + const charCount = + editor.plugins?.wordcount?.body?.getCharacterCount() ?? 0; + + onCharacterCountChange(charCount); + }} + onInit={(_, editor) => { + const charCount = + editor.plugins?.wordcount?.body?.getCharacterCount() ?? 0; + + onCharacterCountChange(charCount); + }} + onKeyDown={(evt) => { + // Makes sure that when scrolling through a collection group, it + // autoscrolls highlighted items that are out of view + if (evt.code === "ArrowDown" || evt.code === "ArrowUp") { + const autocompleterEl = + document.getElementsByClassName("tox-autocompleter"); + + if (autocompleterEl.length) { + const activeAutocompleteItem = + autocompleterEl[0].getElementsByClassName( + "tox-collection__item--active" + ); + + // Needed to scroll to view the first and last item when scroll wrapping + setTimeout(() => { + activeAutocompleteItem?.[0]?.scrollIntoView({ + block: "center", + }); + }, 100); + } + } + }} + onObjectResized={(evt) => { + if (evt.target.nodeName !== "IMG") { + return; } - } - }} - onObjectResized={(evt) => { - if (evt.target.nodeName !== "IMG") { - return; - } - - const clonedCurrentNode = evt.target.cloneNode(); - - // Remove attributes that are not included in the editor value to make replacing easier - clonedCurrentNode.removeAttribute("data-mce-src"); - clonedCurrentNode.removeAttribute("data-mce-selected"); - - const newImageNode = clonedCurrentNode.cloneNode(); - - // Replace the image's width and height - newImageNode.src = `${newImageNode.src.split("?")?.[0]}?width=${ - evt.width - }`; - newImageNode.width = Number(evt.width); - // We want the width to automatically be set to preserve the image proportions - newImageNode.removeAttribute("height"); - - const currentValue = tinymce?.activeEditor?.getContent() ?? ""; - - // Update the content with the new image data - tinymce?.activeEditor?.setContent( - currentValue.replace( - clonedCurrentNode.outerHTML?.replaceAll("&", "&"), - newImageNode.outerHTML - ) - ); - }} - init={{ - plugins: [ - "advlist", - "autolink", - "charmap", - "code", - "codesample", - "emoticons", - "fullscreen", - "help", - "insertdatetime", - "link", - "lists", - "media", - "quickbars", - "searchreplace", - "table", - "wordcount", - "slashcommands", - "socialmediaembed", - "imageresizer", - ], - - // NOTE: premium plugins are being loaded from a self hosted location - // specific to our application. Making this component not usable outside of our context. - external_plugins: externalPlugins ?? {}, - - // Editor Settings - toolbar: - "slashcommands blocks | \ + + const clonedCurrentNode = evt.target.cloneNode(); + + // Remove attributes that are not included in the editor value to make replacing easier + clonedCurrentNode.removeAttribute("data-mce-src"); + clonedCurrentNode.removeAttribute("data-mce-selected"); + + const newImageNode = clonedCurrentNode.cloneNode(); + + // Replace the image's width and height + newImageNode.src = `${newImageNode.src.split("?")?.[0]}?width=${ + evt.width + }`; + newImageNode.width = Number(evt.width); + // We want the width to automatically be set to preserve the image proportions + newImageNode.removeAttribute("height"); + + const currentValue = tinymce?.activeEditor?.getContent() ?? ""; + + // Update the content with the new image data + tinymce?.activeEditor?.setContent( + currentValue.replace( + clonedCurrentNode.outerHTML?.replaceAll("&", "&"), + newImageNode.outerHTML + ) + ); + }} + init={{ + plugins: [ + "advlist", + "autolink", + "charmap", + "code", + "codesample", + "emoticons", + "fullscreen", + "help", + "insertdatetime", + "link", + "lists", + "media", + "quickbars", + "searchreplace", + "table", + "wordcount", + "slashcommands", + "socialmediaembed", + "imageresizer", + ], + + // NOTE: premium plugins are being loaded from a self hosted location + // specific to our application. Making this component not usable outside of our context. + external_plugins: externalPlugins ?? {}, + + // Editor Settings + toolbar: + "slashcommands blocks | \ bold italic underline backcolor | \ zestyMediaApp media link socialmediaembed table | \ align bullist numlist outdent indent | \ @@ -228,69 +219,69 @@ export const FieldTypeTinyMCE = React.memo(function FieldTypeTinyMCE({ undo redo | \ code help | \ fullscreen", - contextmenu: "bold italic link | copy paste", - toolbar_mode: "wrap", - relative_urls: false, - branding: false, - menubar: false, - statusbar: false, - object_resizing: true, - block_formats: - "Paragraph=p; Heading 1=h1; Heading 2=h2; Heading 3=h3; Heading 4=h4; Heading 5=h5; Heading 6=h6; Blockquoute=blockquote; Preformatted=pre", - color_default_background: "none", - help_tabs: ["shortcuts", "keyboardnav", "versions"], - - // file_picker_callback: (callback, value, meta) => { - // console.log(callback, value, meta); - // }, - - // imagetools_proxy: "path/to/proxy", - // imagetools_toolbar: "imageoptions", - // imagetools_fetch_image: function(img) { - // console.log("IMAGE", img); - // return new tinymce.util.Promise(function(resolve) { - // // Fetch the image and return a blob containing the image content - // fetch(img.src, { - // mode: "no-cors", - // cache: "no-cache" - // }) - // .then(res => res.blob()) - // .then(blob => resolve(blob)); - // }); - // }, - - // Plugin Settings - quickbars_insert_toolbar: false, - quickbars_image_toolbar: false, - quickbars_selection_toolbar: - "blocks | bold italic underline backcolor superscript subscript strikethrough removeformat | align bullist numlist outdent indent", - help_accessibility: false, - - // powerpaste_word_import: "prompt", - // media_live_embeds: true, - image_advtab: true, - - // Allows for embeds with script tags - // extended_valid_elements: "script[src|async|defer|type|charset]", - valid_elements: "*[*]", - - // Autoresizer does not work with the resize handle. - // Therefore we opt for the resize handle over auto resizing - resize: false, - min_height: EDITOR_HEIGHT, - - // skin: false, - skin_url: "/vendors/tinymce/skins/ui/Zesty", - icon_url: "/vendors/tinymce/icons/material-rounded/icons.js", - icons: "material-rounded", - - // If a content_css file is not provided tinymce will attempt - // loading the default which is not available - content_css: [ - "https://fonts.googleapis.com/css2?family=Mulish:wght@400;500;600;700", - ], - - content_style: ` + contextmenu: "bold italic link | copy paste", + toolbar_mode: "wrap", + relative_urls: false, + branding: false, + menubar: false, + statusbar: false, + object_resizing: true, + block_formats: + "Paragraph=p; Heading 1=h1; Heading 2=h2; Heading 3=h3; Heading 4=h4; Heading 5=h5; Heading 6=h6; Blockquoute=blockquote; Preformatted=pre", + color_default_background: "none", + help_tabs: ["shortcuts", "keyboardnav", "versions"], + + // file_picker_callback: (callback, value, meta) => { + // console.log(callback, value, meta); + // }, + + // imagetools_proxy: "path/to/proxy", + // imagetools_toolbar: "imageoptions", + // imagetools_fetch_image: function(img) { + // console.log("IMAGE", img); + // return new tinymce.util.Promise(function(resolve) { + // // Fetch the image and return a blob containing the image content + // fetch(img.src, { + // mode: "no-cors", + // cache: "no-cache" + // }) + // .then(res => res.blob()) + // .then(blob => resolve(blob)); + // }); + // }, + + // Plugin Settings + quickbars_insert_toolbar: false, + quickbars_image_toolbar: false, + quickbars_selection_toolbar: + "blocks | bold italic underline backcolor superscript subscript strikethrough removeformat | align bullist numlist outdent indent", + help_accessibility: false, + + // powerpaste_word_import: "prompt", + // media_live_embeds: true, + image_advtab: true, + + // Allows for embeds with script tags + // extended_valid_elements: "script[src|async|defer|type|charset]", + valid_elements: "*[*]", + + // Autoresizer does not work with the resize handle. + // Therefore we opt for the resize handle over auto resizing + resize: false, + min_height: EDITOR_HEIGHT, + + // skin: false, + skin_url: "/vendors/tinymce/skins/ui/Zesty", + icon_url: "/vendors/tinymce/icons/material-rounded/icons.js", + icons: "material-rounded", + + // If a content_css file is not provided tinymce will attempt + // loading the default which is not available + content_css: [ + "https://fonts.googleapis.com/css2?family=Mulish:wght@400;500;600;700", + ], + + content_style: ` html { justify-content: center }\ body { font-family: 'Mulish', Arial, sans-serif; color: #101828; font-size: 16px; }\ img { max-width: 100%; height: auto}\ @@ -308,114 +299,117 @@ export const FieldTypeTinyMCE = React.memo(function FieldTypeTinyMCE({ video { width: 100%; height: 100%; object-fill: fill; aspect-ratio: auto;}\ #tinymce { margin: 16px }`, - // init_instance_callback: (editor) => { - // tinymce.DOM.styleSheetLoader - // .load("/vendors/tinymce/skins/ui/Zesty/skin.min.css") - // .then(() => editor.render()); - // console.log(editor.dom.st); - // }, - - // Customize editor buttons and actions - setup: (editor: any) => { - editor.on("SkinLoaded", () => { - setIsSkinLoaded(true); - }); - - // Limits the content width to 640px when in fullscreen - editor.on("FullscreenStateChanged", (evt: any) => { - if (evt.state) { - editor.contentDocument.documentElement.style.display = "flex"; - editor.contentDocument.body.style.width = "640px"; - } else { - editor.contentDocument.documentElement.style.display = "block"; - editor.contentDocument.body.style.width = "auto"; - } - }); - - /** - * Handle save key command - */ - editor.shortcuts.add("meta+s", "Save item", onSave); - - /** - * This does not work as the resizing action provides an element with the data attributes striped - * so we lose context on this image ZUID, preventing modify calls to the media service - */ - // Request resized image from media service - // editor.on("ObjectResized", function(evt) { - // evt.target.src = `http://svc.zesty.localdev:3007/media-resolver-service/resolve/${evt.target.dataset.id}/getimage/?w=${evt.width}&h=${evt.height}`; - // }); - - /** - * Zesty Media App - */ - const mediaBrowserDialog = (ui?: boolean, filetype?: string) => { - mediaBrowser({ - limit: 10, - filetype, - callback: (files: File[]) => { - const imageFileTypes = [ - ".jpg", - ".jpeg", - ".gif", - ".webp", - ".png", - ".svg", - ".ico", - ]; - const videoFileTypes = [ - ".mp4", - ".mov", - ".avi", - ".wmv", - ".mkv", - ".webm", - ".flv", - ".f4v", - ".swf", - ".avch", - ".html5", - ]; - - editor.insertContent( - files - .map((file: File) => { - if ( - imageFileTypes.some((fileType) => - file.filename?.includes(fileType) - ) - ) { - return `${file.title}`; - } - - if ( - videoFileTypes.some((fileType) => - file.filename?.includes(fileType) - ) - ) { - return ` + // init_instance_callback: (editor) => { + // tinymce.DOM.styleSheetLoader + // .load("/vendors/tinymce/skins/ui/Zesty/skin.min.css") + // .then(() => editor.render()); + // console.log(editor.dom.st); + // }, + + // Customize editor buttons and actions + setup: (editor: any) => { + editor.on("SkinLoaded", () => { + console.log("skin loaded"); + setIsSkinLoaded(true); + }); + + // Limits the content width to 640px when in fullscreen + editor.on("FullscreenStateChanged", (evt: any) => { + if (evt.state) { + editor.contentDocument.documentElement.style.display = "flex"; + editor.contentDocument.body.style.width = "640px"; + } else { + editor.contentDocument.documentElement.style.display = + "block"; + editor.contentDocument.body.style.width = "auto"; + } + }); + + /** + * Handle save key command + */ + editor.shortcuts.add("meta+s", "Save item", onSave); + + /** + * This does not work as the resizing action provides an element with the data attributes striped + * so we lose context on this image ZUID, preventing modify calls to the media service + */ + // Request resized image from media service + // editor.on("ObjectResized", function(evt) { + // evt.target.src = `http://svc.zesty.localdev:3007/media-resolver-service/resolve/${evt.target.dataset.id}/getimage/?w=${evt.width}&h=${evt.height}`; + // }); + + /** + * Zesty Media App + */ + const mediaBrowserDialog = (ui?: boolean, filetype?: string) => { + mediaBrowser({ + limit: 10, + filetype, + callback: (files: File[]) => { + const imageFileTypes = [ + ".jpg", + ".jpeg", + ".gif", + ".webp", + ".png", + ".svg", + ".ico", + ]; + const videoFileTypes = [ + ".mp4", + ".mov", + ".avi", + ".wmv", + ".mkv", + ".webm", + ".flv", + ".f4v", + ".swf", + ".avch", + ".html5", + ]; + + editor.insertContent( + files + .map((file: File) => { + if ( + imageFileTypes.some((fileType) => + file.filename?.includes(fileType) + ) + ) { + return `${file.title}`; + } + + if ( + videoFileTypes.some((fileType) => + file.filename?.includes(fileType) + ) + ) { + return ` ); });