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>
</ol>
</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'>
<label>实心圆点列表:</label>
<ul style="list-style-type: disc;">

@ -6,7 +6,7 @@ import { ListStyle, ListType, UlStyle } from '../../../dataset/enum/List'
import { DeepRequired } from '../../../interface/Common'
import { IEditorOption } from '../../../interface/Editor'
import { IElement, IElementPosition } from '../../../interface/Element'
import { IRow } from '../../../interface/Row'
import { IRow, IRowElement } from '../../../interface/Row'
import { getUUID } from '../../../utils'
import { RangeManager } from '../../range/RangeManager'
import { Draw } from '../Draw'
@ -174,15 +174,7 @@ export class ListParticle {
if (element?.type !== ElementType.TAB) break
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 {
coordinate: {
leftTop: [startX, startY]
@ -190,9 +182,37 @@ export class ListParticle {
} = position
const x = startX - offsetX! + tabWidth
const y = startY + ascent
ctx.save()
ctx.font = `${defaultSize * scale}px ${defaultFont}`
ctx.fillText(text, x, y)
ctx.restore()
// 复选框样式特殊处理
if (startElement.listStyle === ListStyle.CHECKBOX) {
const { width, height, gap } = this.options.checkbox
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 { ControlComponent } from '../../dataset/enum/Control'
import {
@ -358,6 +358,7 @@ export class Position {
coordinate: { leftTop, rightTop, leftBottom }
} = positionList[j]
if (positionNo !== pageNo) continue
if (pageNo > positionNo) break
// 命中元素
if (
leftTop[0] - left <= x &&
@ -482,17 +483,15 @@ export class Position {
for (let j = 0; j < lastLetterList.length; j++) {
const {
index,
pageNo,
rowNo,
coordinate: { leftTop, leftBottom }
} = lastLetterList[j]
if (positionNo !== pageNo) continue
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) {
const headIndex = positionList.findIndex(
p => p.pageNo === positionNo && p.rowNo === lastLetterList[j].rowNo
)
if (x < this.options.margins[3]) {
// 头部元素为空元素时无需选中
if (~headIndex) {
if (positionList[headIndex].value === ZERO) {
@ -505,6 +504,17 @@ export class Position {
curPositionIndex = index
}
} else {
// 是否是复选框列表
if (
elementList[headIndex].listStyle === ListStyle.CHECKBOX &&
x < leftTop[0]
) {
return {
index: headIndex,
isDirectHit: true,
isCheckbox: true
}
}
curPositionIndex = index
}
isLastArea = true

@ -3,7 +3,8 @@ import { ListStyle, ListType, UlStyle } from '../enum/List'
export const ulStyleMapping: Record<UlStyle, string> = {
[UlStyle.DISC]: '•',
[UlStyle.CIRCLE]: '◦',
[UlStyle.SQUARE]: '▫︎'
[UlStyle.SQUARE]: '▫︎',
[UlStyle.CHECKBOX]: '☑️'
}
export const listTypeElementMapping: Record<ListType, string> = {
@ -15,5 +16,6 @@ export const listStyleCSSMapping: Record<ListStyle, string> = {
[ListStyle.DISC]: 'disc',
[ListStyle.CIRCLE]: 'circle',
[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_LIKE_REG = /[0-9.]/
export const CHINESE_REG = /[\u4e00-\u9fa5]/
@ -13,3 +15,5 @@ export const UNICODE_SYMBOL_REG = new RegExp(
export const PUNCTUATION_REG =
/[、,。?!;:……「」“”‘’*()【】〔〕〖〗〘〙〚〛《》———﹝﹞–—\\/·.,!?;:`~<>()[\]{}'"|]/
export const START_LINE_BREAK_REG = new RegExp(`^[${ZERO}\n]`)

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

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

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

Loading…
Cancel
Save