### In-Progress
- Tables
+- Iframe/Media
### Features
- Checkbox/TODO list items
- Code blocks
- Indents
-- Iframe/Media
- Attachment integration (Drag & drop)
- Template system integration.
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
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",
}
};
import itemAnchorButtonItem from "./item-anchor-button";
import itemHtmlSourceButton from "./item-html-source-button";
+import itemIframeButton from "./item-iframe-button";
function cmdItem(cmd, options) {
new DropdownSubmenu([
new TableCreatorGrid()
], {icon: icons.table}),
+ itemIframeButton(),
itemHtmlSourceButton(),
];
--- /dev/null
+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
}
};
+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",
code_block,
text,
image,
+ iframe,
hard_break,
callout,
ordered_list,
</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>