diff --git a/bbcode/Editor.vue b/bbcode/Editor.vue index c13b467b..536686e1 100644 --- a/bbcode/Editor.vue +++ b/bbcode/Editor.vue @@ -1,152 +1,183 @@ @@ -197,6 +228,9 @@ @Prop({ default: true }) readonly hasToolbar!: boolean; + @Prop({ default: 'bottom' }) + readonly toolbarPosition!: 'bottom' | 'top'; + @Prop({ default: false, type: Boolean }) readonly invalid!: boolean; @@ -237,6 +271,10 @@ protected parser!: BBCodeParser; protected defaultButtons = defaultButtons; + showOverflowMenu = false; + visibleButtonCount = 14; + isFocused = false; + private isShiftPressed = false; private undoStack: string[] = []; private undoIndex = 0; @@ -245,9 +283,77 @@ private awaitingNoMatch: boolean = false; private awaitingNoMatchTimer: number | null = null; private awaitingBuffer: string = ''; + // tslint:disable-next-line:no-any + private overflowResizeObserver: any = null; //tslint:disable:strict-boolean-expressions private resizeListener!: () => void; + get visibleButtons(): EditorButton[] { + return this.buttons.slice(0, this.visibleButtonCount); + } + + get overflowButtons(): EditorButton[] { + return this.buttons.slice(this.visibleButtonCount); + } + + toggleOverflowMenu(): void { + this.showOverflowMenu = !this.showOverflowMenu; + } + + closeOverflowMenu(event?: MouseEvent): void { + if (event) { + const trigger = this.$refs['overflowTrigger'] as HTMLElement; + if (trigger && trigger.contains(event.target as Node)) { + return; + } + } + this.showOverflowMenu = false; + } + + applyFromOverflow(button: EditorButton): void { + this.apply(button); + this.closeOverflowMenu(); + } + + private calculateVisibleButtons(): void { + if (!this.hasToolbar) return; + + const wrapper = this.$refs['toolbarButtonsWrapper'] as HTMLElement; + if (!wrapper) return; + + const wrapperWidth = wrapper.clientWidth; + const buttonWidth = 32; + const overflowBtnWidth = 32; + const previewBtnWidth = 32; + const characterBtnWidth = this.characterName ? 32 : 0; + const padding = 16; + + const totalButtons = this.buttons.length; + const availableWithoutOverflow = + wrapperWidth - previewBtnWidth - characterBtnWidth - padding; + const maxButtonsWithoutOverflow = Math.floor( + availableWithoutOverflow / buttonWidth + ); + + if (maxButtonsWithoutOverflow >= totalButtons) { + this.visibleButtonCount = totalButtons; + } else { + const availableWithOverflow = + wrapperWidth - + overflowBtnWidth - + previewBtnWidth - + characterBtnWidth - + padding; + const maxButtonsWithOverflow = Math.floor( + availableWithOverflow / buttonWidth + ); + this.visibleButtonCount = Math.max( + 4, + Math.min(maxButtonsWithOverflow, totalButtons - 1) + ); + } + } + private getMatches(prefix: string): string[] { const topRow = new Set(this.buttonColors.slice(0, 8)); return this.buttonColors @@ -321,6 +427,19 @@ this.resize(); window.addEventListener('resize', this.resizeListener); this.editorContainer = this.$refs['editorContainer'] as HTMLElement; + + this.$nextTick(() => { + this.calculateVisibleButtons(); + const wrapper = this.$refs['toolbarButtonsWrapper'] as HTMLElement; + // tslint:disable-next-line:no-any + const RO = (window as any).ResizeObserver; + if (wrapper && typeof RO !== 'undefined') { + this.overflowResizeObserver = new RO(() => { + this.calculateVisibleButtons(); + }); + this.overflowResizeObserver.observe(wrapper); + } + }); } //tslint:enable @@ -329,6 +448,10 @@ destroyed(): void { // console.log('EDITOR', 'destroyed'); window.removeEventListener('resize', this.resizeListener); + if (this.overflowResizeObserver) { + this.overflowResizeObserver.disconnect(); + this.overflowResizeObserver = null; + } } get finalClasses(): string | undefined { @@ -366,6 +489,31 @@ return btn; } + @Watch('toolbarPosition') + onToolbarPositionChanged(): void { + this.$nextTick(() => { + this.reattachResizeObserver(); + this.calculateVisibleButtons(); + }); + } + + private reattachResizeObserver(): void { + if (this.overflowResizeObserver) { + this.overflowResizeObserver.disconnect(); + } + const wrapper = this.$refs['toolbarButtonsWrapper'] as HTMLElement; + // tslint:disable-next-line:no-any + const RO = (window as any).ResizeObserver; + if (wrapper && typeof RO !== 'undefined') { + if (!this.overflowResizeObserver) { + this.overflowResizeObserver = new RO(() => { + this.calculateVisibleButtons(); + }); + } + this.overflowResizeObserver.observe(wrapper); + } + } + @Watch('value') watchValue(newValue: string): void { this.$nextTick(() => this.resize()); @@ -748,118 +896,324 @@ }