]> BookStack Code Mirror - bookstack/commitdiff
Added support for iframe node blocks
authorDan Brown <redacted>
Thu, 20 Jan 2022 13:38:16 +0000 (13:38 +0000)
committerDan Brown <redacted>
Thu, 20 Jan 2022 13:38:16 +0000 (13:38 +0000)
TODO
resources/js/editor/markdown-serializer.js
resources/js/editor/menu/icons.js
resources/js/editor/menu/index.js
resources/js/editor/menu/item-iframe-button.js [new file with mode: 0644]
resources/js/editor/schema-nodes.js
resources/views/editor-test.blade.php

diff --git a/TODO b/TODO
index 2fad1346e920a014f32d296e00e9ba930d157d0f..a882f23ef344df5c0b6e513d9b929a8cd718e717 100644 (file)
--- 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.
 
index ad7783243018484fc15f2cec59ed9add1864ecaa..c0e3fcb0f7fdd73c47c0dcacab092336970f054e 100644 (file)
@@ -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
index 3166f5dac7ffa2fdf72d851cd7c93835f60ab096..3445ac10de5f566b8c3ccd30d49ed141fe2428a0 100644 (file)
@@ -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",
   }
 };
 
index 665d5f9ef654406d6a1e8f35a6c7c95d1d50ca3c..ecf574642b14a6c50b2728e4ed2ff265f2403b02 100644 (file)
@@ -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 (file)
index 0000000..da2596f
--- /dev/null
@@ -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
index 69a253f20c1583dc46151683bba3ea4d1898a066..b26e17772a0a17a878e5ec74b06cf59908424544 100644 (file)
@@ -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,
index ef7e63cac9da8351d5f67df4d30e1d44d54b6cb8..bf76a13f145c34a4baf47290459dcf3467efb95e 100644 (file)
@@ -34,6 +34,8 @@
                 </tbody>
             </table>
 
+            <iframe width="560" height="315" src="https://www.youtube.com/embed/n6hIa-fPx0M" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
+
             <p><img src="/user_avatar.png" alt="Logo"></p>
             <ul>
                 <li>Item A</li>