adds editorjs and some simple extensions

master
koehr 5 years ago committed by Norman
parent bfe3e00351
commit adb8cb2dd1

@ -14,6 +14,8 @@
"vue-router": "^3.1.5"
},
"devDependencies": {
"@editorjs/editorjs": "^2.17.0",
"@editorjs/list": "^1.4.0",
"@typescript-eslint/eslint-plugin": "^2.18.0",
"@typescript-eslint/parser": "^2.18.0",
"@vue/cli-plugin-babel": "^4.2.0",
@ -30,6 +32,7 @@
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^6.1.2",
"lint-staged": "^9.5.0",
"raw-loader": "^4.0.0",
"typescript": "~3.7.5",
"vue-property-decorator": "^8.4.0",
"vue-template-compiler": "^2.6.11"

@ -132,3 +132,7 @@ button.action-close {
border-radius: 1em;
cursor: pointer;
}
.codex-editor--narrow .codex-editor__redactor {
margin-right: 0;
}

@ -0,0 +1 @@
<svg width="19" height="4" viewBox="0 0 19 4" xmlns="http://www.w3.org/2000/svg"><path d="M1.25 0H7a1.25 1.25 0 1 1 0 2.5H1.25a1.25 1.25 0 1 1 0-2.5zM11 0h5.75a1.25 1.25 0 0 1 0 2.5H11A1.25 1.25 0 0 1 11 0z"/></svg>

After

Width:  |  Height:  |  Size: 216 B

@ -0,0 +1 @@
<svg width="10" height="14" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 14"><path d="M7.6 8.15H2.25v4.525a1.125 1.125 0 0 1-2.25 0V1.125a1.125 1.125 0 1 1 2.25 0V5.9H7.6V1.125a1.125 1.125 0 0 1 2.25 0v11.55a1.125 1.125 0 0 1-2.25 0V8.15z"/></svg>

After

Width:  |  Height:  |  Size: 254 B

@ -0,0 +1 @@
<svg width="16" height="14" xmlns="http://www.w3.org/2000/svg"><path d="M2.14 1.494V4.98h4.62V1.494c0-.498.098-.871.293-1.12A.927.927 0 0 1 7.82 0c.322 0 .583.123.782.37.2.246.3.62.3 1.124v9.588c0 .503-.101.88-.303 1.128a.957.957 0 0 1-.779.374.921.921 0 0 1-.77-.378c-.193-.251-.29-.626-.29-1.124V6.989H2.14v4.093c0 .503-.1.88-.302 1.128a.957.957 0 0 1-.778.374.921.921 0 0 1-.772-.378C.096 11.955 0 11.58 0 11.082V1.494C0 .996.095.623.285.374A.922.922 0 0 1 1.06 0c.321 0 .582.123.782.37.199.246.299.62.299 1.124zm11.653 9.985V5.27c-1.279.887-2.14 1.33-2.583 1.33a.802.802 0 0 1-.563-.228.703.703 0 0 1-.245-.529c0-.232.08-.402.241-.511.161-.11.446-.25.854-.424.61-.259 1.096-.532 1.462-.818a5.84 5.84 0 0 0 .97-.962c.282-.355.466-.573.552-.655.085-.082.246-.123.483-.123.267 0 .481.093.642.28.161.186.242.443.242.77v7.813c0 .914-.345 1.371-1.035 1.371-.307 0-.554-.093-.74-.28-.187-.186-.28-.461-.28-.825z"/></svg>

After

Width:  |  Height:  |  Size: 918 B

@ -0,0 +1 @@
<svg width="18" height="14" xmlns="http://www.w3.org/2000/svg"><path d="M2.152 1.494V4.98h4.646V1.494c0-.498.097-.871.293-1.12A.934.934 0 0 1 7.863 0c.324 0 .586.123.786.37.2.246.301.62.301 1.124v9.588c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378c-.194-.251-.29-.626-.29-1.124V6.989H2.152v4.093c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378C.097 11.955 0 11.58 0 11.082V1.494C0 .996.095.623.286.374A.929.929 0 0 1 1.066 0c.323 0 .585.123.786.37.2.246.3.62.3 1.124zm10.99 9.288h3.527c.351 0 .62.072.804.216.185.144.277.34.277.588 0 .22-.073.408-.22.56-.146.154-.368.23-.665.23h-4.972c-.338 0-.601-.093-.79-.28a.896.896 0 0 1-.284-.659c0-.162.06-.377.182-.645s.255-.478.399-.631a38.617 38.617 0 0 1 1.621-1.598c.482-.444.827-.735 1.034-.875.369-.261.676-.523.922-.787.245-.263.432-.534.56-.81.129-.278.193-.549.193-.815 0-.288-.069-.546-.206-.773a1.428 1.428 0 0 0-.56-.53 1.618 1.618 0 0 0-.774-.19c-.59 0-1.054.26-1.392.777-.045.068-.12.252-.226.554-.106.302-.225.534-.358.696-.133.162-.328.243-.585.243a.76.76 0 0 1-.56-.223c-.149-.148-.223-.351-.223-.608 0-.31.07-.635.21-.972.139-.338.347-.645.624-.92a3.093 3.093 0 0 1 1.054-.665c.426-.169.924-.253 1.496-.253.69 0 1.277.108 1.764.324.315.144.592.343.83.595.24.252.425.544.558.875.133.33.2.674.2 1.03 0 .558-.14 1.066-.416 1.523-.277.457-.56.815-.848 1.074-.288.26-.771.666-1.45 1.22-.677.554-1.142.984-1.394 1.29a3.836 3.836 0 0 0-.331.44z"/></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@ -0,0 +1 @@
<svg width="18" height="14" xmlns="http://www.w3.org/2000/svg"><path d="M2.152 1.494V4.98h4.646V1.494c0-.498.097-.871.293-1.12A.934.934 0 0 1 7.863 0c.324 0 .586.123.786.37.2.246.301.62.301 1.124v9.588c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378c-.194-.251-.29-.626-.29-1.124V6.989H2.152v4.093c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378C.097 11.955 0 11.58 0 11.082V1.494C0 .996.095.623.286.374A.929.929 0 0 1 1.066 0c.323 0 .585.123.786.37.2.246.3.62.3 1.124zm11.61 4.919c.418 0 .778-.123 1.08-.368.301-.245.452-.597.452-1.055 0-.35-.12-.65-.36-.902-.241-.252-.566-.378-.974-.378-.277 0-.505.038-.684.116a1.1 1.1 0 0 0-.426.306 2.31 2.31 0 0 0-.296.49c-.093.2-.178.388-.255.565a.479.479 0 0 1-.245.225.965.965 0 0 1-.409.081.706.706 0 0 1-.5-.22c-.152-.148-.228-.345-.228-.59 0-.236.071-.484.214-.745a2.72 2.72 0 0 1 .627-.746 3.149 3.149 0 0 1 1.024-.568 4.122 4.122 0 0 1 1.368-.214c.44 0 .842.06 1.205.18.364.12.679.294.947.52.267.228.47.49.606.79.136.3.204.622.204.967 0 .454-.099.843-.296 1.168-.198.324-.48.64-.848.95.354.19.653.408.895.653.243.245.426.516.548.813.123.298.184.619.184.964 0 .413-.083.812-.248 1.198-.166.386-.41.73-.732 1.031a3.49 3.49 0 0 1-1.147.708c-.443.17-.932.256-1.467.256a3.512 3.512 0 0 1-1.464-.293 3.332 3.332 0 0 1-1.699-1.64c-.142-.314-.214-.573-.214-.777 0-.263.085-.475.255-.636a.89.89 0 0 1 .637-.242c.127 0 .25.037.367.112a.53.53 0 0 1 .232.27c.236.63.489 1.099.759 1.405.27.306.65.46 1.14.46a1.714 1.714 0 0 0 1.46-.824c.17-.273.256-.588.256-.947 0-.53-.145-.947-.436-1.249-.29-.302-.694-.453-1.212-.453-.09 0-.231.01-.422.028-.19.018-.313.027-.367.027-.25 0-.443-.062-.579-.187-.136-.125-.204-.299-.204-.521 0-.218.081-.394.245-.528.163-.134.406-.2.728-.2h.28z"/></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@ -0,0 +1 @@
<svg width="20" height="14" xmlns="http://www.w3.org/2000/svg"><path d="M2.152 1.494V4.98h4.646V1.494c0-.498.097-.871.293-1.12A.934.934 0 0 1 7.863 0c.324 0 .586.123.786.37.2.246.301.62.301 1.124v9.588c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378c-.194-.251-.29-.626-.29-1.124V6.989H2.152v4.093c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378C.097 11.955 0 11.58 0 11.082V1.494C0 .996.095.623.286.374A.929.929 0 0 1 1.066 0c.323 0 .585.123.786.37.2.246.3.62.3 1.124zm13.003 10.09v-1.252h-3.38c-.427 0-.746-.097-.96-.29-.213-.193-.32-.456-.32-.788 0-.085.016-.171.048-.259.031-.088.078-.18.141-.276.063-.097.128-.19.195-.28.068-.09.15-.2.25-.33l3.568-4.774a5.44 5.44 0 0 1 .576-.683.763.763 0 0 1 .542-.212c.682 0 1.023.39 1.023 1.171v5.212h.29c.346 0 .623.047.832.142.208.094.313.3.313.62 0 .26-.086.45-.256.568-.17.12-.427.179-.768.179h-.41v1.252c0 .346-.077.603-.23.771-.152.168-.356.253-.612.253a.78.78 0 0 1-.61-.26c-.154-.173-.232-.427-.232-.764zm-2.895-2.76h2.895V4.91L12.26 8.823z"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@ -0,0 +1 @@
<svg width="18" height="14" xmlns="http://www.w3.org/2000/svg"><path d="M2.152 1.494V4.98h4.646V1.494c0-.498.097-.871.293-1.12A.934.934 0 0 1 7.863 0c.324 0 .586.123.786.37.2.246.301.62.301 1.124v9.588c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378c-.194-.251-.29-.626-.29-1.124V6.989H2.152v4.093c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378C.097 11.955 0 11.58 0 11.082V1.494C0 .996.095.623.286.374A.929.929 0 0 1 1.066 0c.323 0 .585.123.786.37.2.246.3.62.3 1.124zm14.16 2.645h-3.234l-.388 2.205c.644-.344 1.239-.517 1.783-.517.436 0 .843.082 1.222.245.38.164.712.39.998.677.286.289.51.63.674 1.025.163.395.245.82.245 1.273 0 .658-.148 1.257-.443 1.797-.295.54-.72.97-1.276 1.287-.556.318-1.197.477-1.923.477-.813 0-1.472-.15-1.978-.45-.506-.3-.865-.643-1.076-1.031-.21-.388-.316-.727-.316-1.018 0-.177.073-.345.22-.504a.725.725 0 0 1 .556-.238c.381 0 .665.22.85.66.182.404.427.719.736.943.309.225.654.337 1.035.337.35 0 .656-.09.919-.272.263-.182.466-.431.61-.749.142-.318.214-.678.214-1.082 0-.436-.078-.808-.232-1.117a1.607 1.607 0 0 0-.62-.69 1.674 1.674 0 0 0-.864-.229c-.39 0-.67.048-.837.143-.168.095-.41.262-.725.5-.316.239-.576.358-.78.358a.843.843 0 0 1-.592-.242c-.173-.16-.259-.344-.259-.548 0-.022.025-.177.075-.463l.572-3.26c.063-.39.181-.675.354-.852.172-.177.454-.265.844-.265h3.595c.708 0 1.062.27 1.062.81a.711.711 0 0 1-.26.572c-.172.145-.426.218-.762.218z"/></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

@ -0,0 +1 @@
<svg width="18" height="14" xmlns="http://www.w3.org/2000/svg"><path d="M2.152 1.494V4.98h4.646V1.494c0-.498.097-.871.293-1.12A.934.934 0 0 1 7.863 0c.324 0 .586.123.786.37.2.246.301.62.301 1.124v9.588c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378c-.194-.251-.29-.626-.29-1.124V6.989H2.152v4.093c0 .503-.101.88-.304 1.128a.964.964 0 0 1-.783.374.928.928 0 0 1-.775-.378C.097 11.955 0 11.58 0 11.082V1.494C0 .996.095.623.286.374A.929.929 0 0 1 1.066 0c.323 0 .585.123.786.37.2.246.3.62.3 1.124zM12.53 7.058a3.093 3.093 0 0 1 1.004-.814 2.734 2.734 0 0 1 1.214-.264c.43 0 .827.08 1.19.24.365.161.684.39.957.686.274.296.485.645.635 1.048a3.6 3.6 0 0 1 .223 1.262c0 .637-.145 1.216-.437 1.736-.292.52-.699.926-1.221 1.218-.522.292-1.114.438-1.774.438-.76 0-1.416-.186-1.967-.557-.552-.37-.974-.919-1.265-1.645-.292-.726-.438-1.613-.438-2.662 0-.855.088-1.62.265-2.293.176-.674.43-1.233.76-1.676.33-.443.73-.778 1.2-1.004.47-.226 1.006-.339 1.608-.339.579 0 1.089.113 1.53.34.44.225.773.506.997.84.224.335.335.656.335.964 0 .185-.07.354-.21.505a.698.698 0 0 1-.536.227.874.874 0 0 1-.529-.18 1.039 1.039 0 0 1-.36-.498 1.42 1.42 0 0 0-.495-.655 1.3 1.3 0 0 0-.786-.247c-.24 0-.479.069-.716.207a1.863 1.863 0 0 0-.6.56c-.33.479-.525 1.333-.584 2.563zm1.832 4.213c.456 0 .834-.186 1.133-.56.298-.373.447-.862.447-1.468 0-.412-.07-.766-.21-1.062a1.584 1.584 0 0 0-.577-.678 1.47 1.47 0 0 0-.807-.234c-.28 0-.548.074-.804.224-.255.149-.461.365-.617.647a2.024 2.024 0 0 0-.234.994c0 .61.158 1.12.475 1.527.316.407.714.61 1.194.61z"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -1,45 +0,0 @@
<template>
<ol>
<li :key="n" v-for="n in amount" :style="style"> </li>
</ol>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'
@Component
export default class DeckCardBoxes extends Vue {
@Prop() public readonly params!: string[]
private get amount () {
return parseInt(this.params[0], 10)
}
private get style () {
const size = parseFloat(this.params[1])
return {
width: `calc(${size}em - 0.25em)`,
height: `calc(${size}em - 0.25em)`
}
}
}
</script>
<style scoped>
ol {
display: flex;
flex-flow: row wrap;
justify-content: flex-start;
list-style: none;
margin: 0 0 1em 0;
padding: 0;
}
ol > li {
display: inline-block;
width: 1rem;
height: 1rem;
font-size: 1rem;
margin: 0 .3em .3em 0;
border: 0.25em solid var(--highlight-color);
}
</style>

@ -1,51 +0,0 @@
<template>
<ul>
<li v-for="(param, i) in params"
:key="`param${i}`"
v-editable:[i]="editable"
@keydown="handleKey(i, $event)">
{{ param }}
</li>
</ul>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'
@Component
export default class DeckCardBulletList extends Vue {
@Prop() public readonly params!: string[]
@Prop() public readonly editable!: boolean
private addEntry (index: number) {
const newParams = [...this.params]
newParams.splice(index + 1, 0, '')
this.$emit('replace', newParams)
}
private removeEntry (index: number) {
const newParams = [...this.params]
newParams.splice(index, 1)
this.$emit('replace', newParams)
}
private handleKey (index: number, event: KeyboardEvent) {
const { key, shiftKey } = event
if (key === 'Enter' && shiftKey) {
event.preventDefault()
this.addEntry(index)
} else if (key === 'Backspace') {
const text = (event.target as HTMLElement).innerText
if (text.trim() === '') this.removeEntry(index)
}
}
}
</script>
<style scoped>
ul {
list-style-position: inside;
margin: 0;
padding-left: .5em;
}
</style>

@ -1,22 +0,0 @@
<template>
<p>
<span v-editable:0="editable" class="title">{{ params[0] }}</span>
<span v-editable:1="editable">{{ params[1] }}</span>
</p>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'
@Component
export default class DeckCardDescription extends Vue {
@Prop() public readonly params!: string[]
@Prop() public readonly editable!: boolean
}
</script>
<style scoped>
p { margin: 0; line-height: 1.2; }
p > .title { font-weight: bold; font-style: italic; }
p > .title::after { content: ' '; }
</style>

@ -1,73 +0,0 @@
<template>
<ol>
<li :key="titles[i]" v-for="(v, i) in params">
<span class="title">{{ titles[i] }}</span>
<span class="description">{{ v }} ({{ mod(v) }})</span>
</li>
</ol>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'
@Component
export default class DeckCardDndstats extends Vue {
@Prop() public readonly params!: string[]
private titles = ['STR', 'DEX', 'CON', 'INT', 'WIS', 'CHA']
private mod (v: number): string {
switch (v) {
case 1: return '-5'
case 2:
case 3: return '-4'
case 4:
case 5: return '-3'
case 6:
case 7: return '-2'
case 8:
case 9: return '-1'
case 10:
case 11: return '0'
case 12:
case 13: return '+1'
case 14:
case 15: return '+2'
case 16:
case 17: return '+3'
case 18:
case 19: return '+4'
case 20:
case 21: return '+5'
case 22:
case 23: return '+6'
case 24:
case 25: return '+7'
case 26:
case 27: return '+8'
case 28:
case 29: return '+9'
default: return '+10'
}
}
}
</script>
<style scoped>
ol {
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
list-style: none;
margin: 0;
padding: 0;
}
ol > li {
display: flex;
flex-flow: column nowrap;
justify-content: center;
font-size: 1rem;
text-align: center;
}
.title { font-weight: bold; }
</style>

@ -1,136 +0,0 @@
<template>
<menu class="menu-bar" :class="{ active }">
<button class="editor-button-bold" :class="{ active: value.bold }" @click="menuAction('bold')" />
<button class="editor-button-italic" :class="{ active: value.italic }" @click="menuAction('italic')" />
<button class="editor-button-paragraph" :class="{ active: value.paragraph }" @click="menuAction('paragraph')" />
<button class="editor-button-heading2" :class="{ active: value.heading2 }" @click="menuAction('heading2')" />
<button class="editor-button-heading3" :class="{ active: value.heading3 }" @click="menuAction('heading3')" />
<button class="editor-button-bullet-list" :class="{ active: value.bulletList }" @click="menuAction('bulletList')" />
<button class="editor-button-horizontal-rule" :class="{ active: value.separator}" @click="menuAction('separator')" />
<button class="editor-button-dropdown" :class="{ active: dropdownOpen }" @click="toggleDropdown" />
<div class="extended-menu" v-show="dropdownOpen">
<button class="extended-menu-button" @click="extMenuAction('statBlock')">Stat Block (DnD5e)</button>
<button class="extended-menu-button" @click="extMenuAction('boxes')">Empty Boxes</button>
</div>
</menu>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'
import { blocks, marks, State } from '@/editor'
@Component
export default class DeckCardEditorMenu extends Vue {
@Prop() public readonly active!: boolean
@Prop() public readonly value!: State
private dropdownOpen = false
private menuAction (name: string) {
const newState = { ...this.value }
if (blocks.indexOf(name) >= 0) { // blocks behave like radio buttons
blocks.forEach(block => {
newState[block] = false
})
newState[name] = true
} else if (marks.indexOf(name)) { // marks behave like checkboxes
newState[name] = !newState[name]
}
this.$emit('input', newState)
this.$emit('action', name)
}
private toggleDropdown () {
this.dropdownOpen = !this.dropdownOpen
this.$emit('action', 'refocus')
}
private extMenuAction (name: string) {
this.menuAction(name)
this.dropdownOpen = false
}
}
</script>
<style scoped>
.card-front > main > .menu-bar {
position: absolute;
width: 70%;
margin: -3rem 0 0 -1rem;
padding: .2rem 1rem;
visibility: hidden;
opacity: 0;
transition: opacity .3s .2s, visibility .3s .2s;
background-color: var(--highlight-color);
z-index: 2;
}
.card-front > main > .menu-bar.active {
opacity: 1.0;
visibility: visible;
}
.menu-bar > button {
position: relative;
width: 1.6rem;
height: 1.6rem;
margin: 0 .1rem;
background-color: #EEE;
background-position: center;
background-repeat: no-repeat;
background-size: 75%;
border: none;
border-radius: 0;
}
.menu-bar > button:after {
position: absolute;
top: 0;
left: 0;
height: 1.6rem;
width: 1.6rem;
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); }
.editor-button-heading2:after { content: 'H2'; }
.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'; }
.menu-bar > button.editor-button-dropdown {
width: 3.6rem;
}
.menu-bar > button.editor-button-dropdown:after {
content: ' more ';
width: 90%;
font-size: 1rem;
}
.extended-menu {
width: 100%;
height: 4rem;
padding-top: .5rem;
background: var(--highlight-color);
}
.extended-menu-button {
width: 97%;
height: 1.6rem;
margin: 0 .1rem;
background-color: #EEE;
color: black;
font-size: 1rem;
border: none;
}
</style>

@ -1,149 +1,48 @@
<template>
<main>
<deck-card-editor-menu
:active="contentInFocus"
@action="editorAction"
v-model="menuState"
/>
<div
ref="content"
class="card-content"
:contenteditable="active"
@focus="start"
@click="syncMenuStateIfFocussed"
@keyup="syncMenuStateOnKeyPress"
@blur="stop"
>
<h2>card content</h2>
<hr />
<p><b>foo:</b> boom</p>
<p><b>bar:</b> blam</p>
<hr />
<p>Some description maybe?</p>
</div>
</main>
<main :id="id" class="card-content"></main>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'
import DeckCardEditorMenu from '@/components/deck-card-editor-menu.vue'
import {
menuActionToCommand,
getActiveMarksAndBlocks,
State,
movementKeys,
controlSequenceKeys,
isRootNode,
isTextNode,
moveCaretToEOL
} from '@/editor'
@Component({
components: { DeckCardEditorMenu }
})
import Editor from '@editorjs/editorjs'
import List from '@editorjs/list'
import { Heading, Delimiter } from '@/editor'
@Component
export default class DeckCardEditor extends Vue {
@Prop() public readonly cardId!: string
@Prop() public readonly active!: boolean
@Prop() public readonly content!: Card['content']
private contentInFocus = false
private editor!: Editor
private defaultMenuState (): State {
return {
bold: false,
italic: false,
paragraph: true,
heading1: false,
heading2: false,
heading3: false,
bulletList: false,
spacer: false,
separator: false,
statBlock: false
}
private get id () {
return `${this.cardId}-editor`
}
private menuState = this.defaultMenuState()
private resetMenuState () {
this.menuState = this.defaultMenuState()
}
private setMenuState (marks: string[], block: string) {
this.resetMenuState()
marks.forEach(mark => { this.menuState[mark] = true })
if (block !== 'paragraph') {
this.menuState.paragraph = false
this.menuState[block] = true
}
}
private editorAction (action: string) {
const content = this.$refs.content as HTMLElement
content.focus()
const cmd = menuActionToCommand[action]
if (cmd) cmd()
this.$nextTick(() => this.syncMenuState())
}
private syncMenuState () {
const sel = window.getSelection()?.focusNode
if (!sel) return
const { marks, block } = getActiveMarksAndBlocks(sel as HTMLElement)
this.setMenuState(marks, block)
}
private syncMenuStateIfFocussed () {
if (this.contentInFocus) this.syncMenuState()
}
private syncMenuStateOnKeyPress (event: KeyboardEvent) {
// undo/redo/cut/paste
const isCtrlSq = event.ctrlKey && controlSequenceKeys.indexOf(event.key) >= 0
// arrow keys, enter, delete, etc
const isMove = movementKeys.indexOf(event.key) >= 0
if (isCtrlSq || isMove) {
return this.syncMenuState()
} else if (!event.ctrlKey && event.key.length === 1) {
// this should capture all normal typable letters and numbers
// TODO: this needs to be done on text pasting as well
// some browsers create bogus root level text nodes, so whenever
// something is typed in such a root level node, we simply wrap it with
// a paragraph
const sel = window.getSelection()?.focusNode
if (sel && isTextNode(sel) && isRootNode(sel.parentElement as HTMLElement)) {
console.debug(`Typed letter "${event.key} into root node, throwing a <p> at it!"`)
document.execCommand('formatblock', false, 'P')
// Firefox behaves nicely and leaves the caret alone after surrounding
// the text node with a <p>. Unlike Chromium that moves the caret to
// the beginning of the new paragraph. To mitigate that, we set the
// caret to end-of-line manually.
moveCaretToEOL()
}
}
}
private start () {
this.contentInFocus = true
this.syncMenuState()
// insert paragraphs instead of DIVs on enter
document.execCommand('defaultParagraphSeparator', false, 'p')
}
private stop () {
this.contentInFocus = false
private mounted () {
this.editor = new Editor({
holderId: this.id,
autofocus: false,
tools: {
header: Heading,
list: List,
delimiter: Delimiter
},
// data: {},
placeholder: 'Click here to write your card.'
})
}
}
</script>
<style>
.card-content p {
.card-content .cdx-block {
padding: 0;
}
.card-content .ce-paragraph, .card-content p {
margin: 0;
line-height: 1.2;
}
@ -174,19 +73,20 @@ export default class DeckCardEditor extends Vue {
border-bottom: 1px solid var(--highlight-color);
}
.card-content hr {
.card-content .card-delimiter {
height: 0;
margin: .2em 0;
padding: 0;
border: 2px solid var(--highlight-color);
}
.card-content hr.pointing-right {
.card-content .card-delimiter.pointing-right {
height: 0;
margin: .2em 0;
border-style: solid;
border-width: 2px 0 2px 220px;
border-color: transparent transparent transparent var(--highlight-color);
}
.card-content hr.pointing-left {
.card-content .card-delimiter.pointing-left {
height: 0;
margin: .2em 0;
border-style: solid;

@ -1,18 +0,0 @@
<template>
<div :style="{flex: params[0]}"> </div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'
@Component
export default class DeckCardFill extends Vue {
@Prop() public readonly params!: string[]
}
</script>
<style scoped>
div {
flex: 1;
}
</style>

@ -1,17 +0,0 @@
<template>
<p v-editable:0="editable">{{ params[0] }}</p>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'
@Component
export default class DeckCardNote extends Vue {
@Prop() public readonly params!: string[]
@Prop() public readonly editable!: boolean
}
</script>
<style scoped>
p { margin: 0 0 .5em 0; line-height: 1.2; font-style: italic; }
</style>

@ -1,22 +0,0 @@
<template>
<p>
<span class="title" v-editable:0="editable">{{ params[0] }}</span>
<span class="description" v-editable:1="editable">{{ params[1] }}</span>
</p>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'
@Component
export default class DeckCardProperty extends Vue {
@Prop() public readonly params!: string[]
@Prop() public readonly editable!: boolean
}
</script>
<style scoped>
p { margin: 0 0 0 1em; line-height: 1.2; text-indent: -1em; }
p > .title { font-weight: bold; }
p > .title::after { content: ' '; }
</style>

@ -1,34 +0,0 @@
<template>
<hr :class="params[0]" />
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'
@Component
export default class DeckCardRule extends Vue {
@Prop() public readonly params!: string[]
}
</script>
<style scoped>
hr {
height: 0;
margin: .2em 0;
border: 2px solid var(--highlight-color);
}
hr.pointing-right {
height: 0;
margin: .2em 0;
border-style: solid;
border-width: 2px 0 2px 220px;
border-color: transparent transparent transparent var(--highlight-color);
}
hr.pointing-left {
height: 0;
margin: .2em 0;
border-style: solid;
border-width: 2px 220px 2px 0;
border-color: transparent var(--highlight-color) transparent transparent;
}
</style>

@ -1,25 +0,0 @@
<template>
<h4 v-editable:0="editable">{{ params[0] }}</h4>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'
@Component
export default class DeckCardSection extends Vue {
@Prop() public readonly params!: string[]
@Prop() public readonly editable!: boolean
}
</script>
<style scoped>
h4 {
font-size: 1.4rem;
color: var(--highlight-color);
margin: 0 0 .2em 0;
font-weight: normal;
font-variant: small-caps;
line-height: .9em;
border-bottom: 1px solid var(--highlight-color);
}
</style>

@ -1,22 +0,0 @@
<template>
<h3 v-editable:0="editable">{{ params[0] }}</h3>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'
@Component
export default class DeckCardSubtitle extends Vue {
@Prop() public readonly params!: string[]
@Prop() public readonly editable!: boolean
}
</script>
<style scoped>
h3 {
font-size: 1.4rem;
color: var(--highlight-color);
margin: 0;
font-weight: normal;
}
</style>

@ -1,17 +0,0 @@
<template>
<p v-editable:0="editable">{{ params[0] }}</p>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'
@Component
export default class DeckCardText extends Vue {
@Prop() public readonly params!: string[]
@Prop() public readonly editable!: boolean
}
</script>
<style scoped>
p { margin: 0 0 .5em 0; line-height: 1.2; }
</style>

@ -10,14 +10,18 @@
<section name="card-front" class="card-front">
<header>
<h1 :contenteditable="isSelection"
@focus="selectLine"
@blur="editField('name', $event)"
@keypress.enter.prevent="editField('name', $event)">
{{ card.name }}
</h1>
<img :src="icon" />
</header>
<deck-card-editor :active="isSelection" :content="card.content" @input="$emit('edit', $event)" />
<deck-card-editor
:card-id="card.id"
:active="isSelection"
:content="card.content"
@input="$emit('edit', $event)"
/>
</section>
<section name="card-back" class="card-back">
<div class="icon-wrapper">
@ -33,7 +37,6 @@
import { Component, Prop, Vue } from 'vue-property-decorator'
import { cardWHtoStyle, iconPath } from '@/lib'
import DeckCardEditor from '@/components/deck-card-editor.vue'
import { selectLine } from '@/editor'
@Component({
components: { DeckCardEditor }
@ -104,10 +107,6 @@ export default class DeckCard extends Vue {
return style
}
private selectLine () {
selectLine()
}
}
</script>
@ -201,6 +200,7 @@ export default class DeckCard extends Vue {
border-radius: 1rem;
font-size: 1.2rem;
color: black;
overflow: hidden;
}
.card-back {

@ -0,0 +1,45 @@
import { BlockTool, BlockToolData, ToolConfig, ToolboxConfig, API } from '@editorjs/editorjs'
export interface BlockToolArgs {
api: API;
config: ToolConfig;
data?: BlockToolData;
}
export class BlockToolExt implements BlockTool {
protected api: API
protected _element: HTMLElement
protected _data: object
protected _config: ToolConfig
constructor ({ data, config, api }: BlockToolArgs) {
this.api = api
this._config = config
this._data = data || {}
this._element = this._render()
}
protected get _CSS (): { [key: string]: string } {
return { block: this.api.styles.block }
}
protected _render (): HTMLElement {
const el = document.createElement('DIV')
el.classList.add(this._CSS.block)
return el
}
render (): HTMLElement {
return this._element
}
save (_toolsContent: HTMLElement): object {
return {}
}
static get toolbox (): ToolboxConfig {
return { icon: '<svg></svg>', title: 'UnnamedPlugin' }
}
}
export default BlockToolExt

@ -1,33 +0,0 @@
import { getFocussedNode } from './node'
function applyRange (callback: (range: Range) => void) {
const range = document.createRange()
callback(range)
const sel = window.getSelection()
if (sel) {
sel.removeAllRanges()
sel.addRange(range)
}
}
function collapseRange (node: Node, toStart = false) {
applyRange(range => {
range.selectNode(node)
range.collapse(toStart)
})
}
export function moveCaretToBOL () {
const node = getFocussedNode()
if (node) collapseRange(node, true)
}
export function moveCaretToEOL () {
const node = getFocussedNode()
if (node) collapseRange(node, false)
}
export function selectLine () {
const node = getFocussedNode()
if (node) {
applyRange(range => range.selectNodeContents(node))
}
}

@ -1,41 +0,0 @@
export const movementKeys = [
'ArrowLeft',
'ArrowRight',
'ArrowUp',
'ArrowDown',
'Delete',
'Backspace',
'Enter',
'Home',
'End',
'PageUp',
'PageDown'
]
export const controlSequenceKeys = ['p', 'x', 'y', 'z', 'Z']
export const elementNameToMenuState: KV<string> = {
B: 'bold',
STRONG: 'bold',
I: 'italic',
EM: 'italic',
P: 'paragraph',
H1: 'heading1',
H2: 'heading2',
H3: 'heading3',
UL: 'bulletList',
OL: 'numberedList',
HR: 'separator'
}
export const marks = ['bold', 'italic']
export const blocks = [
'paragraph',
'heading1',
'heading2',
'heading3',
'bulletList',
'spacer',
'separator',
'statBlock'
]

@ -0,0 +1,29 @@
import { ToolConstructable } from '@editorjs/editorjs'
import BlockTool from './block-tool'
import icon from '../assets/editor/delimiter.svg.txt'
const title = 'Delimiter'
export class Delimiter extends BlockTool {
static get contentless () {
return true
}
protected get _CSS () {
return {
block: this.api.styles.block,
wrapper: 'card-delimiter'
}
}
protected _render (): HTMLElement {
const el = document.createElement('HR')
el.classList.add(this._CSS.wrapper, this._CSS.block)
return el
}
static get toolbox () {
return { icon, title }
}
}
export default Delimiter as ToolConstructable

@ -0,0 +1,202 @@
import { ToolConstructable, ToolConfig, HTMLPasteEvent } from '@editorjs/editorjs'
import { BlockToolExt, BlockToolArgs } from './block-tool'
import icon from '../assets/editor/header.svg.txt'
import icon1 from '../assets/editor/header1.svg.txt'
import icon2 from '../assets/editor/header2.svg.txt'
import icon3 from '../assets/editor/header3.svg.txt'
import icon4 from '../assets/editor/header4.svg.txt'
import icon5 from '../assets/editor/header5.svg.txt'
import icon6 from '../assets/editor/header6.svg.txt'
const title = 'Heading'
enum HeadingLevel {
One = 1,
Two = 2,
Three = 3,
Four = 4,
Five = 5,
Six = 6
}
interface HeaderConfig extends ToolConfig {
placeholder?: string;
levels?: HeadingLevel[];
defaultLevel?: HeadingLevel;
}
interface HeaderData {
text?: string;
level?: HeadingLevel;
}
class Heading extends BlockToolExt {
protected _config: HeaderConfig
protected settingsButtons: HTMLElement[] = []
private levels: HeadingLevel[]
private defaultLevel: HeadingLevel
private currentLevel: HeadingLevel
private icons: Map<HeadingLevel, string> = new Map([
[HeadingLevel.One, icon1],
[HeadingLevel.Two, icon2],
[HeadingLevel.Three, icon3],
[HeadingLevel.Four, icon4],
[HeadingLevel.Five, icon5],
[HeadingLevel.Six, icon6]
])
constructor (args: BlockToolArgs) {
super(args)
this._config = args.config as HeaderConfig
if (this._config.levels === undefined) {
this._config.levels = [HeadingLevel.Two, HeadingLevel.Three]
}
if (this._config.defaultLevel === undefined) {
this._config.defaultLevel = HeadingLevel.Two
}
if (this._config.levels.indexOf(this._config.defaultLevel) === -1) {
console.warn('(ง\'̀-\'́)ง Heading Tool: the default level specified was not found in available levels')
}
this.levels = this._config.levels
this.defaultLevel = this._config.defaultLevel
this.currentLevel = this.defaultLevel
// setting data will rerender the element with the right settings
this.data = {
level: this.currentLevel,
text: (args.data as HeaderData).text || ''
}
}
public renderSettings (): HTMLElement {
const wrapper = document.createElement('DIV')
this.levels.forEach(level => {
const { settingsButton, settingsButtonActive } = this._CSS
const btn = document.createElement('SPAN')
btn.classList.add(settingsButton)
btn.dataset.level = `${level}`
btn.innerHTML = this.icons.get(level) || icon
if (this.currentLevel === level) btn.classList.add(settingsButtonActive)
btn.addEventListener('click', () => this.setLevel(level))
wrapper.appendChild(btn)
this.settingsButtons.push(btn)
})
return wrapper
}
public get data (): HeaderData {
return this._data as HeaderData
}
public set data (data: HeaderData) {
const currentData = this._data as HeaderData
if (data.level === undefined) data.level = currentData.level || this.defaultLevel
if (data.text === undefined) data.text = currentData.text || ''
this._data = data
this.currentLevel = data.level
const newHeader = this._render()
if (this._element.parentNode) {
this._element.parentNode.replaceChild(newHeader, this._element)
}
this._element = newHeader
}
private setLevel (level: HeadingLevel) {
this.data = { level, text: this._element.innerHTML }
}
protected get _CSS () {
return {
block: this.api.styles.block,
settingsButton: this.api.styles.settingsButton,
settingsButtonActive: this.api.styles.settingsButtonActive,
wrapper: 'card-heading'
}
}
protected _render (): HTMLElement {
console.log('render', `H${this.currentLevel}`, this.data)
const el = document.createElement(`H${this.currentLevel}`)
el.innerHTML = this.data.text || ''
el.classList.add(this._CSS.wrapper, this._CSS.block)
el.contentEditable = 'true'
el.dataset.placeholder = this._config.placeholder || ''
return el
}
// Handle pasted H1-H6 tags to substitute with header tool
onPaste (event: HTMLPasteEvent) {
const content = event.detail.data
const text = content.innerHTML
let level = this.defaultLevel
const tagMatch = content.tagName.match(/H(\d)/)
if (tagMatch) level = parseInt(tagMatch[1], 10)
// Fallback to nearest level when specified not available
if (this._config.levels) {
level = this._config.levels.reduce((prevLevel, currLevel) => {
return Math.abs(currLevel - level) < Math.abs(prevLevel - level) ? currLevel : prevLevel
})
}
this.data = { level, text }
}
// Used by Editor.js paste handling API.
// Provides configuration to handle H1-H6 tags.
static get pasteConfig () {
return {
tags: ['H1', 'H2', 'H3', 'H4', 'H5', 'H6']
}
}
// Method that specified how to merge two Text blocks.
// Called by Editor.js by backspace at the beginning of the Block
public merge (data: HeaderData) {
this.data = {
text: this.data.text + (data.text || ''),
level: this.data.level
}
}
// validate text block data
validate (blockData: HeaderData): boolean {
if (!blockData.text) return false
return blockData.text.trim() !== ''
}
// extract tools data from view
save (toolsContent: HTMLElement): HeaderData {
return {
text: toolsContent.innerHTML,
level: this.currentLevel
}
}
// Allow Heading to be converted to/from other blocks
static get conversionConfig () {
return {
export: 'text', // use 'text' property for other blocks
import: 'text' // fill 'text' property from other block's export string
}
}
static get sanitize (): object {
return { level: {} }
}
static get toolbox () {
return { icon, title }
}
}
export default Heading as ToolConstructable

@ -1,81 +1,2 @@
import { elementNameToMenuState, marks, blocks } from './constants'
export {
isRootNode,
isRootChild,
isElementNode,
isTextNode,
isEmptyTextNode,
getFocussedNode
} from './node'
export {
moveCaretToBOL,
moveCaretToEOL,
selectLine
} from './caret'
export type State = KV<boolean>
export {
movementKeys,
controlSequenceKeys,
marks,
blocks
} from './constants'
function simpleAction (cmd: string, arg?: string): () => boolean {
return () => {
return document.execCommand(cmd, false, arg)
}
}
export const menuActionToCommand: KV<() => boolean> = {
paragraph: simpleAction('formatblock', 'P'),
heading1: simpleAction('formatblock', 'H1'),
heading2: simpleAction('formatblock', 'H2'),
heading3: simpleAction('formatblock', 'H3'),
bulletList: simpleAction('insertUnorderedList'),
numberedList: simpleAction('insertOrderedList'),
separator: simpleAction('insertHorizontalRule'),
bold: simpleAction('bold'),
italic: simpleAction('italic')
}
export function getActiveMarksAndBlocks (el: HTMLElement): {
marks: string[];
block: string;
} {
let activeBlock = 'paragraph'
const activeMarks: string[] = []
const focussedEl = el.nodeName === '#text' ? el.parentElement : el
if (!focussedEl) return { marks: activeMarks, block: activeBlock }
const focussedState = elementNameToMenuState[focussedEl.nodeName]
if (!focussedState) return { marks: activeMarks, block: activeBlock }
if (blocks.indexOf(focussedState) >= 0) {
activeBlock = focussedState
return { marks: activeMarks, block: activeBlock }
}
let wrappingEl = focussedEl.parentElement
let wrappingState: string
if (marks.indexOf(focussedState) >= 0) {
activeMarks.push(focussedState)
while (wrappingEl) {
wrappingState = elementNameToMenuState[wrappingEl.nodeName]
if (marks.indexOf(wrappingState) < 0) {
if (blocks.indexOf(wrappingState) >= 0) activeBlock = wrappingState
break
}
activeMarks.push(wrappingState)
wrappingEl = wrappingEl.parentElement
}
}
return { marks: activeMarks, block: activeBlock }
}
export { default as Delimiter } from './delimiter'
export { default as Heading } from './heading'

@ -1,20 +0,0 @@
const { TEXT_NODE, ELEMENT_NODE } = Node
export function getFocussedNode (): Node | null {
return window.getSelection()?.focusNode || null
}
export function isTextNode ({ nodeType }: Node): boolean {
return nodeType === TEXT_NODE
}
export function isElementNode ({ nodeType }: Node): boolean {
return nodeType === ELEMENT_NODE
}
export function isEmptyTextNode (node: Node): boolean {
return isTextNode(node) && (node as CharacterData).data === ''
}
export function isRootNode (node: Node): boolean {
return (node as HTMLElement).contentEditable === 'true'
}
export function isRootChild (node: Node): boolean {
// TODO: maybe use a data attribute or something for saver identification
return node.parentElement?.contentEditable === 'true'
}

12
src/modules.d.ts vendored

@ -0,0 +1,12 @@
declare module '*.vue' {
import Vue from 'vue'
export default Vue
}
declare module '*.txt' {
const content: string
export default content
}
declare module '@editorjs/paragraph'
declare module '@editorjs/list'

@ -1,4 +0,0 @@
declare module '*.vue' {
import Vue from 'vue'
export default Vue
}

@ -0,0 +1,10 @@
module.exports = {
chainWebpack: config => {
config.module
.rule('raw')
.test(/\.txt$/)
.use('raw-loader')
.loader('raw-loader')
.end()
}
}

@ -770,6 +770,19 @@
lodash "^4.17.13"
to-fast-properties "^2.0.0"
"@editorjs/editorjs@^2.17.0":
version "2.17.0"
resolved "https://registry.yarnpkg.com/@editorjs/editorjs/-/editorjs-2.17.0.tgz#38f20d7f99bc21868904b6b937905b6daad5a2a2"
integrity sha512-5rMjZLdiFOiUGESe5MZagvuVaLggORXBEolbbDLLVWHslR+r4+TACOXBcN8A6m9hMmnpHIJsC3442MZEWdNfQA==
dependencies:
codex-notifier "^1.1.2"
codex-tooltip "^1.0.0"
"@editorjs/list@^1.4.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@editorjs/list/-/list-1.4.0.tgz#e92459a8ac2305bc4385245e329c8b5c8437456a"
integrity sha512-iYDXGbVXvsAJbSxbjFMP4p7kS1zhQyNDqVNzkfMRhItulzKYlOMlFjTIGHqu5SxPy6NrcckhVFaWdfGDn5/gEA==
"@hapi/address@2.x.x":
version "2.1.4"
resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5"
@ -2359,6 +2372,16 @@ code-point-at@^1.0.0:
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
codex-notifier@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/codex-notifier/-/codex-notifier-1.1.2.tgz#a733079185f4c927fa296f1d71eb8753fe080895"
integrity sha512-DCp6xe/LGueJ1N5sXEwcBc3r3PyVkEEDNWCVigfvywAkeXcZMk9K41a31tkEFBW0Ptlwji6/JlAb49E3Yrxbtg==
codex-tooltip@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/codex-tooltip/-/codex-tooltip-1.0.0.tgz#720353b27fadc40f2d054d171479b016ffcb63ea"
integrity sha512-Wa/p/om166GVjg+q436BERBZZz3yvTnCDDzMV2kjKIzsUkj6vCWphTSTo+M0QJRfwODKzhXYaw8+S4EXPW6r0g==
collection-visit@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0"
@ -6234,11 +6257,6 @@ ora@^3.4.0:
strip-ansi "^5.2.0"
wcwidth "^1.0.1"
orderedmap@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/orderedmap/-/orderedmap-1.1.1.tgz#c618e77611b3b21d0fe3edc92586265e0059c789"
integrity sha512-3Ux8um0zXbVacKUkcytc0u3HgC0b0bBLT+I60r2J/En72cI0nZffqrA7Xtf2Hqs27j1g82llR5Mhbd0Z1XW4AQ==
original@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f"
@ -7004,13 +7022,6 @@ promise-inflight@^1.0.1:
resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=
prosemirror-model@1.8.2:
version "1.8.2"
resolved "https://registry.yarnpkg.com/prosemirror-model/-/prosemirror-model-1.8.2.tgz#c74eaacb0bbfea49b59a6d89fef5516181666a56"
integrity sha512-piffokzW7opZVCjf/9YaoXvTC0g7zMRWKJib1hpphPfC+4x6ZXe5CiExgycoWZJe59VxxP7uHX8aFiwg2i9mUQ==
dependencies:
orderedmap "^1.1.0"
proxy-addr@~2.0.5:
version "2.0.6"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf"
@ -7154,6 +7165,14 @@ raw-body@2.4.0:
iconv-lite "0.4.24"
unpipe "1.0.0"
raw-loader@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-4.0.0.tgz#d639c40fb9d72b5c7f8abc1fb2ddb25b29d3d540"
integrity sha512-iINUOYvl1cGEmfoaLjnZXt4bKfT2LJnZZib5N/LLyAphC+Dd11vNP9CNVb38j+SAJpFI1uo8j9frmih53ASy7Q==
dependencies:
loader-utils "^1.2.3"
schema-utils "^2.5.0"
read-pkg-up@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be"

Loading…
Cancel
Save