diff --git a/src/__test__/integration/ui/toolbar.spec.ts b/src/__test__/integration/ui/toolbar.spec.ts index 0e36d6fe8d..3162958730 100644 --- a/src/__test__/integration/ui/toolbar.spec.ts +++ b/src/__test__/integration/ui/toolbar.spec.ts @@ -493,6 +493,8 @@ describe('custom button toolbar', () => { }); describe('custom toolbar element', () => { + const onUpdatedSpy = jest.fn(); + function createCustomItem() { const el = document.createElement('div'); @@ -503,6 +505,8 @@ describe('custom toolbar element', () => { el, name: 'myToolbar', tooltip: 'custom1!', + state: 'strong', + onUpdated: onUpdatedSpy, }; } @@ -541,6 +545,14 @@ describe('custom toolbar element', () => { }; } + function clickMarkdownWriteTab() { + document.querySelectorAll('.tab-item')![0].click(); + } + + function clickMarkdownPreviewTab() { + document.querySelectorAll('.tab-item')![1].click(); + } + const customItem = createCustomItem(); const customItemWithPopup = createCustomItemWithPopup(); @@ -550,6 +562,7 @@ describe('custom toolbar element', () => { container = document.createElement('div'); document.body.appendChild(container); + onUpdatedSpy.mockReset(); em = new EventEmitter(); destroy = render( @@ -557,7 +570,7 @@ describe('custom toolbar element', () => { html` <${Toolbar} eventEmitter=${em} - previewStyle="vertical" + previewStyle="tab" toolbarItems=${toolbarItems} editorType="markdown" /> @@ -605,6 +618,30 @@ describe('custom toolbar element', () => { expect(spy).toHaveBeenCalledWith('heading', { level: 3 }); }); + + it('should toggle active state properly when toolbar state is changed', () => { + em.emit('changeToolbarState', { toolbarState: { strong: true } }); + + expect(onUpdatedSpy).toHaveBeenCalledWith(expect.objectContaining({ active: true })); + + em.emit('changeToolbarState', { toolbarState: { strong: false } }); + + expect(onUpdatedSpy).toHaveBeenCalledWith(expect.objectContaining({ active: false })); + }); + + it('should toggle disabled state when changing markdown tab mode', () => { + em.emit('changePreviewStyle', 'tab'); + + // change markdown tab mode to preview tab + clickMarkdownPreviewTab(); + + expect(onUpdatedSpy).toHaveBeenCalledWith(expect.objectContaining({ disabled: true })); + + // change markdown tab mode to write tab + clickMarkdownWriteTab(); + + expect(onUpdatedSpy).toHaveBeenCalledWith(expect.objectContaining({ disabled: false })); + }); }); describe('API', () => { diff --git a/src/css/editor.css b/src/css/editor.css index 9c8607e217..1b39db4ce5 100644 --- a/src/css/editor.css +++ b/src/css/editor.css @@ -405,7 +405,7 @@ display: flex; } -.tui-toolbar-item-wrapper { +.toastui-editor-toolbar-item-wrapper { margin: 7px 5px; height: 32px; line-height: 32px; diff --git a/src/ui/components/layout.ts b/src/ui/components/layout.ts index 764f453ae9..efbaf04fe8 100644 --- a/src/ui/components/layout.ts +++ b/src/ui/components/layout.ts @@ -63,9 +63,9 @@ export class Layout extends Component { const { eventEmitter, hideModeSwitch, toolbarItems, theme } = this.props; const { hide, previewStyle, editorType } = this.state; const displayClassName = hide ? ' hidden' : ''; - const editorTypeClassName = editorType === 'markdown' ? cls('md-mode') : cls('ww-mode'); + const editorTypeClassName = cls(editorType === 'markdown' ? 'md-mode' : 'ww-mode'); const previewClassName = `${cls('md')}-${previewStyle}-style`; - const themeClassName = theme === 'light' ? '' : `${cls(theme)} `; + const themeClassName = cls([theme !== 'light', `${theme} `]); return html`
{ + return class ButtonHOC extends Component { + constructor(props: Props) { + super(props); + this.state = { active: false }; + this.addEvent(); + } + + private addEvent() { + const { item, eventEmitter } = this.props; + + if (item.state) { + eventEmitter.listen('changeToolbarState', ({ toolbarState }: Payload) => { + const active = !!toolbarState[item.state!]; + + this.setState({ active }); + }); + } + } + private getBound(el: HTMLElement) { const { offsetLeft, offsetTop } = getTotalOffset( el, @@ -56,6 +83,7 @@ export function connectHOC(WrappedComponent: ComponentClass) { return html` <${WrappedComponent} ...${this.props} + active=${this.state.active} showTooltip=${this.showTooltip} hideTooltip=${this.hideTooltip} getBound=${this.getBound} diff --git a/src/ui/components/toolbar/customToolbarItem.ts b/src/ui/components/toolbar/customToolbarItem.ts index 97a51c638f..44921d009a 100644 --- a/src/ui/components/toolbar/customToolbarItem.ts +++ b/src/ui/components/toolbar/customToolbarItem.ts @@ -10,7 +10,7 @@ import { import { Emitter } from '@t/event'; import html from '@/ui/vdom/template'; import { Component } from '@/ui/vdom/component'; -import { getOuterWidth } from '@/utils/dom'; +import { cls, getOuterWidth } from '@/utils/dom'; import { createPopupInfo } from '@/ui/toolbarItemFactory'; import { connectHOC } from './buttonHoc'; @@ -18,6 +18,7 @@ interface Props { disabled: boolean; eventEmitter: Emitter; item: ToolbarCustomOptions; + active: boolean; execCommand: ExecCommand; setPopupInfo: SetPopupInfo; showTooltip: ShowTooltip; @@ -42,6 +43,14 @@ class CustomToolbarItemComp extends Component { } } + updated(prevProps: Props) { + const { item, active, disabled } = this.props; + + if (prevProps.active !== active || prevProps.disabled !== disabled) { + item.onUpdated?.({ active, disabled }); + } + } + private showTooltip = () => { this.props.showTooltip(this.refs.el); }; @@ -59,16 +68,18 @@ class CustomToolbarItemComp extends Component { }; render() { - const style = { display: this.props.item.hidden ? 'none' : 'inline-block' }; + const { disabled, item } = this.props; + const style = { display: item.hidden ? 'none' : 'inline-block' }; + const getListener = (listener: Function) => (disabled ? null : listener); return html`
(this.refs.el = el)} style=${style} - class="tui-toolbar-item-wrapper" - onClick=${this.showPopup} - onMouseover=${this.showTooltip} - onMouseout=${this.props.hideTooltip} + class=${cls('toolbar-item-wrapper')} + onClick=${getListener(this.showPopup)} + onMouseover=${getListener(this.showTooltip)} + onMouseout=${getListener(this.props.hideTooltip)} >
`; } diff --git a/src/ui/components/toolbar/toolbarButton.ts b/src/ui/components/toolbar/toolbarButton.ts index 724ee9ade7..26cc421d95 100644 --- a/src/ui/components/toolbar/toolbarButton.ts +++ b/src/ui/components/toolbar/toolbarButton.ts @@ -1,7 +1,6 @@ import { ExecCommand, SetPopupInfo, - ToolbarState, SetItemWidth, GetBound, HideTooltip, @@ -15,14 +14,11 @@ import { createPopupInfo } from '@/ui/toolbarItemFactory'; import { getOuterWidth } from '@/utils/dom'; import { connectHOC } from './buttonHoc'; -interface Payload { - toolbarState: ToolbarState; -} - interface Props { disabled: boolean; eventEmitter: Emitter; item: ToolbarButtonInfo; + active: boolean; execCommand: ExecCommand; setPopupInfo: SetPopupInfo; showTooltip: ShowTooltip; @@ -31,29 +27,9 @@ interface Props { setItemWidth?: SetItemWidth; } -interface State { - active: boolean; -} - const DEFAULT_WIDTH = 80; -export class ToolbarButtonComp extends Component { - constructor(props: Props) { - super(props); - this.state = { active: false }; - this.addEvent(); - } - - addEvent() { - if (this.props.item.state) { - this.props.eventEmitter.listen('changeToolbarState', ({ toolbarState }: Payload) => { - const active = !!toolbarState[this.props.item.state!]; - - this.setState({ active }); - }); - } - } - +export class ToolbarButtonComp extends Component { mounted() { this.setItemWidth(); } @@ -98,9 +74,9 @@ export class ToolbarButtonComp extends Component { }; render() { - const { hideTooltip, disabled, item } = this.props; + const { hideTooltip, disabled, item, active } = this.props; const style = { display: item.hidden ? 'none' : null, ...item.style }; - const classNames = `${item.className || ''}${this.state.active ? ' active' : ''}`; + const classNames = `${item.className || ''}${active ? ' active' : ''}`; return html`