rpg-cards-ng/src/components/deck-card-editor.vue
koehr c64b9a95fb remove tiptap, start simple homegrown richtext functionalities
* menu now sends command events and button states
* menu state is synced with the elements at caret postion
* menu functionalities are not yet implemented
2020-05-25 11:38:18 +02:00

184 lines
4.1 KiB
Vue

<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>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'
import DeckCardEditorMenu from '@/components/deck-card-editor-menu.vue'
import {
elementNameToMenuState,
getElementAndParentName,
marks,
blocks,
State,
movementKeys,
controlSequenceKeys
} from '@/editor.ts'
@Component({
components: { DeckCardEditorMenu }
})
export default class DeckCardEditor extends Vue {
@Prop() public readonly active!: boolean
@Prop() public readonly content!: Card['content']
private contentInFocus = false
private menuState: State = {
bold: false,
italic: false,
paragraph: true,
heading1: false,
heading2: false,
heading3: false,
bulletList: false,
spacer: false,
separator: false,
statBlock: false
}
private clearMarks () {
marks.forEach(mark => {
this.menuState[mark] = false
})
}
private toggleBlock (name: string) {
blocks.forEach(block => {
this.menuState[block] = false
})
this.menuState[name] = true
}
private setMenuState (elementName: string, parentName?: string) {
const stateName = elementNameToMenuState[elementName]
// marks are always inside a block element
if (marks.indexOf(stateName) >= 0 && parentName) {
const parentStateName = elementNameToMenuState[parentName]
// marks are inclusive like checkboxes
this.menuState[stateName] = true
// but blocks are exclusive like radio buttons
this.toggleBlock(parentStateName)
} else {
this.clearMarks()
this.toggleBlock(stateName)
}
}
private editorAction (action: string) {
console.log('action', action)
// const content = this.$refs.content
}
private syncMenuState () {
const sel = window.getSelection()?.focusNode
if (!sel) return
const [elementName, parentName] = getElementAndParentName(sel)
console.log('focussed element', elementName, parentName)
if (!elementName) return
this.setMenuState(elementName, parentName)
}
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) this.syncMenuState()
}
private start () {
this.contentInFocus = true
this.syncMenuState()
}
private stop () {
this.contentInFocus = false
}
}
</script>
<style scoped>
.card-content p {
margin: 0;
line-height: 1.2;
}
.card-content ul {
list-style-position: inside;
margin: 0;
padding-left: .5em;
}
.card-content li > p {
display: inline;
}
.card-content h2 {
font-size: 1.4rem;
color: var(--highlight-color);
margin: 0;
font-weight: normal;
}
.card-content h3 {
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);
}
.card-content hr {
height: 0;
margin: .2em 0;
border: 2px solid var(--highlight-color);
}
.card-content 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);
}
.card-content 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;
}
[contenteditable="true"] { outline: none; }
</style>