diff --git a/src/Kernel/Store/TextStore.ts b/src/Kernel/Store/TextStore.ts index a7dc47b..0fd0bb8 100644 --- a/src/Kernel/Store/TextStore.ts +++ b/src/Kernel/Store/TextStore.ts @@ -169,6 +169,10 @@ export class TextStore { history.exec(command) } + getAtoms(index: number, length: number) { + return this._slice(index, length) + } + toPlain(): string { return this._store.reduce((text, current) => text + current.text, '') } diff --git a/src/Kernel/index.ts b/src/Kernel/index.ts index 6d00366..9c121c5 100644 --- a/src/Kernel/index.ts +++ b/src/Kernel/index.ts @@ -3,11 +3,23 @@ import { HistoryManager } from './HistoryManager' import { Slide } from './Slide' import { TableBlock } from '@BlockHub/TableBlock/TableBlock' import { PictureBlock } from '@BlockHub/PictureBlock/PictureBlock' +import { EventManager } from './EventManager' +import { TextAtom } from './Store/TextStore' + +interface RichTextStateChange { + selection: { + index: number + length: number + } + atoms: Array +} export const kernel = { currentIndex: 0, slides: [new Slide([new TextBoxBlock(100, 50), new TableBlock(400, 300, 4, 3), new PictureBlock(100, 300)])], + richTextObserver: new EventManager(), + get currentSlide() { return this.slides[this.currentIndex] }, diff --git a/src/RichText/RichText.ts b/src/RichText/RichText.ts index 4b254c2..639670f 100644 --- a/src/RichText/RichText.ts +++ b/src/RichText/RichText.ts @@ -1,9 +1,12 @@ import type { AttributeValue, TextStore } from '@Kernel/Store/TextStore' -import { SelectionHandler } from './handler/SelectionHandler' +import { Selection, SelectionHandler } from './handler/SelectionHandler' import { EventHandler } from './handler/EventHandler' +import { EventManager } from '@Kernel/EventManager' +import { kernel } from '@Kernel/index' export interface RichTextController { format(name: string, value: AttributeValue): void + formatAll(name: string, value: AttributeValue): void } export class RichText { @@ -16,8 +19,19 @@ export class RichText { setSelectionByInput = this.selectionHandler.setSelectionByInput.bind(this.selectionHandler) setSelectionByNative = this.selectionHandler.setSelectionByNative.bind(this.selectionHandler) + events = { + selectChange: new EventManager(), + } + constructor(textStore: TextStore) { this.textStore = textStore + this.events.selectChange.on((selection) => { + const atoms = textStore.getAtoms(selection.index, selection.length) + kernel.richTextObserver.emit({ + selection: selection, + atoms: atoms, + }) + }) } mount(element: HTMLElement) { @@ -34,6 +48,14 @@ export class RichText { }) this.setSelectionByInput({ index, length }) }, + formatAll: (name: string, value: AttributeValue) => { + const index = 0 + const length = this.textStore.length + this.textStore.format(index, length, { + [name]: value, + }) + this.setSelectionByInput({ index, length }) + }, } } } diff --git a/src/RichText/handler/EventHandler.ts b/src/RichText/handler/EventHandler.ts index e4362a3..31054fc 100644 --- a/src/RichText/handler/EventHandler.ts +++ b/src/RichText/handler/EventHandler.ts @@ -3,6 +3,7 @@ import { handleInput } from './utils/handleInput' export class EventHandler { richText: RichText + _isCurrentElement: boolean = false _isComposing: boolean constructor(richText: RichText) { @@ -10,7 +11,23 @@ export class EventHandler { this._isComposing = false } + onSelectionChange() { + const range = document.getSelection()?.getRangeAt(0) as Range + this._isCurrentElement = range.intersectsNode(this.richText.element as HTMLElement) + if (!this._isCurrentElement) { + return + } + if (this._isComposing) { + // prevent modify inner selection by native selection change + return + } + this.richText.setSelectionByNative() + } + onBeforeInput(event: InputEvent) { + if (!this._isCurrentElement) { + return + } event.preventDefault() if (this._isComposing) { return @@ -23,10 +40,16 @@ export class EventHandler { } onCompositionStart() { + if (!this._isCurrentElement) { + return + } this._isComposing = true } onCompositionEnd(event: CompositionEvent) { + if (!this._isCurrentElement) { + return + } this._isComposing = false const data = event.data // https://github.com/w3c/input-events/issues/137 @@ -39,14 +62,6 @@ export class EventHandler { }) } - onSelectionChange() { - if (this._isComposing) { - // prevent modify inner selection by native selection change - return - } - this.richText.setSelectionByNative() - } - mount() { const element = this.richText.element as HTMLElement element.addEventListener('beforeinput', this.onBeforeInput.bind(this)) diff --git a/src/RichText/handler/SelectionHandler.ts b/src/RichText/handler/SelectionHandler.ts index d5923c7..622ac2e 100644 --- a/src/RichText/handler/SelectionHandler.ts +++ b/src/RichText/handler/SelectionHandler.ts @@ -64,12 +64,17 @@ export class SelectionHandler { nativeSelection?.addRange(range) } + private _setSelection(selection: Selection) { + this._selection = selection + this.richText.events.selectChange.emit(selection) + } + getSelection() { return { ...this._selection } } setSelectionByInput(selection: Selection) { - this._selection = selection + this._setSelection(selection) this._updateNativeSelection() } @@ -103,9 +108,16 @@ export class SelectionHandler { } currentIndex += 1 } - this._selection = { + + // avoid _setSelection redundantly + const { index: oldIndex, length: oldLength } = this.getSelection() + if (oldIndex === selectionIndex && oldLength === selectionLength) { + return + } + + this._setSelection({ index: selectionIndex, length: selectionLength, - } + }) } } diff --git a/src/UserInterface/ToolBar/components/Home/FontStyle.vue b/src/UserInterface/ToolBar/components/Home/FontStyle.vue index 53d84ab..f4bb9bd 100644 --- a/src/UserInterface/ToolBar/components/Home/FontStyle.vue +++ b/src/UserInterface/ToolBar/components/Home/FontStyle.vue @@ -1,6 +1,11 @@