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()]
+  }
+}