feat: add checkbox list #385

pr675
Hufe 2 years ago committed by GitHub
parent 237e570179
commit a546262b9d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -185,6 +185,12 @@
<li>________</li> <li>________</li>
</ol> </ol>
</li> </li>
<li data-list-type="ul" data-list-style='checkbox'>
<label>复选框列表:</label>
<ul style="list-style-type: '☑️ ';">
<li>________</li>
</ul>
</li>
<li data-list-type="ul" data-list-style='disc'> <li data-list-type="ul" data-list-style='disc'>
<label>实心圆点列表:</label> <label>实心圆点列表:</label>
<ul style="list-style-type: disc;"> <ul style="list-style-type: disc;">

@ -6,7 +6,7 @@ import { ListStyle, ListType, UlStyle } from '../../../dataset/enum/List'
import { DeepRequired } from '../../../interface/Common' import { DeepRequired } from '../../../interface/Common'
import { IEditorOption } from '../../../interface/Editor' import { IEditorOption } from '../../../interface/Editor'
import { IElement, IElementPosition } from '../../../interface/Element' import { IElement, IElementPosition } from '../../../interface/Element'
import { IRow } from '../../../interface/Row' import { IRow, IRowElement } from '../../../interface/Row'
import { getUUID } from '../../../utils' import { getUUID } from '../../../utils'
import { RangeManager } from '../../range/RangeManager' import { RangeManager } from '../../range/RangeManager'
import { Draw } from '../Draw' import { Draw } from '../Draw'
@ -174,15 +174,7 @@ export class ListParticle {
if (element?.type !== ElementType.TAB) break if (element?.type !== ElementType.TAB) break
tabWidth += defaultTabWidth * scale tabWidth += defaultTabWidth * scale
} }
let text = '' // 列表样式渲染
if (startElement.listType === ListType.UL) {
text =
ulStyleMapping[<UlStyle>(<unknown>startElement.listStyle)] ||
ulStyleMapping[UlStyle.DISC]
} else {
text = `${listIndex! + 1}${KeyMap.PERIOD}`
}
if (!text) return
const { const {
coordinate: { coordinate: {
leftTop: [startX, startY] leftTop: [startX, startY]
@ -190,9 +182,37 @@ export class ListParticle {
} = position } = position
const x = startX - offsetX! + tabWidth const x = startX - offsetX! + tabWidth
const y = startY + ascent const y = startY + ascent
ctx.save() // 复选框样式特殊处理
ctx.font = `${defaultSize * scale}px ${defaultFont}` if (startElement.listStyle === ListStyle.CHECKBOX) {
ctx.fillText(text, x, y) const { width, height, gap } = this.options.checkbox
ctx.restore() const checkboxRowElement: IRowElement = {
...startElement,
checkbox: {
value: !!startElement.checkbox?.value
},
metrics: {
...startElement.metrics,
width: (width + gap * 2) * scale,
height: height * scale
}
}
this.draw
.getCheckboxParticle()
.render(ctx, checkboxRowElement, x - gap * scale, y)
} else {
let text = ''
if (startElement.listType === ListType.UL) {
text =
ulStyleMapping[<UlStyle>(<unknown>startElement.listStyle)] ||
ulStyleMapping[UlStyle.DISC]
} else {
text = `${listIndex! + 1}${KeyMap.PERIOD}`
}
if (!text) return
ctx.save()
ctx.font = `${defaultSize * scale}px ${defaultFont}`
ctx.fillText(text, x, y)
ctx.restore()
}
} }
} }

@ -1,4 +1,4 @@
import { ElementType, RowFlex, VerticalAlign } from '../..' import { ElementType, ListStyle, RowFlex, VerticalAlign } from '../..'
import { ZERO } from '../../dataset/constant/Common' import { ZERO } from '../../dataset/constant/Common'
import { ControlComponent } from '../../dataset/enum/Control' import { ControlComponent } from '../../dataset/enum/Control'
import { import {
@ -358,6 +358,7 @@ export class Position {
coordinate: { leftTop, rightTop, leftBottom } coordinate: { leftTop, rightTop, leftBottom }
} = positionList[j] } = positionList[j]
if (positionNo !== pageNo) continue if (positionNo !== pageNo) continue
if (pageNo > positionNo) break
// 命中元素 // 命中元素
if ( if (
leftTop[0] - left <= x && leftTop[0] - left <= x &&
@ -482,17 +483,15 @@ export class Position {
for (let j = 0; j < lastLetterList.length; j++) { for (let j = 0; j < lastLetterList.length; j++) {
const { const {
index, index,
pageNo, rowNo,
coordinate: { leftTop, leftBottom } coordinate: { leftTop, leftBottom }
} = lastLetterList[j] } = lastLetterList[j]
if (positionNo !== pageNo) continue
if (y > leftTop[1] && y <= leftBottom[1]) { if (y > leftTop[1] && y <= leftBottom[1]) {
const isHead = x < this.options.margins[3] const headIndex = positionList.findIndex(
p => p.pageNo === positionNo && p.rowNo === rowNo
)
// 是否在头部 // 是否在头部
if (isHead) { if (x < this.options.margins[3]) {
const headIndex = positionList.findIndex(
p => p.pageNo === positionNo && p.rowNo === lastLetterList[j].rowNo
)
// 头部元素为空元素时无需选中 // 头部元素为空元素时无需选中
if (~headIndex) { if (~headIndex) {
if (positionList[headIndex].value === ZERO) { if (positionList[headIndex].value === ZERO) {
@ -505,6 +504,17 @@ export class Position {
curPositionIndex = index curPositionIndex = index
} }
} else { } else {
// 是否是复选框列表
if (
elementList[headIndex].listStyle === ListStyle.CHECKBOX &&
x < leftTop[0]
) {
return {
index: headIndex,
isDirectHit: true,
isCheckbox: true
}
}
curPositionIndex = index curPositionIndex = index
} }
isLastArea = true isLastArea = true

@ -3,7 +3,8 @@ import { ListStyle, ListType, UlStyle } from '../enum/List'
export const ulStyleMapping: Record<UlStyle, string> = { export const ulStyleMapping: Record<UlStyle, string> = {
[UlStyle.DISC]: '•', [UlStyle.DISC]: '•',
[UlStyle.CIRCLE]: '◦', [UlStyle.CIRCLE]: '◦',
[UlStyle.SQUARE]: '▫︎' [UlStyle.SQUARE]: '▫︎',
[UlStyle.CHECKBOX]: '☑️'
} }
export const listTypeElementMapping: Record<ListType, string> = { export const listTypeElementMapping: Record<ListType, string> = {
@ -15,5 +16,6 @@ export const listStyleCSSMapping: Record<ListStyle, string> = {
[ListStyle.DISC]: 'disc', [ListStyle.DISC]: 'disc',
[ListStyle.CIRCLE]: 'circle', [ListStyle.CIRCLE]: 'circle',
[ListStyle.SQUARE]: 'square', [ListStyle.SQUARE]: 'square',
[ListStyle.DECIMAL]: 'decimal' [ListStyle.DECIMAL]: 'decimal',
[ListStyle.CHECKBOX]: 'checkbox'
} }

@ -1,3 +1,5 @@
import { ZERO } from './Common'
export const NUMBER_REG = /[0-9]/ export const NUMBER_REG = /[0-9]/
export const NUMBER_LIKE_REG = /[0-9.]/ export const NUMBER_LIKE_REG = /[0-9.]/
export const CHINESE_REG = /[\u4e00-\u9fa5]/ export const CHINESE_REG = /[\u4e00-\u9fa5]/
@ -13,3 +15,5 @@ export const UNICODE_SYMBOL_REG = new RegExp(
export const PUNCTUATION_REG = export const PUNCTUATION_REG =
/[、,。?!;:……「」“”‘’*()【】〔〕〖〗〘〙〚〛《》———﹝﹞–—\\/·.,!?;:`~<>()[\]{}'"|]/ /[、,。?!;:……「」“”‘’*()【】〔〕〖〗〘〙〚〛《》———﹝﹞–—\\/·.,!?;:`~<>()[\]{}'"|]/
export const START_LINE_BREAK_REG = new RegExp(`^[${ZERO}\n]`)

@ -6,7 +6,8 @@ export enum ListType {
export enum UlStyle { export enum UlStyle {
DISC = 'disc', // 实心圆点 DISC = 'disc', // 实心圆点
CIRCLE = 'circle', // 空心圆点 CIRCLE = 'circle', // 空心圆点
SQUARE = 'square' // 实心方块 SQUARE = 'square', // 实心方块
CHECKBOX = 'checkbox' // 复选框
} }
export enum OlStyle { export enum OlStyle {
@ -17,5 +18,6 @@ export enum ListStyle {
DISC = UlStyle.DISC, DISC = UlStyle.DISC,
CIRCLE = UlStyle.CIRCLE, CIRCLE = UlStyle.CIRCLE,
SQUARE = UlStyle.SQUARE, SQUARE = UlStyle.SQUARE,
DECIMAL = OlStyle.DECIMAL DECIMAL = OlStyle.DECIMAL,
CHECKBOX = UlStyle.CHECKBOX
} }

@ -30,6 +30,7 @@ import {
listStyleCSSMapping, listStyleCSSMapping,
listTypeElementMapping listTypeElementMapping
} from '../dataset/constant/List' } from '../dataset/constant/List'
import { START_LINE_BREAK_REG } from '../dataset/constant/Regular'
import { import {
titleNodeNameMapping, titleNodeNameMapping,
titleOrderNumberMapping, titleOrderNumberMapping,
@ -71,7 +72,7 @@ export function formatElementList(
if ( if (
isHandleFirstElement && isHandleFirstElement &&
((startElement?.type && startElement.type !== ElementType.TEXT) || ((startElement?.type && startElement.type !== ElementType.TEXT) ||
(startElement?.value !== ZERO && startElement?.value !== '\n')) !START_LINE_BREAK_REG.test(startElement?.value))
) { ) {
elementList.unshift({ elementList.unshift({
value: ZERO value: ZERO
@ -431,10 +432,11 @@ export function zipElementList(payload: IElement[]): IElement[] {
let e = 0 let e = 0
while (e < elementList.length) { while (e < elementList.length) {
let element = elementList[e] let element = elementList[e]
// 上下文首字符(占位符) // 上下文首字符(占位符)-列表首字符要保留避免是复选框
if ( if (
e === 0 && e === 0 &&
element.value === ZERO && element.value === ZERO &&
!element.listId &&
(!element.type || element.type === ElementType.TEXT) (!element.type || element.type === ElementType.TEXT)
) { ) {
e++ e++
@ -444,51 +446,55 @@ export function zipElementList(payload: IElement[]): IElement[] {
if (element.titleId && element.level) { if (element.titleId && element.level) {
// 标题处理 // 标题处理
const titleId = element.titleId const titleId = element.titleId
const level = element.level if (titleId) {
const titleElement: IElement = { const level = element.level
type: ElementType.TITLE, const titleElement: IElement = {
value: '', type: ElementType.TITLE,
level value: '',
} level
const valueList: IElement[] = []
while (e < elementList.length) {
const titleE = elementList[e]
if (titleId !== titleE.titleId) {
e--
break
} }
delete titleE.level const valueList: IElement[] = []
valueList.push(titleE) while (e < elementList.length) {
e++ const titleE = elementList[e]
if (titleId !== titleE.titleId) {
e--
break
}
delete titleE.level
valueList.push(titleE)
e++
}
titleElement.valueList = zipElementList(valueList)
element = titleElement
} }
titleElement.valueList = zipElementList(valueList)
element = titleElement
} else if (element.listId && element.listType) { } else if (element.listId && element.listType) {
// 列表处理 // 列表处理
const listId = element.listId const listId = element.listId
const listType = element.listType if (listId) {
const listStyle = element.listStyle const listType = element.listType
const listElement: IElement = { const listStyle = element.listStyle
type: ElementType.LIST, const listElement: IElement = {
value: '', type: ElementType.LIST,
listId, value: '',
listType, listId,
listStyle listType,
} listStyle
const valueList: IElement[] = []
while (e < elementList.length) {
const listE = elementList[e]
if (listId !== listE.listId) {
e--
break
} }
delete listE.listType const valueList: IElement[] = []
delete listE.listStyle while (e < elementList.length) {
valueList.push(listE) const listE = elementList[e]
e++ if (listId !== listE.listId) {
e--
break
}
delete listE.listType
delete listE.listStyle
valueList.push(listE)
e++
}
listElement.valueList = zipElementList(valueList)
element = listElement
} }
listElement.valueList = zipElementList(valueList)
element = listElement
} else if (element.type === ElementType.TABLE) { } else if (element.type === ElementType.TABLE) {
// 分页表格先进行合并 // 分页表格先进行合并
if (element.pagingId) { if (element.pagingId) {
@ -532,71 +538,77 @@ export function zipElementList(payload: IElement[]): IElement[] {
} else if (element.type === ElementType.HYPERLINK) { } else if (element.type === ElementType.HYPERLINK) {
// 超链接处理 // 超链接处理
const hyperlinkId = element.hyperlinkId const hyperlinkId = element.hyperlinkId
const hyperlinkElement: IElement = { if (hyperlinkId) {
type: ElementType.HYPERLINK, const hyperlinkElement: IElement = {
value: '', type: ElementType.HYPERLINK,
url: element.url value: '',
} url: element.url
const valueList: IElement[] = []
while (e < elementList.length) {
const hyperlinkE = elementList[e]
if (hyperlinkId !== hyperlinkE.hyperlinkId) {
e--
break
} }
delete hyperlinkE.type const valueList: IElement[] = []
delete hyperlinkE.url while (e < elementList.length) {
valueList.push(hyperlinkE) const hyperlinkE = elementList[e]
e++ if (hyperlinkId !== hyperlinkE.hyperlinkId) {
e--
break
}
delete hyperlinkE.type
delete hyperlinkE.url
valueList.push(hyperlinkE)
e++
}
hyperlinkElement.valueList = zipElementList(valueList)
element = hyperlinkElement
} }
hyperlinkElement.valueList = zipElementList(valueList)
element = hyperlinkElement
} else if (element.type === ElementType.DATE) { } else if (element.type === ElementType.DATE) {
const dateId = element.dateId const dateId = element.dateId
const dateElement: IElement = { if (dateId) {
type: ElementType.DATE, const dateElement: IElement = {
value: '', type: ElementType.DATE,
dateFormat: element.dateFormat value: '',
} dateFormat: element.dateFormat
const valueList: IElement[] = []
while (e < elementList.length) {
const dateE = elementList[e]
if (dateId !== dateE.dateId) {
e--
break
} }
delete dateE.type const valueList: IElement[] = []
delete dateE.dateFormat while (e < elementList.length) {
valueList.push(dateE) const dateE = elementList[e]
e++ if (dateId !== dateE.dateId) {
e--
break
}
delete dateE.type
delete dateE.dateFormat
valueList.push(dateE)
e++
}
dateElement.valueList = zipElementList(valueList)
element = dateElement
} }
dateElement.valueList = zipElementList(valueList)
element = dateElement
} else if (element.controlId) { } else if (element.controlId) {
// 控件处理 // 控件处理
const controlId = element.controlId const controlId = element.controlId
const control = element.control! if (controlId) {
const controlElement: IElement = { const control = element.control!
type: ElementType.CONTROL, const controlElement: IElement = {
value: '', type: ElementType.CONTROL,
control value: '',
} control
const valueList: IElement[] = []
while (e < elementList.length) {
const controlE = elementList[e]
if (controlId !== controlE.controlId) {
e--
break
} }
if (controlE.controlComponent === ControlComponent.VALUE) { const valueList: IElement[] = []
delete controlE.control while (e < elementList.length) {
delete controlE.controlId const controlE = elementList[e]
valueList.push(controlE) if (controlId !== controlE.controlId) {
e--
break
}
if (controlE.controlComponent === ControlComponent.VALUE) {
delete controlE.control
delete controlE.controlId
valueList.push(controlE)
}
e++
} }
e++ controlElement.control!.value = zipElementList(valueList)
element = controlElement
} }
controlElement.control!.value = zipElementList(valueList)
element = controlElement
} }
// 组合元素 // 组合元素
const pickElement = pickElementAttr(element) const pickElement = pickElementAttr(element)
@ -768,6 +780,11 @@ export function splitListElement(
const listElementListMap: Map<number, IElement[]> = new Map() const listElementListMap: Map<number, IElement[]> = new Map()
for (let e = 0; e < elementList.length; e++) { for (let e = 0; e < elementList.length; e++) {
const element = elementList[e] const element = elementList[e]
// 移除列表首行换行字符-如果是复选框直接忽略
if (e === 0) {
if (element.checkbox) continue
element.value = element.value.replace(START_LINE_BREAK_REG, '')
}
if (element.listWrap) { if (element.listWrap) {
const listElementList = listElementListMap.get(curListIndex) || [] const listElementList = listElementListMap.get(curListIndex) || []
listElementList.push(element) listElementList.push(element)

@ -367,6 +367,10 @@ ul {
margin-left: 18px; margin-left: 18px;
} }
.menu-item__list .options>ul>li[data-list-style='checkbox'] li::marker {
font-size: 11px;
}
.menu-item__image i { .menu-item__image i {
background-image: url('./assets/images/image.svg'); background-image: url('./assets/images/image.svg');
} }

Loading…
Cancel
Save