mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Added support for iframe node blocks
This commit is contained in:
parent
b1f5495a7f
commit
20f37292a1
2
TODO
2
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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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(),
|
||||
];
|
||||
|
||||
|
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 = {
|
||||
inline: true,
|
||||
group: "inline",
|
||||
@ -270,6 +291,7 @@ const nodes = {
|
||||
code_block,
|
||||
text,
|
||||
image,
|
||||
iframe,
|
||||
hard_break,
|
||||
callout,
|
||||
ordered_list,
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user