From 2bd1f34c15196968f69d4711613f1c81f1dade68 Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Tue, 23 Jul 2024 21:03:47 +0800 Subject: [PATCH 01/42] fix: word breaking when scaling the page #666 --- src/editor/core/draw/Draw.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index 4c77789..fb4a2c4 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -1573,8 +1573,9 @@ export class Draw { i ) // 单词宽度大于行可用宽度,无需折行 - if (width <= availableWidth) { - curRowWidth += width + const wordWidth = width * scale + if (wordWidth <= availableWidth) { + curRowWidth += wordWidth nextElement = endElement } } From ccd0627a6fad975acb43e9f16dda3fa13972c908 Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Wed, 24 Jul 2024 18:50:06 +0800 Subject: [PATCH 02/42] feat: delete cell contents when selecting rows and columns #706 --- .../core/event/handlers/keydown/backspace.ts | 39 ++++++++++++++++--- .../core/event/handlers/keydown/delete.ts | 36 ++++++++++++++--- 2 files changed, 65 insertions(+), 10 deletions(-) diff --git a/src/editor/core/event/handlers/keydown/backspace.ts b/src/editor/core/event/handlers/keydown/backspace.ts index 39058ac..57cf3ca 100644 --- a/src/editor/core/event/handlers/keydown/backspace.ts +++ b/src/editor/core/event/handlers/keydown/backspace.ts @@ -7,9 +7,30 @@ export function backspace(evt: KeyboardEvent, host: CanvasEvent) { // 可输入性验证 const rangeManager = draw.getRange() if (!rangeManager.getIsCanInput()) return + const { startIndex, endIndex, isCrossRowCol } = rangeManager.getRange() const control = draw.getControl() let curIndex: number | null - if (control.getActiveControl() && control.getIsRangeCanCaptureEvent()) { + if (isCrossRowCol) { + // 表格跨行列选中时清空单元格内容 + const rowCol = draw.getTableParticle().getRangeRowCol() + if (!rowCol) return + let isDeleted = false + for (let r = 0; r < rowCol.length; r++) { + const row = rowCol[r] + for (let c = 0; c < row.length; c++) { + const col = row[c] + if (col.value.length > 1) { + draw.spliceElementList(col.value, 1, col.value.length - 1) + isDeleted = true + } + } + } + // 删除成功后定位 + curIndex = isDeleted ? 0 : null + } else if ( + control.getActiveControl() && + control.getIsRangeCanCaptureEvent() + ) { // 光标在控件内 curIndex = control.keydown(evt) } else { @@ -18,7 +39,6 @@ export function backspace(evt: KeyboardEvent, host: CanvasEvent) { const cursorPosition = position.getCursorPosition() if (!cursorPosition) return const { index } = cursorPosition - const { startIndex, endIndex } = rangeManager.getRange() const isCollapsed = rangeManager.getIsCollapsed() const elementList = draw.getElementList() // 判断是否允许删除 @@ -51,8 +71,17 @@ export function backspace(evt: KeyboardEvent, host: CanvasEvent) { } curIndex = isCollapsed ? index - 1 : startIndex } - if (curIndex === null) return draw.getGlobalEvent().setCanvasEventAbility() - rangeManager.setRange(curIndex, curIndex) - draw.render({ curIndex }) + if (curIndex === null) { + rangeManager.setRange(startIndex, startIndex) + draw.render({ + curIndex: startIndex, + isSubmitHistory: false + }) + } else { + rangeManager.setRange(curIndex, curIndex) + draw.render({ + curIndex + }) + } } diff --git a/src/editor/core/event/handlers/keydown/delete.ts b/src/editor/core/event/handlers/keydown/delete.ts index c983ef2..28d2f4c 100644 --- a/src/editor/core/event/handlers/keydown/delete.ts +++ b/src/editor/core/event/handlers/keydown/delete.ts @@ -6,11 +6,28 @@ export function del(evt: KeyboardEvent, host: CanvasEvent) { // 可输入性验证 const rangeManager = draw.getRange() if (!rangeManager.getIsCanInput()) return - const { startIndex, endIndex } = rangeManager.getRange() + const { startIndex, endIndex, isCrossRowCol } = rangeManager.getRange() const elementList = draw.getElementList() const control = draw.getControl() let curIndex: number | null - if (control.getActiveControl() && control.getIsRangeWithinControl()) { + if (isCrossRowCol) { + // 表格跨行列选中时清空单元格内容 + const rowCol = draw.getTableParticle().getRangeRowCol() + if (!rowCol) return + let isDeleted = false + for (let r = 0; r < rowCol.length; r++) { + const row = rowCol[r] + for (let c = 0; c < row.length; c++) { + const col = row[c] + if (col.value.length > 1) { + draw.spliceElementList(col.value, 1, col.value.length - 1) + isDeleted = true + } + } + } + // 删除成功后定位 + curIndex = isDeleted ? 0 : null + } else if (control.getActiveControl() && control.getIsRangeWithinControl()) { // 光标在控件内 curIndex = control.keydown(evt) } else if (elementList[endIndex + 1]?.controlId) { @@ -42,8 +59,17 @@ export function del(evt: KeyboardEvent, host: CanvasEvent) { curIndex = isCollapsed ? index : startIndex } } - if (curIndex === null) return draw.getGlobalEvent().setCanvasEventAbility() - rangeManager.setRange(curIndex, curIndex) - draw.render({ curIndex }) + if (curIndex === null) { + rangeManager.setRange(startIndex, startIndex) + draw.render({ + curIndex: startIndex, + isSubmitHistory: false + }) + } else { + rangeManager.setRange(curIndex, curIndex) + draw.render({ + curIndex + }) + } } From e14fbd622d1076cba5f34bd4fa29530af4f70261 Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Wed, 24 Jul 2024 22:43:46 +0800 Subject: [PATCH 03/42] fix: set control highlight limit component type --- .../core/draw/control/interactive/ControlSearch.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/editor/core/draw/control/interactive/ControlSearch.ts b/src/editor/core/draw/control/interactive/ControlSearch.ts index 9d57287..f7090c8 100644 --- a/src/editor/core/draw/control/interactive/ControlSearch.ts +++ b/src/editor/core/draw/control/interactive/ControlSearch.ts @@ -1,3 +1,5 @@ +import { ZERO } from '../../../../dataset/constant/Common' +import { ControlComponent } from '../../../../dataset/enum/Control' import { ElementType } from '../../../../dataset/enum/Element' import { DeepRequired } from '../../../../interface/Common' import { @@ -85,7 +87,13 @@ export class ControlSearch { } i = newEndIndex // 高亮信息 - const controlElementList = elementList.slice(startIndex, newEndIndex) + const controlElementList = elementList + .slice(startIndex, newEndIndex) + .map(element => + element.controlComponent === ControlComponent.VALUE + ? element + : { value: ZERO } + ) const highlight = this.highlightList[highlightIndex] const { ruleList } = highlight for (let r = 0; r < ruleList.length; r++) { From 72686fda1bf12353699fd57273493d8a9b4caee4 Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Thu, 25 Jul 2024 20:11:06 +0800 Subject: [PATCH 04/42] fix: format of checkbox and radio control value --- src/editor/core/draw/control/Control.ts | 2 +- src/editor/core/event/handlers/mousedown.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/editor/core/draw/control/Control.ts b/src/editor/core/draw/control/Control.ts index 231a023..1efcc76 100644 --- a/src/editor/core/draw/control/Control.ts +++ b/src/editor/core/draw/control/Control.ts @@ -712,7 +712,7 @@ export class Control { } else if (type === ControlType.CHECKBOX) { const checkbox = new CheckboxControl(element, this) this.activeControl = checkbox - const codes = value?.split(',') || [] + const codes = value ? value.split(',') : [] checkbox.setSelect(codes, controlContext, controlRule) } else if (type === ControlType.RADIO) { const radio = new RadioControl(element, this) diff --git a/src/editor/core/event/handlers/mousedown.ts b/src/editor/core/event/handlers/mousedown.ts index b0ec76f..d3cc682 100644 --- a/src/editor/core/event/handlers/mousedown.ts +++ b/src/editor/core/event/handlers/mousedown.ts @@ -91,7 +91,7 @@ export function mousedown(evt: MouseEvent, host: CanvasEvent) { if (!control) { draw.getCheckboxParticle().setSelect(curElement) } else { - const codes = control?.code?.split(',') || [] + const codes = control?.code ? control.code.split(',') : [] if (checkbox?.value) { const codeIndex = codes.findIndex(c => c === checkbox.code) codes.splice(codeIndex, 1) From d939aa35ba7b19e5d1b5c4121fb873b2c579cc55 Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Thu, 25 Jul 2024 20:12:45 +0800 Subject: [PATCH 05/42] fix: highlight checkbox and radio control #707 --- src/editor/utils/element.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/editor/utils/element.ts b/src/editor/utils/element.ts index fded443..f271367 100644 --- a/src/editor/utils/element.ts +++ b/src/editor/utils/element.ts @@ -285,6 +285,7 @@ export function formatElementList( // checkbox组件 elementList.splice(i, 0, { ...controlContext, + ...controlDefaultStyle, controlId, value: '', type: el.type, @@ -332,6 +333,7 @@ export function formatElementList( // radio组件 elementList.splice(i, 0, { ...controlContext, + ...controlDefaultStyle, controlId, value: '', type: el.type, From 83cb47913195c43cde2b3ae344ff1d89a223e6a6 Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Thu, 25 Jul 2024 20:54:29 +0800 Subject: [PATCH 06/42] fix: update punctuation width when scaling the page #712 --- src/editor/core/draw/Draw.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index fb4a2c4..a3f851b 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -1580,10 +1580,11 @@ export class Draw { } } // 标点符号 - curRowWidth += this.textParticle.measurePunctuationWidth( + const punctuationWidth = this.textParticle.measurePunctuationWidth( ctx, nextElement ) + curRowWidth += punctuationWidth * scale } } // 列表信息 From b226566e2bf6f1b4bc3ae11577c7a96ae3cbf2d0 Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Fri, 26 Jul 2024 21:04:24 +0800 Subject: [PATCH 07/42] feat: set the container scrollbar to automatically scroll #711 --- src/editor/core/observer/SelectionObserver.ts | 82 ++++++++++++++----- 1 file changed, 63 insertions(+), 19 deletions(-) diff --git a/src/editor/core/observer/SelectionObserver.ts b/src/editor/core/observer/SelectionObserver.ts index 2ab8ab7..f617532 100644 --- a/src/editor/core/observer/SelectionObserver.ts +++ b/src/editor/core/observer/SelectionObserver.ts @@ -13,34 +13,65 @@ export class SelectionObserver { right: number ] = [70, 40, 10, 20] + private selectionContainer: Element | Document private rangeManager: RangeManager private requestAnimationFrameId: number | null private isMousedown: boolean private isMoving: boolean + private clientWidth: number + private clientHeight: number + private containerRect: DOMRect | null constructor(draw: Draw) { + this.rangeManager = draw.getRange() + // 优先使用配置的滚动容器dom + const { scrollContainerSelector } = draw.getOptions() + this.selectionContainer = scrollContainerSelector + ? document.querySelector(scrollContainerSelector) || document + : document this.requestAnimationFrameId = null this.isMousedown = false this.isMoving = false - this.rangeManager = draw.getRange() - + // 缓存尺寸 + this.clientWidth = 0 + this.clientHeight = 0 + this.containerRect = null + // 添加监听 this._addEvent() } private _addEvent() { - document.addEventListener('mousedown', this._mousedown) - document.addEventListener('mousemove', this._mousemove) - document.addEventListener('mouseup', this._mouseup) + const container = this.selectionContainer + container.addEventListener('mousedown', this._mousedown) + container.addEventListener('mousemove', this._mousemove) + container.addEventListener('mouseup', this._mouseup) + document.addEventListener('mouseleave', this._mouseup) } public removeEvent() { - document.removeEventListener('mousedown', this._mousedown) - document.removeEventListener('mousemove', this._mousemove) - document.removeEventListener('mouseup', this._mouseup) + const container = this.selectionContainer + container.removeEventListener('mousedown', this._mousedown) + container.removeEventListener('mousemove', this._mousemove) + container.removeEventListener('mouseup', this._mouseup) + document.removeEventListener('mouseleave', this._mouseup) } private _mousedown = () => { this.isMousedown = true + // 更新容器宽高 + this.clientWidth = + this.selectionContainer instanceof Document + ? document.documentElement.clientWidth + : this.selectionContainer.clientWidth + this.clientHeight = + this.selectionContainer instanceof Document + ? document.documentElement.clientHeight + : this.selectionContainer.clientHeight + // 更新容器位置信息 + if (!(this.selectionContainer instanceof Document)) { + const rect = this.selectionContainer.getBoundingClientRect() + this.containerRect = rect + } } private _mouseup = () => { @@ -50,16 +81,18 @@ export class SelectionObserver { private _mousemove = (evt: MouseEvent) => { if (!this.isMousedown || this.rangeManager.getIsCollapsed()) return - const { x, y } = evt - const clientWidth = document.documentElement.clientWidth - const clientHeight = document.documentElement.clientHeight + let { x, y } = evt + if (this.containerRect) { + x = x - this.containerRect.x + y = y - this.containerRect.y + } if (y < this.thresholdPoints[0]) { this._startMove(MoveDirection.UP) - } else if (clientHeight - y <= this.thresholdPoints[1]) { + } else if (this.clientHeight - y <= this.thresholdPoints[1]) { this._startMove(MoveDirection.DOWN) } else if (x < this.thresholdPoints[2]) { this._startMove(MoveDirection.LEFT) - } else if (clientWidth - x < this.thresholdPoints[3]) { + } else if (this.clientWidth - x < this.thresholdPoints[3]) { this._startMove(MoveDirection.RIGHT) } else { this._stopMove() @@ -67,16 +100,27 @@ export class SelectionObserver { } private _move(direction: MoveDirection) { - const x = window.scrollX - const y = window.scrollY + // Document使用window + const container = + this.selectionContainer instanceof Document + ? window + : this.selectionContainer + const x = + this.selectionContainer instanceof Document + ? window.scrollX + : (container).scrollLeft + const y = + this.selectionContainer instanceof Document + ? window.scrollY + : (container).scrollTop if (direction === MoveDirection.DOWN) { - window.scrollTo(x, y + this.step) + container.scrollTo(x, y + this.step) } else if (direction === MoveDirection.UP) { - window.scrollTo(x, y - this.step) + container.scrollTo(x, y - this.step) } else if (direction === MoveDirection.LEFT) { - window.scrollTo(x - this.step, y) + container.scrollTo(x - this.step, y) } else { - window.scrollTo(x + this.step, y) + container.scrollTo(x + this.step, y) } this.requestAnimationFrameId = window.requestAnimationFrame( this._move.bind(this, direction) From 46488fbc2a3975bf0146444ed624f39b5be01c82 Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Fri, 26 Jul 2024 21:09:32 +0800 Subject: [PATCH 08/42] release: v0.9.87 --- CHANGELOG.md | 22 ++++++++++++++++++++++ package.json | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96b0ab8..bdb46a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,25 @@ +## [0.9.87](https://github.com/Hufe921/canvas-editor/compare/v0.9.86...v0.9.87) (2024-07-26) + + +### Bug Fixes + +* format of checkbox and radio control value ([72686fd](https://github.com/Hufe921/canvas-editor/commit/72686fda1bf12353699fd57273493d8a9b4caee4)) +* highlight checkbox and radio control #707 ([d939aa3](https://github.com/Hufe921/canvas-editor/commit/d939aa35ba7b19e5d1b5c4121fb873b2c579cc55)), closes [#707](https://github.com/Hufe921/canvas-editor/issues/707) +* set control highlight limit component type ([e14fbd6](https://github.com/Hufe921/canvas-editor/commit/e14fbd622d1076cba5f34bd4fa29530af4f70261)) +* update punctuation width when scaling the page #712 ([83cb479](https://github.com/Hufe921/canvas-editor/commit/83cb47913195c43cde2b3ae344ff1d89a223e6a6)), closes [#712](https://github.com/Hufe921/canvas-editor/issues/712) +* word breaking when scaling the page #666 ([2bd1f34](https://github.com/Hufe921/canvas-editor/commit/2bd1f34c15196968f69d4711613f1c81f1dade68)), closes [#666](https://github.com/Hufe921/canvas-editor/issues/666) + + +### Features + +* add custom field to getValue api #699 ([67c63f8](https://github.com/Hufe921/canvas-editor/commit/67c63f856f2c0e3e8c0644e39694357639c18d7e)), closes [#699](https://github.com/Hufe921/canvas-editor/issues/699) +* delete cell contents when selecting rows and columns #706 ([ccd0627](https://github.com/Hufe921/canvas-editor/commit/ccd0627a6fad975acb43e9f16dda3fa13972c908)), closes [#706](https://github.com/Hufe921/canvas-editor/issues/706) +* optimize text selection at the beginning of a line #695 ([97ac2da](https://github.com/Hufe921/canvas-editor/commit/97ac2daaf8f0688b181cb8baea9ba74ae1664361)), closes [#695](https://github.com/Hufe921/canvas-editor/issues/695) +* set control properties in read-only mode #679 ([26a3468](https://github.com/Hufe921/canvas-editor/commit/26a3468f66d67bf8249cdc1a679c740d7cf1a9c9)), closes [#679](https://github.com/Hufe921/canvas-editor/issues/679) +* set the container scrollbar to automatically scroll #711 ([b226566](https://github.com/Hufe921/canvas-editor/commit/b226566e2bf6f1b4bc3ae11577c7a96ae3cbf2d0)), closes [#711](https://github.com/Hufe921/canvas-editor/issues/711) + + + ## [0.9.86](https://github.com/Hufe921/canvas-editor/compare/v0.9.85...v0.9.86) (2024-07-13) diff --git a/package.json b/package.json index 651ae56..0cd7e2d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@hufe921/canvas-editor", "author": "Hufe", "license": "MIT", - "version": "0.9.86", + "version": "0.9.87", "description": "rich text editor by canvas/svg", "publishConfig": { "registry": "https://registry.npmjs.org/", From c3ef2907ae31be21cd3bc61a5600ad381230034f Mon Sep 17 00:00:00 2001 From: Douglas Meirelles Date: Mon, 29 Jul 2024 07:51:10 -0300 Subject: [PATCH 09/42] chore: add touch support to signature component Co-authored-by: Hufe921 --- src/components/signature/Signature.ts | 31 ++++++++++++++++++++++++++ src/components/signature/signature.css | 4 ++++ 2 files changed, 35 insertions(+) diff --git a/src/components/signature/Signature.ts b/src/components/signature/Signature.ts index 465a2a9..f50dc1b 100644 --- a/src/components/signature/Signature.ts +++ b/src/components/signature/Signature.ts @@ -55,6 +55,10 @@ export class Signature { this.ctx.lineCap = 'round' this._bindEvent() this._clearUndoFn() + // this is necessary so that the screen does not move when moving - it is removed when closing the modal + document.documentElement.classList.add('overflow-hidden') + document.body.classList.add('overflow-hidden') + this.container.classList.add('overflow-hidden') } private _render() { @@ -166,6 +170,9 @@ export class Signature { this.canvas.onmousedown = this._startDraw.bind(this) this.canvas.onmousemove = this._draw.bind(this) this.container.onmouseup = this._stopDraw.bind(this) + this.container.ontouchmove = this.registerTouchmove.bind(this) + this.container.ontouchstart = this.registerTouchstart.bind(this) + this.container.ontouchend = this.registerTouchend.bind(this) } private _undo() { @@ -302,8 +309,32 @@ export class Signature { } } + private registerTouchmove(evt: TouchEvent) { + this.registerTouchEvent(evt, 'mousemove') + } + + private registerTouchstart(evt: TouchEvent) { + this.registerTouchEvent(evt, 'mousedown') + } + + private registerTouchend() { + const me = new MouseEvent('mouseup', {}) + this.canvas.dispatchEvent(me) + } + + private registerTouchEvent(evt: TouchEvent, eventName: string) { + const touch = evt.touches[0] + const me = new MouseEvent(eventName, { + clientX: touch.clientX, + clientY: touch.clientY + }) + this.canvas.dispatchEvent(me) + } + private _dispose() { this.mask.remove() this.container.remove() + document.documentElement.classList.remove('overflow-hidden') + document.body.classList.remove('overflow-hidden') } } diff --git a/src/components/signature/signature.css b/src/components/signature/signature.css index 287944c..e5e4d38 100644 --- a/src/components/signature/signature.css +++ b/src/components/signature/signature.css @@ -125,4 +125,8 @@ .signature-menu button[type='submit']:hover { background: #5b9cf3; border-color: #5b9cf3; +} + +.overflow-hidden { + overflow: hidden !important; } \ No newline at end of file From eea301eb11fdc3d5870da2408abccf4d96d8532f Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Mon, 29 Jul 2024 19:56:01 +0800 Subject: [PATCH 10/42] chore: update issue template --- .github/ISSUE_TEMPLATE/bug_report.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 1255468..79a5961 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -51,6 +51,12 @@ body: label: What is actually happening? validations: required: true + - type: textarea + id: editor-value + attributes: + label: Editor Value + description: Output of `instance.command.getValue()` + render: json - type: textarea id: system-info attributes: @@ -62,4 +68,4 @@ body: id: additional-comments attributes: label: Any additional comments? - description: e.g. some background/context of how you ran into this bug. \ No newline at end of file + description: e.g. some background/context of how you ran into this bug. From f5113f539c5b224fdb9af8cde8e61f632dc7e49f Mon Sep 17 00:00:00 2001 From: daria2023 <95205482+daria2023@users.noreply.github.com> Date: Tue, 30 Jul 2024 20:04:26 +0800 Subject: [PATCH 11/42] fix: float image position boundary error #716 Co-authored-by: Hufe921 --- src/editor/core/position/Position.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/editor/core/position/Position.ts b/src/editor/core/position/Position.ts index dbe0783..85eebd2 100644 --- a/src/editor/core/position/Position.ts +++ b/src/editor/core/position/Position.ts @@ -643,6 +643,7 @@ export class Position { payload: IGetFloatPositionByXYPayload ): ICurrentPosition | void { const { x, y } = payload + const currentPageNo = payload.pageNo ?? this.draw.getPageNo() const currentZone = this.draw.getZone().getZone() for (let f = 0; f < this.floatPositionList.length; f++) { const { @@ -653,9 +654,11 @@ export class Position { trIndex, tdIndex, tdValueIndex, - zone: floatElementZone + zone: floatElementZone, + pageNo } = this.floatPositionList[f] if ( + currentPageNo === pageNo && element.type === ElementType.IMAGE && element.imgDisplay === payload.imgDisplay && (!floatElementZone || floatElementZone === currentZone) From 3235e5ae386bd1f48f5f64427e1eb0ed3782838d Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Tue, 30 Jul 2024 20:15:21 +0800 Subject: [PATCH 12/42] feat: add cursor setting option to executeSetValue api #715 --- docs/en/guide/command-execute.md | 2 +- docs/guide/command-execute.md | 2 +- src/editor/core/command/CommandAdapt.ts | 5 +++-- src/editor/core/draw/Draw.ts | 17 ++++++++++++++--- src/editor/interface/Editor.ts | 4 ++++ 5 files changed, 23 insertions(+), 7 deletions(-) diff --git a/docs/en/guide/command-execute.md b/docs/en/guide/command-execute.md index 7b0cd77..f1a6d35 100644 --- a/docs/en/guide/command-execute.md +++ b/docs/en/guide/command-execute.md @@ -789,7 +789,7 @@ Feature: Set the editor data Usage: ```javascript -instance.command.executeSetValue(payload: Partial) +instance.command.executeSetValue(payload: Partial, options?: ISetValueOption) ``` ## executeRemoveControl diff --git a/docs/guide/command-execute.md b/docs/guide/command-execute.md index edd3da2..c62ad20 100644 --- a/docs/guide/command-execute.md +++ b/docs/guide/command-execute.md @@ -789,7 +789,7 @@ instance.command.executeUpdateElementById(payload: IUpdateElementByIdOption) 用法: ```javascript -instance.command.executeSetValue(payload: Partial) +instance.command.executeSetValue(payload: Partial, options?: ISetValueOption) ``` ## executeRemoveControl diff --git a/src/editor/core/command/CommandAdapt.ts b/src/editor/core/command/CommandAdapt.ts index a957f63..799ec0e 100644 --- a/src/editor/core/command/CommandAdapt.ts +++ b/src/editor/core/command/CommandAdapt.ts @@ -51,6 +51,7 @@ import { IEditorOption, IEditorResult, IEditorText, + ISetValueOption, IUpdateOption } from '../../interface/Editor' import { @@ -2301,8 +2302,8 @@ export class CommandAdapt { } } - public setValue(payload: Partial) { - this.draw.setValue(payload) + public setValue(payload: Partial, options?: ISetValueOption) { + this.draw.setValue(payload, options) } public removeControl() { diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index a3f851b..f5d5dfd 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -15,7 +15,8 @@ import { import { IEditorData, IEditorOption, - IEditorResult + IEditorResult, + ISetValueOption } from '../../interface/Editor' import { IElement, @@ -1034,9 +1035,10 @@ export class Draw { } } - public setValue(payload: Partial) { + public setValue(payload: Partial, options?: ISetValueOption) { const { header, main, footer } = deepClone(payload) if (!header && !main && !footer) return + const { isSetCursor = false } = options || {} const pageComponentData = [header, main, footer] pageComponentData.forEach(data => { if (!data) return @@ -1051,8 +1053,17 @@ export class Draw { }) // 渲染&计算&清空历史记录 this.historyManager.recovery() + const curIndex = isSetCursor + ? main?.length + ? main.length - 1 + : 0 + : undefined + if (curIndex !== undefined) { + this.range.setRange(curIndex, curIndex) + } this.render({ - isSetCursor: false, + curIndex, + isSetCursor, isFirstRender: true }) } diff --git a/src/editor/interface/Editor.ts b/src/editor/interface/Editor.ts index ca1495b..0dcb176 100644 --- a/src/editor/interface/Editor.ts +++ b/src/editor/interface/Editor.ts @@ -117,3 +117,7 @@ export type IUpdateOption = Omit< | 'historyMaxRecordCount' | 'scrollContainerSelector' > + +export interface ISetValueOption { + isSetCursor?: boolean +} From 87a8dbea1596bcdee28edbb58b02fbe2a36e6c58 Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Thu, 1 Aug 2024 20:10:34 +0800 Subject: [PATCH 13/42] feat: add title disabled property #680 --- docs/en/guide/schema.md | 1 + docs/guide/schema.md | 1 + src/editor/core/command/CommandAdapt.ts | 45 ++++++++--------------- src/editor/core/draw/Draw.ts | 17 +++++++++ src/editor/core/draw/control/Control.ts | 11 +++++- src/editor/core/event/CanvasEvent.ts | 2 + src/editor/core/event/handlers/input.ts | 44 +++++++++++----------- src/editor/core/event/handlers/mouseup.ts | 5 ++- src/editor/core/event/handlers/paste.ts | 15 +++----- src/editor/interface/Title.ts | 1 + src/editor/utils/element.ts | 14 +++++-- 11 files changed, 89 insertions(+), 67 deletions(-) diff --git a/docs/en/guide/schema.md b/docs/en/guide/schema.md index e9fe133..3d65c3f 100644 --- a/docs/en/guide/schema.md +++ b/docs/en/guide/schema.md @@ -157,6 +157,7 @@ interface IElement { title?: { conceptId?: string; deletable?: boolean; + disabled?: boolean; }; // list listType?: ListType; diff --git a/docs/guide/schema.md b/docs/guide/schema.md index 11c5c55..64f04f8 100644 --- a/docs/guide/schema.md +++ b/docs/guide/schema.md @@ -157,6 +157,7 @@ interface IElement { title?: { conceptId?: string; deletable?: boolean; + disabled?: boolean; }; // 列表 listType?: ListType; diff --git a/src/editor/core/command/CommandAdapt.ts b/src/editor/core/command/CommandAdapt.ts index 799ec0e..a25c823 100644 --- a/src/editor/core/command/CommandAdapt.ts +++ b/src/editor/core/command/CommandAdapt.ts @@ -300,15 +300,13 @@ export class CommandAdapt { } public applyPainterStyle() { - const isDisabled = - this.draw.isReadonly() || this.control.getIsDisabledControl() + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled() if (isDisabled) return this.canvasEvent.applyPainterStyle() } public format() { - const isDisabled = - this.draw.isReadonly() || this.control.getIsDisabledControl() + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled() if (isDisabled) return const selection = this.range.getSelectionElementList() // 选区设置或设置换行处样式 @@ -336,8 +334,7 @@ export class CommandAdapt { } public font(payload: string) { - const isDisabled = - this.draw.isReadonly() || this.control.getIsDisabledControl() + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled() if (isDisabled) return const selection = this.range.getSelectionElementList() if (selection?.length) { @@ -359,8 +356,7 @@ export class CommandAdapt { public size(payload: number) { const { minSize, maxSize, defaultSize } = this.options if (payload < minSize || payload > maxSize) return - const isDisabled = - this.draw.isReadonly() || this.control.getIsDisabledControl() + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled() if (isDisabled) return // 选区设置或设置换行处样式 let renderOption: IDrawOption = {} @@ -396,8 +392,7 @@ export class CommandAdapt { } public sizeAdd() { - const isDisabled = - this.draw.isReadonly() || this.control.getIsDisabledControl() + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled() if (isDisabled) return const selection = this.range.getTextLikeSelectionElementList() // 选区设置或设置换行处样式 @@ -436,8 +431,7 @@ export class CommandAdapt { } public sizeMinus() { - const isDisabled = - this.draw.isReadonly() || this.control.getIsDisabledControl() + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled() if (isDisabled) return const selection = this.range.getTextLikeSelectionElementList() // 选区设置或设置换行处样式 @@ -476,8 +470,7 @@ export class CommandAdapt { } public bold() { - const isDisabled = - this.draw.isReadonly() || this.control.getIsDisabledControl() + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled() if (isDisabled) return const selection = this.range.getSelectionElementList() if (selection?.length) { @@ -498,8 +491,7 @@ export class CommandAdapt { } public italic() { - const isDisabled = - this.draw.isReadonly() || this.control.getIsDisabledControl() + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled() if (isDisabled) return const selection = this.range.getSelectionElementList() if (selection?.length) { @@ -520,8 +512,7 @@ export class CommandAdapt { } public underline(textDecoration?: ITextDecoration) { - const isDisabled = - this.draw.isReadonly() || this.control.getIsDisabledControl() + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled() if (isDisabled) return const selection = this.range.getSelectionElementList() if (selection?.length) { @@ -559,8 +550,7 @@ export class CommandAdapt { } public strikeout() { - const isDisabled = - this.draw.isReadonly() || this.control.getIsDisabledControl() + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled() if (isDisabled) return const selection = this.range.getSelectionElementList() if (selection?.length) { @@ -584,8 +574,7 @@ export class CommandAdapt { } public superscript() { - const isDisabled = - this.draw.isReadonly() || this.control.getIsDisabledControl() + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled() if (isDisabled) return const selection = this.range.getSelectionElementList() if (!selection) return @@ -614,8 +603,7 @@ export class CommandAdapt { } public subscript() { - const isDisabled = - this.draw.isReadonly() || this.control.getIsDisabledControl() + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled() if (isDisabled) return const selection = this.range.getSelectionElementList() if (!selection) return @@ -644,8 +632,7 @@ export class CommandAdapt { } public color(payload: string | null) { - const isDisabled = - this.draw.isReadonly() || this.control.getIsDisabledControl() + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled() if (isDisabled) return const selection = this.range.getSelectionElementList() if (selection?.length) { @@ -676,8 +663,7 @@ export class CommandAdapt { } public highlight(payload: string | null) { - const isDisabled = - this.draw.isReadonly() || this.control.getIsDisabledControl() + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled() if (isDisabled) return const selection = this.range.getSelectionElementList() if (selection?.length) { @@ -1802,8 +1788,7 @@ export class CommandAdapt { } public image(payload: IDrawImagePayload) { - const isDisabled = - this.draw.isReadonly() || this.control.getIsDisabledControl() + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled() if (isDisabled) return const { startIndex, endIndex } = this.range.getRange() if (!~startIndex && !~endIndex) return diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index f5d5dfd..5c1d311 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -317,6 +317,23 @@ export class Draw { } } + public isDisabled() { + const { startIndex, endIndex } = this.range.getRange() + const elementList = this.getElementList() + if (startIndex === endIndex) { + const startElement = elementList[startIndex] + const nextElement = elementList[startIndex + 1] + return !!( + (startElement?.title?.disabled && nextElement?.title?.disabled) || + (startElement?.control?.disabled && nextElement?.control?.disabled) + ) + } + const selectionElementList = elementList.slice(startIndex + 1, endIndex + 1) + return selectionElementList.some( + element => element.title?.disabled || element.control?.disabled + ) + } + public getOriginalWidth(): number { const { paperDirection, width, height } = this.options return paperDirection === PaperDirection.VERTICAL ? width : height diff --git a/src/editor/core/draw/control/Control.ts b/src/editor/core/draw/control/Control.ts index 1efcc76..4420246 100644 --- a/src/editor/core/draw/control/Control.ts +++ b/src/editor/core/draw/control/Control.ts @@ -208,7 +208,16 @@ export class Control { } public getIsDisabledControl(): boolean { - return !!this.activeControl?.getElement().control?.disabled + if (!this.activeControl) return false + const { startIndex, endIndex } = this.range.getRange() + if (startIndex === endIndex) { + const elementList = this.getElementList() + const startElement = elementList[startIndex] + if (startElement.controlComponent === ControlComponent.POSTFIX) { + return false + } + } + return !!this.activeControl.getElement()?.control?.disabled } public getContainer(): HTMLDivElement { diff --git a/src/editor/core/event/CanvasEvent.ts b/src/editor/core/event/CanvasEvent.ts index d20598d..0dafcda 100644 --- a/src/editor/core/event/CanvasEvent.ts +++ b/src/editor/core/event/CanvasEvent.ts @@ -106,6 +106,8 @@ export class CanvasEvent { public applyPainterStyle() { const painterStyle = this.draw.getPainterStyle() if (!painterStyle) return + const isDisabled = this.draw.isReadonly() || this.draw.isDisabled() + if (isDisabled) return const selection = this.range.getSelection() if (!selection) return const painterStyleKeys = Object.keys(painterStyle) diff --git a/src/editor/core/event/handlers/input.ts b/src/editor/core/event/handlers/input.ts index c2abcdf..215d208 100644 --- a/src/editor/core/event/handlers/input.ts +++ b/src/editor/core/event/handlers/input.ts @@ -8,7 +8,7 @@ import { CanvasEvent } from '../CanvasEvent' export function input(data: string, host: CanvasEvent) { const draw = host.getDraw() - if (draw.isReadonly()) return + if (draw.isReadonly() || draw.isDisabled()) return const position = draw.getPosition() const cursorPosition = position.getCursorPosition() if (!data || !cursorPosition) return @@ -34,26 +34,28 @@ export function input(data: string, host: CanvasEvent) { const newElement: IElement = { value } - const nextElement = elementList[endIndex + 1] - if ( - !copyElement.type || - copyElement.type === TEXT || - (copyElement.type === HYPERLINK && nextElement?.type === HYPERLINK) || - (copyElement.type === DATE && nextElement?.type === DATE) || - (copyElement.type === SUBSCRIPT && nextElement?.type === SUBSCRIPT) || - (copyElement.type === SUPERSCRIPT && nextElement?.type === SUPERSCRIPT) - ) { - EDITOR_ELEMENT_COPY_ATTR.forEach(attr => { - // 在分组外无需复制分组信息 - if (attr === 'groupIds' && !nextElement?.groupIds) return - const value = copyElement[attr] as never - if (value !== undefined) { - newElement[attr] = value - } - }) - } - if (isComposing) { - newElement.underline = true + if (!copyElement.title?.disabled && !copyElement.control?.disabled) { + const nextElement = elementList[endIndex + 1] + if ( + !copyElement.type || + copyElement.type === TEXT || + (copyElement.type === HYPERLINK && nextElement?.type === HYPERLINK) || + (copyElement.type === DATE && nextElement?.type === DATE) || + (copyElement.type === SUBSCRIPT && nextElement?.type === SUBSCRIPT) || + (copyElement.type === SUPERSCRIPT && nextElement?.type === SUPERSCRIPT) + ) { + EDITOR_ELEMENT_COPY_ATTR.forEach(attr => { + // 在分组外无需复制分组信息 + if (attr === 'groupIds' && !nextElement?.groupIds) return + const value = copyElement[attr] as never + if (value !== undefined) { + newElement[attr] = value + } + }) + } + if (isComposing) { + newElement.underline = true + } } return newElement }) diff --git a/src/editor/core/event/handlers/mouseup.ts b/src/editor/core/event/handlers/mouseup.ts index 9b30448..23aac0d 100644 --- a/src/editor/core/event/handlers/mouseup.ts +++ b/src/editor/core/event/handlers/mouseup.ts @@ -48,7 +48,10 @@ export function mouseup(evt: MouseEvent, host: CanvasEvent) { // 判断是否允许拖放 if (host.isAllowDrop) { const draw = host.getDraw() - if (draw.isReadonly()) return + if (draw.isReadonly() || draw.isDisabled()) { + host.mousedown(evt) + return + } const position = draw.getPosition() const positionList = position.getPositionList() const positionContext = position.getPositionContext() diff --git a/src/editor/core/event/handlers/paste.ts b/src/editor/core/event/handlers/paste.ts index 0480eb1..6a26e67 100644 --- a/src/editor/core/event/handlers/paste.ts +++ b/src/editor/core/event/handlers/paste.ts @@ -17,8 +17,7 @@ import { IOverrideResult } from '../../override/Override' export function pasteElement(host: CanvasEvent, elementList: IElement[]) { const draw = host.getDraw() - const isReadonly = draw.isReadonly() - if (isReadonly) return + if (draw.isReadonly() || draw.isDisabled()) return const rangeManager = draw.getRange() const { startIndex } = rangeManager.getRange() const originalElementList = draw.getElementList() @@ -59,8 +58,7 @@ export function pasteElement(host: CanvasEvent, elementList: IElement[]) { export function pasteHTML(host: CanvasEvent, htmlText: string) { const draw = host.getDraw() - const isReadonly = draw.isReadonly() - if (isReadonly) return + if (draw.isReadonly() || draw.isDisabled()) return const elementList = getElementListByHTML(htmlText, { innerWidth: draw.getOriginalInnerWidth() }) @@ -69,8 +67,7 @@ export function pasteHTML(host: CanvasEvent, htmlText: string) { export function pasteImage(host: CanvasEvent, file: File | Blob) { const draw = host.getDraw() - const isReadonly = draw.isReadonly() - if (isReadonly) return + if (draw.isReadonly() || draw.isDisabled()) return const rangeManager = draw.getRange() const { startIndex } = rangeManager.getRange() const elementList = draw.getElementList() @@ -99,8 +96,7 @@ export function pasteImage(host: CanvasEvent, file: File | Blob) { export function pasteByEvent(host: CanvasEvent, evt: ClipboardEvent) { const draw = host.getDraw() - const isReadonly = draw.isReadonly() - if (isReadonly) return + if (draw.isReadonly() || draw.isDisabled()) return const clipboardData = evt.clipboardData if (!clipboardData) return // 自定义粘贴事件 @@ -157,8 +153,7 @@ export function pasteByEvent(host: CanvasEvent, evt: ClipboardEvent) { export async function pasteByApi(host: CanvasEvent, options?: IPasteOption) { const draw = host.getDraw() - const isReadonly = draw.isReadonly() - if (isReadonly) return + if (draw.isReadonly() || draw.isDisabled()) return // 自定义粘贴事件 const { paste } = draw.getOverride() if (paste) { diff --git a/src/editor/interface/Title.ts b/src/editor/interface/Title.ts index 1e97ac2..066aa6b 100644 --- a/src/editor/interface/Title.ts +++ b/src/editor/interface/Title.ts @@ -14,6 +14,7 @@ export type ITitleOption = ITitleSizeOption & {} export interface ITitleRule { deletable?: boolean + disabled?: boolean } export type ITitle = ITitleRule & { diff --git a/src/editor/utils/element.ts b/src/editor/utils/element.ts index f271367..fd30ba4 100644 --- a/src/editor/utils/element.ts +++ b/src/editor/utils/element.ts @@ -4,6 +4,7 @@ import { deepCloneOmitKeys, getUUID, isArrayEqual, + omitObject, pickObject, splitText } from '.' @@ -29,7 +30,8 @@ import { INLINE_NODE_NAME, TABLE_CONTEXT_ATTR, TABLE_TD_ZIP_ATTR, - TEXTLIKE_ELEMENT_TYPE + TEXTLIKE_ELEMENT_TYPE, + TITLE_CONTEXT_ATTR } from '../dataset/constant/Element' import { listStyleCSSMapping, @@ -818,8 +820,12 @@ export function formatElementContext( anchorIndex: number, options?: IFormatElementContextOption ) { - const copyElement = getAnchorElement(sourceElementList, anchorIndex) + let copyElement = getAnchorElement(sourceElementList, anchorIndex) if (!copyElement) return + // 标题元素禁用时不复制标题属性 + if (copyElement.title?.disabled) { + copyElement = omitObject(copyElement, TITLE_CONTEXT_ATTR) + } const { isBreakWhenWrap = false } = options || {} // 是否已经换行 let isBreakWarped = false @@ -839,9 +845,9 @@ export function formatElementContext( (!copyElement.listId && targetElement.type === ElementType.LIST) ) { const cloneAttr = [...TABLE_CONTEXT_ATTR, ...EDITOR_ROW_ATTR] - cloneProperty(cloneAttr, copyElement, targetElement) + cloneProperty(cloneAttr, copyElement!, targetElement) targetElement.valueList?.forEach(valueItem => { - cloneProperty(cloneAttr, copyElement, valueItem) + cloneProperty(cloneAttr, copyElement!, valueItem) }) continue } From 8d112a8518656edd14201cb9bd16a417752fcdf9 Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Fri, 2 Aug 2024 19:52:17 +0800 Subject: [PATCH 14/42] feat: add applyPageNumbers attribute to background option #729 --- docs/en/guide/option.md | 2 +- docs/guide/option.md | 2 +- src/editor/core/draw/frame/Background.ts | 11 ++++++++--- src/editor/dataset/constant/Background.ts | 3 ++- src/editor/interface/Background.ts | 1 + 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/docs/en/guide/option.md b/docs/en/guide/option.md index 1b27863..b2c6557 100644 --- a/docs/en/guide/option.md +++ b/docs/en/guide/option.md @@ -68,7 +68,7 @@ interface IEditorOption { group?: IGroup // Group option. {opacity?:number; backgroundColor?:string; activeOpacity?:number; activeBackgroundColor?:string; disabled?:boolean} pageBreak?: IPageBreak // PageBreak option。{font?:string; fontSize?:number; lineDash?:number[];} zone?: IZoneOption // Zone option。{tipDisabled?:boolean;} - background?: IBackgroundOption // Background option. {color?:string; image?:string; size?:BackgroundSize; repeat?:BackgroundRepeat;}。default: {color: '#FFFFFF'} + background?: IBackgroundOption // Background option. {color?:string; image?:string; size?:BackgroundSize; repeat?:BackgroundRepeat; applyPageNumbers?:number[]}。default: {color: '#FFFFFF'} lineBreak?: ILineBreakOption // LineBreak option. {disabled?:boolean; color?:string; lineWidth?:number;} separator?: ISeparatorOption // Separator option. {lineWidth?:number; strokeStyle?:string;} } diff --git a/docs/guide/option.md b/docs/guide/option.md index b3158e7..6ed887d 100644 --- a/docs/guide/option.md +++ b/docs/guide/option.md @@ -68,7 +68,7 @@ interface IEditorOption { group?: IGroup // 成组配置。{opacity?:number; backgroundColor?:string; activeOpacity?:number; activeBackgroundColor?:string; disabled?:boolean} pageBreak?: IPageBreak // 分页符配置。{font?:string; fontSize?:number; lineDash?:number[];} zone?: IZoneOption // 编辑器区域配置。{tipDisabled?:boolean;} - background?: IBackgroundOption // 背景配置。{color?:string; image?:string; size?:BackgroundSize; repeat?:BackgroundRepeat;}。默认:{color: '#FFFFFF'} + background?: IBackgroundOption // 背景配置。{color?:string; image?:string; size?:BackgroundSize; repeat?:BackgroundRepeat; applyPageNumbers?:number[]}。默认:{color: '#FFFFFF'} lineBreak?: ILineBreakOption // 换行符配置。{disabled?:boolean; color?:string; lineWidth?:number;} separator?: ISeparatorOption // 分隔符配置。{lineWidth?:number; strokeStyle?:string;} } diff --git a/src/editor/core/draw/frame/Background.ts b/src/editor/core/draw/frame/Background.ts index 232e307..a1e4641 100644 --- a/src/editor/core/draw/frame/Background.ts +++ b/src/editor/core/draw/frame/Background.ts @@ -99,14 +99,19 @@ export class Background { } public render(ctx: CanvasRenderingContext2D, pageNo: number) { - const { background } = this.options - if (background.image) { + const { + background: { image, color, applyPageNumbers } + } = this.options + if ( + image && + (!applyPageNumbers?.length || applyPageNumbers.includes(pageNo)) + ) { const { width, height } = this.options this._renderBackgroundImage(ctx, width, height) } else { const width = this.draw.getCanvasWidth(pageNo) const height = this.draw.getCanvasHeight(pageNo) - this._renderBackgroundColor(ctx, background.color, width, height) + this._renderBackgroundColor(ctx, color, width, height) } } } diff --git a/src/editor/dataset/constant/Background.ts b/src/editor/dataset/constant/Background.ts index 8088928..7f6f1ca 100644 --- a/src/editor/dataset/constant/Background.ts +++ b/src/editor/dataset/constant/Background.ts @@ -5,5 +5,6 @@ export const defaultBackground: Readonly> = { color: '#FFFFFF', image: '', size: BackgroundSize.COVER, - repeat: BackgroundRepeat.NO_REPEAT + repeat: BackgroundRepeat.NO_REPEAT, + applyPageNumbers: [] } diff --git a/src/editor/interface/Background.ts b/src/editor/interface/Background.ts index 46f1f5f..58b3e26 100644 --- a/src/editor/interface/Background.ts +++ b/src/editor/interface/Background.ts @@ -5,4 +5,5 @@ export interface IBackgroundOption { image?: string size?: BackgroundSize repeat?: BackgroundRepeat + applyPageNumbers?: number[] } From 8be79bd94093cd1fc8ba15d90a5d1fea99a1e54d Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Fri, 2 Aug 2024 20:09:53 +0800 Subject: [PATCH 15/42] release: v0.9.88 --- CHANGELOG.md | 22 ++++++++++++++++++++++ package.json | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdb46a6..8ac6b62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,25 @@ +## [0.9.88](https://github.com/Hufe921/canvas-editor/compare/v0.9.87...v0.9.88) (2024-08-02) + + +### Bug Fixes + +* float image position boundary error #716 ([f5113f5](https://github.com/Hufe921/canvas-editor/commit/f5113f539c5b224fdb9af8cde8e61f632dc7e49f)), closes [#716](https://github.com/Hufe921/canvas-editor/issues/716) + + +### Chores + +* add touch support to signature component ([c3ef290](https://github.com/Hufe921/canvas-editor/commit/c3ef2907ae31be21cd3bc61a5600ad381230034f)) +* update issue template ([eea301e](https://github.com/Hufe921/canvas-editor/commit/eea301eb11fdc3d5870da2408abccf4d96d8532f)) + + +### Features + +* add applyPageNumbers attribute to background option #729 ([8d112a8](https://github.com/Hufe921/canvas-editor/commit/8d112a8518656edd14201cb9bd16a417752fcdf9)), closes [#729](https://github.com/Hufe921/canvas-editor/issues/729) +* add cursor setting option to executeSetValue api #715 ([3235e5a](https://github.com/Hufe921/canvas-editor/commit/3235e5ae386bd1f48f5f64427e1eb0ed3782838d)), closes [#715](https://github.com/Hufe921/canvas-editor/issues/715) +* add title disabled property #680 ([87a8dbe](https://github.com/Hufe921/canvas-editor/commit/87a8dbea1596bcdee28edbb58b02fbe2a36e6c58)), closes [#680](https://github.com/Hufe921/canvas-editor/issues/680) + + + ## [0.9.87](https://github.com/Hufe921/canvas-editor/compare/v0.9.86...v0.9.87) (2024-07-26) diff --git a/package.json b/package.json index 0cd7e2d..e063a61 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@hufe921/canvas-editor", "author": "Hufe", "license": "MIT", - "version": "0.9.87", + "version": "0.9.88", "description": "rich text editor by canvas/svg", "publishConfig": { "registry": "https://registry.npmjs.org/", From 8878fd7734ae0a566f16ca2db14e64ded5a05f38 Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Sat, 3 Aug 2024 21:05:55 +0800 Subject: [PATCH 16/42] feat: set range using the shift shortcut key #728 --- src/editor/core/event/handlers/mousedown.ts | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/editor/core/event/handlers/mousedown.ts b/src/editor/core/event/handlers/mousedown.ts index d3cc682..c1bf0ac 100644 --- a/src/editor/core/event/handlers/mousedown.ts +++ b/src/editor/core/event/handlers/mousedown.ts @@ -48,6 +48,8 @@ export function mousedown(evt: MouseEvent, host: CanvasEvent) { draw.setPageNo(Number(pageIndex)) } host.isAllowSelection = true + // 缓存旧上下文信息 + const oldPositionContext = deepClone(position.getPositionContext()) const positionResult = position.adjustPositionContext({ x: evt.offsetX, y: evt.offsetY @@ -79,7 +81,23 @@ export function mousedown(evt: MouseEvent, host: CanvasEvent) { const isDirectHitCheckbox = !!(isDirectHit && isCheckbox) const isDirectHitRadio = !!(isDirectHit && isRadio) if (~index) { - rangeManager.setRange(curIndex, curIndex) + let startIndex = curIndex + let endIndex = curIndex + // shift激活时进行选区处理 + if (evt.shiftKey) { + const { startIndex: oldStartIndex } = rangeManager.getRange() + if (~oldStartIndex) { + const newPositionContext = position.getPositionContext() + if (newPositionContext.tdId === oldPositionContext.tdId) { + if (curIndex > oldStartIndex) { + startIndex = oldStartIndex + } else { + endIndex = oldStartIndex + } + } + } + } + rangeManager.setRange(startIndex, endIndex) position.setCursorPosition(positionList[curIndex]) // 复选框 const isSetCheckbox = isDirectHitCheckbox && !isReadonly From d89218a916fce5a7b7b6bf27fa4663ecc4d15cf6 Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Sun, 4 Aug 2024 21:31:00 +0800 Subject: [PATCH 17/42] feat: add line number option #734 --- docs/en/guide/option.md | 40 ++++++++++++++------- docs/guide/option.md | 14 ++++++++ src/editor/core/draw/Draw.ts | 16 ++++++++- src/editor/core/draw/frame/LineNumber.ts | 43 +++++++++++++++++++++++ src/editor/dataset/constant/LineNumber.ts | 11 ++++++ src/editor/dataset/enum/LineNumber.ts | 4 +++ src/editor/index.ts | 4 ++- src/editor/interface/Editor.ts | 2 ++ src/editor/interface/LineNumber.ts | 10 ++++++ src/editor/interface/Row.ts | 1 + src/editor/utils/option.ts | 9 ++++- 11 files changed, 138 insertions(+), 16 deletions(-) create mode 100644 src/editor/core/draw/frame/LineNumber.ts create mode 100644 src/editor/dataset/constant/LineNumber.ts create mode 100644 src/editor/dataset/enum/LineNumber.ts create mode 100644 src/editor/interface/LineNumber.ts diff --git a/docs/en/guide/option.md b/docs/en/guide/option.md index b2c6557..06bfe11 100644 --- a/docs/en/guide/option.md +++ b/docs/en/guide/option.md @@ -71,6 +71,7 @@ interface IEditorOption { background?: IBackgroundOption // Background option. {color?:string; image?:string; size?:BackgroundSize; repeat?:BackgroundRepeat; applyPageNumbers?:number[]}。default: {color: '#FFFFFF'} lineBreak?: ILineBreakOption // LineBreak option. {disabled?:boolean; color?:string; lineWidth?:number;} separator?: ISeparatorOption // Separator option. {lineWidth?:number; strokeStyle?:string;} + lineNumber?: ILineNumberOption // LineNumber option. {size?:number; font?:string; color?:string; disabled?:boolean; right?:number} } ``` @@ -109,11 +110,11 @@ interface IFooter { ```typescript interface IPageNumber { bottom?: number // The size from the bottom of the page.default: 60 - size?: number // font size.default: 12 - font?: string // font.default: Microsoft YaHei - color?: string // font color.default: #000000 - rowFlex?: RowFlex // Line alignment.default: CENTER - format?: string // Page number format.default: {pageNo}。example:{pageNo}/{pageCount} + size?: number // font size. default: 12 + font?: string // font. default: Microsoft YaHei + color?: string // font color. default: #000000 + rowFlex?: RowFlex // Line alignment. default: CENTER + format?: string // Page number format. default: {pageNo}。example:{pageNo}/{pageCount} numberType?: NumberType // The numeric type. default: ARABIC disabled?: boolean // Whether to disable startPageNo?: number // Start page number.default: 1 @@ -127,10 +128,10 @@ interface IPageNumber { ```typescript interface IWatermark { data: string // text. - color?: string // color.default: #AEB5C0 - opacity?: number // transparency.default: 0.3 - size?: number // font size.default: 200 - font?: string // font.default: Microsoft YaHei + color?: string // color. default: #AEB5C0 + opacity?: number // transparency. default: 0.3 + size?: number // font size. default: 200 + font?: string // font. default: Microsoft YaHei } ``` @@ -139,9 +140,22 @@ interface IWatermark { ```typescript interface IPlaceholder { data: string // text. - color?: string // color.default: #DCDFE6 - opacity?: number // transparency.default: 1 - size?: number // font size.default: 16 - font?: string // font.default: Microsoft YaHei + color?: string // color. default: #DCDFE6 + opacity?: number // transparency. default: 1 + size?: number // font size. default: 16 + font?: string // font. default: Microsoft YaHei +} +``` + +## LineNumber Configuration + +```typescript +interface ILineNumberOption { + size?: number // font size. default: 12 + font?: string // font. default: Microsoft YaHei + color?: string // color. default: #000000 + disabled?: boolean // Whether to disable. default: false + right?: number // Distance from the main text. default: 20 + type?: LineNumberType // Number type (renumber each page, consecutive numbering). default: continuity } ``` diff --git a/docs/guide/option.md b/docs/guide/option.md index 6ed887d..8fdc3c3 100644 --- a/docs/guide/option.md +++ b/docs/guide/option.md @@ -71,6 +71,7 @@ interface IEditorOption { background?: IBackgroundOption // 背景配置。{color?:string; image?:string; size?:BackgroundSize; repeat?:BackgroundRepeat; applyPageNumbers?:number[]}。默认:{color: '#FFFFFF'} lineBreak?: ILineBreakOption // 换行符配置。{disabled?:boolean; color?:string; lineWidth?:number;} separator?: ISeparatorOption // 分隔符配置。{lineWidth?:number; strokeStyle?:string;} + lineNumber?: ILineNumberOption // 行号配置。{size?:number; font?:string; color?:string; disabled?:boolean; right?:number} } ``` @@ -145,3 +146,16 @@ interface IPlaceholder { font?: string // 字体。默认:Microsoft YaHei } ``` + +## 行号配置 + +```typescript +interface ILineNumberOption { + size?: number // 字体大小。默认:12 + font?: string // 字体。默认:Microsoft YaHei + color?: string // 颜色。默认:#000000 + disabled?: boolean // 是否禁用。默认:true + right?: number // 距离正文距离。默认:20 + type?: LineNumberType // 编号类型(每页重新编号、连续编号)。默认:连续编号 +} +``` diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index 5c1d311..d3ecd7e 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -101,6 +101,7 @@ import { ImageDisplay } from '../../dataset/enum/Common' import { PUNCTUATION_REG } from '../../dataset/constant/Regular' import { LineBreakParticle } from './particle/LineBreakParticle' import { MouseObserver } from '../observer/MouseObserver' +import { LineNumber } from './frame/LineNumber' export class Draw { private container: HTMLDivElement @@ -138,6 +139,7 @@ export class Draw { private tableParticle: TableParticle private tableTool: TableTool private pageNumber: PageNumber + private lineNumber: LineNumber private waterMark: Watermark private placeholder: Placeholder private header: Header @@ -213,6 +215,7 @@ export class Draw { this.tableParticle = new TableParticle(this) this.tableTool = new TableTool(this) this.pageNumber = new PageNumber(this) + this.lineNumber = new LineNumber(this) this.waterMark = new Watermark(this) this.placeholder = new Placeholder(this) this.header = new Header(this, data.header) @@ -549,6 +552,10 @@ export class Draw { return this.lineBreakParticle } + public getTextParticle(): TextParticle { + return this.textParticle + } + public getHeaderElementList(): IElement[] { return this.header.getElementList() } @@ -1194,6 +1201,7 @@ export class Draw { ascent: 0, elementList: [], startIndex: 0, + rowIndex: 0, rowFlex: elementList?.[0]?.rowFlex || elementList?.[1]?.rowFlex }) } @@ -1645,6 +1653,7 @@ export class Draw { startIndex: i, elementList: [rowElement], ascent, + rowIndex: curRow.rowIndex + 1, rowFlex: elementList[i]?.rowFlex || elementList[i + 1]?.rowFlex, isPageBreak: element.type === ElementType.PAGE_BREAK } @@ -2186,7 +2195,8 @@ export class Draw { private _drawPage(payload: IDrawPagePayload) { const { elementList, positionList, rowList, pageNo } = payload - const { inactiveAlpha, pageMode, header, footer, pageNumber } = this.options + const { inactiveAlpha, pageMode, header, footer, pageNumber, lineNumber } = + this.options const innerWidth = this.getInnerWidth() const ctx = this.ctxList[pageNo] // 判断当前激活区域-非正文区域时元素透明度降低 @@ -2247,6 +2257,10 @@ export class Draw { if (this.elementList.length <= 1 && !this.elementList[0]?.listId) { this.placeholder.render(ctx) } + // 渲染行数 + if (!lineNumber.disabled) { + this.lineNumber.render(ctx, pageNo) + } } private _disconnectLazyRender() { diff --git a/src/editor/core/draw/frame/LineNumber.ts b/src/editor/core/draw/frame/LineNumber.ts new file mode 100644 index 0000000..305f875 --- /dev/null +++ b/src/editor/core/draw/frame/LineNumber.ts @@ -0,0 +1,43 @@ +import { LineNumberType } from '../../../dataset/enum/LineNumber' +import { DeepRequired } from '../../../interface/Common' +import { IEditorOption } from '../../../interface/Editor' +import { Draw } from '../Draw' + +export class LineNumber { + private draw: Draw + private options: DeepRequired + + constructor(draw: Draw) { + this.draw = draw + this.options = draw.getOptions() + } + + public render(ctx: CanvasRenderingContext2D, pageNo: number) { + const { + scale, + lineNumber: { color, size, font, right, type } + } = this.options + const textParticle = this.draw.getTextParticle() + const margins = this.draw.getMargins() + const positionList = this.draw.getPosition().getOriginalMainPositionList() + const pageRowList = this.draw.getPageRowList() + const rowList = pageRowList[pageNo] + ctx.save() + ctx.fillStyle = color + ctx.font = `${size * scale}px ${font}` + for (let i = 0; i < rowList.length; i++) { + const row = rowList[i] + const { + coordinate: { leftBottom } + } = positionList[row.startIndex] + const seq = type === LineNumberType.PAGE ? i + 1 : row.rowIndex + 1 + const textMetrics = textParticle.measureText(ctx, { + value: `${seq}` + }) + const x = margins[3] - (textMetrics.width + right) * scale + const y = leftBottom[1] - textMetrics.actualBoundingBoxAscent * scale + ctx.fillText(`${seq}`, x, y) + } + ctx.restore() + } +} diff --git a/src/editor/dataset/constant/LineNumber.ts b/src/editor/dataset/constant/LineNumber.ts new file mode 100644 index 0000000..141d034 --- /dev/null +++ b/src/editor/dataset/constant/LineNumber.ts @@ -0,0 +1,11 @@ +import { ILineNumberOption } from '../../interface/LineNumber' +import { LineNumberType } from '../enum/LineNumber' + +export const defaultLineNumberOption: Readonly> = { + size: 12, + font: 'Microsoft YaHei', + color: '#000000', + disabled: true, + right: 20, + type: LineNumberType.CONTINUITY +} diff --git a/src/editor/dataset/enum/LineNumber.ts b/src/editor/dataset/enum/LineNumber.ts new file mode 100644 index 0000000..53e689b --- /dev/null +++ b/src/editor/dataset/enum/LineNumber.ts @@ -0,0 +1,4 @@ +export enum LineNumberType { + PAGE = 'page', + CONTINUITY = 'continuity' +} diff --git a/src/editor/index.ts b/src/editor/index.ts index d8dd3c0..2cb398c 100644 --- a/src/editor/index.ts +++ b/src/editor/index.ts @@ -52,6 +52,7 @@ import { deepClone, splitText } from './utils' import { BackgroundRepeat, BackgroundSize } from './dataset/enum/Background' import { TextDecorationStyle } from './dataset/enum/Text' import { mergeOption } from './utils/option' +import { LineNumberType } from './dataset/enum/LineNumber' export default class Editor { public command: Command @@ -169,7 +170,8 @@ export { ControlIndentation, BackgroundRepeat, BackgroundSize, - TextDecorationStyle + TextDecorationStyle, + LineNumberType } // 对外类型 diff --git a/src/editor/interface/Editor.ts b/src/editor/interface/Editor.ts index 0dcb176..8be2278 100644 --- a/src/editor/interface/Editor.ts +++ b/src/editor/interface/Editor.ts @@ -24,6 +24,7 @@ import { IWatermark } from './Watermark' import { IZoneOption } from './Zone' import { ISeparatorOption } from './Separator' import { ITableOption } from './table/Table' +import { ILineNumberOption } from './LineNumber' export interface IEditorData { header?: IElement[] @@ -89,6 +90,7 @@ export interface IEditorOption { background?: IBackgroundOption lineBreak?: ILineBreakOption separator?: ISeparatorOption + lineNumber?: ILineNumberOption } export interface IEditorResult { diff --git a/src/editor/interface/LineNumber.ts b/src/editor/interface/LineNumber.ts new file mode 100644 index 0000000..7b4dfe1 --- /dev/null +++ b/src/editor/interface/LineNumber.ts @@ -0,0 +1,10 @@ +import { LineNumberType } from '../dataset/enum/LineNumber' + +export interface ILineNumberOption { + size?: number + font?: string + color?: string + disabled?: boolean + right?: number + type?: LineNumberType +} diff --git a/src/editor/interface/Row.ts b/src/editor/interface/Row.ts index c8bc6f6..c77cf36 100644 --- a/src/editor/interface/Row.ts +++ b/src/editor/interface/Row.ts @@ -19,4 +19,5 @@ export interface IRow { offsetX?: number elementList: IRowElement[] isWidthNotEnough?: boolean + rowIndex: number } diff --git a/src/editor/utils/option.ts b/src/editor/utils/option.ts index 0121a20..7441fd0 100644 --- a/src/editor/utils/option.ts +++ b/src/editor/utils/option.ts @@ -16,6 +16,7 @@ import { defaultTableOption } from '../dataset/constant/Table' import { defaultTitleOption } from '../dataset/constant/Title' import { defaultWatermarkOption } from '../dataset/constant/Watermark' import { defaultZoneOption } from '../dataset/constant/Zone' +import { defaultLineNumberOption } from '../dataset/constant/LineNumber' import { IBackgroundOption } from '../interface/Background' import { ICheckboxOption } from '../interface/Checkbox' import { DeepRequired } from '../interface/Common' @@ -35,6 +36,7 @@ import { ITableOption } from '../interface/table/Table' import { ITitleOption } from '../interface/Title' import { IWatermark } from '../interface/Watermark' import { IZoneOption } from '../interface/Zone' +import { ILineNumberOption } from '../interface/LineNumber' import { EditorMode, PageMode, @@ -114,6 +116,10 @@ export function mergeOption( ...defaultSeparatorOption, ...options.separator } + const lineNumberOptions: Required = { + ...defaultLineNumberOption, + ...options.lineNumber + } return { mode: EditorMode.EDIT, @@ -173,6 +179,7 @@ export function mergeOption( zone: zoneOptions, background: backgroundOptions, lineBreak: lineBreakOptions, - separator: separatorOptions + separator: separatorOptions, + lineNumber: lineNumberOptions } } From 997ecc03e89afeaaf6b903f9f253c5c5090b3f78 Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Mon, 5 Aug 2024 17:50:06 +0800 Subject: [PATCH 18/42] feat: add id property to contextMenu context #737 --- src/editor/core/contextmenu/ContextMenu.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/editor/core/contextmenu/ContextMenu.ts b/src/editor/core/contextmenu/ContextMenu.ts index 828709d..9d862d6 100644 --- a/src/editor/core/contextmenu/ContextMenu.ts +++ b/src/editor/core/contextmenu/ContextMenu.ts @@ -159,7 +159,9 @@ export class ContextMenu { const originalElementList = this.draw.getOriginalElementList() const originTableElement = originalElementList[index!] || null if (originTableElement) { - tableElement = zipElementList([originTableElement])[0] + tableElement = zipElementList([originTableElement], { + extraPickAttrs: ['id'] + })[0] } } // 是否存在跨行/列 From d0390cc775bbb7fb51c528b2df06850a44a6366b Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Mon, 5 Aug 2024 17:51:24 +0800 Subject: [PATCH 19/42] types: update IUpdateElementByIdOption interface #737 --- src/editor/interface/Element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/interface/Element.ts b/src/editor/interface/Element.ts index eb1656e..52ec6f2 100644 --- a/src/editor/interface/Element.ts +++ b/src/editor/interface/Element.ts @@ -177,5 +177,5 @@ export interface IElementFillRect { export interface IUpdateElementByIdOption { id: string - properties: Omit + properties: Omit, 'id'> } From dd1b53ee6297bc72b0064c09522b597118919c50 Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Tue, 6 Aug 2024 20:18:19 +0800 Subject: [PATCH 20/42] feat: control related apis support the control id property --- src/editor/core/command/CommandAdapt.ts | 8 +-- src/editor/core/draw/control/Control.ts | 64 +++++++++++++------ .../draw/control/checkbox/CheckboxControl.ts | 5 +- .../core/draw/control/date/DateControl.ts | 12 +++- .../draw/control/interactive/ControlSearch.ts | 9 ++- .../core/draw/control/radio/RadioControl.ts | 5 +- .../core/draw/control/select/SelectControl.ts | 7 +- .../core/draw/control/text/TextControl.ts | 10 ++- src/editor/interface/Control.ts | 15 +++-- 9 files changed, 95 insertions(+), 40 deletions(-) diff --git a/src/editor/core/command/CommandAdapt.ts b/src/editor/core/command/CommandAdapt.ts index a25c823..000baed 100644 --- a/src/editor/core/command/CommandAdapt.ts +++ b/src/editor/core/command/CommandAdapt.ts @@ -2435,19 +2435,19 @@ export class CommandAdapt { public getControlValue( payload: IGetControlValueOption ): IGetControlValueResult | null { - return this.draw.getControl().getValueByConceptId(payload) + return this.draw.getControl().getValueById(payload) } public setControlValue(payload: ISetControlValueOption) { - this.draw.getControl().setValueByConceptId(payload) + this.draw.getControl().setValueById(payload) } public setControlExtension(payload: ISetControlExtensionOption) { - this.draw.getControl().setExtensionByConceptId(payload) + this.draw.getControl().setExtensionById(payload) } public setControlProperties(payload: ISetControlProperties) { - this.draw.getControl().setPropertiesByConceptId(payload) + this.draw.getControl().setPropertiesById(payload) } public setControlHighlight(payload: ISetControlHighlightOption) { diff --git a/src/editor/core/draw/control/Control.ts b/src/editor/core/draw/control/Control.ts index 4420246..7c3d97f 100644 --- a/src/editor/core/draw/control/Control.ts +++ b/src/editor/core/draw/control/Control.ts @@ -207,11 +207,11 @@ export class Control { return prefixCount === postfixCount } - public getIsDisabledControl(): boolean { + public getIsDisabledControl(context: IControlContext = {}): boolean { if (!this.activeControl) return false - const { startIndex, endIndex } = this.range.getRange() - if (startIndex === endIndex) { - const elementList = this.getElementList() + const { startIndex, endIndex } = context.range || this.range.getRange() + if (startIndex === endIndex && ~startIndex && ~endIndex) { + const elementList = context.elementList || this.getElementList() const startElement = elementList[startIndex] if (startElement.controlComponent === ControlComponent.POSTFIX) { return false @@ -569,11 +569,10 @@ export class Control { return this.activeControl.cut() } - public getValueByConceptId( - payload: IGetControlValueOption - ): IGetControlValueResult { - const { conceptId } = payload + public getValueById(payload: IGetControlValueOption): IGetControlValueResult { + const { id, conceptId } = payload const result: IGetControlValueResult = [] + if (!id && !conceptId) return result const getValue = (elementList: IElement[], zone: EditorZone) => { let i = 0 while (i < elementList.length) { @@ -590,8 +589,14 @@ export class Control { } } } - if (element?.control?.conceptId !== conceptId) continue - const { type, code, valueSets } = element.control! + if ( + !element.control || + (id && element.controlId !== id) || + (conceptId && element.control.conceptId !== conceptId) + ) { + continue + } + const { type, code, valueSets } = element.control let j = i let textControlValue = '' while (j < elementList.length) { @@ -655,9 +660,10 @@ export class Control { return result } - public setValueByConceptId(payload: ISetControlValueOption) { + public setValueById(payload: ISetControlValueOption) { let isExistSet = false - const { conceptId, value } = payload + const { id, conceptId, value } = payload + if (!id && !conceptId) return // 设置值 const setValue = (elementList: IElement[]) => { let i = 0 @@ -675,7 +681,13 @@ export class Control { } } } - if (element?.control?.conceptId !== conceptId) continue + if ( + !element.control || + (id && element.controlId !== id) || + (conceptId && element.control.conceptId !== conceptId) + ) { + continue + } isExistSet = true const { type } = element.control! // 当前控件结束索引 @@ -767,8 +779,9 @@ export class Control { } } - public setExtensionByConceptId(payload: ISetControlExtensionOption) { - const { conceptId, extension } = payload + public setExtensionById(payload: ISetControlExtensionOption) { + const { id, conceptId, extension } = payload + if (!id && !conceptId) return const setExtension = (elementList: IElement[]) => { let i = 0 while (i < elementList.length) { @@ -785,7 +798,13 @@ export class Control { } } } - if (element?.control?.conceptId !== conceptId) continue + if ( + !element.control || + (id && element.controlId !== id) || + (conceptId && element.control.conceptId !== conceptId) + ) { + continue + } element.control.extension = extension // 修改后控件结束索引 let newEndIndex = i @@ -807,8 +826,9 @@ export class Control { } } - public setPropertiesByConceptId(payload: ISetControlProperties) { - const { conceptId, properties } = payload + public setPropertiesById(payload: ISetControlProperties) { + const { id, conceptId, properties } = payload + if (!id && !conceptId) return let isExistUpdate = false function setProperties(elementList: IElement[]) { let i = 0 @@ -825,7 +845,13 @@ export class Control { } } } - if (element?.control?.conceptId !== conceptId) continue + if ( + !element.control || + (id && element.controlId !== id) || + (conceptId && element.control.conceptId !== conceptId) + ) { + continue + } isExistUpdate = true element.control = { ...element.control, diff --git a/src/editor/core/draw/control/checkbox/CheckboxControl.ts b/src/editor/core/draw/control/checkbox/CheckboxControl.ts index 75850dd..ac744be 100644 --- a/src/editor/core/draw/control/checkbox/CheckboxControl.ts +++ b/src/editor/core/draw/control/checkbox/CheckboxControl.ts @@ -77,7 +77,10 @@ export class CheckboxControl implements IControlInstance { options: IControlRuleOption = {} ) { // 校验是否可以设置 - if (!options.isIgnoreDisabledRule && this.control.getIsDisabledControl()) { + if ( + !options.isIgnoreDisabledRule && + this.control.getIsDisabledControl(context) + ) { return } const { control } = this.element diff --git a/src/editor/core/draw/control/date/DateControl.ts b/src/editor/core/draw/control/date/DateControl.ts index 964ec0f..6e13a99 100644 --- a/src/editor/core/draw/control/date/DateControl.ts +++ b/src/editor/core/draw/control/date/DateControl.ts @@ -99,7 +99,10 @@ export class DateControl implements IControlInstance { options: IControlRuleOption = {} ): number { // 校验是否可以设置 - if (!options.isIgnoreDisabledRule && this.control.getIsDisabledControl()) { + if ( + !options.isIgnoreDisabledRule && + this.control.getIsDisabledControl(context) + ) { return -1 } const elementList = context.elementList || this.control.getElementList() @@ -147,7 +150,7 @@ export class DateControl implements IControlInstance { ): number { const { isIgnoreDisabledRule = false, isAddPlaceholder = true } = options // 校验是否可以设置 - if (!isIgnoreDisabledRule && this.control.getIsDisabledControl()) { + if (!isIgnoreDisabledRule && this.control.getIsDisabledControl(context)) { return -1 } const range = this.getValueRange(context) @@ -171,7 +174,10 @@ export class DateControl implements IControlInstance { options: IControlRuleOption = {} ) { // 校验是否可以设置 - if (!options.isIgnoreDisabledRule && this.control.getIsDisabledControl()) { + if ( + !options.isIgnoreDisabledRule && + this.control.getIsDisabledControl(context) + ) { return } const elementList = context.elementList || this.control.getElementList() diff --git a/src/editor/core/draw/control/interactive/ControlSearch.ts b/src/editor/core/draw/control/interactive/ControlSearch.ts index f7090c8..5c50ec4 100644 --- a/src/editor/core/draw/control/interactive/ControlSearch.ts +++ b/src/editor/core/draw/control/interactive/ControlSearch.ts @@ -71,10 +71,13 @@ export class ControlSearch { } } } - const controlConceptId = element?.control?.conceptId - if (!controlConceptId) continue + const currentControl = element?.control + if (!currentControl) continue const highlightIndex = this.highlightList.findIndex( - highlight => highlight.conceptId === controlConceptId + highlight => + highlight.id === element.controlId || + (currentControl.conceptId && + currentControl.conceptId === highlight.conceptId) ) if (!~highlightIndex) continue // 搜索后控件结束索引 diff --git a/src/editor/core/draw/control/radio/RadioControl.ts b/src/editor/core/draw/control/radio/RadioControl.ts index 9965379..0b066a1 100644 --- a/src/editor/core/draw/control/radio/RadioControl.ts +++ b/src/editor/core/draw/control/radio/RadioControl.ts @@ -12,7 +12,10 @@ export class RadioControl extends CheckboxControl { options: IControlRuleOption = {} ) { // 校验是否可以设置 - if (!options.isIgnoreDisabledRule && this.control.getIsDisabledControl()) { + if ( + !options.isIgnoreDisabledRule && + this.control.getIsDisabledControl(context) + ) { return } const { control } = this.element diff --git a/src/editor/core/draw/control/select/SelectControl.ts b/src/editor/core/draw/control/select/SelectControl.ts index d2bba27..a45de98 100644 --- a/src/editor/core/draw/control/select/SelectControl.ts +++ b/src/editor/core/draw/control/select/SelectControl.ts @@ -163,7 +163,7 @@ export class SelectControl implements IControlInstance { ): number { const { isIgnoreDisabledRule = false, isAddPlaceholder = true } = options // 校验是否可以设置 - if (!isIgnoreDisabledRule && this.control.getIsDisabledControl()) { + if (!isIgnoreDisabledRule && this.control.getIsDisabledControl(context)) { return -1 } const elementList = context.elementList || this.control.getElementList() @@ -215,7 +215,10 @@ export class SelectControl implements IControlInstance { options: IControlRuleOption = {} ) { // 校验是否可以设置 - if (!options.isIgnoreDisabledRule && this.control.getIsDisabledControl()) { + if ( + !options.isIgnoreDisabledRule && + this.control.getIsDisabledControl(context) + ) { return } const elementList = context.elementList || this.control.getElementList() diff --git a/src/editor/core/draw/control/text/TextControl.ts b/src/editor/core/draw/control/text/TextControl.ts index ef08b3e..82c1fa6 100644 --- a/src/editor/core/draw/control/text/TextControl.ts +++ b/src/editor/core/draw/control/text/TextControl.ts @@ -75,7 +75,10 @@ export class TextControl implements IControlInstance { options: IControlRuleOption = {} ): number { // 校验是否可以设置 - if (!options.isIgnoreDisabledRule && this.control.getIsDisabledControl()) { + if ( + !options.isIgnoreDisabledRule && + this.control.getIsDisabledControl(context) + ) { return -1 } const elementList = context.elementList || this.control.getElementList() @@ -122,7 +125,10 @@ export class TextControl implements IControlInstance { options: IControlRuleOption = {} ): number { // 校验是否可以设置 - if (!options.isIgnoreDisabledRule && this.control.getIsDisabledControl()) { + if ( + !options.isIgnoreDisabledRule && + this.control.getIsDisabledControl(context) + ) { return -1 } const elementList = context.elementList || this.control.getElementList() diff --git a/src/editor/interface/Control.ts b/src/editor/interface/Control.ts index 6a18fd5..018d4cd 100644 --- a/src/editor/interface/Control.ts +++ b/src/editor/interface/Control.ts @@ -40,7 +40,8 @@ export interface IControlHighlightRule { export interface IControlHighlight { ruleList: IControlHighlightRule[] - conceptId: string + id?: string + conceptId?: string } export interface IControlRule { @@ -124,7 +125,8 @@ export interface IControlRuleOption { } export interface IGetControlValueOption { - conceptId: string + id?: string + conceptId?: string } export type IGetControlValueResult = (Omit & { @@ -134,19 +136,22 @@ export type IGetControlValueResult = (Omit & { })[] export interface ISetControlValueOption { - conceptId: string + id?: string + conceptId?: string value: string } export interface ISetControlExtensionOption { - conceptId: string + id?: string + conceptId?: string extension: unknown } export type ISetControlHighlightOption = IControlHighlight[] export type ISetControlProperties = { - conceptId: string + id?: string + conceptId?: string properties: Partial> } From 9dd192ffd26f9f271efa06c81b6bd5e32557d872 Mon Sep 17 00:00:00 2001 From: daria2023 <95205482+daria2023@users.noreply.github.com> Date: Wed, 7 Aug 2024 18:50:53 +0800 Subject: [PATCH 21/42] fix: three click selection paragraph boundary error #742 Co-authored-by: Hufe --- src/editor/core/event/handlers/click.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/editor/core/event/handlers/click.ts b/src/editor/core/event/handlers/click.ts index 5c3fc05..a033d36 100644 --- a/src/editor/core/event/handlers/click.ts +++ b/src/editor/core/event/handlers/click.ts @@ -195,7 +195,10 @@ function threeClick(host: CanvasEvent) { } if (newStartIndex < 0) return let newEndIndex = index + downCount + 1 - if (elementList[newEndIndex]?.value === ZERO) { + if ( + elementList[newEndIndex]?.value === ZERO || + newEndIndex > elementList.length - 1 + ) { newEndIndex -= 1 } rangeManager.setRange(newStartIndex, newEndIndex) From c2d3e941b19a6bc766b0bfa8dac7911578170b6c Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Thu, 8 Aug 2024 20:42:20 +0800 Subject: [PATCH 22/42] docs: update plugin markdown --- docs/.vitepress/config.ts | 18 ++- docs/en/guide/{plugin.md => plugin-custom.md} | 5 +- docs/en/guide/plugin-internal.md | 109 ++++++++++++++++++ docs/guide/{plugin.md => plugin-custom.md} | 5 +- docs/guide/plugin-internal.md | 109 ++++++++++++++++++ 5 files changed, 240 insertions(+), 6 deletions(-) rename docs/en/guide/{plugin.md => plugin-custom.md} (64%) create mode 100644 docs/en/guide/plugin-internal.md rename docs/guide/{plugin.md => plugin-custom.md} (70%) create mode 100644 docs/guide/plugin-internal.md diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 95bfe60..8cdb33f 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -21,6 +21,10 @@ export default defineConfig({ { text: 'Demo', link: 'https://hufe.club/canvas-editor' + }, + { + text: '官方插件', + link: '/guide/plugin-internal.html' } ], sidebar: [ @@ -71,7 +75,10 @@ export default defineConfig({ }, { text: '插件', - items: [{ text: '自定义插件', link: '/guide/plugin' }] + items: [ + { text: '自定义插件', link: '/guide/plugin-custom' }, + { text: '官方插件', link: '/guide/plugin-internal' } + ] } ], socialLinks: [ @@ -104,6 +111,10 @@ export default defineConfig({ { text: 'Demo', link: 'https://hufe.club/canvas-editor' + }, + { + text: 'Official plugin', + link: '/en/guide/plugin-internal.html' } ], sidebar: [ @@ -154,7 +165,10 @@ export default defineConfig({ }, { text: 'Plugin', - items: [{ text: 'plugin', link: '/en/guide/plugin' }] + items: [ + { text: 'custom', link: '/en/guide/plugin-custom' }, + { text: 'official', link: '/en/guide/plugin-internal' } + ] } ] } diff --git a/docs/en/guide/plugin.md b/docs/en/guide/plugin-custom.md similarity index 64% rename from docs/en/guide/plugin.md rename to docs/en/guide/plugin-custom.md index 672d4d4..05d519f 100644 --- a/docs/en/guide/plugin.md +++ b/docs/en/guide/plugin-custom.md @@ -1,8 +1,7 @@ # Custom Plugin ::: tip -1. Currently, only methods can be added and modified to the editor instance, and more functions will be extended in the future -2. Official maintenance plugin: https://github.com/Hufe921/canvas-editor-plugin +Official plugin: https://github.com/Hufe921/canvas-editor-plugin ::: ## Write a Plugin @@ -14,6 +13,8 @@ export function myPlugin(editor: Editor, options?: Option) { // 2. add,see more:src/plugins/markdown editor.command.addFunction = () => {} + + // 3. listener, eventbus, shortcut, contextmenu, override... } ``` diff --git a/docs/en/guide/plugin-internal.md b/docs/en/guide/plugin-internal.md new file mode 100644 index 0000000..1f59f41 --- /dev/null +++ b/docs/en/guide/plugin-internal.md @@ -0,0 +1,109 @@ +# Official plugin + +::: tip +Official plugin: https://github.com/Hufe921/canvas-editor-plugin +::: + +## Barcode1d + +```javascript +import Editor from "@hufe921/canvas-editor" +import barcode1DPlugin from "@hufe921/canvas-editor-plugin-barcode1d" + +const instance = new Editor() +instance.use(barcode1DPlugin) + +instance.executeInsertBarcode1D( + content: string, + width: number, + height: number, + options?: JsBarcode.Options +) +``` + +## Barcode2d + +```javascript +import Editor from "@hufe921/canvas-editor" +import barcode2DPlugin from "@hufe921/canvas-editor-plugin-barcode2d" + +const instance = new Editor() +instance.use(barcode2DPlugin, options?: IBarcode2DOption) + +instance.executeInsertBarcode2D( + content: string, + width: number, + height: number, + hints?: Map +) +``` + +## Code block + +```javascript +import Editor from "@hufe921/canvas-editor" +import codeblockPlugin from "@hufe921/canvas-editor-plugin-codeblock" + +const instance = new Editor() +instance.use(codeblockPlugin) + +instance.executeInsertCodeblock(content: string) +``` + +## Word + +```javascript +import Editor from '@hufe921/canvas-editor' +import docxPlugin from '@hufe921/canvas-editor-plugin-docx' + +const instance = new Editor() +instance.use(docxPlugin) + +command.executeImportDocx({ + arrayBuffer: buffer +}) + +instance.executeExportDocx({ + fileName: string +}) +``` + +## Excel + +```javascript +import Editor from '@hufe921/canvas-editor' +import excelPlugin from '@hufe921/canvas-editor-plugin-excel' + +const instance = new Editor() +instance.use(excelPlugin) + +command.executeImportExcel({ + arrayBuffer: buffer +}) +``` + +## Floating toolbar + +```javascript +import Editor from '@hufe921/canvas-editor' +import floatingToolbarPlugin from '@hufe921/canvas-editor-plugin-floating-toolbar' + +const instance = new Editor() +instance.use(floatingToolbarPlugin) +``` + +## Diagram + +```javascript +import Editor from '@hufe921/canvas-editor' +import diagramPlugin from '@hufe921/canvas-editor-plugin-diagram' + +const instance = new Editor() +instance.use(diagramPlugin) + +command.executeLoadDiagram({ + lang?: Lang + data?: string + onDestroy?: (message?: any) => void +}) +``` diff --git a/docs/guide/plugin.md b/docs/guide/plugin-custom.md similarity index 70% rename from docs/guide/plugin.md rename to docs/guide/plugin-custom.md index d939b6a..6bd54f6 100644 --- a/docs/guide/plugin.md +++ b/docs/guide/plugin-custom.md @@ -1,8 +1,7 @@ # 自定义插件 ::: tip -1. 目前仅支持对编辑器实例进行方法的增加及修改,后续扩展更多功能 -2. 官方维护插件:https://github.com/Hufe921/canvas-editor-plugin +官方维护插件仓库:https://github.com/Hufe921/canvas-editor-plugin ::: ## 开发插件 @@ -14,6 +13,8 @@ export function myPlugin(editor: Editor, options?: Option) { // 2. 增加方法,详见:src/plugins/markdown editor.command.addFunction = () => {} + + // 3. 事件监听、快捷键、右键菜单、重写方法等组合处理 } ``` diff --git a/docs/guide/plugin-internal.md b/docs/guide/plugin-internal.md new file mode 100644 index 0000000..08b79d4 --- /dev/null +++ b/docs/guide/plugin-internal.md @@ -0,0 +1,109 @@ +# 官方插件 + +::: tip +官方维护插件仓库:https://github.com/Hufe921/canvas-editor-plugin +::: + +## 条形码 + +```javascript +import Editor from "@hufe921/canvas-editor" +import barcode1DPlugin from "@hufe921/canvas-editor-plugin-barcode1d" + +const instance = new Editor() +instance.use(barcode1DPlugin) + +instance.executeInsertBarcode1D( + content: string, + width: number, + height: number, + options?: JsBarcode.Options +) +``` + +## 二维码 + +```javascript +import Editor from "@hufe921/canvas-editor" +import barcode2DPlugin from "@hufe921/canvas-editor-plugin-barcode2d" + +const instance = new Editor() +instance.use(barcode2DPlugin, options?: IBarcode2DOption) + +instance.executeInsertBarcode2D( + content: string, + width: number, + height: number, + hints?: Map +) +``` + +## 代码块 + +```javascript +import Editor from "@hufe921/canvas-editor" +import codeblockPlugin from "@hufe921/canvas-editor-plugin-codeblock" + +const instance = new Editor() +instance.use(codeblockPlugin) + +instance.executeInsertCodeblock(content: string) +``` + +## Word + +```javascript +import Editor from '@hufe921/canvas-editor' +import docxPlugin from '@hufe921/canvas-editor-plugin-docx' + +const instance = new Editor() +instance.use(docxPlugin) + +command.executeImportDocx({ + arrayBuffer: buffer +}) + +instance.executeExportDocx({ + fileName: string +}) +``` + +## Excel + +```javascript +import Editor from '@hufe921/canvas-editor' +import excelPlugin from '@hufe921/canvas-editor-plugin-excel' + +const instance = new Editor() +instance.use(excelPlugin) + +command.executeImportExcel({ + arrayBuffer: buffer +}) +``` + +## 悬浮工具 + +```javascript +import Editor from '@hufe921/canvas-editor' +import floatingToolbarPlugin from '@hufe921/canvas-editor-plugin-floating-toolbar' + +const instance = new Editor() +instance.use(floatingToolbarPlugin) +``` + +## 流程图 + +```javascript +import Editor from '@hufe921/canvas-editor' +import diagramPlugin from '@hufe921/canvas-editor-plugin-diagram' + +const instance = new Editor() +instance.use(diagramPlugin) + +command.executeLoadDiagram({ + lang?: Lang + data?: string + onDestroy?: (message?: any) => void +}) +``` From ba4f3942a60c7319bd78eca277ba01f008b78b27 Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Fri, 9 Aug 2024 21:31:48 +0800 Subject: [PATCH 23/42] release: v0.9.89 --- CHANGELOG.md | 22 ++++++++++++++++++++++ package.json | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ac6b62..f107550 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,25 @@ +## [0.9.89](https://github.com/Hufe921/canvas-editor/compare/v0.9.88...v0.9.89) (2024-08-09) + + +### Bug Fixes + +* three click selection paragraph boundary error #742 ([9dd192f](https://github.com/Hufe921/canvas-editor/commit/9dd192ffd26f9f271efa06c81b6bd5e32557d872)), closes [#742](https://github.com/Hufe921/canvas-editor/issues/742) + + +### Documentation + +* update plugin markdown ([c2d3e94](https://github.com/Hufe921/canvas-editor/commit/c2d3e941b19a6bc766b0bfa8dac7911578170b6c)) + + +### Features + +* add id property to contextMenu context #737 ([997ecc0](https://github.com/Hufe921/canvas-editor/commit/997ecc03e89afeaaf6b903f9f253c5c5090b3f78)), closes [#737](https://github.com/Hufe921/canvas-editor/issues/737) +* add line number option #734 ([d89218a](https://github.com/Hufe921/canvas-editor/commit/d89218a916fce5a7b7b6bf27fa4663ecc4d15cf6)), closes [#734](https://github.com/Hufe921/canvas-editor/issues/734) +* control related apis support the control id property ([dd1b53e](https://github.com/Hufe921/canvas-editor/commit/dd1b53ee6297bc72b0064c09522b597118919c50)) +* set range using the shift shortcut key #728 ([8878fd7](https://github.com/Hufe921/canvas-editor/commit/8878fd7734ae0a566f16ca2db14e64ded5a05f38)), closes [#728](https://github.com/Hufe921/canvas-editor/issues/728) + + + ## [0.9.88](https://github.com/Hufe921/canvas-editor/compare/v0.9.87...v0.9.88) (2024-08-02) diff --git a/package.json b/package.json index e063a61..324443e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@hufe921/canvas-editor", "author": "Hufe", "license": "MIT", - "version": "0.9.88", + "version": "0.9.89", "description": "rich text editor by canvas/svg", "publishConfig": { "registry": "https://registry.npmjs.org/", From 136b1ffa55b7b0b78bf3d114400489e1bbab4f17 Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Mon, 12 Aug 2024 22:01:17 +0800 Subject: [PATCH 24/42] fix: insert block element row flex error #754 --- src/editor/dataset/constant/Element.ts | 3 +-- src/editor/utils/element.ts | 16 ++++++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/editor/dataset/constant/Element.ts b/src/editor/dataset/constant/Element.ts index 75e70ff..fa117e5 100644 --- a/src/editor/dataset/constant/Element.ts +++ b/src/editor/dataset/constant/Element.ts @@ -120,8 +120,7 @@ export const CONTROL_STYLE_ATTR: Array = [ export const EDITOR_ELEMENT_CONTEXT_ATTR: Array = [ ...TABLE_CONTEXT_ATTR, ...TITLE_CONTEXT_ATTR, - ...LIST_CONTEXT_ATTR, - ...EDITOR_ROW_ATTR + ...LIST_CONTEXT_ATTR ] export const TEXTLIKE_ELEMENT_TYPE: ElementType[] = [ diff --git a/src/editor/utils/element.ts b/src/editor/utils/element.ts index fd30ba4..a6e2789 100644 --- a/src/editor/utils/element.ts +++ b/src/editor/utils/element.ts @@ -236,7 +236,10 @@ export function formatElementList( // 移除父节点 elementList.splice(i, 1) // 控件上下文提取(压缩后的控件上下文无法提取) - const controlContext = pickObject(el, EDITOR_ELEMENT_CONTEXT_ATTR) + const controlContext = pickObject(el, [ + ...EDITOR_ELEMENT_CONTEXT_ATTR, + ...EDITOR_ROW_ATTR + ]) // 控件设置的默认样式(以前缀为基准) const controlDefaultStyle = pickObject( (el.control), @@ -858,11 +861,12 @@ export function formatElementContext( anchorIndex ) } - cloneProperty( - EDITOR_ELEMENT_CONTEXT_ATTR, - copyElement, - targetElement - ) + // 非块类元素,需处理行属性 + const cloneAttr = [...EDITOR_ELEMENT_CONTEXT_ATTR] + if (!getIsBlockElement(targetElement)) { + cloneAttr.push(...EDITOR_ROW_ATTR) + } + cloneProperty(cloneAttr, copyElement, targetElement) } } From 2f272dee58169607783e4cfd1347534f49db0834 Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Mon, 12 Aug 2024 22:04:51 +0800 Subject: [PATCH 25/42] fix: set row flex boundary error when deleting element --- .../core/event/handlers/keydown/backspace.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/editor/core/event/handlers/keydown/backspace.ts b/src/editor/core/event/handlers/keydown/backspace.ts index 57cf3ca..70865a4 100644 --- a/src/editor/core/event/handlers/keydown/backspace.ts +++ b/src/editor/core/event/handlers/keydown/backspace.ts @@ -53,16 +53,16 @@ export function backspace(evt: KeyboardEvent, host: CanvasEvent) { return } } - // 清空当前行对齐方式 + // 替换当前行对齐方式 const startElement = elementList[startIndex] if (isCollapsed && startElement.rowFlex && startElement.value === ZERO) { - const rowList = draw.getRowList() - const positionList = position.getPositionList() - const rowNo = positionList[startIndex].rowNo - const rowFlexElementList = rowList[rowNo].elementList - rowFlexElementList.forEach(element => { - delete element.rowFlex - }) + const rowFlexElementList = rangeManager.getRangeRowElementList() + if (rowFlexElementList) { + const preElement = elementList[startIndex - 1] + rowFlexElementList.forEach(element => { + element.rowFlex = preElement?.rowFlex + }) + } } if (!isCollapsed) { draw.spliceElementList(elementList, startIndex + 1, endIndex - startIndex) From 500cec3e0b63e012f6572dcedb071befa114956e Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Tue, 13 Aug 2024 21:28:49 +0800 Subject: [PATCH 26/42] fix: set editor mode option error #755 --- src/editor/core/draw/Draw.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index d3ecd7e..f0ca513 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -302,6 +302,7 @@ export class Draw { this.clearSideEffect() this.range.clearRange() this.mode = payload + this.options.mode = payload this.render({ isSetCursor: false, isSubmitHistory: false From 4653fe7427f4a9ec40700f6a86ff284d1d46088a Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Wed, 14 Aug 2024 21:11:43 +0800 Subject: [PATCH 27/42] fix: get range paragraph info boundary error #758 --- src/editor/core/range/RangeManager.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/editor/core/range/RangeManager.ts b/src/editor/core/range/RangeManager.ts index bba90f5..a377f24 100644 --- a/src/editor/core/range/RangeManager.ts +++ b/src/editor/core/range/RangeManager.ts @@ -171,8 +171,9 @@ export class RangeManager { } start-- } + const isCollapsed = startIndex === endIndex // 中间选择 - if (startIndex !== endIndex) { + if (!isCollapsed) { let middle = startIndex + 1 while (middle < endIndex) { const { pageNo, rowNo } = positionList[middle] @@ -189,6 +190,10 @@ export class RangeManager { } // 向下查找 let end = endIndex + // 闭合选区&&首字符为换行符时继续向下查找 + if (isCollapsed && elementList[startIndex].value === ZERO) { + end += 1 + } while (end < positionList.length) { const element = elementList[end] const nextElement = elementList[end + 1] From 7a6dd753e59bde4cb581ac215b038dd1cd08c96f Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Sat, 17 Aug 2024 18:44:15 +0800 Subject: [PATCH 28/42] fix: paper printing size setting #760 --- src/editor/core/command/CommandAdapt.ts | 5 ++- src/editor/utils/print.ts | 45 +++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/src/editor/core/command/CommandAdapt.ts b/src/editor/core/command/CommandAdapt.ts index 000baed..3cd51d8 100644 --- a/src/editor/core/command/CommandAdapt.ts +++ b/src/editor/core/command/CommandAdapt.ts @@ -1948,12 +1948,11 @@ export class CommandAdapt { } public async print() { - const { scale, printPixelRatio, paperDirection } = this.options + const { scale, printPixelRatio, paperDirection, width, height } = + this.options if (scale !== 1) { this.draw.setPageScale(1) } - const width = this.draw.getOriginalWidth() - const height = this.draw.getOriginalHeight() const base64List = await this.draw.getDataURL({ pixelRatio: printPixelRatio, mode: EditorMode.PRINT diff --git a/src/editor/utils/print.ts b/src/editor/utils/print.ts index b7860b5..7a9ef04 100644 --- a/src/editor/utils/print.ts +++ b/src/editor/utils/print.ts @@ -1,5 +1,35 @@ import { PaperDirection } from '../dataset/enum/Editor' +function convertPxToPaperSize(width: number, height: number) { + if (width === 1125 && height === 1593) { + return { + size: 'a3', + width: '297mm', + height: '420mm' + } + } + if (width === 794 && height === 1123) { + return { + size: 'a4', + width: '210mm', + height: '297mm' + } + } + if (width === 565 && height === 796) { + return { + size: 'a5', + width: '148mm', + height: '210mm' + } + } + // 其他默认不转换 + return { + size: '', + width: `${width}px`, + height: `${height}px` + } +} + export interface IPrintImageBase64Option { width: number height: number @@ -24,10 +54,17 @@ export function printImageBase64( const doc = contentWindow.document doc.open() const container = document.createElement('div') + const paperSize = convertPxToPaperSize(width, height) base64List.forEach(base64 => { const image = document.createElement('img') - image.style.width = `${width}px` - image.style.height = `${height}px` + image.style.width = + direction === PaperDirection.HORIZONTAL + ? paperSize.height + : paperSize.width + image.style.height = + direction === PaperDirection.HORIZONTAL + ? paperSize.width + : paperSize.height image.src = base64 container.append(image) }) @@ -39,7 +76,9 @@ export function printImageBase64( } @page { margin: 0; - size: ${direction === PaperDirection.HORIZONTAL ? `landscape` : `portrait`}; + size: ${paperSize.size} ${ + direction === PaperDirection.HORIZONTAL ? `landscape` : `portrait` + }; }` style.append(document.createTextNode(stylesheet)) setTimeout(() => { From d1a1aaa6ae08d8395df1df664d47bfe4fe821869 Mon Sep 17 00:00:00 2001 From: junestars <107550628+junestars@users.noreply.github.com> Date: Sat, 17 Aug 2024 22:00:18 +0800 Subject: [PATCH 29/42] feat: add location property to executeLocationControl api #753 Co-authored-by: Hufe921 --- docs/en/guide/command-execute.md | 2 +- docs/guide/command-execute.md | 2 +- src/editor/core/command/CommandAdapt.ts | 17 ++++++++++++++--- src/editor/dataset/enum/Common.ts | 5 +++++ src/editor/index.ts | 5 +++-- src/editor/interface/Control.ts | 5 +++++ 6 files changed, 29 insertions(+), 7 deletions(-) diff --git a/docs/en/guide/command-execute.md b/docs/en/guide/command-execute.md index f1a6d35..4b0aaa6 100644 --- a/docs/en/guide/command-execute.md +++ b/docs/en/guide/command-execute.md @@ -929,7 +929,7 @@ Feature: Positioning and activating control Usage: ```javascript -instance.command.executeLocationControl(controlId: string) +instance.command.executeLocationControl(controlId: string, options?: ILocationControlOption) ``` ## executeInsertControl diff --git a/docs/guide/command-execute.md b/docs/guide/command-execute.md index c62ad20..c28090b 100644 --- a/docs/guide/command-execute.md +++ b/docs/guide/command-execute.md @@ -929,7 +929,7 @@ instance.command.executeSetControlHighlight(payload: ISetControlHighlightOption) 用法: ```javascript -instance.command.executeLocationControl(controlId: string) +instance.command.executeLocationControl(controlId: string, options?: ILocationControlOption) ``` ## executeInsertControl diff --git a/src/editor/core/command/CommandAdapt.ts b/src/editor/core/command/CommandAdapt.ts index 3cd51d8..e848279 100644 --- a/src/editor/core/command/CommandAdapt.ts +++ b/src/editor/core/command/CommandAdapt.ts @@ -10,7 +10,7 @@ import { titleSizeMapping } from '../../dataset/constant/Title' import { defaultWatermarkOption } from '../../dataset/constant/Watermark' -import { ImageDisplay } from '../../dataset/enum/Common' +import { ImageDisplay, LocationPosition } from '../../dataset/enum/Common' import { ControlComponent } from '../../dataset/enum/Control' import { EditorContext, @@ -31,6 +31,7 @@ import { DeepRequired } from '../../interface/Common' import { IGetControlValueOption, IGetControlValueResult, + ILocationControlOption, ISetControlExtensionOption, ISetControlHighlightOption, ISetControlProperties, @@ -2468,7 +2469,8 @@ export class CommandAdapt { return this.draw.getControl().getList() } - public locationControl(controlId: string) { + public locationControl(controlId: string, options?: ILocationControlOption) { + const isLocationAfter = options?.position === LocationPosition.AFTER function location( elementList: IElement[], zone: EditorZone @@ -2502,7 +2504,16 @@ export class CommandAdapt { } } if (element?.controlId !== controlId) continue - const curIndex = i - 1 + let curIndex = i - 1 + if (isLocationAfter) { + curIndex -= 1 + if ( + element.controlComponent !== ControlComponent.PLACEHOLDER && + element.controlComponent !== ControlComponent.POSTFIX + ) { + continue + } + } return { zone, range: { diff --git a/src/editor/dataset/enum/Common.ts b/src/editor/dataset/enum/Common.ts index 0b310cf..4922f55 100644 --- a/src/editor/dataset/enum/Common.ts +++ b/src/editor/dataset/enum/Common.ts @@ -15,3 +15,8 @@ export enum ImageDisplay { FLOAT_TOP = 'float-top', FLOAT_BOTTOM = 'float-bottom' } + +export enum LocationPosition { + BEFORE = 'before', + AFTER = 'after' +} diff --git a/src/editor/index.ts b/src/editor/index.ts index 2cb398c..4cf9a1b 100644 --- a/src/editor/index.ts +++ b/src/editor/index.ts @@ -6,7 +6,7 @@ import { Command } from './core/command/Command' import { CommandAdapt } from './core/command/CommandAdapt' import { Listener } from './core/listener/Listener' import { RowFlex } from './dataset/enum/Row' -import { ImageDisplay } from './dataset/enum/Common' +import { ImageDisplay, LocationPosition } from './dataset/enum/Common' import { ElementType } from './dataset/enum/Element' import { formatElementList } from './utils/element' import { Register } from './core/register/Register' @@ -171,7 +171,8 @@ export { BackgroundRepeat, BackgroundSize, TextDecorationStyle, - LineNumberType + LineNumberType, + LocationPosition } // 对外类型 diff --git a/src/editor/interface/Control.ts b/src/editor/interface/Control.ts index 018d4cd..2d38ec7 100644 --- a/src/editor/interface/Control.ts +++ b/src/editor/interface/Control.ts @@ -1,3 +1,4 @@ +import { LocationPosition } from '../dataset/enum/Common' import { ControlType, ControlIndentation } from '../dataset/enum/Control' import { EditorZone } from '../dataset/enum/Editor' import { MoveDirection } from '../dataset/enum/Observer' @@ -168,3 +169,7 @@ export interface INextControlContext { export interface IInitNextControlOption { direction?: MoveDirection } + +export interface ILocationControlOption { + position: LocationPosition +} From c3754663cee25003a7021a6079dd46e2a3a0abd8 Mon Sep 17 00:00:00 2001 From: zhoujingfu <43539608+zhoujingfu@users.noreply.github.com> Date: Sun, 18 Aug 2024 16:34:50 +0800 Subject: [PATCH 30/42] feat: add radio and checkbox vertical align setting Co-authored-by: Hufe921 --- docs/en/guide/option.md | 4 +- docs/guide/option.md | 4 +- src/editor/core/draw/Draw.ts | 16 +++++- .../core/draw/particle/CheckboxParticle.ts | 52 +++++++++++++++---- src/editor/core/draw/particle/ListParticle.ts | 13 +++-- .../core/draw/particle/RadioParticle.ts | 52 +++++++++++++++---- src/editor/dataset/constant/Checkbox.ts | 4 +- src/editor/dataset/constant/Radio.ts | 4 +- src/editor/interface/Checkbox.ts | 3 ++ src/editor/interface/Radio.ts | 3 ++ 10 files changed, 126 insertions(+), 29 deletions(-) diff --git a/docs/en/guide/option.md b/docs/en/guide/option.md index 06bfe11..d8f8087 100644 --- a/docs/en/guide/option.md +++ b/docs/en/guide/option.md @@ -60,8 +60,8 @@ interface IEditorOption { wordBreak?: WordBreak // Word and punctuation breaks: No punctuation in the first line of the BREAK_WORD &The word is not split, and the line is folded after BREAK_ALL full according to the width of the character. default: BREAK_WORD watermark?: IWatermark // Watermark{data:string; color?:string; opacity?:number; size?:number; font?:string;} control?: IControlOption // Control {placeholderColor?:string; bracketColor?:string; prefix?:string; postfix?:string; borderWidth?: number; borderColor?: string;} - checkbox?: ICheckboxOption // Checkbox {width?:number; height?:number; gap?:number; lineWidth?:number; fillStyle?:string; strokeStyle?: string;} - radio?: IRadioOption // Radio {width?:number; height?:number; gap?:number; lineWidth?:number; fillStyle?:string; strokeStyle?: string;} + checkbox?: ICheckboxOption // Checkbox {width?:number; height?:number; gap?:number; lineWidth?:number; fillStyle?:string; strokeStyle?: string; verticalAlign?: VerticalAlign;} + radio?: IRadioOption // Radio {width?:number; height?:number; gap?:number; lineWidth?:number; fillStyle?:string; strokeStyle?: string; verticalAlign?: VerticalAlign;} cursor?: ICursorOption // Cursor style. {width?: number; color?: string; dragWidth?: number; dragColor?: string;} title?: ITitleOption // Title configuration.{ defaultFirstSize?: number; defaultSecondSize?: number; defaultThirdSize?: number defaultFourthSize?: number; defaultFifthSize?: number; defaultSixthSize?: number;} placeholder?: IPlaceholder // Placeholder text diff --git a/docs/guide/option.md b/docs/guide/option.md index 8fdc3c3..9736b42 100644 --- a/docs/guide/option.md +++ b/docs/guide/option.md @@ -60,8 +60,8 @@ interface IEditorOption { wordBreak?: WordBreak // 单词与标点断行:BREAK_WORD首行不出现标点&单词不拆分、BREAK_ALL按字符宽度撑满后折行。默认:BREAK_WORD watermark?: IWatermark // 水印信息。{data:string; color?:string; opacity?:number; size?:number; font?:string;} control?: IControlOption // 控件信息。 {placeholderColor?:string; bracketColor?:string; prefix?:string; postfix?:string; borderWidth?: number; borderColor?: string;} - checkbox?: ICheckboxOption // 复选框信息。{width?:number; height?:number; gap?:number; lineWidth?:number; fillStyle?:string; strokeStyle?: string;} - radio?: IRadioOption // 单选框信息。{width?:number; height?:number; gap?:number; lineWidth?:number; fillStyle?:string; strokeStyle?: string;} + checkbox?: ICheckboxOption // 复选框信息。{width?:number; height?:number; gap?:number; lineWidth?:number; fillStyle?:string; strokeStyle?: string; verticalAlign?: VerticalAlign;} + radio?: IRadioOption // 单选框信息。{width?:number; height?:number; gap?:number; lineWidth?:number; fillStyle?:string; strokeStyle?: string; verticalAlign?: VerticalAlign;} cursor?: ICursorOption // 光标样式。{width?: number; color?: string; dragWidth?: number; dragColor?: string;} title?: ITitleOption // 标题配置。{ defaultFirstSize?: number; defaultSecondSize?: number; defaultThirdSize?: number defaultFourthSize?: number; defaultFifthSize?: number; defaultSixthSize?: number;} placeholder?: IPlaceholder // 编辑器空白占位文本 diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index f0ca513..556208e 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -1912,13 +1912,25 @@ export class Draw { element.controlComponent === ControlComponent.CHECKBOX ) { this.textParticle.complete() - this.checkboxParticle.render(ctx, element, x, y + offsetY) + this.checkboxParticle.render({ + ctx, + x, + y: y + offsetY, + index: j, + row: curRow + }) } else if ( element.type === ElementType.RADIO || element.controlComponent === ControlComponent.RADIO ) { this.textParticle.complete() - this.radioParticle.render(ctx, element, x, y + offsetY) + this.radioParticle.render({ + ctx, + x, + y: y + offsetY, + index: j, + row: curRow + }) } else if (element.type === ElementType.TAB) { this.textParticle.complete() } else if ( diff --git a/src/editor/core/draw/particle/CheckboxParticle.ts b/src/editor/core/draw/particle/CheckboxParticle.ts index 1a5817b..1ec7faf 100644 --- a/src/editor/core/draw/particle/CheckboxParticle.ts +++ b/src/editor/core/draw/particle/CheckboxParticle.ts @@ -1,9 +1,19 @@ +import { NBSP, ZERO } from '../../../dataset/constant/Common' +import { VerticalAlign } from '../../../dataset/enum/VerticalAlign' import { DeepRequired } from '../../../interface/Common' import { IEditorOption } from '../../../interface/Editor' import { IElement } from '../../../interface/Element' -import { IRowElement } from '../../../interface/Row' +import { IRow, IRowElement } from '../../../interface/Row' import { Draw } from '../Draw' +interface ICheckboxRenderOption { + ctx: CanvasRenderingContext2D + x: number + y: number + row: IRow + index: number +} + export class CheckboxParticle { private draw: Draw private options: DeepRequired @@ -28,17 +38,41 @@ export class CheckboxParticle { }) } - public render( - ctx: CanvasRenderingContext2D, - element: IRowElement, - x: number, - y: number - ) { + public render(payload: ICheckboxRenderOption) { + const { ctx, x, index, row } = payload + let { y } = payload const { - checkbox: { gap, lineWidth, fillStyle, strokeStyle }, + checkbox: { gap, lineWidth, fillStyle, strokeStyle, verticalAlign }, scale } = this.options - const { metrics, checkbox } = element + const { metrics, checkbox } = row.elementList[index] + // 垂直布局设置 + if ( + verticalAlign === VerticalAlign.TOP || + verticalAlign === VerticalAlign.MIDDLE + ) { + let nextIndex = index + 1 + let nextElement: IRowElement | null = null + while (nextIndex < row.elementList.length) { + nextElement = row.elementList[nextIndex] + if (nextElement.value !== ZERO && nextElement.value !== NBSP) break + nextIndex++ + } + // 以后一个非空格元素为基准 + if (nextElement) { + const { + metrics: { boundingBoxAscent, boundingBoxDescent } + } = nextElement + const textHeight = boundingBoxAscent + boundingBoxDescent + if (textHeight > metrics.height) { + if (verticalAlign === VerticalAlign.TOP) { + y -= boundingBoxAscent - metrics.height + } else if (verticalAlign === VerticalAlign.MIDDLE) { + y -= (textHeight - metrics.height) / 2 + } + } + } + } // left top 四舍五入避免1像素问题 const left = Math.round(x + gap * scale) const top = Math.round(y - metrics.height + lineWidth) diff --git a/src/editor/core/draw/particle/ListParticle.ts b/src/editor/core/draw/particle/ListParticle.ts index 1548979..f4e1c49 100644 --- a/src/editor/core/draw/particle/ListParticle.ts +++ b/src/editor/core/draw/particle/ListParticle.ts @@ -199,9 +199,16 @@ export class ListParticle { height: height * scale } } - this.draw - .getCheckboxParticle() - .render(ctx, checkboxRowElement, x - gap * scale, y) + this.draw.getCheckboxParticle().render({ + ctx, + x: x - gap * scale, + y, + index: 0, + row: { + ...row, + elementList: [checkboxRowElement, ...row.elementList] + } + }) } else { let text = '' if (startElement.listType === ListType.UL) { diff --git a/src/editor/core/draw/particle/RadioParticle.ts b/src/editor/core/draw/particle/RadioParticle.ts index a6a2e25..1df91f3 100644 --- a/src/editor/core/draw/particle/RadioParticle.ts +++ b/src/editor/core/draw/particle/RadioParticle.ts @@ -1,9 +1,19 @@ +import { NBSP, ZERO } from '../../../dataset/constant/Common' +import { VerticalAlign } from '../../../dataset/enum/VerticalAlign' import { DeepRequired } from '../../../interface/Common' import { IEditorOption } from '../../../interface/Editor' import { IElement } from '../../../interface/Element' -import { IRowElement } from '../../../interface/Row' +import { IRow, IRowElement } from '../../../interface/Row' import { Draw } from '../Draw' +interface IRadioRenderOption { + ctx: CanvasRenderingContext2D + x: number + y: number + row: IRow + index: number +} + export class RadioParticle { private draw: Draw private options: DeepRequired @@ -28,17 +38,41 @@ export class RadioParticle { }) } - public render( - ctx: CanvasRenderingContext2D, - element: IRowElement, - x: number, - y: number - ) { + public render(payload: IRadioRenderOption) { + const { ctx, x, index, row } = payload + let { y } = payload const { - radio: { gap, lineWidth, fillStyle, strokeStyle }, + radio: { gap, lineWidth, fillStyle, strokeStyle, verticalAlign }, scale } = this.options - const { metrics, radio } = element + const { metrics, radio } = row.elementList[index] + // 垂直布局设置 + if ( + verticalAlign === VerticalAlign.TOP || + verticalAlign === VerticalAlign.MIDDLE + ) { + let nextIndex = index + 1 + let nextElement: IRowElement | null = null + while (nextIndex < row.elementList.length) { + nextElement = row.elementList[nextIndex] + if (nextElement.value !== ZERO && nextElement.value !== NBSP) break + nextIndex++ + } + // 以后一个非空格元素为基准 + if (nextElement) { + const { + metrics: { boundingBoxAscent, boundingBoxDescent } + } = nextElement + const textHeight = boundingBoxAscent + boundingBoxDescent + if (textHeight > metrics.height) { + if (verticalAlign === VerticalAlign.TOP) { + y -= boundingBoxAscent - metrics.height + } else if (verticalAlign === VerticalAlign.MIDDLE) { + y -= (textHeight - metrics.height) / 2 + } + } + } + } // left top 四舍五入避免1像素问题 const left = Math.round(x + gap * scale) const top = Math.round(y - metrics.height + lineWidth) diff --git a/src/editor/dataset/constant/Checkbox.ts b/src/editor/dataset/constant/Checkbox.ts index 3153027..5ff7b04 100644 --- a/src/editor/dataset/constant/Checkbox.ts +++ b/src/editor/dataset/constant/Checkbox.ts @@ -1,4 +1,5 @@ import { ICheckboxOption } from '../../interface/Checkbox' +import { VerticalAlign } from '../enum/VerticalAlign' export const defaultCheckboxOption: Readonly> = { width: 14, @@ -6,5 +7,6 @@ export const defaultCheckboxOption: Readonly> = { gap: 5, lineWidth: 1, fillStyle: '#5175f4', - strokeStyle: '#ffffff' + strokeStyle: '#ffffff', + verticalAlign: VerticalAlign.BOTTOM } diff --git a/src/editor/dataset/constant/Radio.ts b/src/editor/dataset/constant/Radio.ts index 118d783..a16c0e9 100644 --- a/src/editor/dataset/constant/Radio.ts +++ b/src/editor/dataset/constant/Radio.ts @@ -1,4 +1,5 @@ import { IRadioOption } from '../../interface/Radio' +import { VerticalAlign } from '../enum/VerticalAlign' export const defaultRadioOption: Readonly> = { width: 14, @@ -6,5 +7,6 @@ export const defaultRadioOption: Readonly> = { gap: 5, lineWidth: 1, fillStyle: '#5175f4', - strokeStyle: '#000000' + strokeStyle: '#000000', + verticalAlign: VerticalAlign.BOTTOM } diff --git a/src/editor/interface/Checkbox.ts b/src/editor/interface/Checkbox.ts index 13ad547..456bd24 100644 --- a/src/editor/interface/Checkbox.ts +++ b/src/editor/interface/Checkbox.ts @@ -1,3 +1,5 @@ +import { VerticalAlign } from '../dataset/enum/VerticalAlign' + export interface ICheckbox { value: boolean | null code?: string @@ -11,4 +13,5 @@ export interface ICheckboxOption { lineWidth?: number fillStyle?: string strokeStyle?: string + verticalAlign?: VerticalAlign } diff --git a/src/editor/interface/Radio.ts b/src/editor/interface/Radio.ts index 1f4fc61..95a42dc 100644 --- a/src/editor/interface/Radio.ts +++ b/src/editor/interface/Radio.ts @@ -1,3 +1,5 @@ +import { VerticalAlign } from '../dataset/enum/VerticalAlign' + export interface IRadio { value: boolean | null code?: string @@ -11,4 +13,5 @@ export interface IRadioOption { lineWidth?: number fillStyle?: string strokeStyle?: string + verticalAlign?: VerticalAlign } From 187498ed3dfba4386375d812d41b31c057ac3af8 Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Sun, 18 Aug 2024 17:43:30 +0800 Subject: [PATCH 31/42] feat: get context content width --- src/editor/core/command/CommandAdapt.ts | 2 +- src/editor/core/draw/Draw.ts | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/editor/core/command/CommandAdapt.ts b/src/editor/core/command/CommandAdapt.ts index e848279..1b888a4 100644 --- a/src/editor/core/command/CommandAdapt.ts +++ b/src/editor/core/command/CommandAdapt.ts @@ -789,7 +789,7 @@ export class CommandAdapt { const row = rowList[rowIndex] offsetX = row?.offsetX || 0 } - const innerWidth = this.draw.getOriginalInnerWidth() - offsetX + const innerWidth = this.draw.getContextInnerWidth() - offsetX // colgroup const colgroup: IColgroup[] = [] const colWidth = innerWidth / col diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index 556208e..833d3d1 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -390,6 +390,18 @@ export class Draw { return width - margins[1] - margins[3] } + public getContextInnerWidth(): number { + const positionContext = this.position.getPositionContext() + if (positionContext.isTable) { + const { index, trIndex, tdIndex } = positionContext + const elementList = this.getOriginalElementList() + const td = elementList[index!].trList![trIndex!].tdList[tdIndex!] + const tdPadding = this.getTdPadding() + return td!.width! - tdPadding[1] - tdPadding[3] + } + return this.getOriginalInnerWidth() + } + public getMargins(): IMargin { return this.getOriginalMargins().map(m => m * this.options.scale) } From c249b9eec215fe573b325d1430212f5f01c32099 Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Sun, 18 Aug 2024 18:23:31 +0800 Subject: [PATCH 32/42] fix: float image position when scaling the page #766 --- src/editor/core/draw/Draw.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index 833d3d1..fb3387c 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -2183,6 +2183,7 @@ export class Draw { ctx: CanvasRenderingContext2D, payload: IDrawFloatPayload ) { + const { scale } = this.options const floatPositionList = this.position.getFloatPositionList() const { imgDisplay, pageNo } = payload for (let e = 0; e < floatPositionList.length; e++) { @@ -2199,8 +2200,8 @@ export class Draw { this.imageParticle.render( ctx, element, - imgFloatPosition.x, - imgFloatPosition.y + imgFloatPosition.x * scale, + imgFloatPosition.y * scale ) } } From 8382a2b45c643d8d608263524f50f7ed785c1822 Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Sun, 18 Aug 2024 18:24:35 +0800 Subject: [PATCH 33/42] release: v0.9.90 --- CHANGELOG.md | 21 +++++++++++++++++++++ package.json | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f107550..5da7e70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +## [0.9.90](https://github.com/Hufe921/canvas-editor/compare/v0.9.89...v0.9.90) (2024-08-18) + + +### Bug Fixes + +* float image position when scaling the page #766 ([c249b9e](https://github.com/Hufe921/canvas-editor/commit/c249b9eec215fe573b325d1430212f5f01c32099)), closes [#766](https://github.com/Hufe921/canvas-editor/issues/766) +* get range paragraph info boundary error #758 ([4653fe7](https://github.com/Hufe921/canvas-editor/commit/4653fe7427f4a9ec40700f6a86ff284d1d46088a)), closes [#758](https://github.com/Hufe921/canvas-editor/issues/758) +* insert block element row flex error #754 ([136b1ff](https://github.com/Hufe921/canvas-editor/commit/136b1ffa55b7b0b78bf3d114400489e1bbab4f17)), closes [#754](https://github.com/Hufe921/canvas-editor/issues/754) +* paper printing size setting #760 ([7a6dd75](https://github.com/Hufe921/canvas-editor/commit/7a6dd753e59bde4cb581ac215b038dd1cd08c96f)), closes [#760](https://github.com/Hufe921/canvas-editor/issues/760) +* set editor mode option error #755 ([500cec3](https://github.com/Hufe921/canvas-editor/commit/500cec3e0b63e012f6572dcedb071befa114956e)), closes [#755](https://github.com/Hufe921/canvas-editor/issues/755) +* set row flex boundary error when deleting element ([2f272de](https://github.com/Hufe921/canvas-editor/commit/2f272dee58169607783e4cfd1347534f49db0834)) + + +### Features + +* add location property to executeLocationControl api #753 ([d1a1aaa](https://github.com/Hufe921/canvas-editor/commit/d1a1aaa6ae08d8395df1df664d47bfe4fe821869)), closes [#753](https://github.com/Hufe921/canvas-editor/issues/753) +* add radio and checkbox vertical align setting ([c375466](https://github.com/Hufe921/canvas-editor/commit/c3754663cee25003a7021a6079dd46e2a3a0abd8)) +* get context content width ([187498e](https://github.com/Hufe921/canvas-editor/commit/187498ed3dfba4386375d812d41b31c057ac3af8)) + + + ## [0.9.89](https://github.com/Hufe921/canvas-editor/compare/v0.9.88...v0.9.89) (2024-08-09) diff --git a/package.json b/package.json index 324443e..ffc7cda 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@hufe921/canvas-editor", "author": "Hufe", "license": "MIT", - "version": "0.9.89", + "version": "0.9.90", "description": "rich text editor by canvas/svg", "publishConfig": { "registry": "https://registry.npmjs.org/", From 31b76b6fb6640ea75383569cb16ad1b5a2ccf25f Mon Sep 17 00:00:00 2001 From: zhoujingfu <43539608+zhoujingfu@users.noreply.github.com> Date: Mon, 19 Aug 2024 19:36:54 +0800 Subject: [PATCH 34/42] feat: hit checkbox/radio control when click on label #651 Co-authored-by: Hufe921 --- src/editor/core/event/handlers/mousedown.ts | 93 +++++++++++++-------- 1 file changed, 60 insertions(+), 33 deletions(-) diff --git a/src/editor/core/event/handlers/mousedown.ts b/src/editor/core/event/handlers/mousedown.ts index c1bf0ac..8f67026 100644 --- a/src/editor/core/event/handlers/mousedown.ts +++ b/src/editor/core/event/handlers/mousedown.ts @@ -2,12 +2,16 @@ import { ImageDisplay } from '../../../dataset/enum/Common' import { EditorMode } from '../../../dataset/enum/Editor' import { ElementType } from '../../../dataset/enum/Element' import { MouseEventButton } from '../../../dataset/enum/Event' +import { ControlComponent } from '../../../dataset/enum/Control' +import { ControlType } from '../../../dataset/enum/Control' import { IPreviewerDrawOption } from '../../../interface/Previewer' import { deepClone } from '../../../utils' import { isMod } from '../../../utils/hotkey' import { CheckboxControl } from '../../draw/control/checkbox/CheckboxControl' import { RadioControl } from '../../draw/control/radio/RadioControl' import { CanvasEvent } from '../CanvasEvent' +import { IElement } from '../../../interface/Element' +import { Draw } from '../../draw/Draw' export function setRangeCache(host: CanvasEvent) { const draw = host.getDraw() @@ -21,6 +25,42 @@ export function setRangeCache(host: CanvasEvent) { host.cachePositionContext = position.getPositionContext() } +export function hitCheckbox(element: IElement, draw: Draw) { + const { checkbox, control } = element + // 复选框不在控件内独立控制 + if (!control) { + draw.getCheckboxParticle().setSelect(element) + } else { + const codes = control?.code ? control.code.split(',') : [] + if (checkbox?.value) { + const codeIndex = codes.findIndex(c => c === checkbox.code) + codes.splice(codeIndex, 1) + } else { + if (checkbox?.code) { + codes.push(checkbox.code) + } + } + const activeControl = draw.getControl().getActiveControl() + if (activeControl instanceof CheckboxControl) { + activeControl.setSelect(codes) + } + } +} + +export function hitRadio(element: IElement, draw: Draw) { + const { radio, control } = element + // 单选框不在控件内独立控制 + if (!control) { + draw.getRadioParticle().setSelect(element) + } else { + const codes = radio?.code ? [radio.code] : [] + const activeControl = draw.getControl().getActiveControl() + if (activeControl instanceof RadioControl) { + activeControl.setSelect(codes) + } + } +} + export function mousedown(evt: MouseEvent, host: CanvasEvent) { if (evt.button === MouseEventButton.RIGHT) return const draw = host.getDraw() @@ -100,40 +140,27 @@ export function mousedown(evt: MouseEvent, host: CanvasEvent) { rangeManager.setRange(startIndex, endIndex) position.setCursorPosition(positionList[curIndex]) // 复选框 - const isSetCheckbox = isDirectHitCheckbox && !isReadonly - // 单选框 - const isSetRadio = isDirectHitRadio && !isReadonly - if (isSetCheckbox) { - const { checkbox, control } = curElement - // 复选框不在控件内独立控制 - if (!control) { - draw.getCheckboxParticle().setSelect(curElement) - } else { - const codes = control?.code ? control.code.split(',') : [] - if (checkbox?.value) { - const codeIndex = codes.findIndex(c => c === checkbox.code) - codes.splice(codeIndex, 1) - } else { - if (checkbox?.code) { - codes.push(checkbox.code) - } - } - const activeControl = draw.getControl().getActiveControl() - if (activeControl instanceof CheckboxControl) { - activeControl.setSelect(codes) - } - } - } else if (isSetRadio) { - const { control, radio } = curElement - // 单选框不在控件内独立控制 - if (!control) { - draw.getRadioParticle().setSelect(curElement) - } else { - const codes = radio?.code ? [radio.code] : [] - const activeControl = draw.getControl().getActiveControl() - if (activeControl instanceof RadioControl) { - activeControl.setSelect(codes) + if (isDirectHitCheckbox && !isReadonly) { + hitCheckbox(curElement, draw) + } else if (isDirectHitRadio && !isReadonly) { + hitRadio(curElement, draw) + } else if ( + curElement.controlComponent === ControlComponent.VALUE && + (curElement.control?.type === ControlType.CHECKBOX || + curElement.control?.type === ControlType.RADIO) + ) { + // 向左查找 + let preIndex = curIndex + while (preIndex > 0) { + const preElement = elementList[preIndex] + if (preElement.controlComponent === ControlComponent.CHECKBOX) { + hitCheckbox(preElement, draw) + break + } else if (preElement.controlComponent === ControlComponent.RADIO) { + hitRadio(preElement, draw) + break } + preIndex-- } } else { draw.render({ From f65ff87e44728322417d235d2347ceca25cc6667 Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Tue, 20 Aug 2024 18:44:32 +0800 Subject: [PATCH 35/42] fix: format different types of line breaks #769 --- src/editor/utils/element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/utils/element.ts b/src/editor/utils/element.ts index a6e2789..e766a7d 100644 --- a/src/editor/utils/element.ts +++ b/src/editor/utils/element.ts @@ -451,7 +451,7 @@ export function formatElementList( } el = elementList[i] } - if (el.value === '\n') { + if (el.value === '\n' || el.value == '\r\n') { el.value = ZERO } if (el.type === ElementType.IMAGE || el.type === ElementType.BLOCK) { From 528517094373b5fa9ca2145bb259889db865a588 Mon Sep 17 00:00:00 2001 From: zjx-git <53986795+zjx-git@users.noreply.github.com> Date: Wed, 21 Aug 2024 17:51:55 +0800 Subject: [PATCH 36/42] fix: set row margin boundary error --- src/editor/core/draw/Draw.ts | 4 ++-- src/editor/core/range/RangeManager.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index fb3387c..2e52b5b 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -1188,7 +1188,7 @@ export class Draw { const { defaultBasicRowMarginHeight, defaultRowMargin, scale } = this.options return ( - defaultBasicRowMarginHeight * (el.rowMargin || defaultRowMargin) * scale + defaultBasicRowMarginHeight * (el.rowMargin ?? defaultRowMargin) * scale ) } @@ -1227,7 +1227,7 @@ export class Draw { const curRow: IRow = rowList[rowList.length - 1] const element = elementList[i] const rowMargin = - defaultBasicRowMarginHeight * (element.rowMargin || defaultRowMargin) + defaultBasicRowMarginHeight * (element.rowMargin ?? defaultRowMargin) const metrics: IElementMetrics = { width: 0, height: 0, diff --git a/src/editor/core/range/RangeManager.ts b/src/editor/core/range/RangeManager.ts index a377f24..f0ad4b8 100644 --- a/src/editor/core/range/RangeManager.ts +++ b/src/editor/core/range/RangeManager.ts @@ -430,7 +430,7 @@ export class RangeManager { const color = curElement.color || null const highlight = curElement.highlight || null const rowFlex = curElement.rowFlex || null - const rowMargin = curElement.rowMargin || this.options.defaultRowMargin + const rowMargin = curElement.rowMargin ?? this.options.defaultRowMargin const dashArray = curElement.dashArray || [] const level = curElement.level || null const listType = curElement.listType || null From f62a315a7bd04e49e9e17bc7737ccd1f0e751d69 Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Fri, 23 Aug 2024 19:00:51 +0800 Subject: [PATCH 37/42] fix: format initial data boundary error #771 #784 --- src/editor/core/command/CommandAdapt.ts | 2 +- src/editor/core/draw/Draw.ts | 3 ++- src/editor/core/draw/control/Control.ts | 3 ++- src/editor/core/draw/frame/Placeholder.ts | 3 ++- src/editor/index.ts | 3 ++- src/editor/utils/element.ts | 33 ++++++++++++++--------- 6 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/editor/core/command/CommandAdapt.ts b/src/editor/core/command/CommandAdapt.ts index 1b888a4..8ab6588 100644 --- a/src/editor/core/command/CommandAdapt.ts +++ b/src/editor/core/command/CommandAdapt.ts @@ -810,7 +810,7 @@ export class CommandAdapt { tdList.push({ colspan: 1, rowspan: 1, - value: [{ value: ZERO, size: 16 }] + value: [] }) } trList.push(tr) diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index 2e52b5b..a0830a4 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -1080,7 +1080,8 @@ export class Draw { pageComponentData.forEach(data => { if (!data) return formatElementList(data, { - editorOptions: this.options + editorOptions: this.options, + isForceCompensation: true }) }) this.setEditorData({ diff --git a/src/editor/core/draw/control/Control.ts b/src/editor/core/draw/control/Control.ts index 7c3d97f..4773787 100644 --- a/src/editor/core/draw/control/Control.ts +++ b/src/editor/core/draw/control/Control.ts @@ -892,7 +892,8 @@ export class Control { const elementList = zipElementList(pageComponentData[pageComponentKey]!) pageComponentData[pageComponentKey] = elementList formatElementList(elementList, { - editorOptions: this.options + editorOptions: this.options, + isForceCompensation: true }) } this.draw.setEditorData(pageComponentData) diff --git a/src/editor/core/draw/frame/Placeholder.ts b/src/editor/core/draw/frame/Placeholder.ts index a6e4eb1..db21327 100644 --- a/src/editor/core/draw/frame/Placeholder.ts +++ b/src/editor/core/draw/frame/Placeholder.ts @@ -84,7 +84,8 @@ export class Placeholder { } ] formatElementList(this.elementList, { - editorOptions: this.options + editorOptions: this.options, + isForceCompensation: true }) // 计算 this._compute() diff --git a/src/editor/index.ts b/src/editor/index.ts index 4cf9a1b..c7f60a3 100644 --- a/src/editor/index.ts +++ b/src/editor/index.ts @@ -89,7 +89,8 @@ export default class Editor { ] pageComponentData.forEach(elementList => { formatElementList(elementList, { - editorOptions + editorOptions, + isForceCompensation: true }) }) // 监听 diff --git a/src/editor/utils/element.ts b/src/editor/utils/element.ts index e766a7d..434f7f8 100644 --- a/src/editor/utils/element.ts +++ b/src/editor/utils/element.ts @@ -65,7 +65,8 @@ export function unzipElementList(elementList: IElement[]): IElement[] { } interface IFormatElementListOption { - isHandleFirstElement?: boolean + isHandleFirstElement?: boolean // 根据上下文确定首字符处理逻辑(处理首字符补偿) + isForceCompensation?: boolean // 强制补偿字符 editorOptions: DeepRequired } @@ -73,17 +74,19 @@ export function formatElementList( elementList: IElement[], options: IFormatElementListOption ) { - const { isHandleFirstElement, editorOptions } = { - isHandleFirstElement: true, - ...options - } + const { + isHandleFirstElement = true, + isForceCompensation = false, + editorOptions + } = options const startElement = elementList[0] // 非首字符零宽节点文本元素则补偿-列表元素内部会补偿此处忽略 if ( - isHandleFirstElement && - startElement?.type !== ElementType.LIST && - ((startElement?.type && startElement.type !== ElementType.TEXT) || - !START_LINE_BREAK_REG.test(startElement?.value)) + isForceCompensation || + (isHandleFirstElement && + startElement?.type !== ElementType.LIST && + ((startElement?.type && startElement.type !== ElementType.TEXT) || + !START_LINE_BREAK_REG.test(startElement?.value))) ) { elementList.unshift({ value: ZERO @@ -100,7 +103,8 @@ export function formatElementList( const valueList = el.valueList || [] formatElementList(valueList, { ...options, - isHandleFirstElement: false + isHandleFirstElement: false, + isForceCompensation: false }) // 追加节点 if (valueList.length) { @@ -134,7 +138,8 @@ export function formatElementList( const valueList = el.valueList || [] formatElementList(valueList, { ...options, - isHandleFirstElement: true + isHandleFirstElement: true, + isForceCompensation: false }) // 追加节点 if (valueList.length) { @@ -170,7 +175,8 @@ export function formatElementList( td.id = tdId formatElementList(td.value, { ...options, - isHandleFirstElement: true + isHandleFirstElement: true, + isForceCompensation: true }) for (let v = 0; v < td.value.length; v++) { const value = td.value[v] @@ -385,7 +391,8 @@ export function formatElementList( } formatElementList(valueList, { ...options, - isHandleFirstElement: false + isHandleFirstElement: false, + isForceCompensation: false }) for (let v = 0; v < valueList.length; v++) { const element = valueList[v] From 3f6bdf6fcb583ab370cb5abe3eafebf6100f415e Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Sat, 24 Aug 2024 22:09:00 +0800 Subject: [PATCH 38/42] feat: add page border --- docs/en/guide/option.md | 12 ++++++ docs/guide/option.md | 12 ++++++ src/editor/core/draw/Draw.ts | 18 ++++++++- src/editor/core/draw/frame/PageBorder.ts | 47 +++++++++++++++++++++++ src/editor/dataset/constant/PageBorder.ts | 8 ++++ src/editor/interface/Editor.ts | 2 + src/editor/interface/PageBorder.ts | 8 ++++ src/editor/utils/option.ts | 9 ++++- 8 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 src/editor/core/draw/frame/PageBorder.ts create mode 100644 src/editor/dataset/constant/PageBorder.ts create mode 100644 src/editor/interface/PageBorder.ts diff --git a/docs/en/guide/option.md b/docs/en/guide/option.md index d8f8087..7c7c9eb 100644 --- a/docs/en/guide/option.md +++ b/docs/en/guide/option.md @@ -72,6 +72,7 @@ interface IEditorOption { lineBreak?: ILineBreakOption // LineBreak option. {disabled?:boolean; color?:string; lineWidth?:number;} separator?: ISeparatorOption // Separator option. {lineWidth?:number; strokeStyle?:string;} lineNumber?: ILineNumberOption // LineNumber option. {size?:number; font?:string; color?:string; disabled?:boolean; right?:number} + pageBorder?: IPageBorderOption // PageBorder option. {color?:string; lineWidth:number; padding?:IPadding; disabled?:boolean;} } ``` @@ -159,3 +160,14 @@ interface ILineNumberOption { type?: LineNumberType // Number type (renumber each page, consecutive numbering). default: continuity } ``` + +## PageBorder Configuration + +```typescript +interface IPageBorderOption { + color?: string // color. default: #000000 + lineWidth?: number // line width. default: 1 + padding?: IPadding // padding. default: [0, 0, 0, 0] + disabled?: boolean // Whether to disable. default: true +} +``` diff --git a/docs/guide/option.md b/docs/guide/option.md index 9736b42..d30fc41 100644 --- a/docs/guide/option.md +++ b/docs/guide/option.md @@ -72,6 +72,7 @@ interface IEditorOption { lineBreak?: ILineBreakOption // 换行符配置。{disabled?:boolean; color?:string; lineWidth?:number;} separator?: ISeparatorOption // 分隔符配置。{lineWidth?:number; strokeStyle?:string;} lineNumber?: ILineNumberOption // 行号配置。{size?:number; font?:string; color?:string; disabled?:boolean; right?:number} + pageBorder?: IPageBorderOption // 页面边框配置。{color?:string; lineWidth:number; padding?:IPadding; disabled?:boolean;} } ``` @@ -159,3 +160,14 @@ interface ILineNumberOption { type?: LineNumberType // 编号类型(每页重新编号、连续编号)。默认:连续编号 } ``` + +## 页面边框配置 + +```typescript +interface IPageBorderOption { + color?: string // 颜色。默认:#000000 + lineWidth?: number // 宽度。默认:1 + padding?: IPadding // 距离正文内边距。默认:[0, 5, 0, 5] + disabled?: boolean // 是否禁用。默认:true +} +``` diff --git a/src/editor/core/draw/Draw.ts b/src/editor/core/draw/Draw.ts index a0830a4..1625281 100644 --- a/src/editor/core/draw/Draw.ts +++ b/src/editor/core/draw/Draw.ts @@ -102,6 +102,7 @@ import { PUNCTUATION_REG } from '../../dataset/constant/Regular' import { LineBreakParticle } from './particle/LineBreakParticle' import { MouseObserver } from '../observer/MouseObserver' import { LineNumber } from './frame/LineNumber' +import { PageBorder } from './frame/PageBorder' export class Draw { private container: HTMLDivElement @@ -156,6 +157,7 @@ export class Draw { private listParticle: ListParticle private lineBreakParticle: LineBreakParticle private control: Control + private pageBorder: PageBorder private workerManager: WorkerManager private scrollObserver: ScrollObserver private selectionObserver: SelectionObserver @@ -232,6 +234,7 @@ export class Draw { this.listParticle = new ListParticle(this) this.lineBreakParticle = new LineBreakParticle(this) this.control = new Control(this) + this.pageBorder = new PageBorder(this) this.scrollObserver = new ScrollObserver(this) this.selectionObserver = new SelectionObserver(this) @@ -2222,8 +2225,15 @@ export class Draw { private _drawPage(payload: IDrawPagePayload) { const { elementList, positionList, rowList, pageNo } = payload - const { inactiveAlpha, pageMode, header, footer, pageNumber, lineNumber } = - this.options + const { + inactiveAlpha, + pageMode, + header, + footer, + pageNumber, + lineNumber, + pageBorder + } = this.options const innerWidth = this.getInnerWidth() const ctx = this.ctxList[pageNo] // 判断当前激活区域-非正文区域时元素透明度降低 @@ -2288,6 +2298,10 @@ export class Draw { if (!lineNumber.disabled) { this.lineNumber.render(ctx, pageNo) } + // 绘制页面边框 + if (!pageBorder.disabled) { + this.pageBorder.render(ctx) + } } private _disconnectLazyRender() { diff --git a/src/editor/core/draw/frame/PageBorder.ts b/src/editor/core/draw/frame/PageBorder.ts new file mode 100644 index 0000000..3a6da9d --- /dev/null +++ b/src/editor/core/draw/frame/PageBorder.ts @@ -0,0 +1,47 @@ +import { DeepRequired } from '../../../interface/Common' +import { IEditorOption } from '../../../interface/Editor' +import { Draw } from '../Draw' +import { Footer } from './Footer' +import { Header } from './Header' + +export class PageBorder { + private draw: Draw + private header: Header + private footer: Footer + private options: DeepRequired + + constructor(draw: Draw) { + this.draw = draw + this.header = draw.getHeader() + this.footer = draw.getFooter() + this.options = draw.getOptions() + } + + public render(ctx: CanvasRenderingContext2D) { + const { + scale, + pageBorder: { color, lineWidth, padding } + } = this.options + ctx.save() + ctx.translate(0.5, 0.5) + ctx.strokeStyle = color + ctx.lineWidth = lineWidth * scale + const margins = this.draw.getMargins() + // x:左边距 - 左距离正文距离 + const x = margins[3] - padding[3] * scale + // y:页眉上边距 + 页眉高度 - 上距离正文距离 + const y = margins[0] + this.header.getExtraHeight() - padding[0] * scale + // width:页面宽度 + 左右距离正文距离 + const width = this.draw.getInnerWidth() + (padding[1] + padding[3]) * scale + // height:页面高度 - 正文起始位置 - 页脚高度 - 下边距 - 下距离正文距离 + const height = + this.draw.getHeight() - + y - + this.footer.getExtraHeight() - + margins[2] + + padding[2] * scale + ctx.rect(x, y, width, height) + ctx.stroke() + ctx.restore() + } +} diff --git a/src/editor/dataset/constant/PageBorder.ts b/src/editor/dataset/constant/PageBorder.ts new file mode 100644 index 0000000..5751c37 --- /dev/null +++ b/src/editor/dataset/constant/PageBorder.ts @@ -0,0 +1,8 @@ +import { IPageBorderOption } from '../../interface/PageBorder' + +export const defaultPageBorderOption: Readonly> = { + color: '#000000', + lineWidth: 1, + padding: [0, 5, 0, 5], + disabled: true +} diff --git a/src/editor/interface/Editor.ts b/src/editor/interface/Editor.ts index 8be2278..f926eca 100644 --- a/src/editor/interface/Editor.ts +++ b/src/editor/interface/Editor.ts @@ -25,6 +25,7 @@ import { IZoneOption } from './Zone' import { ISeparatorOption } from './Separator' import { ITableOption } from './table/Table' import { ILineNumberOption } from './LineNumber' +import { IPageBorderOption } from './PageBorder' export interface IEditorData { header?: IElement[] @@ -91,6 +92,7 @@ export interface IEditorOption { lineBreak?: ILineBreakOption separator?: ISeparatorOption lineNumber?: ILineNumberOption + pageBorder?: IPageBorderOption } export interface IEditorResult { diff --git a/src/editor/interface/PageBorder.ts b/src/editor/interface/PageBorder.ts new file mode 100644 index 0000000..1fe8d25 --- /dev/null +++ b/src/editor/interface/PageBorder.ts @@ -0,0 +1,8 @@ +import { IPadding } from './Common' + +export interface IPageBorderOption { + color?: string + lineWidth?: number + padding?: IPadding + disabled?: boolean +} diff --git a/src/editor/utils/option.ts b/src/editor/utils/option.ts index 7441fd0..c4a14a5 100644 --- a/src/editor/utils/option.ts +++ b/src/editor/utils/option.ts @@ -37,6 +37,8 @@ import { ITitleOption } from '../interface/Title' import { IWatermark } from '../interface/Watermark' import { IZoneOption } from '../interface/Zone' import { ILineNumberOption } from '../interface/LineNumber' +import { IPageBorderOption } from '../interface/PageBorder' +import { defaultPageBorderOption } from '../dataset/constant/PageBorder' import { EditorMode, PageMode, @@ -120,6 +122,10 @@ export function mergeOption( ...defaultLineNumberOption, ...options.lineNumber } + const pageBorderOptions: Required = { + ...defaultPageBorderOption, + ...options.pageBorder + } return { mode: EditorMode.EDIT, @@ -180,6 +186,7 @@ export function mergeOption( background: backgroundOptions, lineBreak: lineBreakOptions, separator: separatorOptions, - lineNumber: lineNumberOptions + lineNumber: lineNumberOptions, + pageBorder: pageBorderOptions } } From 059cddf7e45eeceeed3c93e3d5fce268b43a4c38 Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Sun, 25 Aug 2024 20:43:30 +0800 Subject: [PATCH 39/42] release: v0.9.91 --- CHANGELOG.md | 17 +++++++++++++++++ package.json | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5da7e70..e465cb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +## [0.9.91](https://github.com/Hufe921/canvas-editor/compare/v0.9.90...v0.9.91) (2024-08-25) + + +### Bug Fixes + +* format different types of line breaks #769 ([f65ff87](https://github.com/Hufe921/canvas-editor/commit/f65ff87e44728322417d235d2347ceca25cc6667)), closes [#769](https://github.com/Hufe921/canvas-editor/issues/769) +* format initial data boundary error #771 #784 ([f62a315](https://github.com/Hufe921/canvas-editor/commit/f62a315a7bd04e49e9e17bc7737ccd1f0e751d69)), closes [#771](https://github.com/Hufe921/canvas-editor/issues/771) [#784](https://github.com/Hufe921/canvas-editor/issues/784) +* set row margin boundary error ([5285170](https://github.com/Hufe921/canvas-editor/commit/528517094373b5fa9ca2145bb259889db865a588)) + + +### Features + +* add page border ([3f6bdf6](https://github.com/Hufe921/canvas-editor/commit/3f6bdf6fcb583ab370cb5abe3eafebf6100f415e)) +* hit checkbox/radio control when click on label #651 ([31b76b6](https://github.com/Hufe921/canvas-editor/commit/31b76b6fb6640ea75383569cb16ad1b5a2ccf25f)), closes [#651](https://github.com/Hufe921/canvas-editor/issues/651) + + + ## [0.9.90](https://github.com/Hufe921/canvas-editor/compare/v0.9.89...v0.9.90) (2024-08-18) diff --git a/package.json b/package.json index ffc7cda..e08414a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@hufe921/canvas-editor", "author": "Hufe", "license": "MIT", - "version": "0.9.90", + "version": "0.9.91", "description": "rich text editor by canvas/svg", "publishConfig": { "registry": "https://registry.npmjs.org/", From 91c68e8dcd117e3189bbb9385fca4c42b2074c23 Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Mon, 26 Aug 2024 19:26:28 +0800 Subject: [PATCH 40/42] docs: update plugin markdown #789 --- docs/en/guide/plugin-custom.md | 2 +- docs/guide/plugin-custom.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/guide/plugin-custom.md b/docs/en/guide/plugin-custom.md index 05d519f..a0df005 100644 --- a/docs/en/guide/plugin-custom.md +++ b/docs/en/guide/plugin-custom.md @@ -21,5 +21,5 @@ export function myPlugin(editor: Editor, options?: Option) { ## Use Plugin ```javascript -instance.add(myPlugin, options?: Option) +instance.use(myPlugin, options?: Option) ``` diff --git a/docs/guide/plugin-custom.md b/docs/guide/plugin-custom.md index 6bd54f6..1b104ed 100644 --- a/docs/guide/plugin-custom.md +++ b/docs/guide/plugin-custom.md @@ -21,5 +21,5 @@ export function myPlugin(editor: Editor, options?: Option) { ## 使用插件 ```javascript -instance.add(myPlugin, options?: Option) +instance.use(myPlugin, options?: Option) ``` From 3c598bae1a4fb10d74fd330e46f95aa386edc956 Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Tue, 27 Aug 2024 20:26:04 +0800 Subject: [PATCH 41/42] docs: update command markdown #792 --- docs/en/guide/command-execute.md | 2 +- docs/guide/command-execute.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/guide/command-execute.md b/docs/en/guide/command-execute.md index 4b0aaa6..241022d 100644 --- a/docs/en/guide/command-execute.md +++ b/docs/en/guide/command-execute.md @@ -679,7 +679,7 @@ Feature: Change how image rows are displayed Usage: ```javascript -instance.command.executeSaveAsImageElement(element: IElement, display: ImageDisplay) +instance.command.executeChangeImageDisplay(element: IElement, display: ImageDisplay) ``` ## executePageMode diff --git a/docs/guide/command-execute.md b/docs/guide/command-execute.md index c28090b..dd51078 100644 --- a/docs/guide/command-execute.md +++ b/docs/guide/command-execute.md @@ -679,7 +679,7 @@ instance.command.executeSaveAsImageElement() 用法: ```javascript -instance.command.executeSaveAsImageElement(element: IElement, display: ImageDisplay) +instance.command.executeChangeImageDisplay(element: IElement, display: ImageDisplay) ``` ## executePageMode From 3c176318e079d8f793367df552c1a3c6a6294840 Mon Sep 17 00:00:00 2001 From: Hufe921 Date: Wed, 28 Aug 2024 20:58:41 +0800 Subject: [PATCH 42/42] feat: add executeFocus api #796 --- docs/en/guide/command-execute.md | 10 ++++++++++ docs/guide/command-execute.md | 10 ++++++++++ src/editor/core/command/Command.ts | 2 ++ src/editor/core/command/CommandAdapt.ts | 21 +++++++++++++++++++++ src/editor/interface/Editor.ts | 6 +++++- 5 files changed, 48 insertions(+), 1 deletion(-) diff --git a/docs/en/guide/command-execute.md b/docs/en/guide/command-execute.md index 241022d..0fdf7e4 100644 --- a/docs/en/guide/command-execute.md +++ b/docs/en/guide/command-execute.md @@ -961,3 +961,13 @@ Usage: ```javascript instance.command.executeInsertTitle(payload: IElement) ``` + +## executeFocus + +Feature: focus + +Usage: + +```javascript +instance.command.executeFocus(payload?: IFocusOption) +``` diff --git a/docs/guide/command-execute.md b/docs/guide/command-execute.md index dd51078..b7a82ff 100644 --- a/docs/guide/command-execute.md +++ b/docs/guide/command-execute.md @@ -961,3 +961,13 @@ instance.command.executeUpdateOptions(payload: IUpdateOption) ```javascript instance.command.executeInsertTitle(payload: IElement) ``` + +## executeFocus + +功能:光标聚焦 + +用法: + +```javascript +instance.command.executeFocus(payload?: IFocusOption) +``` diff --git a/src/editor/core/command/Command.ts b/src/editor/core/command/Command.ts index aadab4d..38076e7 100644 --- a/src/editor/core/command/Command.ts +++ b/src/editor/core/command/Command.ts @@ -95,6 +95,7 @@ export class Command { public executeInsertControl: CommandAdapt['insertControl'] public executeUpdateOptions: CommandAdapt['updateOptions'] public executeInsertTitle: CommandAdapt['insertTitle'] + public executeFocus: CommandAdapt['focus'] public getCatalog: CommandAdapt['getCatalog'] public getImage: CommandAdapt['getImage'] public getOptions: CommandAdapt['getOptions'] @@ -214,6 +215,7 @@ export class Command { this.executeSetZone = adapt.setZone.bind(adapt) this.executeUpdateOptions = adapt.updateOptions.bind(adapt) this.executeInsertTitle = adapt.insertTitle.bind(adapt) + this.executeFocus = adapt.focus.bind(adapt) // 获取 this.getImage = adapt.getImage.bind(adapt) this.getOptions = adapt.getOptions.bind(adapt) diff --git a/src/editor/core/command/CommandAdapt.ts b/src/editor/core/command/CommandAdapt.ts index 8ab6588..68b7b31 100644 --- a/src/editor/core/command/CommandAdapt.ts +++ b/src/editor/core/command/CommandAdapt.ts @@ -22,6 +22,7 @@ import { import { ElementType } from '../../dataset/enum/Element' import { ElementStyleKey } from '../../dataset/enum/ElementStyle' import { ListStyle, ListType } from '../../dataset/enum/List' +import { MoveDirection } from '../../dataset/enum/Observer' import { RowFlex } from '../../dataset/enum/Row' import { TableBorder, TdBorder, TdSlash } from '../../dataset/enum/table/Table' import { TitleLevel } from '../../dataset/enum/Title' @@ -52,6 +53,7 @@ import { IEditorOption, IEditorResult, IEditorText, + IFocusOption, ISetValueOption, IUpdateOption } from '../../interface/Editor' @@ -2726,4 +2728,23 @@ export class CommandAdapt { // 插入标题 this.draw.insertElementList([cloneElement]) } + + public focus(payload?: IFocusOption) { + const { position = LocationPosition.AFTER } = payload || {} + const curIndex = + position === LocationPosition.BEFORE + ? 0 + : this.draw.getOriginalMainElementList().length - 1 + this.range.setRange(curIndex, curIndex) + this.draw.render({ + curIndex, + isCompute: false, + isSubmitHistory: false + }) + const positionList = this.draw.getPosition().getPositionList() + this.draw.getCursor().moveCursorToVisible({ + cursorPosition: positionList[curIndex], + direction: MoveDirection.DOWN + }) + } } diff --git a/src/editor/interface/Editor.ts b/src/editor/interface/Editor.ts index f926eca..7d26670 100644 --- a/src/editor/interface/Editor.ts +++ b/src/editor/interface/Editor.ts @@ -1,4 +1,4 @@ -import { IElement } from '..' +import { IElement, LocationPosition } from '..' import { EditorMode, PageMode, @@ -125,3 +125,7 @@ export type IUpdateOption = Omit< export interface ISetValueOption { isSetCursor?: boolean } + +export interface IFocusOption { + position?: LocationPosition +}