From 20f37292a1f8267fe3b52a0064221f6b3485bcab Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 20 Jan 2022 13:38:16 +0000 Subject: [PATCH] Added support for iframe node blocks --- TODO | 2 +- resources/js/editor/markdown-serializer.js | 4 + resources/js/editor/menu/icons.js | 4 + resources/js/editor/menu/index.js | 2 + .../js/editor/menu/item-iframe-button.js | 114 ++++++++++++++++++ resources/js/editor/schema-nodes.js | 22 ++++ resources/views/editor-test.blade.php | 2 + 7 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 resources/js/editor/menu/item-iframe-button.js diff --git a/TODO b/TODO index 2fad1346e..a882f23ef 100644 --- a/TODO +++ b/TODO @@ -7,6 +7,7 @@ ### In-Progress - Tables +- Iframe/Media ### Features @@ -20,7 +21,6 @@ - Checkbox/TODO list items - Code blocks - Indents -- Iframe/Media - Attachment integration (Drag & drop) - Template system integration. diff --git a/resources/js/editor/markdown-serializer.js b/resources/js/editor/markdown-serializer.js index ad7783243..c0e3fcb0f 100644 --- a/resources/js/editor/markdown-serializer.js +++ b/resources/js/editor/markdown-serializer.js @@ -13,6 +13,10 @@ nodes.table = function (state, node) { writeNodeAsHtml(state, node); }; +nodes.iframe = function (state, node) { + writeNodeAsHtml(state, node); +}; + function isPlainURL(link, parent, index, side) { if (link.attrs.title || !/^\w+:/.test(link.attrs.href)) { return false diff --git a/resources/js/editor/menu/icons.js b/resources/js/editor/menu/icons.js index 3166f5dac..3445ac10d 100644 --- a/resources/js/editor/menu/icons.js +++ b/resources/js/editor/menu/icons.js @@ -103,6 +103,10 @@ export const icons = { table: { width: 24, height: 24, path: "M20 2H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM8 20H4v-4h4v4zm0-6H4v-4h4v4zm0-6H4V4h4v4zm6 12h-4v-4h4v4zm0-6h-4v-4h4v4zm0-6h-4V4h4v4zm6 12h-4v-4h4v4zm0-6h-4v-4h4v4zm0-6h-4V4h4v4z", + }, + iframe: { + width: 24, height: 24, + path: "m 22.71,18.43 c 0.03,-0.29 0.04,-0.58 0.01,-0.86 l 1.07,-0.85 c 0.1,-0.08 0.12,-0.21 0.06,-0.32 L 22.82,14.61 C 22.76,14.5 22.63,14.46 22.51,14.5 L 21.23,15 C 21,14.83 20.75,14.69 20.48,14.58 l -0.2,-1.36 C 20.26,13.09 20.16,13 20.03,13 h -2.07 c -0.12,0 -0.23,0.09 -0.25,0.21 l -0.2,1.36 c -0.26,0.11 -0.51,0.26 -0.74,0.42 l -1.28,-0.5 c -0.12,-0.05 -0.25,0 -0.31,0.11 l -1.03,1.79 c -0.06,0.11 -0.04,0.24 0.06,0.32 l 1.07,0.86 c -0.03,0.29 -0.04,0.58 -0.01,0.86 l -1.07,0.85 c -0.1,0.08 -0.12,0.21 -0.06,0.32 l 1.03,1.79 c 0.06,0.11 0.19,0.15 0.31,0.11 L 16.75,21 c 0.23,0.17 0.48,0.31 0.75,0.42 l 0.2,1.36 c 0.02,0.12 0.12,0.21 0.25,0.21 h 2.07 c 0.12,0 0.23,-0.09 0.25,-0.21 l 0.2,-1.36 c 0.26,-0.11 0.51,-0.26 0.74,-0.42 l 1.28,0.5 c 0.12,0.05 0.25,0 0.31,-0.11 l 1.03,-1.79 c 0.06,-0.11 0.04,-0.24 -0.06,-0.32 z M 19,19.5 c -0.83,0 -1.5,-0.67 -1.5,-1.5 0,-0.83 0.67,-1.5 1.5,-1.5 0.83,0 1.5,0.67 1.5,1.5 0,0.83 -0.67,1.5 -1.5,1.5 z M 15,12 9,8 v 8 z M 3,6 h 18 v 5 h 2 V 6 C 23,4.9 22.1,4 21,4 H 3 C 1.9,4 1,4.9 1,6 v 12 c 0,1.1 0.9,2 2,2 h 9 V 18 H 3 Z", } }; diff --git a/resources/js/editor/menu/index.js b/resources/js/editor/menu/index.js index 665d5f9ef..ecf574642 100644 --- a/resources/js/editor/menu/index.js +++ b/resources/js/editor/menu/index.js @@ -12,6 +12,7 @@ import {removeMarks} from "../commands"; import itemAnchorButtonItem from "./item-anchor-button"; import itemHtmlSourceButton from "./item-html-source-button"; +import itemIframeButton from "./item-iframe-button"; function cmdItem(cmd, options) { @@ -158,6 +159,7 @@ const inserts = [ new DropdownSubmenu([ new TableCreatorGrid() ], {icon: icons.table}), + itemIframeButton(), itemHtmlSourceButton(), ]; diff --git a/resources/js/editor/menu/item-iframe-button.js b/resources/js/editor/menu/item-iframe-button.js new file mode 100644 index 000000000..da2596fca --- /dev/null +++ b/resources/js/editor/menu/item-iframe-button.js @@ -0,0 +1,114 @@ +import DialogBox from "./DialogBox"; +import DialogForm from "./DialogForm"; +import DialogInput from "./DialogInput"; +import schema from "../schema"; + +import {MenuItem} from "./menu"; +import {icons} from "./icons"; +import {nullifyEmptyValues} from "../util"; + +/** + * @param {PmNodeType} nodeType + * @param {String} attribute + * @return {(function(PmEditorState): (string|null))} + */ +function getNodeAttribute(nodeType, attribute) { + return function (state) { + const node = state.selection.node; + if (node && node.type === nodeType) { + return node.attrs[attribute]; + } + + return null; + }; +} + +/** + * @param {(function(FormData))} submitter + * @param {Function} closer + * @return {DialogBox} + */ +function getLinkDialog(submitter, closer) { + return new DialogBox([ + new DialogForm([ + new DialogInput({ + label: 'Source URL', + id: 'src', + value: getNodeAttribute(schema.nodes.iframe, 'src'), + }), + new DialogInput({ + label: 'Hover Label', + id: 'title', + value: getNodeAttribute(schema.nodes.iframe, 'title'), + }), + new DialogInput({ + label: 'Width', + id: 'width', + value: getNodeAttribute(schema.nodes.iframe, 'width'), + }), + new DialogInput({ + label: 'Height', + id: 'height', + value: getNodeAttribute(schema.nodes.iframe, 'height'), + }), + ], { + canceler: closer, + action: submitter, + }), + ], { + label: 'Insert Embedded Content', + closer: closer, + }); +} + +/** + * @param {FormData} formData + * @param {PmEditorState} state + * @param {PmDispatchFunction} dispatch + * @return {boolean} + */ +function applyIframe(formData, state, dispatch) { + const attrs = nullifyEmptyValues(Object.fromEntries(formData)); + if (!dispatch) return true; + + const tr = state.tr; + const currentNodeAttrs = state.selection?.nodes?.attrs || {}; + const newAttrs = Object.assign({}, currentNodeAttrs, attrs); + tr.replaceSelectionWith(schema.nodes.iframe.create(newAttrs)); + + dispatch(tr); + return true; +} + +/** + * @param {PmEditorState} state + * @param {PmDispatchFunction} dispatch + * @param {PmView} view + * @param {Event} e + */ +function onPress(state, dispatch, view, e) { + const dialog = getLinkDialog((data) => { + applyIframe(data, state, dispatch); + dom.remove(); + }, () => { + dom.remove(); + }) + + const {dom, update} = dialog.render(view); + update(state); + document.body.appendChild(dom); +} + +/** + * @return {MenuItem} + */ +function iframeButtonItem() { + return new MenuItem({ + title: "Embed Content", + run: onPress, + enable: state => true, + icon: icons.iframe, + }); +} + +export default iframeButtonItem; \ No newline at end of file diff --git a/resources/js/editor/schema-nodes.js b/resources/js/editor/schema-nodes.js index 69a253f20..b26e17772 100644 --- a/resources/js/editor/schema-nodes.js +++ b/resources/js/editor/schema-nodes.js @@ -196,6 +196,27 @@ const image = { } }; +const iframe = { + attrs: { + src: {}, + height: {default: null}, + width: {default: null}, + title: {default: null}, + allow: {default: null}, + sandbox: {default: null}, + }, + group: "block", + draggable: true, + parseDOM: [{ + tag: "iframe", + getAttrs: domAttrsToAttrsParser(["src", "width", "height", "title", "allow", "sandbox"]), + }], + toDOM(node) { + const attrs = extractAttrsForDom(node, ["src", "width", "height", "title", "allow", "sandbox"]) + return ["iframe", attrs]; + } +}; + const hard_break = { inline: true, group: "inline", @@ -270,6 +291,7 @@ const nodes = { code_block, text, image, + iframe, hard_break, callout, ordered_list, diff --git a/resources/views/editor-test.blade.php b/resources/views/editor-test.blade.php index ef7e63cac..bf76a13f1 100644 --- a/resources/views/editor-test.blade.php +++ b/resources/views/editor-test.blade.php @@ -34,6 +34,8 @@ + +

Logo