mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-07-21 09:22:09 +03:00
Added support for iframe node blocks
This commit is contained in:
2
TODO
2
TODO
@ -7,6 +7,7 @@
|
|||||||
### In-Progress
|
### In-Progress
|
||||||
|
|
||||||
- Tables
|
- Tables
|
||||||
|
- Iframe/Media
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
@ -20,7 +21,6 @@
|
|||||||
- Checkbox/TODO list items
|
- Checkbox/TODO list items
|
||||||
- Code blocks
|
- Code blocks
|
||||||
- Indents
|
- Indents
|
||||||
- Iframe/Media
|
|
||||||
- Attachment integration (Drag & drop)
|
- Attachment integration (Drag & drop)
|
||||||
- Template system integration.
|
- Template system integration.
|
||||||
|
|
||||||
|
@ -13,6 +13,10 @@ nodes.table = function (state, node) {
|
|||||||
writeNodeAsHtml(state, node);
|
writeNodeAsHtml(state, node);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
nodes.iframe = function (state, node) {
|
||||||
|
writeNodeAsHtml(state, node);
|
||||||
|
};
|
||||||
|
|
||||||
function isPlainURL(link, parent, index, side) {
|
function isPlainURL(link, parent, index, side) {
|
||||||
if (link.attrs.title || !/^\w+:/.test(link.attrs.href)) {
|
if (link.attrs.title || !/^\w+:/.test(link.attrs.href)) {
|
||||||
return false
|
return false
|
||||||
|
@ -103,6 +103,10 @@ export const icons = {
|
|||||||
table: {
|
table: {
|
||||||
width: 24, height: 24,
|
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",
|
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",
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ import {removeMarks} from "../commands";
|
|||||||
|
|
||||||
import itemAnchorButtonItem from "./item-anchor-button";
|
import itemAnchorButtonItem from "./item-anchor-button";
|
||||||
import itemHtmlSourceButton from "./item-html-source-button";
|
import itemHtmlSourceButton from "./item-html-source-button";
|
||||||
|
import itemIframeButton from "./item-iframe-button";
|
||||||
|
|
||||||
|
|
||||||
function cmdItem(cmd, options) {
|
function cmdItem(cmd, options) {
|
||||||
@ -158,6 +159,7 @@ const inserts = [
|
|||||||
new DropdownSubmenu([
|
new DropdownSubmenu([
|
||||||
new TableCreatorGrid()
|
new TableCreatorGrid()
|
||||||
], {icon: icons.table}),
|
], {icon: icons.table}),
|
||||||
|
itemIframeButton(),
|
||||||
itemHtmlSourceButton(),
|
itemHtmlSourceButton(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
114
resources/js/editor/menu/item-iframe-button.js
Normal file
114
resources/js/editor/menu/item-iframe-button.js
Normal file
@ -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;
|
@ -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 = {
|
const hard_break = {
|
||||||
inline: true,
|
inline: true,
|
||||||
group: "inline",
|
group: "inline",
|
||||||
@ -270,6 +291,7 @@ const nodes = {
|
|||||||
code_block,
|
code_block,
|
||||||
text,
|
text,
|
||||||
image,
|
image,
|
||||||
|
iframe,
|
||||||
hard_break,
|
hard_break,
|
||||||
callout,
|
callout,
|
||||||
ordered_list,
|
ordered_list,
|
||||||
|
@ -34,6 +34,8 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</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>
|
<p><img src="/user_avatar.png" alt="Logo"></p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Item A</li>
|
<li>Item A</li>
|
||||||
|
Reference in New Issue
Block a user