From a33363eef0663afaad27c6dfc6744292939d9757 Mon Sep 17 00:00:00 2001 From: koehr <n@koehr.in> Date: Sun, 29 Mar 2020 20:08:15 +0200 Subject: [PATCH] trying to extend that editor --- src/components/deck-card-editor-menu.vue | 41 ++++++++++++++---- src/components/deck-card.vue | 18 +++++++- src/editor/stat-block.js | 53 ++++++++++++++++++++++++ src/editor/stat-block.ts | 49 ++++++++++++++++++++++ 4 files changed, 152 insertions(+), 9 deletions(-) create mode 100644 src/editor/stat-block.js create mode 100644 src/editor/stat-block.ts diff --git a/src/components/deck-card-editor-menu.vue b/src/components/deck-card-editor-menu.vue index 1732fc4..a0296db 100644 --- a/src/components/deck-card-editor-menu.vue +++ b/src/components/deck-card-editor-menu.vue @@ -1,15 +1,17 @@ <template> <editor-menu-bar :editor="editor" v-slot="{ commands, isActive, focused }"> <div class="menu-bar" :class="{ active: focused }"> - <button class="editor-button-bold" :class="{ active: isActive.bold() }" @click="commands.bold" /> - <button class="editor-button-italic" :class="{ active: isActive.italic() }" @click="commands.italic" /> + <button class="editor-button-bold" :class="{ active: active.bold }" @click="menuAction('bold', commands.bold, isActive)" /> + <button class="editor-button-italic" :class="{ active: active.italic }" @click="menuAction('italic', commands.italic, isActive)" /> - <button class="editor-button-paragraph" :class="{ active: isActive.paragraph() }" @click="commands.paragraph" /> - <button class="editor-button-heading2" :class="{ active: isActive.heading({ level: 2 }) }" @click="commands.heading({ level: 2})" /> - <button class="editor-button-heading3" :class="{ active: isActive.heading({ level: 3 }) }" @click="commands.heading({ level: 3})" /> + <button class="editor-button-paragraph" :class="{ active: active.paragraph }" @click="menuAction('paragraph', commands.paragraph, isActive)" /> + <button class="editor-button-heading2" :class="{ active: active.heading2 }" @click="menuAction('heading2', commands.heading({ level: 2}), isActive)" /> + <button class="editor-button-heading3" :class="{ active: active.heading3 }" @click="menuAction('heading3', commands.heading({ level: 3}), isActive)" /> - <button class="editor-button-bullet-list" :class="{ active: isActive.bullet_list() }" @click="commands.bullet_list" /> - <button class="editor-button-horizontal-rule" :class="{ active: isActive.horizontal_rule() }" @click="commands.horizontal_rule" /> + <button class="editor-button-bullet-list" :class="{ active: active.bullet_list }" @click="menuAction('bullet_list', commands.bullet_list, isActive)" /> + <button class="editor-button-horizontal-rule" :class="{ active: active.horizontal_rule }" @click="menuAction('horizontal_rule', commands.horizontal_rule, isActive)" /> + + <button class="editor-button-stat-block" :class="{ active: active.stat_block }" @click="menuAction('stat_block', commands.stat_block, isActive)" /> </div> </editor-menu-bar> </template> @@ -23,6 +25,26 @@ import { Editor, EditorMenuBar } from 'tiptap' }) export default class DeckCardEditorMenu extends Vue { @Prop() public readonly editor!: Editor + + private active: {[key: string]: boolean} = { + bold: false, + italic: false, + + paragraph: false, + heading2: false, + heading3: false, + + bulletList: false, + horizontalLule: false + } + + private menuAction (name: string, command: () => void, isActive: {[key: string]: () => boolean}) { + command() + + Object.keys(this.active).forEach(action => { + this.active[action] = isActive[action]() + }) + } } </script> @@ -63,6 +85,9 @@ export default class DeckCardEditorMenu extends Vue { font-size: 1.2rem; color: black; } +.menu-bar > button.active { + background-color: #FF0; +} .editor-button-bold { background-image: url(../assets/zondicons/format-bold.svg); } .editor-button-italic { background-image: url(../assets/zondicons/format-italic.svg); } .editor-button-bullet-list { background-image: url(../assets/zondicons/list-bullet.svg); } @@ -71,4 +96,6 @@ export default class DeckCardEditorMenu extends Vue { .editor-button-heading3:after { content: 'H3'; } .editor-button-paragraph:after { content: 'P'; } .editor-button-horizontal-rule:after { content: '—'; } + +.editor-button-stat-block:after { content: 'ST'; } </style> diff --git a/src/components/deck-card.vue b/src/components/deck-card.vue index a3354e8..405f5bc 100644 --- a/src/components/deck-card.vue +++ b/src/components/deck-card.vue @@ -34,7 +34,20 @@ import { Component, Prop, Vue } from 'vue-property-decorator' import { cardWHtoStyle, iconPath } from '@/lib' import { Editor, EditorContent } from 'tiptap' -import { Heading, Bold, Italic, HorizontalRule, BulletList, ListItem, History } from 'tiptap-extensions' +import { + Heading, + Bold, + Italic, + HorizontalRule, + BulletList, + ListItem, + History, + Table, + TableCell, + TableRow, + TableHeader +} from 'tiptap-extensions' +import StatBlock from '@/editor/stat-block.js' import DeckCardEditorMenu from '@/components/deck-card-editor-menu.vue' interface EditorContext { @@ -51,7 +64,8 @@ const extensions = [ new HorizontalRule(), new BulletList(), new ListItem(), - new History() + new History(), + new StatBlock() ] @Component({ diff --git a/src/editor/stat-block.js b/src/editor/stat-block.js new file mode 100644 index 0000000..db9801d --- /dev/null +++ b/src/editor/stat-block.js @@ -0,0 +1,53 @@ +import { Node } from 'tiptap' +import { tableNodes, tableEditing, goToNextCell, deleteTable } from 'prosemirror-tables' +import { createTable } from 'prosemirror-utils' +import { TextSelection } from 'prosemirror-state' + +export default class StatBlock extends Node { + get name () { + return 'stat_block' + } + + get defaultOptions () { + return { + resizable: false + } + } + + get schema () { + return { + group: 'block', + content: 'stat_column+', + toDOM: () => ['ol', { 'data-type': this.name }, 0], + parseDOM: [{ + priority: 51, + tag: `[data-type="${this.name}"]` + }] + } + } + + commands ({ schema }) { + return () => (state, dispatch) => { + const offset = state.tr.selection.anchor + 1 + + const nodes = createTable(schema, 2, 6, true) + const tr = state.tr.replaceSelectionWith(nodes).scrollIntoView() + const resolvedPos = tr.doc.resolve(offset) + + tr.setSelection(TextSelection.near(resolvedPos)) + + dispatch(tr) + } + } + + keys () { + return { + Tab: goToNextCell(1), + 'Shift-Tab': goToNextCell(-1) + } + } + + get plugins () { + return [tableEditing()] + } +} diff --git a/src/editor/stat-block.ts b/src/editor/stat-block.ts new file mode 100644 index 0000000..d1a387d --- /dev/null +++ b/src/editor/stat-block.ts @@ -0,0 +1,49 @@ +import { Node } from 'tiptap' +import { tableEditing, goToNextCell, deleteTable } from 'prosemirror-tables' +import { createTable } from 'prosemirror-utils' +import { TextSelection } from 'prosemirror-state' +import { TableNodes } from 'tiptap-extensions' + +export default class StatBlock extends Node { + public get name () { + return 'stat_block' + } + + public get defaultOptions () { + return { + resizable: false + } + } + + public get schema () { + return TableNodes.table + } + + public commands ({ schema }) { + return { + createStatBlock: () => (state, dispatch) => { + const offset = state.tr.selection.anchor + 1 + + const nodes = createTable(schema, 2, 6, true) + const tr = state.tr.replaceSelectionWith(nodes).scrollIntoView() + const resolvedPos = tr.doc.resolve(offset) + + tr.setSelection(TextSelection.near(resolvedPos)) + + dispatch(tr) + }, + deleteTable: () => deleteTable + } + } + + public keys () { + return { + Tab: goToNextCell(1), + 'Shift-Tab': goToNextCell(-1) + } + } + + public get plugins () { + return [tableEditing()] + } +}