Skip to content

Commit

Permalink
feat: different level format (#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
devlzl authored Jan 5, 2024
1 parent abe9388 commit 5a6a782
Show file tree
Hide file tree
Showing 12 changed files with 168 additions and 127 deletions.
2 changes: 1 addition & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"printWidth": 80,
"printWidth": 120,
"tabWidth": 2,
"useTabs": false,
"semi": false,
Expand Down
16 changes: 14 additions & 2 deletions src/BlockHub/Block/Block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { MapStore } from '@Kernel/Store/MapStore'
import { blockHub } from '../BlockHub'
import { RichTextController } from '@RichText/RichText'
import { AttributeValue } from '@Kernel/Store/TextStore'
import { intersectAttributes } from '@Utils/intersectAttributes'

export class Block {
static id = 0
Expand Down Expand Up @@ -77,13 +78,24 @@ export class Block {
this.props.set('rotate', rotate)
}

getBlockFormat() {
const controllers = Object.values(this.controllerMap)
const attributesList = controllers.map((controller) => controller.getCommonAttributes())
return intersectAttributes(attributesList)
}

formatBlock(name: string, value: AttributeValue) {
for (const controller of Object.values(this.controllerMap)) {
controller.formatAll(name, value)
controller.format(name, value)
}
}

getController(...params: any) {
getController(...params: any): RichTextController {
// should be overridden by subclass
return {
isFocus: () => false,
getCommonAttributes: () => ({}),
format: (name: string, value: AttributeValue) => {},
}
}
}
23 changes: 3 additions & 20 deletions src/BlockHub/TableBlock/Table.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { type TableBlock } from './TableBlock'
import RichText from '@RichText/RichText.vue'
import { type ArrayStore } from '@Kernel/Store/ArrayStore'
import type { AttributeValue, TextStore } from '@Kernel/Store/TextStore'
import type { TextStore } from '@Kernel/Store/TextStore'
import { shallowRef } from 'vue'
const { block } = defineProps<{
Expand All @@ -17,34 +17,17 @@ for (const row of block.data) {
}
tableData.value.push(rowData as Array<TextStore>)
}
function formatBlock(name: string, value: AttributeValue) {
block.formatBlock(name, value)
}
function format(name: string, value: AttributeValue) {
block.getController().format(name, value)
}
</script>

<template>
<div
class="table absolute"
class="table absolute border border-dashed border-secondary-border"
:style="{
left: `${block.x}px`,
top: `${block.y}px`,
padding: '30px',
}"
>
<button
class="border border-primary rounded-sm text-primary bg-white hover:bg-primary hover:text-white m-2 px-2"
@click="formatBlock('bold', true)"
>
bold block
</button>
<button class="border border-primary rounded-sm text-primary bg-white hover:bg-primary hover:text-white m-2 px-2" @click="format('bold', true)">
bold
</button>
<input type="color" @input="(event) => format('color', (event.target as HTMLInputElement).value)" />
<div class="row" v-for="(row, rowIndex) of tableData">
<div class="cell inline-block border w-[100px]" v-for="(cell, columnIndex) of row">
<RichText
Expand Down
64 changes: 1 addition & 63 deletions src/BlockHub/TextBoxBlock/TextBox.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<script setup lang="ts">
import { type AttributeValue } from '@Kernel/Store/TextStore'
import { type TextBoxBlock } from './TextBoxBlock'
import RichText from '@RichText/RichText.vue'
import { ref } from 'vue'
Expand All @@ -10,14 +9,6 @@ const { block } = defineProps<{
const { x, y, textStore, bindController } = block
function formatBlock(name: string, value: AttributeValue) {
block.formatBlock(name, value)
}
function format(name: string, value: AttributeValue) {
block.getController().format(name, value)
}
const width = ref(block.width)
const height = ref(block.height)
block.props.events.update.on(({ key, to }) => {
Expand All @@ -37,62 +28,9 @@ block.props.events.update.on(({ key, to }) => {
top: `${y}px`,
width: `${width}px`,
height: `${height}px`,
padding: '30px',
}"
>
<button
class="border border-primary rounded-sm text-primary bg-white hover:bg-primary hover:text-white m-2 px-2"
@click="formatBlock('bold', true)"
>
bold block
</button>
<button
class="border border-primary rounded-sm text-primary bg-white hover:bg-primary hover:text-white m-2 px-2"
@click="formatBlock('italic', true)"
>
italic block
</button>
<button
class="border border-primary rounded-sm text-primary bg-white hover:bg-primary hover:text-white m-2 px-2"
@click="formatBlock('underline', true)"
>
underline block
</button>
<button
class="border border-primary rounded-sm text-primary bg-white hover:bg-primary hover:text-white m-2 px-2"
@click="formatBlock('strike', true)"
>
strike block
</button>
<br />
<button class="border border-primary rounded-sm text-primary bg-white hover:bg-primary hover:text-white m-2 px-2" @click="format('bold', true)">
bold
</button>
<button class="border border-primary rounded-sm text-primary bg-white hover:bg-primary hover:text-white m-2 px-2" @click="format('italic', true)">
italic
</button>
<button
class="border border-primary rounded-sm text-primary bg-white hover:bg-primary hover:text-white m-2 px-2"
@click="format('underline', true)"
>
underline
</button>
<button class="border border-primary rounded-sm text-primary bg-white hover:bg-primary hover:text-white m-2 px-2" @click="format('strike', true)">
strike
</button>
<select @change="(event) => format('fontSize', (event.target as HTMLSelectElement).value)">
<option v-for="option in [14, 20, 30, 50, 100]" :value="option">
{{ option }}
</option>
</select>
<input type="color" @input="(event) => format('color', (event.target as HTMLInputElement).value)" />
<input type="color" @input="(event) => format('background', (event.target as HTMLInputElement).value)" />

<br />
width
<input type="range" value="500" min="0" max="1000" @input="(event) => (block.width = Number((event.target as HTMLInputElement).value))" /><br />
height
<input type="range" value="200" min="0" max="400" @input="(event) => (block.height = Number((event.target as HTMLInputElement).value))" /><br />

<RichText :textStore="textStore" :bindController="bindController" />
</div>
</template>
18 changes: 18 additions & 0 deletions src/Kernel/Store/TextStore.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { EventManager } from '@Kernel/EventManager'
import { Command } from '@Kernel/HistoryManager'
import { history } from '@Kernel/index'
import { intersectAttributes } from '@Utils/intersectAttributes'

export type AttributeValue = string | number | boolean
interface Attributes {
Expand All @@ -26,6 +27,18 @@ export class TextStore {
if (atom.text.length === 0) {
continue
}

if (atom.text === '\n') {
result.push(currentAtom)
currentAtom = atom
continue
}
if (currentAtom.text === '\n') {
result.push(currentAtom)
currentAtom = this._store[i]
continue
}

if (JSON.stringify(currentAtom.attributes) === JSON.stringify(atom.attributes)) {
currentAtom.text += atom.text
} else {
Expand Down Expand Up @@ -153,6 +166,11 @@ export class TextStore {
return structuredClone(this._store)
}

get commonAttributes() {
const attributesList = this.atoms.map((atom) => atom.attributes)
return intersectAttributes(attributesList)
}

insert(index: number, atom: TextAtom) {
const command = new Command(
() => this._insertAtom(index, atom),
Expand Down
37 changes: 27 additions & 10 deletions src/RichText/RichText.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import { EventManager } from '@Kernel/EventManager'
import { kernel } from '@Kernel/index'

export interface RichTextController {
isFocus(): boolean
getCommonAttributes(): { [key: string]: AttributeValue }
format(name: string, value: AttributeValue): void
formatAll(name: string, value: AttributeValue): void
}

export class RichText {
element?: HTMLElement
focus: boolean = false
textStore: TextStore
eventHandler = new EventHandler(this)
selectionHandler = new SelectionHandler(this)
Expand All @@ -21,6 +23,7 @@ export class RichText {

events = {
selectChange: new EventManager<Selection>(),
formatChange: new EventManager<Selection>(),
}

constructor(textStore: TextStore) {
Expand All @@ -32,6 +35,13 @@ export class RichText {
atoms: atoms,
})
})
this.events.formatChange.on((selection) => {
const atoms = textStore.getAtoms(selection.index, selection.length)
kernel.richTextObserver.emit({
selection: selection,
atoms: atoms,
})
})
}

mount(element: HTMLElement) {
Expand All @@ -41,19 +51,26 @@ export class RichText {

get controller(): RichTextController {
return {
format: (name: string, value: AttributeValue) => {
const { index, length } = this.getSelection()
this.textStore.format(index, length, {
[name]: value,
})
this.setSelectionByInput({ index, length })
isFocus: () => {
return this.focus
},
getCommonAttributes: () => {
return this.textStore.commonAttributes
},
formatAll: (name: string, value: AttributeValue) => {
const index = 0
const length = this.textStore.length
format: (name: string, value: AttributeValue) => {
const selectedLength = this.getSelection().length
let { index, length } = this.getSelection()
if (selectedLength === 0) {
index = 0
length = this.textStore.length
}
this.textStore.format(index, length, {
[name]: value,
})
if (selectedLength > 0) {
this.setSelectionByInput({ index, length })
}
this.events.formatChange.emit({ index, length })
},
}
}
Expand Down
7 changes: 6 additions & 1 deletion src/RichText/RichText.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,12 @@ history.events.update.on((eventType) => {
</script>

<template>
<div ref="richTextRef" contenteditable="true" class="rich-text h-full focus:outline-none whitespace-break-spaces" spellcheck="false">
<div
ref="richTextRef"
contenteditable="true"
class="rich-text h-full focus:outline-none whitespace-break-spaces"
spellcheck="false"
>
<Row v-for="row of rows" :atoms="row" />
</div>
</template>
3 changes: 2 additions & 1 deletion src/RichText/components/Atom.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ const { atom } = defineProps<{
:style="{
color: atom.attributes.color as string,
background: atom.attributes.background as string,
fontSize: `${atom.attributes.fontSize}px`
fontFamily: `${atom.attributes.fontFamily ?? 'sans-serif'}`,
fontSize: `${atom.attributes.fontSize ?? 16}px`
}"
>{{ atom.text }}</span
>
Expand Down
6 changes: 6 additions & 0 deletions src/RichText/handler/EventHandler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { type RichText } from '@RichText/RichText'
import { handleInput } from './utils/handleInput'
import { sleep } from '@Utils/sleep'

export class EventHandler {
richText: RichText
Expand Down Expand Up @@ -64,6 +65,11 @@ export class EventHandler {

mount() {
const element = this.richText.element as HTMLElement
element.addEventListener('focus', () => (this.richText.focus = true))
element.addEventListener('blur', async () => {
await sleep(100)
this.richText.focus = false
})
element.addEventListener('beforeinput', this.onBeforeInput.bind(this))
element.addEventListener('compositionstart', this.onCompositionStart.bind(this))
element.addEventListener('compositionend', this.onCompositionEnd.bind(this))
Expand Down
Loading

0 comments on commit 5a6a782

Please sign in to comment.