diff --git a/index.html b/index.html index 44a07f8..72c5e9e 100644 --- a/index.html +++ b/index.html @@ -122,6 +122,28 @@ diff --git a/src/assets/images/line-dash-dot-dot.svg b/src/assets/images/line-dash-dot-dot.svg new file mode 100644 index 0000000..30ab5ac --- /dev/null +++ b/src/assets/images/line-dash-dot-dot.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/line-dash-dot.svg b/src/assets/images/line-dash-dot.svg new file mode 100644 index 0000000..1958671 --- /dev/null +++ b/src/assets/images/line-dash-dot.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/line-dash-large-gap.svg b/src/assets/images/line-dash-large-gap.svg new file mode 100644 index 0000000..2e38e60 --- /dev/null +++ b/src/assets/images/line-dash-large-gap.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/line-dash-small-gap.svg b/src/assets/images/line-dash-small-gap.svg new file mode 100644 index 0000000..88d6082 --- /dev/null +++ b/src/assets/images/line-dash-small-gap.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/line-dot.svg b/src/assets/images/line-dot.svg new file mode 100644 index 0000000..c08b564 --- /dev/null +++ b/src/assets/images/line-dot.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/images/line-single.svg b/src/assets/images/line-single.svg new file mode 100644 index 0000000..453d4fa --- /dev/null +++ b/src/assets/images/line-single.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/editor/core/command/Command.ts b/src/editor/core/command/Command.ts index f434299..53415e2 100644 --- a/src/editor/core/command/Command.ts +++ b/src/editor/core/command/Command.ts @@ -235,8 +235,8 @@ export class Command { return Command.image(payload) } - public executeSeparator() { - return Command.separator() + public executeSeparator(payload: number[]) { + return Command.separator(payload) } public executeSearch(payload: string | null) { diff --git a/src/editor/core/command/CommandAdapt.ts b/src/editor/core/command/CommandAdapt.ts index 8e649fe..6fae94f 100644 --- a/src/editor/core/command/CommandAdapt.ts +++ b/src/editor/core/command/CommandAdapt.ts @@ -890,18 +890,32 @@ export class CommandAdapt { this.draw.render({ curIndex }) } - public separator() { + public separator(payload: number[]) { const { startIndex, endIndex } = this.range.getRange() if (!~startIndex && !~endIndex) return const elementList = this.draw.getElementList() - // 光标后是否存在分割线 - if (elementList[endIndex]?.type === ElementType.SEPARATOR) return - const element: IElement = { - value: '\n', - type: ElementType.SEPARATOR + let curIndex = -1 + // 光标存在分割线,则判断为修改线段逻辑 + const endElement = elementList[endIndex + 1] + if (endElement && endElement.type === ElementType.SEPARATOR) { + if (endElement.dashArray && endElement.dashArray.join() === payload.join()) return + curIndex = endIndex + endElement.dashArray = payload + } else { + const newElement: IElement = { + value: '\n', + type: ElementType.SEPARATOR, + dashArray: payload + } + // 从行头增加分割线 + if (startIndex !== 0 && elementList[startIndex].value === ZERO) { + elementList.splice(startIndex, 1, newElement) + curIndex = startIndex - 1 + } else { + elementList.splice(startIndex + 1, 0, newElement) + curIndex = startIndex + } } - const curIndex = startIndex + 1 - elementList.splice(curIndex, 0, element) this.range.setRange(curIndex, curIndex) this.draw.render({ curIndex }) } diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index 722386c..daa0967 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -104,7 +104,7 @@ export class Draw { this.pageNumber = new PageNumber(this) this.header = new Header(this) this.hyperlinkParticle = new HyperlinkParticle(this) - this.separatorParticle = new SeparatorParticle(this) + this.separatorParticle = new SeparatorParticle() this.superscriptParticle = new SuperscriptParticle() this.subscriptParticle = new SubscriptParticle() @@ -447,6 +447,7 @@ export class Draw { metrics.boundingBoxDescent = elementHeight metrics.boundingBoxAscent = 0 } else if (element.type === ElementType.SEPARATOR) { + element.width = innerWidth metrics.width = innerWidth metrics.height = this.options.defaultSize metrics.boundingBoxAscent = -rowMargin diff --git a/src/editor/core/draw/particle/Separator.ts b/src/editor/core/draw/particle/Separator.ts index 4eb5210..892a965 100644 --- a/src/editor/core/draw/particle/Separator.ts +++ b/src/editor/core/draw/particle/Separator.ts @@ -1,24 +1,18 @@ import { IRowElement } from "../../../interface/Row" -import { Draw } from "../Draw" - export class SeparatorParticle { - private draw: Draw - - constructor(draw: Draw) { - this.draw = draw - } - public render(ctx: CanvasRenderingContext2D, element: IRowElement, x: number, y: number) { - const innerWidth = this.draw.getInnerWidth() ctx.save() if (element.color) { ctx.strokeStyle = element.color } - ctx.translate(0.5, 0.5) + if (element.dashArray && element.dashArray.length) { + ctx.setLineDash(element.dashArray) + } + ctx.translate(0, 0.5) // 从1处渲染,避免线宽度等于3 ctx.beginPath() ctx.moveTo(x, y) - ctx.lineTo(x + innerWidth, y) + ctx.lineTo(x + element.width!, y) ctx.stroke() ctx.restore() } diff --git a/src/editor/core/range/RangeManager.ts b/src/editor/core/range/RangeManager.ts index 46dccde..80a068b 100644 --- a/src/editor/core/range/RangeManager.ts +++ b/src/editor/core/range/RangeManager.ts @@ -78,6 +78,7 @@ export class RangeManager { const highlight = curElement.highlight || null const rowFlex = curElement.rowFlex || null const rowMargin = curElement.rowMargin || this.options.defaultRowMargin + const dashArray = curElement.dashArray || [] // 菜单 const painter = !!this.draw.getPainterStyle() const undo = this.historyManager.isCanUndo() @@ -95,7 +96,8 @@ export class RangeManager { color, highlight, rowFlex, - rowMargin + rowMargin, + dashArray }) } @@ -119,7 +121,8 @@ export class RangeManager { color: null, highlight: null, rowFlex: null, - rowMargin + rowMargin, + dashArray: [] }) } diff --git a/src/editor/interface/Element.ts b/src/editor/interface/Element.ts index f0fdaa1..cc8a598 100644 --- a/src/editor/interface/Element.ts +++ b/src/editor/interface/Element.ts @@ -47,7 +47,16 @@ export interface ISuperscriptSubscript { actualSize?: number; } -export type IElement = IElementBasic & IElementStyle & ITable & IHyperlinkElement & ISuperscriptSubscript +export interface ISeparator { + dashArray?: number[]; +} + +export type IElement = IElementBasic + & IElementStyle + & ITable + & IHyperlinkElement + & ISuperscriptSubscript + & ISeparator export interface IElementMetrics { width: number; diff --git a/src/editor/interface/Listener.ts b/src/editor/interface/Listener.ts index 14d596a..1fa9dae 100644 --- a/src/editor/interface/Listener.ts +++ b/src/editor/interface/Listener.ts @@ -16,6 +16,7 @@ export interface IRangeStyle { highlight: string | null; rowFlex: RowFlex | null; rowMargin: number; + dashArray: number[]; } export type IRangeStyleChange = (payload: IRangeStyle) => void diff --git a/src/main.ts b/src/main.ts index 7ab6aff..0f4c6c4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -465,9 +465,22 @@ window.onload = function () { }) } const separatorDom = document.querySelector('.menu-item__separator')! + const separatorOptionDom = separatorDom.querySelector('.options')! separatorDom.onclick = function () { console.log('separator') - instance.command.executeSeparator() + separatorOptionDom.classList.toggle('visible') + } + separatorOptionDom.onmousedown = function (evt) { + let payload: number[] = [] + const li = evt.target as HTMLLIElement + const separatorDash = li.dataset.separator?.split(',').map(Number) + if (separatorDash) { + const isSingleLine = separatorDash.every(d => d === 0) + if (!isSingleLine) { + payload = separatorDash + } + } + instance.command.executeSeparator(payload) } const collspanDom = document.querySelector('.menu-item__search__collapse') const searchInputDom = document.querySelector('.menu-item__search__collapse__search input') @@ -510,11 +523,22 @@ window.onload = function () { // 控件类型 payload.type === ElementType.SUBSCRIPT ? subscriptDom.classList.add('active') : subscriptDom.classList.remove('active') payload.type === ElementType.SUPERSCRIPT ? superscriptDom.classList.add('active') : superscriptDom.classList.remove('active') + payload.type === ElementType.SEPARATOR ? separatorDom.classList.add('active') : separatorDom.classList.remove('active') + separatorOptionDom.querySelectorAll('li').forEach(li => li.classList.remove('active')) + if (payload.type === ElementType.SEPARATOR) { + const separator = payload.dashArray.join(',') || '0,0' + const curSeparatorDom = separatorOptionDom.querySelector(`[data-separator='${separator}']`)! + if (curSeparatorDom) { + curSeparatorDom.classList.add('active') + } + } // 富文本 - const curFontDom = fontOptionDom.querySelector(`[data-family=${payload.font}]`)! - fontSelectDom.innerText = curFontDom.innerText fontOptionDom.querySelectorAll('li').forEach(li => li.classList.remove('active')) - curFontDom.classList.add('active') + const curFontDom = fontOptionDom.querySelector(`[data-family=${payload.font}]`) + if (curFontDom) { + fontSelectDom.innerText = curFontDom.innerText + curFontDom.classList.add('active') + } payload.bold ? boldDom.classList.add('active') : boldDom.classList.remove('active') payload.italic ? italicDom.classList.add('active') : italicDom.classList.remove('active') payload.underline ? underlineDom.classList.add('active') : underlineDom.classList.remove('active') diff --git a/src/style.css b/src/style.css index 4637f8c..548f585 100644 --- a/src/style.css +++ b/src/style.css @@ -369,10 +369,50 @@ ul { background-image: url('./assets/images/hyperlink.svg'); } -.menu-item__separator i { +.menu-item__separator { + position: relative; +} + +.menu-item__separator>i { background-image: url('./assets/images/separator.svg'); } +.menu-item .menu-item__separator .options { + width: 128px; +} + +.menu-item .menu-item__separator li { + padding: 1px 5px; +} + +.menu-item__separator li i { + pointer-events: none; +} + +.menu-item__separator li[data-separator="0,0"] { + background-image: url('./assets/images/line-single.svg'); +} + +.menu-item__separator li[data-separator="1,1"] { + background-image: url('./assets/images/line-dot.svg'); +} + +.menu-item__separator li[data-separator="3,1"] { + background-image: url('./assets/images/line-dash-small-gap.svg'); +} + +.menu-item__separator li[data-separator="4,4"] { + background-image: url('./assets/images/line-dash-large-gap.svg'); +} + +.menu-item__separator li[data-separator="7,3,3,3"] { + background-image: url('./assets/images/line-dash-dot.svg'); +} + +.menu-item__separator li[data-separator="6,2,2,2,2,2"] { + background-image: url('./assets/images/line-dash-dot-dot.svg'); +} + .menu-item__search { position: relative; }