diff --git a/editors/editoria/src/Editoria.js b/editors/editoria/src/Editoria.js index c3a2218c6d0ed3f4347d549b225334435739494e..44604454877091be1b7300ed8aee77feefcb9547 100644 --- a/editors/editoria/src/Editoria.js +++ b/editors/editoria/src/Editoria.js @@ -3,7 +3,6 @@ import styled, { createGlobalStyle, ThemeProvider } from "styled-components"; import { Wax } from "wax-prosemirror-core"; import { EditoriaLayout } from "wax-prosemirror-layouts"; -import { cokoTheme } from "wax-prosemirror-themes"; import { schema, keys, plugins, rules } from "./EditorConfig"; @@ -63,9 +62,7 @@ class Editoria extends Component { user={user} > {({ editor, view, ...props }) => ( - <ThemeProvider theme={cokoTheme}> - <EditoriaLayout editor={editor} view={view} {...props} /> - </ThemeProvider> + <EditoriaLayout editor={editor} view={view} {...props} /> )} </StyledWax> </Fragment> diff --git a/wax-prosemirror-components/src/components/button/Button.js b/wax-prosemirror-components/src/components/Button.js similarity index 73% rename from wax-prosemirror-components/src/components/button/Button.js rename to wax-prosemirror-components/src/components/Button.js index c2c799786a2a35118f25b9ba192acd1ffabc3f76..c8034b1b9beab6743b38c29ed499da833f2e60fc 100644 --- a/wax-prosemirror-components/src/components/button/Button.js +++ b/wax-prosemirror-components/src/components/Button.js @@ -3,14 +3,16 @@ import styled from "styled-components"; import { ButtonStyles } from "wax-prosemirror-themes"; const ButtonStyled = styled.button` + ${ButtonStyles}; opacity: ${props => (props.select ? 1 : 0.4)}; pointer-events: ${props => (props.select ? "default" : "none")}; - color: ${props => - props.isActive ? props.theme.colorPrimary : props.theme.colorButton}; + color: ${props => (props.isActive ? "white" : props.theme.colorButton)}; + background-color: ${props => + props.isActive ? props.theme.colorPrimary : "transparent"}; &:hover { - color: ${props => (props.isActive ? props.theme.colorPrimary : "#000")}; + background-color: ${props => + props.isActive ? props.theme.colorPrimary : "transparent"}; } - ${ButtonStyles}; `; const Button = ({ view = {}, item }) => ( diff --git a/wax-prosemirror-components/src/helpers.js b/wax-prosemirror-components/src/helpers.js new file mode 100644 index 0000000000000000000000000000000000000000..bdb81d6b452a7fd726ed57c50bd88b7c3f122f85 --- /dev/null +++ b/wax-prosemirror-components/src/helpers.js @@ -0,0 +1,14 @@ +const filtered = (menu, menuItems) => + Object.keys(menu) + .filter(key => menuItems.includes(key)) + .reduce((obj, key) => { + obj[key] = menu[key]; + return obj; + }, {}); + +const setMenuItems = (allMenuItems, menuItems) => { + if (menuItems.length === 0) return allMenuItems; + return filtered(allMenuItems, menuItems); +}; + +export { setMenuItems }; diff --git a/wax-prosemirror-components/src/mainMenuBar/MainMenuBar.js b/wax-prosemirror-components/src/mainMenuBar/MainMenuBar.js index 9e82f61793fca571b6fdbc27c70278290f13372d..088cccf579e45995f80de4d2beae4ccbcdf59201 100644 --- a/wax-prosemirror-components/src/mainMenuBar/MainMenuBar.js +++ b/wax-prosemirror-components/src/mainMenuBar/MainMenuBar.js @@ -1,8 +1,9 @@ import React from "react"; import styled from "styled-components"; -import { forEach, map } from "lodash"; +import { map } from "lodash"; import MainMenuBarItems from "./MainMenuBarItems"; +import { setMenuItems } from "../helpers"; const MainMenuContainer = styled.div` background: #fff; @@ -30,31 +31,7 @@ const MainMenu = styled.div` background: transparent; `; -const filtered = (menu, menuItems) => - Object.keys(menu) - .filter(key => menuItems.includes(key)) - .reduce((obj, key) => { - obj[key] = menu[key]; - return obj; - }, {}); - -const setMenuItems = (menu, menuItems) => { - let items = menuItems; - if (menuItems.length === 0) { - forEach(menu, (key, index) => { - items.push(index); - }); - } - return filtered(menu, items); -}; - -const MainMenuBar = ({ - menuItems = [], - children, - view, - className, - fileUpload -}) => ( +const MainMenuBar = ({ menuItems = [], view, className, fileUpload }) => ( <MainMenuContainer> <MainMenuInner> <MainMenu> diff --git a/wax-prosemirror-components/src/mainMenuBar/MainMenuBarItems.js b/wax-prosemirror-components/src/mainMenuBar/MainMenuBarItems.js index ca9802e27e4662ef1615f922f72b30e056791944..f9c9fc79fb22bedaffe104851ca8ad308dc99b4c 100644 --- a/wax-prosemirror-components/src/mainMenuBar/MainMenuBarItems.js +++ b/wax-prosemirror-components/src/mainMenuBar/MainMenuBarItems.js @@ -1,6 +1,5 @@ import React from "react"; import { v4 as uuid } from "uuid"; - import { joinUp, lift, @@ -17,52 +16,18 @@ import { wrapInList } from "prosemirror-schema-list"; import icons from "../icons/icons"; //Components -import Button from "../components/button/Button"; +import Button from "../components/Button"; import TableDropDown from "../components/TableDropDown"; import ImageUpload from "../components/ImageUpload"; import HeadingsDropDown from "../components/HeadingsDropDown"; -const markActive = type => state => { - const { from, $from, to, empty } = state.selection; - - return empty - ? type.isInSet(state.storedMarks || $from.marks()) - : state.doc.rangeHasMark(from, to, type); -}; - -const blockActive = (type, attrs = {}) => state => { - const { $from, to, node } = state.selection; - - if (node) { - return node.hasMarkup(type, attrs); - } - - return to <= $from.end() && $from.parent.hasMarkup(type, attrs); -}; - -const canInsert = type => state => { - const { $from } = state.selection; - - for (let d = $from.depth; d >= 0; d--) { - const index = $from.index(d); - - if ($from.node(d).canReplaceWith(index, index, type)) { - return true; - } - } - - return false; -}; - -const promptForURL = () => { - let url = window && window.prompt("Enter the URL", "https://"); - - if (url && !/^https?:\/\//i.test(url)) { - url = "http://" + url; - } - - return url; -}; +import { + markActive, + blockActive, + canInsert, + promptForURL, + createTable +} from "./MainMenuCommands"; export default { undo: { @@ -71,7 +36,7 @@ export default { enable: undo, run: undo, select: state => true, - menu: props => <Button key={uuid()} {...props} /> + menu: props => <Button key={uuid()} {...props} /> }, redo: { title: "Redo last undone change", @@ -118,19 +83,19 @@ export default { select: state => true, menu: props => <Button key={uuid()} {...props} /> }, - // small_caps: { - // title: "Toggle Small Caps", - // content: icons.small_caps, - // active: state => { - // return markActive(state.config.schema.marks.small_caps)(state); - // }, - // run(state, dispatch) { - // toggleMark(state.config.schema.marks.small_caps)(state, dispatch); - // }, - // - // select: state => true, - // menu: props => <Button key={uuid()} {...props} /> - // }, + small_caps: { + title: "Toggle Small Caps", + content: icons.small_caps, + active: state => { + return markActive(state.config.schema.marks.small_caps)(state); + }, + run(state, dispatch) { + toggleMark(state.config.schema.marks.small_caps)(state, dispatch); + }, + + select: state => true, + menu: props => <Button key={uuid()} {...props} /> + }, subscript: { title: "Toggle subscript", content: icons.subscript, @@ -204,8 +169,12 @@ export default { blockquote: { title: "Wrap in block quote", content: icons.blockquote, - // active: blockActive(schema.nodes.blockquote), - // enable: wrapIn(schema.nodes.blockquote), + active: state => { + return blockActive(state.config.schema.nodes.blockquote)(state); + }, + enable: state => { + return wrapIn(state.config.schema.nodes.blockquote)(state); + }, run(state, dispatch) { wrapIn(state.config.schema.nodes.blockquote)(state, dispatch); }, @@ -215,8 +184,12 @@ export default { bullet_list: { title: "Wrap in bullet list", content: icons.bullet_list, - // active: blockActive(schema.nodes.bullet_list), - // enable: wrapInList(schema.nodes.bullet_list), + active: state => { + return blockActive(state.config.schema.nodes.bullet_list)(state); + }, + enable: state => { + return wrapInList(state.config.schema.nodes.bullet_list)(state); + }, run(state, dispatch) { wrapInList(state.config.schema.nodes.bullet_list)(state, dispatch); }, @@ -226,8 +199,12 @@ export default { ordered_list: { title: "Wrap in ordered list", content: icons.ordered_list, - // active: blockActive(schema.nodes.ordered_list), - // enable: wrapInList(schema.nodes.ordered_list), + active: state => { + return blockActive(state.config.schema.nodes.ordered_list)(state); + }, + enable: state => { + return wrapInList(state.config.schema.nodes.ordered_list)(state); + }, run(state, dispatch) { wrapInList(state.config.schema.nodes.ordered_list)(state, dispatch); }, @@ -254,7 +231,9 @@ export default { image: { title: "Insert image", content: icons.image, - // enable: canInsert(schema.nodes.image), + enable: state => { + return canInsert(state.config.schema.nodes.image)(state); + }, select: state => true, run: option => true, menu: props => <ImageUpload key={uuid()} {...props} /> @@ -262,26 +241,11 @@ export default { table: { title: "Insert table", content: icons.table, - // enable: canInsert(schema.nodes.table), + enable: state => { + return canInsert(state.config.schema.nodes.table)(state); + }, run: (state, dispatch) => { - // const { from } = state.selection - let rowCount = window && window.prompt("How many rows?", 2); - let colCount = window && window.prompt("How many columns?", 2); - - const cells = []; - while (colCount--) { - cells.push(state.config.schema.nodes.table_cell.createAndFill()); - } - - const rows = []; - while (rowCount--) { - rows.push( - state.config.schema.nodes.table_row.createAndFill(null, cells) - ); - } - - const table = state.config.schema.nodes.table.createAndFill(null, rows); - dispatch(state.tr.replaceSelectionWith(table)); + return createTable(state, dispatch); }, select: state => true, menu: props => <Button key={uuid()} {...props} /> diff --git a/wax-prosemirror-components/src/mainMenuBar/MainMenuCommands.js b/wax-prosemirror-components/src/mainMenuBar/MainMenuCommands.js new file mode 100644 index 0000000000000000000000000000000000000000..ab7f1da0a7d2d652672ef4f784aa6587ad3a39d2 --- /dev/null +++ b/wax-prosemirror-components/src/mainMenuBar/MainMenuCommands.js @@ -0,0 +1,61 @@ +const markActive = type => state => { + const { from, $from, to, empty } = state.selection; + + return empty + ? type.isInSet(state.storedMarks || $from.marks()) + : state.doc.rangeHasMark(from, to, type); +}; + +const blockActive = (type, attrs = {}) => state => { + const { $from, to, node } = state.selection; + + if (node) { + return node.hasMarkup(type, attrs); + } + + return to <= $from.end() && $from.parent.hasMarkup(type, attrs); +}; + +const canInsert = type => state => { + const { $from } = state.selection; + + for (let d = $from.depth; d >= 0; d--) { + const index = $from.index(d); + + if ($from.node(d).canReplaceWith(index, index, type)) { + return true; + } + } + + return false; +}; + +const promptForURL = () => { + let url = window && window.prompt("Enter the URL", "https://"); + + if (url && !/^https?:\/\//i.test(url)) { + url = "http://" + url; + } + + return url; +}; + +const createTable = (state, dispatch) => { + let rowCount = window && window.prompt("How many rows?", 2); + let colCount = window && window.prompt("How many columns?", 2); + + const cells = []; + while (colCount--) { + cells.push(state.config.schema.nodes.table_cell.createAndFill()); + } + + const rows = []; + while (rowCount--) { + rows.push(state.config.schema.nodes.table_row.createAndFill(null, cells)); + } + + const table = state.config.schema.nodes.table.createAndFill(null, rows); + dispatch(state.tr.replaceSelectionWith(table)); +}; + +export { markActive, blockActive, canInsert, promptForURL, createTable }; diff --git a/wax-prosemirror-components/src/sideMenuBar/SideMenuBar.js b/wax-prosemirror-components/src/sideMenuBar/SideMenuBar.js index 20a8d7ce506b6f8618a9faad7bdfba342c44738b..4b2abd2ce9defc5cd4807fc41fc01d27a5ca1a4c 100644 --- a/wax-prosemirror-components/src/sideMenuBar/SideMenuBar.js +++ b/wax-prosemirror-components/src/sideMenuBar/SideMenuBar.js @@ -1,7 +1,8 @@ import React from "react"; import styled from "styled-components"; -import { forEach, map } from "lodash"; +import { map } from "lodash"; import SideMenuItems from "./SideMenuItems"; +import { setMenuItems } from "../helpers"; const SideMenuContainer = styled.div` display: flex; @@ -22,43 +23,19 @@ const SideMenu = styled.div` margin-top: 15px; button { display: flex; - margin-left: 5%; flex-direction: column; + font-family: ${props => props.theme.fontInterface}; + margin-left: 5%; width: 90%; - background: transparent; } `; -const filtered = (menu, menuItems) => - Object.keys(menu) - .filter(key => menuItems.includes(key)) - .reduce((obj, key) => { - obj[key] = menu[key]; - return obj; - }, {}); - -const setMenuItems = (menu, menuItems) => { - let items = menuItems; - if (menuItems.length === 0) { - forEach(menu, (key, index) => { - items.push(index); - }); - } - return filtered(menu, items); -}; - -const SideMenuBar = ({ - menuItems = [], - children, - view, - className, - fileUpload -}) => ( +const SideMenuBar = ({ menuItems = [], view, className }) => ( <SideMenuContainer> <SideMenuInner> <SideMenu> {map(setMenuItems(SideMenuItems, menuItems), item => - item.menu({ view, item, fileUpload }) + item.menu({ view, item }) )} </SideMenu> </SideMenuInner> diff --git a/wax-prosemirror-components/src/sideMenuBar/SideMenuCommands.js b/wax-prosemirror-components/src/sideMenuBar/SideMenuCommands.js new file mode 100644 index 0000000000000000000000000000000000000000..b9620b65822dcfcefe5e41c41de9e80e136bf852 --- /dev/null +++ b/wax-prosemirror-components/src/sideMenuBar/SideMenuCommands.js @@ -0,0 +1,10 @@ +const blockActive = (type, attrs = {}) => state => { + const { $from, to, node } = state.selection; + + if (node) { + return node.hasMarkup(type, attrs); + } + return to <= $from.end() && $from.parent.hasMarkup(type, attrs); +}; + +export { blockActive }; diff --git a/wax-prosemirror-components/src/sideMenuBar/SideMenuItems.js b/wax-prosemirror-components/src/sideMenuBar/SideMenuItems.js index 3514fd0d0fb19c1e5fdc5e61866c6e5c80539f46..efef95626e7910e9f4c8343bfcb9647f1d9fbb9c 100644 --- a/wax-prosemirror-components/src/sideMenuBar/SideMenuItems.js +++ b/wax-prosemirror-components/src/sideMenuBar/SideMenuItems.js @@ -2,21 +2,13 @@ import React from "react"; import { v4 as uuid } from "uuid"; import { setBlockType } from "prosemirror-commands"; -import Button from "../components/button/Button"; - -const blockActive = (type, attrs = {}) => state => { - const { $from, to, node } = state.selection; - - if (node) { - return node.hasMarkup(type, attrs); - } - return to <= $from.end() && $from.parent.hasMarkup(type, attrs); -}; - +import Button from "../components/Button"; +import { blockActive } from "./SideMenuItems"; export default { title: { title: "Change to Title", content: "Title", + group: "", enable: state => { return setBlockType(state.config.schema.nodes.title)(state); }, @@ -30,6 +22,7 @@ export default { subtitle: { title: "Change to Subtilte", content: "Subtilte", + group: "", enable: state => { return setBlockType(state.config.schema.nodes.subtitle)(state); }, @@ -42,6 +35,7 @@ export default { author: { title: "Change to Author", content: "Author", + group: "", enable: state => { return setBlockType(state.config.schema.nodes.author)(state); }, @@ -54,6 +48,7 @@ export default { epigraphPoetry: { title: "Change to Epigraph Poetry", content: "Epigraph Poetry", + group: "", enable: state => { return setBlockType(state.config.schema.nodes.epigraphPoetry)(state); }, @@ -66,6 +61,7 @@ export default { epigraphProse: { title: "Change to Epigraph Prose", content: "Epigraph Prose", + group: "", enable: state => { return setBlockType(state.config.schema.nodes.epigraphProse)(state); }, @@ -77,8 +73,8 @@ export default { }, plain: { title: "Change to General Text", - // content: icons.paragraph, content: "General Text", + group: "", enable: state => { return setBlockType(state.config.schema.nodes.paragraph)(state); }, @@ -92,6 +88,7 @@ export default { heading1: { title: "Change to heading level 1", content: "Heading 1", + group: "", enable: state => { return setBlockType(state.config.schema.nodes.heading, { level: 1, @@ -110,6 +107,7 @@ export default { heading2: { title: "Change to heading level 2", content: "Heading 2", + group: "", enable: state => { return setBlockType(state.config.schema.nodes.heading, { level: 2, @@ -128,6 +126,7 @@ export default { heading3: { title: "Change to heading level 3", content: "Heading 3", + group: "", enable: state => { return setBlockType(state.config.schema.nodes.heading, { level: 3, diff --git a/wax-prosemirror-core/src/Wax.js b/wax-prosemirror-core/src/Wax.js index 4d0c0c1421c97ee0439340b0e00f193c90cf7d8b..10306578d5a983a0c9e13abb78704ec15de3d177 100644 --- a/wax-prosemirror-core/src/Wax.js +++ b/wax-prosemirror-core/src/Wax.js @@ -43,21 +43,21 @@ class Wax extends Component { const { schema, plugins, keys, rules } = options; const WaxOnchange = onChange ? onChange : value => true; - const WaxKeys = - options && options.keys - ? options.keys - : new CreateShortCuts({ schema: schema, shortCuts: {} }); + const WaxShortCuts = keys + ? keys + : new CreateShortCuts({ schema: schema, shortCuts: {} }); const WaxRules = new CreateRules({ schema: schema, rules: rules }); const editorContent = value ? value : ""; + if (plugins) defaultPlugins.push(...plugins); + const finalPlugins = defaultPlugins.concat([ placeholder({ content: this.props.placeholder }), - WaxKeys, + WaxShortCuts, WaxRules ]); - if (plugins) finalPlugins.push(...plugins); this.WaxOptions = { schema, diff --git a/wax-prosemirror-core/src/WaxView.js b/wax-prosemirror-core/src/WaxView.js index be8403f46f506dd889f04d13648ff2236c4859b6..afc9eb25d17d0edd5b233eada40c6c2066a136c9 100644 --- a/wax-prosemirror-core/src/WaxView.js +++ b/wax-prosemirror-core/src/WaxView.js @@ -6,7 +6,6 @@ import { EditorView } from "prosemirror-view"; import placeholderPlugin from "./config/plugins/placeholderPlugin"; import "prosemirror-view/style/prosemirror.css"; -import "prosemirror-gapcursor/style/gapcursor.css"; import trackedTransaction from "./config/track-changes/trackedTransaction"; class WaxView extends Component { diff --git a/wax-prosemirror-schema/index.js b/wax-prosemirror-schema/index.js index 78927a9d0ac16a206f5127d09be1343457c07ac9..b89875b9b3e96355f328988e7ece02c06e3929de 100644 --- a/wax-prosemirror-schema/index.js +++ b/wax-prosemirror-schema/index.js @@ -1,3 +1,3 @@ export { default as DefaultSchema } from "./src/DefaultSchema"; -export { default as EditoriaSchema } from "./src/EditoriaSchema"; +export { default as EditoriaSchema } from "./src/editoria/EditoriaSchema"; export { default as XpubSchema } from "./src/XpubSchema"; diff --git a/wax-prosemirror-schema/src/EditoriaSchema.js b/wax-prosemirror-schema/src/EditoriaSchema.js deleted file mode 100644 index 52ee19cff32ab1e82a3b1347b6a8029409cc4ffa..0000000000000000000000000000000000000000 --- a/wax-prosemirror-schema/src/EditoriaSchema.js +++ /dev/null @@ -1,738 +0,0 @@ -// Npdes -const pDOM = ["p", 0], - brDOM = ["br"]; - -//Marks -const emDOM = ["em", 0], - strongDOM = ["strong", 0], - codeDOM = ["code", 0]; - -const parseFormatList = str => { - if (!str) { - return []; - } - let formatList; - try { - formatList = JSON.parse(str); - } catch (error) { - return []; - } - if (!Array.isArray(formatList)) { - return []; - } - return formatList.filter(format => typeof format === "string"); // ensure there are only strings in list -}; - -const parseTracks = str => { - if (!str) { - return []; - } - let tracks; - try { - tracks = JSON.parse(str); - } catch (error) { - return []; - } - if (!Array.isArray(tracks)) { - return []; - } - return tracks.filter( - ( - track // ensure required fields are present - ) => - track.hasOwnProperty("user") && - track.hasOwnProperty("username") && - track.hasOwnProperty("date") - ); -}; - -const blockLevelToDOM = node => { - const attrs = node.attrs.track.length - ? { - class: node.attrs.class, - "data-track": JSON.stringify(node.attrs.track) - } - : { class: node.attrs.class }; - return attrs; -}; - -const EditoriaSchema = { - nodes: { - doc: { - content: "block+" - }, - - text: { - group: "inline" - }, - - hard_break: { - inline: true, - group: "inline", - selectable: false, - parseDOM: [{ tag: "br" }], - toDOM() { - return brDOM; - } - }, - image: { - inline: true, - attrs: { - src: {}, - alt: { default: null }, - title: { default: null }, - track: { default: [] } - }, - group: "inline", - draggable: true, - parseDOM: [ - { - tag: "img[src]", - getAttrs(dom) { - return { - src: dom.getAttribute("src"), - title: dom.getAttribute("title"), - track: parseTracks(dom.dataset.track), - alt: dom.getAttribute("alt") - }; - } - } - ], - toDOM(node) { - const attrs = {}; - let temp = ""; - if (node.attrs.track.length) { - attrs["data-track"] = JSON.stringify(node.attrs.track); - } - let { src, alt, title } = node.attrs; - return ["img", { src, alt, title }]; - } - }, - paragraph: { - group: "block", - content: "inline*", - attrs: { - class: { default: "paragraph" }, - track: { default: [] } - }, - parseDOM: [ - { - tag: "p.paragraph", - getAttrs(dom) { - return { - class: dom.getAttribute("class"), - track: parseTracks(dom.dataset.track) - }; - } - } - ], - toDOM(node) { - const attrs = blockLevelToDOM(node); - return ["p", attrs, 0]; - } - }, - author: { - content: "inline*", - group: "block", - priority: 0, - defining: true, - attrs: { - class: { default: "author" }, - track: { default: [] } - }, - parseDOM: [ - { - tag: "p.author", - getAttrs(dom) { - return { - class: dom.getAttribute("class"), - track: parseTracks(dom.dataset.track) - }; - } - } - ], - toDOM(node) { - const attrs = blockLevelToDOM(node); - return ["p", attrs, 0]; - } - }, - epigraphProse: { - content: "inline*", - group: "block", - priority: 0, - defining: true, - attrs: { - class: { default: "epigraph-prose" }, - track: { default: [] } - }, - parseDOM: [ - { - tag: "p.epigraph-prose", - getAttrs(dom) { - return { - class: dom.getAttribute("class"), - track: parseTracks(dom.dataset.track) - }; - } - } - ], - toDOM(node) { - const attrs = blockLevelToDOM(node); - return ["p", attrs, 0]; - } - }, - epigraphPoetry: { - content: "inline*", - group: "block", - priority: 0, - defining: true, - attrs: { - class: { default: "epigraph-poetry" }, - track: { default: [] } - }, - parseDOM: [ - { - tag: "p.epigraph-poetry", - getAttrs(dom) { - return { - class: dom.getAttribute("class"), - track: parseTracks(dom.dataset.track) - }; - } - } - ], - toDOM(node) { - const attrs = blockLevelToDOM(node); - return ["p", attrs, 0]; - } - }, - sourceNote: { - content: "inline*", - group: "block", - priority: 0, - defining: true, - attrs: { - class: { default: "source-note" }, - track: { default: [] } - }, - parseDOM: [ - { - tag: "p.source-note", - getAttrs(dom) { - return { - class: dom.getAttribute("class"), - track: parseTracks(dom.dataset.track) - }; - } - } - ], - toDOM(node) { - const attrs = blockLevelToDOM(node); - return ["p", attrs, 0]; - } - }, - paragraphCont: { - content: "inline*", - group: "block", - priority: 0, - defining: true, - attrs: { - class: { default: "paragraph-cont" }, - track: { default: [] } - }, - parseDOM: [ - { - tag: "p.paragraph-cont", - getAttrs(dom) { - return { - class: dom.getAttribute("class"), - track: parseTracks(dom.dataset.track) - }; - } - } - ], - toDOM(node) { - const attrs = blockLevelToDOM(node); - return ["p", attrs, 0]; - } - }, - extractProse: { - content: "inline*", - group: "block", - priority: 0, - defining: true, - attrs: { - class: { default: "extract-prose" }, - track: { default: [] } - }, - parseDOM: [ - { - tag: "p.extract-prose", - getAttrs(dom) { - return { - class: dom.getAttribute("class"), - track: parseTracks(dom.dataset.track) - }; - } - } - ], - toDOM(node) { - const attrs = blockLevelToDOM(node); - return ["p", attrs, 0]; - } - }, - extractPoetry: { - content: "inline*", - group: "block", - priority: 0, - defining: true, - attrs: { - class: { default: "extract-poetry" }, - track: { default: [] } - }, - parseDOM: [ - { - tag: "p.extract-poetry", - getAttrs(dom) { - return { - class: dom.getAttribute("class"), - track: parseTracks(dom.dataset.track) - }; - } - } - ], - toDOM(node) { - const attrs = blockLevelToDOM(node); - return ["p", attrs, 0]; - } - }, - title: { - content: "inline*", - group: "block", - priority: 0, - defining: true, - attrs: { - class: { default: "title" }, - track: { default: [] } - }, - parseDOM: [ - { - tag: "p.title", - getAttrs(dom) { - return { - class: dom.getAttribute("class"), - track: parseTracks(dom.dataset.track) - }; - } - } - ], - toDOM(node) { - const attrs = blockLevelToDOM(node); - return ["p", attrs, 0]; - } - }, - subtitle: { - content: "inline*", - group: "block", - priority: 0, - defining: true, - attrs: { - class: { default: "cst" }, - track: { default: [] } - }, - parseDOM: [ - { - tag: "p.cst", - getAttrs(dom) { - return { - class: dom.getAttribute("class"), - track: parseTracks(dom.dataset.track) - }; - } - } - ], - toDOM(node) { - const attrs = blockLevelToDOM(node); - return ["p", attrs, 0]; - } - }, - heading: { - attrs: { - level: { default: 1 }, - track: { default: [] } - }, - content: "inline*", - group: "block", - defining: true, - parseDOM: [ - { - tag: "h1", - attrs: { level: 1 }, - getAttrs(dom) { - return { - track: parseTracks(dom.dataset.track) - }; - } - }, - { - tag: "h2", - attrs: { level: 2 }, - getAttrs(dom) { - return { - track: parseTracks(dom.dataset.track) - }; - } - }, - { - tag: "h3", - attrs: { level: 3 }, - getAttrs(dom) { - return { - track: parseTracks(dom.dataset.track) - }; - } - } - ], - toDOM(node) { - const attrs = {}; - if (node.attrs.track.length) { - attrs["data-track"] = JSON.stringify(node.attrs.track); - } - return [`h${node.attrs.level}`, attrs, 0]; - } - }, - ordered_list: { - group: "block", - content: "list_item+", - attrs: { - order: { default: 1 }, - track: { default: [] } - }, - parseDOM: [ - { - tag: "ol", - getAttrs(dom) { - return { - order: dom.hasAttribute("start") ? +dom.getAttribute("start") : 1, - track: parseTracks(dom.dataset.track) - }; - } - } - ], - toDOM(node) { - const attrs = {}; - if (node.attrs.order !== 1) { - attrs.start = node.attrs.order; - } - if (node.attrs.track.length) { - attrs["data-track"] = JSON.stringify(node.attrs.track); - } - return ["ol", attrs, 0]; - } - }, - bullet_list: { - group: "block", - content: "list_item+", - attrs: { - track: { default: [] } - }, - parseDOM: [ - { - tag: "ul", - getAttrs(dom) { - return { - track: parseTracks(dom.dataset.track) - }; - } - } - ], - toDOM(node) { - const attrs = {}; - if (node.attrs.track.length) { - attrs["data-track"] = JSON.stringify(node.attrs.track); - } - return ["ul", attrs, 0]; - } - }, - list_item: { - content: "block+", - attrs: { - track: { default: [] } - }, - parseDOM: [ - { - tag: "li", - getAttrs(dom) { - return { - track: parseTracks(dom.dataset.track) - }; - } - } - ], - toDOM(node) { - const attrs = {}; - if (node.attrs.track.length) { - attrs["data-track"] = JSON.stringify(node.attrs.track); - } - return ["li", attrs, 0]; - }, - defining: true - } - }, - marks: { - link: { - attrs: { - href: { default: null }, - rel: { default: "" }, - target: { default: "blank" }, - title: { default: null } - }, - inclusive: false, - parseDOM: [ - { - tag: "a[href]", - getAttrs: dom => { - const href = dom.getAttribute("href"); - const target = href && href.indexOf("#") === 0 ? "" : "blank"; - return { - href: dom.getAttribute("href"), - title: dom.getAttribute("title"), - target - }; - } - } - ], - toDOM(node) { - return ["a", node.attrs, 0]; - } - }, - em: { - parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style=italic" }], - toDOM() { - return emDOM; - } - }, - strong: { - parseDOM: [ - { tag: "strong" }, - { - tag: "b", - getAttrs: node => node.style.fontWeight != "normal" && null - }, - { - style: "font-weight", - getAttrs: value => /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null - } - ], - toDOM() { - return strongDOM; - } - }, - code: { - parseDOM: [{ tag: "code" }], - toDOM() { - return codeDOM; - } - }, - subscript: { - excludes: "superscript", - parseDOM: [{ tag: "sub" }, { style: "vertical-align=sub" }], - toDOM: () => ["sub"] - }, - superscript: { - excludes: "subscript", - parseDOM: [{ tag: "sup" }, { style: "vertical-align=super" }], - toDOM: () => ["sup"] - }, - strikethrough: { - parseDOM: [ - { tag: "strike" }, - { style: "text-decoration:line-through" }, - { style: "text-decoration-line:line-through" } - ], - toDOM: () => [ - "span", - { - style: "text-decoration-line:line-through" - } - ] - }, - underline: { - parseDOM: [{ tag: "u" }, { style: "text-decoration:underline" }], - toDOM: () => [ - "span", - { - style: "text-decoration:underline" - } - ] - }, - // small_caps: { - // attrs: { - // class: { default: "small-caps" } - // }, - // inclusive: false, - // parseDOM: [ - // { - // tag: "span", - // getAttrs(dom) { - // return { class: dom.getAttribute("class") }; - // } - // } - // ], - // toDOM(node) { - // return ["span", node.attrs, 0]; - // } - // }, - source: { - parseDOM: [{ tag: "cite" }], - toDOM() { - return ["cite", 0]; - } - }, - insertion: { - attrs: { - user: { - default: 0 - }, - username: { - default: "" - }, - date: { - default: 0 - }, - approved: { - default: true - } - }, - inclusive: false, - group: "track", - parseDOM: [ - { - tag: "span.insertion", - getAttrs(dom) { - return { - user: parseInt(dom.dataset.user), - username: dom.dataset.username, - date: parseInt(dom.dataset.date), - inline: true, - approved: false - }; - } - }, - { - tag: "span.approved-insertion", - getAttrs(dom) { - return { - user: parseInt(dom.dataset.user), - username: dom.dataset.username, - date: parseInt(dom.dataset.date), - inline: true, - approved: true - }; - } - } - ], - toDOM(node) { - return [ - "span", - { - class: node.attrs.approved - ? "approved-insertion" - : `insertion user-${node.attrs.user}`, - "data-user": node.attrs.user, - "data-username": node.attrs.username, - "data-date": node.attrs.date - } - ]; - } - }, - deletion: { - attrs: { - user: { - default: 0 - }, - username: { - default: "" - }, - date: { - default: 0 - } - }, - inclusive: false, - group: "track", - parseDOM: [ - { - tag: "span.deletion", - getAttrs(dom) { - return { - user: parseInt(dom.dataset.user), - username: dom.dataset.username, - date: parseInt(dom.dataset.date) - }; - } - } - ], - toDOM(node) { - return [ - "span", - { - class: `deletion user-${node.attrs.user}`, - "data-user": node.attrs.user, - "data-username": node.attrs.username, - "data-date": node.attrs.date - } - ]; - } - }, - format_change: { - attrs: { - user: { - default: 0 - }, - username: { - default: "" - }, - date: { - default: 0 - }, - before: { - default: [] - }, - after: { - default: [] - } - }, - inclusive: false, - group: "track", - parseDOM: [ - { - tag: "span.format-change", - getAttrs(dom) { - return { - user: parseInt(dom.dataset.user), - username: dom.dataset.username, - date: parseInt(dom.dataset.date), - before: parseFormatList(dom.dataset.before), - after: parseFormatList(dom.dataset.after) - }; - } - } - ], - toDOM(node) { - return [ - "span", - { - class: `format-change user-${node.attrs.user}`, - "data-user": node.attrs.user, - "data-username": node.attrs.username, - "data-date": node.attrs.date, - "data-before": JSON.stringify(node.attrs.before), - "data-after": JSON.stringify(node.attrs.after) - } - ]; - } - } - } -}; - -export default EditoriaSchema; diff --git a/wax-prosemirror-schema/src/editoria/EditoriaSchema.js b/wax-prosemirror-schema/src/editoria/EditoriaSchema.js new file mode 100644 index 0000000000000000000000000000000000000000..a8411b0c8041fa9d846220dcf9e7896d97d5c41c --- /dev/null +++ b/wax-prosemirror-schema/src/editoria/EditoriaSchema.js @@ -0,0 +1,9 @@ +import nodes from "./nodes"; +import marks from "./marks"; + +const EditoriaSchema = { + nodes, + marks +}; + +export default EditoriaSchema; diff --git a/wax-prosemirror-schema/src/editoria/helpers.js b/wax-prosemirror-schema/src/editoria/helpers.js new file mode 100644 index 0000000000000000000000000000000000000000..3020008e105df94bfb17663ceb35334c02fe3e97 --- /dev/null +++ b/wax-prosemirror-schema/src/editoria/helpers.js @@ -0,0 +1,50 @@ +const parseFormatList = str => { + if (!str) { + return []; + } + let formatList; + try { + formatList = JSON.parse(str); + } catch (error) { + return []; + } + if (!Array.isArray(formatList)) { + return []; + } + return formatList.filter(format => typeof format === "string"); // ensure there are only strings in list +}; + +const parseTracks = str => { + if (!str) { + return []; + } + let tracks; + try { + tracks = JSON.parse(str); + } catch (error) { + return []; + } + if (!Array.isArray(tracks)) { + return []; + } + return tracks.filter( + ( + track // ensure required fields are present + ) => + track.hasOwnProperty("user") && + track.hasOwnProperty("username") && + track.hasOwnProperty("date") + ); +}; + +const blockLevelToDOM = node => { + const attrs = node.attrs.track.length + ? { + class: node.attrs.class, + "data-track": JSON.stringify(node.attrs.track) + } + : { class: node.attrs.class }; + return attrs; +}; + +export { parseFormatList, parseTracks, blockLevelToDOM }; diff --git a/wax-prosemirror-schema/src/editoria/marks.js b/wax-prosemirror-schema/src/editoria/marks.js new file mode 100644 index 0000000000000000000000000000000000000000..d5792ded525b9989fdb56bba3ae003ca835e1e99 --- /dev/null +++ b/wax-prosemirror-schema/src/editoria/marks.js @@ -0,0 +1,259 @@ +const emDOM = ["em", 0], + strongDOM = ["strong", 0], + codeDOM = ["code", 0]; + +const marks = { + link: { + attrs: { + href: { default: null }, + rel: { default: "" }, + target: { default: "blank" }, + title: { default: null } + }, + inclusive: false, + parseDOM: [ + { + tag: "a[href]", + getAttrs: dom => { + const href = dom.getAttribute("href"); + const target = href && href.indexOf("#") === 0 ? "" : "blank"; + return { + href: dom.getAttribute("href"), + title: dom.getAttribute("title"), + target + }; + } + } + ], + toDOM(node) { + return ["a", node.attrs, 0]; + } + }, + em: { + parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style=italic" }], + toDOM() { + return emDOM; + } + }, + strong: { + parseDOM: [ + { tag: "strong" }, + { + tag: "b", + getAttrs: node => node.style.fontWeight != "normal" && null + }, + { + style: "font-weight", + getAttrs: value => /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null + } + ], + toDOM() { + return strongDOM; + } + }, + code: { + parseDOM: [{ tag: "code" }], + toDOM() { + return codeDOM; + } + }, + subscript: { + excludes: "superscript", + parseDOM: [{ tag: "sub" }, { style: "vertical-align=sub" }], + toDOM: () => ["sub"] + }, + superscript: { + excludes: "subscript", + parseDOM: [{ tag: "sup" }, { style: "vertical-align=super" }], + toDOM: () => ["sup"] + }, + strikethrough: { + parseDOM: [ + { tag: "strike" }, + { style: "text-decoration:line-through" }, + { style: "text-decoration-line:line-through" } + ], + toDOM: () => [ + "span", + { + style: "text-decoration-line:line-through" + } + ] + }, + underline: { + parseDOM: [{ tag: "u" }, { style: "text-decoration:underline" }], + toDOM: () => [ + "span", + { + style: "text-decoration:underline" + } + ] + }, + small_caps: { + attrs: { + class: { default: "small-caps" } + }, + // inclusive: false, + parseDOM: [ + { + tag: "span.small-caps", + getAttrs(dom) { + return { class: dom.getAttribute("class") }; + } + } + ], + toDOM(node) { + return ["span", node.attrs, 0]; + } + }, + source: { + parseDOM: [{ tag: "cite" }], + toDOM() { + return ["cite", 0]; + } + }, + insertion: { + attrs: { + user: { + default: 0 + }, + username: { + default: "" + }, + date: { + default: 0 + }, + approved: { + default: true + } + }, + inclusive: false, + group: "track", + parseDOM: [ + { + tag: "span.insertion", + getAttrs(dom) { + return { + user: parseInt(dom.dataset.user), + username: dom.dataset.username, + date: parseInt(dom.dataset.date), + inline: true, + approved: false + }; + } + }, + { + tag: "span.approved-insertion", + getAttrs(dom) { + return { + user: parseInt(dom.dataset.user), + username: dom.dataset.username, + date: parseInt(dom.dataset.date), + inline: true, + approved: true + }; + } + } + ], + toDOM(node) { + return [ + "span", + { + class: node.attrs.approved + ? "approved-insertion" + : `insertion user-${node.attrs.user}`, + "data-user": node.attrs.user, + "data-username": node.attrs.username, + "data-date": node.attrs.date + } + ]; + } + }, + deletion: { + attrs: { + user: { + default: 0 + }, + username: { + default: "" + }, + date: { + default: 0 + } + }, + inclusive: false, + group: "track", + parseDOM: [ + { + tag: "span.deletion", + getAttrs(dom) { + return { + user: parseInt(dom.dataset.user), + username: dom.dataset.username, + date: parseInt(dom.dataset.date) + }; + } + } + ], + toDOM(node) { + return [ + "span", + { + class: `deletion user-${node.attrs.user}`, + "data-user": node.attrs.user, + "data-username": node.attrs.username, + "data-date": node.attrs.date + } + ]; + } + }, + format_change: { + attrs: { + user: { + default: 0 + }, + username: { + default: "" + }, + date: { + default: 0 + }, + before: { + default: [] + }, + after: { + default: [] + } + }, + inclusive: false, + group: "track", + parseDOM: [ + { + tag: "span.format-change", + getAttrs(dom) { + return { + user: parseInt(dom.dataset.user), + username: dom.dataset.username, + date: parseInt(dom.dataset.date), + before: parseFormatList(dom.dataset.before), + after: parseFormatList(dom.dataset.after) + }; + } + } + ], + toDOM(node) { + return [ + "span", + { + class: `format-change user-${node.attrs.user}`, + "data-user": node.attrs.user, + "data-username": node.attrs.username, + "data-date": node.attrs.date, + "data-before": JSON.stringify(node.attrs.before), + "data-after": JSON.stringify(node.attrs.after) + } + ]; + } + } +}; +export default marks; diff --git a/wax-prosemirror-schema/src/editoria/nodes.js b/wax-prosemirror-schema/src/editoria/nodes.js new file mode 100644 index 0000000000000000000000000000000000000000..ddcecac7a9e41b4f6cc23f9a8d4b05c9977ac3c7 --- /dev/null +++ b/wax-prosemirror-schema/src/editoria/nodes.js @@ -0,0 +1,437 @@ +import { parseFormatList, parseTracks, blockLevelToDOM } from "./helpers"; +const pDOM = ["p", 0], + brDOM = ["br"], + blockquoteDOM = ["blockquote", 0]; + +const nodes = { + doc: { + content: "block+" + }, + + text: { + group: "inline" + }, + + hard_break: { + inline: true, + group: "inline", + selectable: false, + parseDOM: [{ tag: "br" }], + toDOM() { + return brDOM; + } + }, + image: { + inline: true, + attrs: { + src: {}, + alt: { default: null }, + title: { default: null }, + track: { default: [] } + }, + group: "inline", + draggable: true, + parseDOM: [ + { + tag: "img[src]", + getAttrs(dom) { + return { + src: dom.getAttribute("src"), + title: dom.getAttribute("title"), + track: parseTracks(dom.dataset.track), + alt: dom.getAttribute("alt") + }; + } + } + ], + toDOM(node) { + const attrs = {}; + let temp = ""; + if (node.attrs.track.length) { + attrs["data-track"] = JSON.stringify(node.attrs.track); + } + let { src, alt, title } = node.attrs; + return ["img", { src, alt, title }]; + } + }, + paragraph: { + group: "block", + content: "inline*", + attrs: { + class: { default: "paragraph" }, + track: { default: [] } + }, + parseDOM: [ + { + tag: "p.paragraph", + getAttrs(dom) { + return { + class: dom.getAttribute("class"), + track: parseTracks(dom.dataset.track) + }; + } + } + ], + toDOM(node) { + const attrs = blockLevelToDOM(node); + return ["p", attrs, 0]; + } + }, + author: { + content: "inline*", + group: "block", + priority: 0, + defining: true, + attrs: { + class: { default: "author" }, + track: { default: [] } + }, + parseDOM: [ + { + tag: "p.author", + getAttrs(dom) { + return { + class: dom.getAttribute("class"), + track: parseTracks(dom.dataset.track) + }; + } + } + ], + toDOM(node) { + const attrs = blockLevelToDOM(node); + return ["p", attrs, 0]; + } + }, + epigraphProse: { + content: "inline*", + group: "block", + priority: 0, + defining: true, + attrs: { + class: { default: "epigraph-prose" }, + track: { default: [] } + }, + parseDOM: [ + { + tag: "p.epigraph-prose", + getAttrs(dom) { + return { + class: dom.getAttribute("class"), + track: parseTracks(dom.dataset.track) + }; + } + } + ], + toDOM(node) { + const attrs = blockLevelToDOM(node); + return ["p", attrs, 0]; + } + }, + epigraphPoetry: { + content: "inline*", + group: "block", + priority: 0, + defining: true, + attrs: { + class: { default: "epigraph-poetry" }, + track: { default: [] } + }, + parseDOM: [ + { + tag: "p.epigraph-poetry", + getAttrs(dom) { + return { + class: dom.getAttribute("class"), + track: parseTracks(dom.dataset.track) + }; + } + } + ], + toDOM(node) { + const attrs = blockLevelToDOM(node); + return ["p", attrs, 0]; + } + }, + sourceNote: { + content: "inline*", + group: "block", + priority: 0, + defining: true, + attrs: { + class: { default: "source-note" }, + track: { default: [] } + }, + parseDOM: [ + { + tag: "p.source-note", + getAttrs(dom) { + return { + class: dom.getAttribute("class"), + track: parseTracks(dom.dataset.track) + }; + } + } + ], + toDOM(node) { + const attrs = blockLevelToDOM(node); + return ["p", attrs, 0]; + } + }, + paragraphCont: { + content: "inline*", + group: "block", + priority: 0, + defining: true, + attrs: { + class: { default: "paragraph-cont" }, + track: { default: [] } + }, + parseDOM: [ + { + tag: "p.paragraph-cont", + getAttrs(dom) { + return { + class: dom.getAttribute("class"), + track: parseTracks(dom.dataset.track) + }; + } + } + ], + toDOM(node) { + const attrs = blockLevelToDOM(node); + return ["p", attrs, 0]; + } + }, + extractProse: { + content: "inline*", + group: "block", + priority: 0, + defining: true, + attrs: { + class: { default: "extract-prose" }, + track: { default: [] } + }, + parseDOM: [ + { + tag: "p.extract-prose", + getAttrs(dom) { + return { + class: dom.getAttribute("class"), + track: parseTracks(dom.dataset.track) + }; + } + } + ], + toDOM(node) { + const attrs = blockLevelToDOM(node); + return ["p", attrs, 0]; + } + }, + extractPoetry: { + content: "inline*", + group: "block", + priority: 0, + defining: true, + attrs: { + class: { default: "extract-poetry" }, + track: { default: [] } + }, + parseDOM: [ + { + tag: "p.extract-poetry", + getAttrs(dom) { + return { + class: dom.getAttribute("class"), + track: parseTracks(dom.dataset.track) + }; + } + } + ], + toDOM(node) { + const attrs = blockLevelToDOM(node); + return ["p", attrs, 0]; + } + }, + title: { + content: "inline*", + group: "block", + priority: 0, + defining: true, + attrs: { + class: { default: "title" }, + track: { default: [] } + }, + parseDOM: [ + { + tag: "p.title", + getAttrs(dom) { + return { + class: dom.getAttribute("class"), + track: parseTracks(dom.dataset.track) + }; + } + } + ], + toDOM(node) { + const attrs = blockLevelToDOM(node); + return ["p", attrs, 0]; + } + }, + subtitle: { + content: "inline*", + group: "block", + priority: 0, + defining: true, + attrs: { + class: { default: "cst" }, + track: { default: [] } + }, + parseDOM: [ + { + tag: "p.cst", + getAttrs(dom) { + return { + class: dom.getAttribute("class"), + track: parseTracks(dom.dataset.track) + }; + } + } + ], + toDOM(node) { + const attrs = blockLevelToDOM(node); + return ["p", attrs, 0]; + } + }, + heading: { + attrs: { + level: { default: 1 }, + track: { default: [] } + }, + content: "inline*", + group: "block", + defining: true, + parseDOM: [ + { + tag: "h1", + attrs: { level: 1 }, + getAttrs(dom) { + return { + track: parseTracks(dom.dataset.track) + }; + } + }, + { + tag: "h2", + attrs: { level: 2 }, + getAttrs(dom) { + return { + track: parseTracks(dom.dataset.track) + }; + } + }, + { + tag: "h3", + attrs: { level: 3 }, + getAttrs(dom) { + return { + track: parseTracks(dom.dataset.track) + }; + } + } + ], + toDOM(node) { + const attrs = {}; + if (node.attrs.track.length) { + attrs["data-track"] = JSON.stringify(node.attrs.track); + } + return [`h${node.attrs.level}`, attrs, 0]; + } + }, + ordered_list: { + group: "block", + content: "list_item+", + attrs: { + order: { default: 1 }, + track: { default: [] } + }, + parseDOM: [ + { + tag: "ol", + getAttrs(dom) { + return { + order: dom.hasAttribute("start") ? +dom.getAttribute("start") : 1, + track: parseTracks(dom.dataset.track) + }; + } + } + ], + toDOM(node) { + const attrs = {}; + if (node.attrs.order !== 1) { + attrs.start = node.attrs.order; + } + if (node.attrs.track.length) { + attrs["data-track"] = JSON.stringify(node.attrs.track); + } + return ["ol", attrs, 0]; + } + }, + bullet_list: { + group: "block", + content: "list_item+", + attrs: { + track: { default: [] } + }, + parseDOM: [ + { + tag: "ul", + getAttrs(dom) { + return { + track: parseTracks(dom.dataset.track) + }; + } + } + ], + toDOM(node) { + const attrs = {}; + if (node.attrs.track.length) { + attrs["data-track"] = JSON.stringify(node.attrs.track); + } + return ["ul", attrs, 0]; + } + }, + list_item: { + content: "block+", + attrs: { + track: { default: [] } + }, + parseDOM: [ + { + tag: "li", + getAttrs(dom) { + return { + track: parseTracks(dom.dataset.track) + }; + } + } + ], + toDOM(node) { + const attrs = {}; + if (node.attrs.track.length) { + attrs["data-track"] = JSON.stringify(node.attrs.track); + } + return ["li", attrs, 0]; + }, + defining: true + }, + blockquote: { + content: "block+", + group: "block", + defining: true, + parseDOM: [{ tag: "blockquote" }], + toDOM() { + return blockquoteDOM; + } + } +}; +export default nodes;