refactor: list
This commit is contained in:
parent
0408181e81
commit
3b1152303f
|
@ -54,7 +54,7 @@ class SelectionRangeTopNodes {
|
|||
* @param $node 节点
|
||||
*/
|
||||
private getNextSibling($node: DomElement): DomElement {
|
||||
return $($node.elems[0].nextSibling)
|
||||
return $($node.elems[0].nextElementSibling)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -238,8 +238,8 @@ class SelectionAndRange {
|
|||
* 获取 当前 选取范围的 顶级(段落) 元素
|
||||
* @param $editor
|
||||
*/
|
||||
public getSelectionRangeTopNodes(editor: Editor): DomElement[] {
|
||||
const item = new SelectionRangeTopNodes(editor)
|
||||
public getSelectionRangeTopNodes(): DomElement[] {
|
||||
const item = new SelectionRangeTopNodes(this.editor)
|
||||
item.init()
|
||||
return item.getSelectionNodes()
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ class BackColor extends DropListMenu implements MenuActive {
|
|||
|
||||
if (isEmptySelection) {
|
||||
if (isSpan && !isSameColor) {
|
||||
const $elems = editor.selection.getSelectionRangeTopNodes(editor)
|
||||
const $elems = editor.selection.getSelectionRangeTopNodes()
|
||||
editor.selection.createRangeByElem($elems[0])
|
||||
editor.selection.moveCursor($elems[0].elems[0])
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ class FontColor extends DropListMenu implements MenuActive {
|
|||
|
||||
if (isEmptySelection) {
|
||||
if (isFont && !isSameColor) {
|
||||
const $elems = editor.selection.getSelectionRangeTopNodes(editor)
|
||||
const $elems = editor.selection.getSelectionRangeTopNodes()
|
||||
editor.selection.createRangeByElem($elems[0])
|
||||
editor.selection.moveCursor($elems[0].elems[0])
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ class FontSize extends DropListMenu implements MenuActive {
|
|||
|
||||
if (isEmptySelection) {
|
||||
if (isFont && !isSameSize) {
|
||||
const $elems = editor.selection.getSelectionRangeTopNodes(editor)
|
||||
const $elems = editor.selection.getSelectionRangeTopNodes()
|
||||
editor.selection.createRangeByElem($elems[0])
|
||||
editor.selection.moveCursor($elems[0].elems[0])
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ class FontStyle extends DropListMenu implements MenuActive {
|
|||
|
||||
if (isEmptySelection) {
|
||||
if (isFont && !isSameValue) {
|
||||
const $elems = editor.selection.getSelectionRangeTopNodes(editor)
|
||||
const $elems = editor.selection.getSelectionRangeTopNodes()
|
||||
editor.selection.createRangeByElem($elems[0])
|
||||
editor.selection.moveCursor($elems[0].elems[0])
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ class Indent extends DropListMenu implements MenuActive {
|
|||
if ($selectionElem && editor.$textElem.equal($selectionElem)) {
|
||||
// 当 当前选区 等于 textElem 时
|
||||
// 代表 当前选区 可能是一个选择了一个完整的段落或者多个段落
|
||||
const $elems = editor.selection.getSelectionRangeTopNodes(editor)
|
||||
const $elems = editor.selection.getSelectionRangeTopNodes()
|
||||
if ($elems.length > 0) {
|
||||
$elems.forEach((item: any) => {
|
||||
operateElement($(item), value, editor)
|
||||
|
|
|
@ -79,7 +79,7 @@ class Justify extends DropListMenu implements MenuActive {
|
|||
selection.saveRange()
|
||||
|
||||
// 获取顶级元素
|
||||
const $elems = editor.selection.getSelectionRangeTopNodes(editor)
|
||||
const $elems = editor.selection.getSelectionRangeTopNodes()
|
||||
if ($selectionElem?.length) {
|
||||
// list 在chrome下默认多包裹一个 p,导致不能通过顶层元素判断,所以单独加个判断
|
||||
if (this.isSpecialNode($selectionElem, $elems[0]) || this.isSpecialTopNode($elems[0])) {
|
||||
|
@ -148,7 +148,7 @@ class Justify extends DropListMenu implements MenuActive {
|
|||
* 默认左对齐,若选择其他对其方式对active进行高亮否则unActive
|
||||
* ?考虑优化的话 是否可以对具体选中的进行高亮
|
||||
*/
|
||||
public tryChangeActive(): void {}
|
||||
public tryChangeActive(): void { }
|
||||
}
|
||||
|
||||
export default Justify
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
import { ContainerFragment } from '.'
|
||||
import $, { DomElement } from '../../../utils/dom-core'
|
||||
import { Exec, HandlerListOptions, ListHandle } from './ListHandle'
|
||||
import {
|
||||
filterSelectionNodes,
|
||||
getEndPoint,
|
||||
insertBefore,
|
||||
createElement,
|
||||
createDocumentFragment,
|
||||
createElementFragment,
|
||||
} from '../utils'
|
||||
|
||||
export default class EndJoinListHandle extends ListHandle implements Exec {
|
||||
constructor(options: HandlerListOptions) {
|
||||
super(options)
|
||||
}
|
||||
|
||||
exec(): void {
|
||||
const { editor, listType, listTarget, $endElem } = this.options
|
||||
|
||||
// 容器 - HTML 文档片段
|
||||
let $containerFragment: ContainerFragment
|
||||
|
||||
// 获取选中的段落
|
||||
const $nodes: DomElement[] = editor.selection.getSelectionRangeTopNodes()
|
||||
|
||||
// 获取结束段落标签名
|
||||
const endNodeName = $endElem?.getNodeName()
|
||||
|
||||
// 弹出 结束序列
|
||||
$nodes.pop()
|
||||
|
||||
// 下序列元素数组
|
||||
const lowerListElems: DomElement[] = []
|
||||
// 获取结束元素
|
||||
let $endDom: DomElement = getEndPoint($endElem)
|
||||
// 获取下半序列中选中的内容
|
||||
while ($endDom.length) {
|
||||
lowerListElems.unshift($endDom)
|
||||
$endDom = $endDom.prev()
|
||||
}
|
||||
|
||||
// =====================================
|
||||
// 当前序列类型和结束序列的类型 一致
|
||||
// 代表当前是一个 融合(把其他段落加入到结束序列中) 的操作
|
||||
// =====================================
|
||||
if (endNodeName === listType) {
|
||||
// 生成 li 元属,并删除原来的 dom 元素
|
||||
$containerFragment = createElementFragment(
|
||||
filterSelectionNodes($nodes), // 过滤元素节点数据
|
||||
createDocumentFragment() // 创建 文档片段
|
||||
)
|
||||
|
||||
lowerListElems.forEach($list => $containerFragment.append($list.elems[0]))
|
||||
|
||||
// 插入到结束序列之前
|
||||
this.selectionRangeElem.set($containerFragment)
|
||||
|
||||
if ($endElem.children()?.length) {
|
||||
const $endElemChild = $endElem.children() as DomElement
|
||||
insertBefore($endElemChild, $containerFragment, $endElemChild.elems[0])
|
||||
} else {
|
||||
$endElem.elems[0].append($containerFragment)
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================
|
||||
// 当前序列类型和结束序列的类型 不一致
|
||||
// 代表当前是一个 设置序列 的操作
|
||||
// =====================================
|
||||
else {
|
||||
// 过滤元素节点数据
|
||||
const $selectionNodes = filterSelectionNodes($nodes)
|
||||
// 把下序列的内容添加到过滤元素中
|
||||
$selectionNodes.push(...lowerListElems)
|
||||
// 生成 li 元素并且添加到序列节点后删除原节点
|
||||
$containerFragment = createElementFragment(
|
||||
$selectionNodes,
|
||||
createElement(listTarget) // 创建 序列节点
|
||||
)
|
||||
|
||||
// 插入到结束序列之前
|
||||
this.selectionRangeElem.set($containerFragment)
|
||||
$($containerFragment).insertBefore($endElem)
|
||||
|
||||
// 序列全选被掏空了后,就卸磨杀驴吧
|
||||
!$endElem.children()?.length && $endElem.remove()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,259 @@
|
|||
import { ContainerFragment } from '.'
|
||||
import $, { DomElement } from '../../../utils/dom-core'
|
||||
import { Exec, HandlerListOptions, ListHandle } from './ListHandle'
|
||||
import {
|
||||
filterSelectionNodes,
|
||||
getStartPoint,
|
||||
getEndPoint,
|
||||
insertBefore,
|
||||
createElement,
|
||||
createDocumentFragment,
|
||||
createElementFragment,
|
||||
} from '../utils'
|
||||
|
||||
export default class JoinListHandle extends ListHandle implements Exec {
|
||||
constructor(options: HandlerListOptions) {
|
||||
super(options)
|
||||
}
|
||||
|
||||
exec(): void {
|
||||
const { editor, listType, listTarget, $startElem, $endElem } = this.options
|
||||
|
||||
// 容器 - HTML 文档片段
|
||||
let $containerFragment: ContainerFragment
|
||||
|
||||
// 获取选中的段落
|
||||
const $nodes: DomElement[] = editor.selection.getSelectionRangeTopNodes()
|
||||
|
||||
// 获取开始段落和结束段落 标签名
|
||||
const startNodeName = $startElem?.getNodeName()
|
||||
const endNodeName = $endElem?.getNodeName()
|
||||
|
||||
// =====================================
|
||||
// 开头结尾都是序列的情况下
|
||||
// 开头序列 和 结尾序列的标签名一致的时候
|
||||
// =====================================
|
||||
if (startNodeName === endNodeName) {
|
||||
// =====================================
|
||||
// 开头序列 和 结尾序列 中间还有其他的段落的时候
|
||||
// =====================================
|
||||
if ($nodes.length > 2) {
|
||||
// 弹出 开头 和 结尾
|
||||
$nodes.shift()
|
||||
$nodes.pop()
|
||||
|
||||
// 把中间部分的节点元素转换成 li 元素并添加到文档片段后删除
|
||||
$containerFragment = createElementFragment(
|
||||
filterSelectionNodes($nodes), // 过滤 $nodes 获取到符合要求的选中元素节点
|
||||
createDocumentFragment() // 创建 文档片段
|
||||
)
|
||||
|
||||
// =====================================
|
||||
// 由于开头序列 和 结尾序列的标签名一样,所以只判断了开头序列的
|
||||
// 当开头序列的标签名和按钮类型 一致 的时候
|
||||
// 代表着当前是一个 设置序列 的操作
|
||||
// =====================================
|
||||
if (startNodeName === listType) {
|
||||
// 把结束序列的 li 元素添加到 文档片段中
|
||||
$endElem.children()?.forEach(($list: HTMLElement) => {
|
||||
$containerFragment.append($list)
|
||||
})
|
||||
|
||||
// 下序列全选被掏空了,就卸磨杀驴吧
|
||||
$endElem.remove()
|
||||
|
||||
// 在开始序列中添加 文档片段
|
||||
this.selectionRangeElem.set($containerFragment)
|
||||
$startElem.elems[0].append($containerFragment)
|
||||
}
|
||||
|
||||
// =====================================
|
||||
// 由于开头序列 和 结尾序列的标签名一样,所以只判断了开头序列的
|
||||
// 当开头序列的标签名和按钮类型 不一致 的时候
|
||||
// 代表着当前是一个 转换序列 的操作
|
||||
// =====================================
|
||||
else {
|
||||
// 创建 开始序列和结束序列的文档片段
|
||||
const $startFragment = document.createDocumentFragment()
|
||||
const $endFragment = document.createDocumentFragment()
|
||||
|
||||
// 获取起点元素
|
||||
let $startDom: DomElement = getStartPoint($startElem)
|
||||
// 获取上半序列中的选中内容,并添加到文档片段中
|
||||
while ($startDom.length) {
|
||||
const _element = $startDom.elems[0]
|
||||
$startDom = $startDom.next()
|
||||
$startFragment.append(_element)
|
||||
}
|
||||
|
||||
// 获取结束元素
|
||||
let $endDom: DomElement = getEndPoint($endElem)
|
||||
// 获取下半序列中选中的内容
|
||||
const domArr: Element[] = []
|
||||
while ($endDom.length) {
|
||||
domArr.unshift($endDom.elems[0])
|
||||
$endDom = $endDom.prev()
|
||||
}
|
||||
// 添加到文档片段中
|
||||
domArr.forEach(($node: Element) => {
|
||||
$endFragment.append($node)
|
||||
})
|
||||
|
||||
// 合并文档片段
|
||||
const $orderFragment = createElement(listTarget)
|
||||
$orderFragment.append($startFragment)
|
||||
$orderFragment.append($containerFragment)
|
||||
$orderFragment.append($endFragment)
|
||||
$containerFragment = $orderFragment
|
||||
|
||||
// 插入
|
||||
this.selectionRangeElem.set($containerFragment)
|
||||
$($orderFragment).insertAfter($startElem)
|
||||
|
||||
// 序列全选被掏空了后,就卸磨杀驴吧
|
||||
!$startElem.children()?.length && $startElem.remove()
|
||||
!$endElem.children()?.length && $endElem.remove()
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================
|
||||
// 开头序列 和 结尾序列 中间没有其他的段落
|
||||
// =====================================
|
||||
else {
|
||||
$nodes.length = 0
|
||||
|
||||
// 获取起点元素
|
||||
let $startDom: DomElement = getStartPoint($startElem)
|
||||
// 获取上半序列中的选中内容
|
||||
while ($startDom.length) {
|
||||
$nodes.push($startDom)
|
||||
$startDom = $startDom.next()
|
||||
}
|
||||
|
||||
// 获取结束元素
|
||||
let $endDom: DomElement = getEndPoint($endElem)
|
||||
// 获取下半序列中选中的内容
|
||||
const domArr: DomElement[] = []
|
||||
// 获取下半序列中的选中内容
|
||||
while ($endDom.length) {
|
||||
domArr.unshift($endDom)
|
||||
$endDom = $endDom.prev()
|
||||
}
|
||||
|
||||
// 融合内容
|
||||
$nodes.push(...domArr)
|
||||
|
||||
// =====================================
|
||||
// 由于开头序列 和 结尾序列的标签名一样,所以只判断了开头序列的
|
||||
// 当开头序列的标签名和按钮类型 一致 的时候
|
||||
// 代表着当前是一个 取消序列 的操作
|
||||
// =====================================
|
||||
if (startNodeName === listType) {
|
||||
// 创建 文档片段
|
||||
// 把 li 转换为 p 标签
|
||||
$containerFragment = createElementFragment(
|
||||
$nodes,
|
||||
createDocumentFragment(),
|
||||
'p'
|
||||
)
|
||||
|
||||
// 插入到 endElem 前
|
||||
this.selectionRangeElem.set($containerFragment)
|
||||
insertBefore($startElem, $containerFragment, $endElem.elems[0])
|
||||
}
|
||||
|
||||
// =====================================
|
||||
// 由于开头序列 和 结尾序列的标签名一样,所以只判断了开头序列的
|
||||
// 当开头序列的标签名和按钮类型 不一致 的时候
|
||||
// 代表着当前是一个 设置序列 的操作
|
||||
// =====================================
|
||||
else {
|
||||
// 创建 序列元素
|
||||
$containerFragment = createElement(listTarget)
|
||||
// li 元素添加到 序列元素 中
|
||||
$nodes.forEach(($list: DomElement) => {
|
||||
$containerFragment.append($list.elems[0])
|
||||
})
|
||||
// 插入到 startElem 之后
|
||||
this.selectionRangeElem.set($containerFragment)
|
||||
$($containerFragment).insertAfter($startElem)
|
||||
}
|
||||
|
||||
// 序列全选被掏空了后,就卸磨杀驴吧
|
||||
!$startElem.children()?.length && $endElem.remove()
|
||||
!$endElem.children()?.length && $endElem.remove()
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================
|
||||
// 由于开头序列 和 结尾序列的标签名不一样
|
||||
// =====================================
|
||||
else {
|
||||
// 下序列元素数组
|
||||
const lowerListElems: DomElement[] = []
|
||||
// 获取结束元素
|
||||
let $endDom: DomElement = getEndPoint($endElem)
|
||||
// 获取下半序列中选中的内容
|
||||
while ($endDom.length) {
|
||||
lowerListElems.unshift($endDom)
|
||||
$endDom = $endDom.prev()
|
||||
}
|
||||
|
||||
// 上序列元素数组
|
||||
const upperListElems: DomElement[] = []
|
||||
// 获取起点元素
|
||||
let $startDom: DomElement = getStartPoint($startElem)
|
||||
// 获取上半序列中的选中内容,并添加到文档片段中
|
||||
while ($startDom.length) {
|
||||
upperListElems.push($startDom)
|
||||
$startDom = $startDom.next()
|
||||
}
|
||||
|
||||
// 创建 文档片段
|
||||
$containerFragment = createDocumentFragment()
|
||||
|
||||
// 弹出开头和结尾的序列
|
||||
$nodes.shift()
|
||||
$nodes.pop()
|
||||
|
||||
// 把头部序列的内容添加到文档片段当中
|
||||
upperListElems.forEach($list => $containerFragment.append($list.elems[0]))
|
||||
|
||||
// 生成 li 标签,并且添加到 文档片段中,删除无用节点
|
||||
$containerFragment = createElementFragment(
|
||||
filterSelectionNodes($nodes), // 序列中间的数据 - 进行数据过滤
|
||||
$containerFragment
|
||||
)
|
||||
|
||||
// 把尾部序列的内容添加到文档片段当中
|
||||
lowerListElems.forEach($list => $containerFragment.append($list.elems[0]))
|
||||
|
||||
// 记录
|
||||
this.selectionRangeElem.set($containerFragment)
|
||||
|
||||
// =====================================
|
||||
// 开头序列 和 设置序列类型相同
|
||||
// =====================================
|
||||
if (startNodeName === listType) {
|
||||
// 插入到 开始序列的尾部(作为子元素)
|
||||
$startElem.elems[0].append($containerFragment)
|
||||
|
||||
// 序列全选被掏空了后,就卸磨杀驴吧
|
||||
!$endElem.children()?.length && $endElem.remove()
|
||||
}
|
||||
|
||||
// =====================================
|
||||
// 结尾序列 和 设置序列类型相同
|
||||
// =====================================
|
||||
else {
|
||||
// 插入到结束序列的顶部(作为子元素)
|
||||
if ($endElem.children()?.length) {
|
||||
const $endElemChild = $endElem.children() as DomElement
|
||||
insertBefore($endElemChild, $containerFragment, $endElemChild.elems[0])
|
||||
} else {
|
||||
$endElem.elems[0].append($containerFragment)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import Editor from '../../../editor'
|
||||
import { DomElement } from '../../../utils/dom-core'
|
||||
import SelectionRangeElem from '../SelectionRangeElem'
|
||||
|
||||
export type HandlerListOptions = {
|
||||
editor: Editor
|
||||
listType: string
|
||||
listTarget: string
|
||||
$selectionElem: DomElement
|
||||
$startElem: DomElement
|
||||
$endElem: DomElement
|
||||
}
|
||||
|
||||
export interface Exec {
|
||||
exec: Function
|
||||
}
|
||||
|
||||
export class ListHandle {
|
||||
public options: HandlerListOptions
|
||||
public selectionRangeElem: SelectionRangeElem
|
||||
|
||||
constructor(options: HandlerListOptions) {
|
||||
this.options = options
|
||||
this.selectionRangeElem = new SelectionRangeElem()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
import { ContainerFragment } from '.'
|
||||
import { DomElement } from '../../../utils/dom-core'
|
||||
import { Exec, HandlerListOptions, ListHandle } from './ListHandle'
|
||||
import { filterSelectionNodes, createElement, createElementFragment } from '../utils'
|
||||
|
||||
export default class OtherListHandle extends ListHandle implements Exec {
|
||||
public range: Range
|
||||
|
||||
constructor(options: HandlerListOptions, range: Range) {
|
||||
super(options)
|
||||
this.range = range
|
||||
}
|
||||
|
||||
exec(): void {
|
||||
const { editor, listTarget } = this.options
|
||||
|
||||
// 获取选中的段落
|
||||
const $nodes: DomElement[] = editor.selection.getSelectionRangeTopNodes()
|
||||
|
||||
// 生成 li 元素并且添加到序列节点后删除原节点
|
||||
const $containerFragment: ContainerFragment = createElementFragment(
|
||||
filterSelectionNodes($nodes), // 过滤选取的元素
|
||||
createElement(listTarget) // 创建 序列节点
|
||||
)
|
||||
|
||||
// 插入节点到选区
|
||||
this.selectionRangeElem.set($containerFragment)
|
||||
this.range.insertNode($containerFragment)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
import { ContainerFragment } from '.'
|
||||
import $, { DomElement } from '../../../utils/dom-core'
|
||||
import { Exec, HandlerListOptions, ListHandle } from './ListHandle'
|
||||
import {
|
||||
filterSelectionNodes,
|
||||
getStartPoint,
|
||||
createElement,
|
||||
createDocumentFragment,
|
||||
createElementFragment,
|
||||
} from '../utils'
|
||||
|
||||
export default class StartJoinListHandle extends ListHandle implements Exec {
|
||||
constructor(options: HandlerListOptions) {
|
||||
super(options)
|
||||
}
|
||||
|
||||
exec(): void {
|
||||
const { editor, listType, listTarget, $startElem } = this.options
|
||||
|
||||
// 容器 - HTML 文档片段
|
||||
let $containerFragment: ContainerFragment
|
||||
|
||||
// 获取选中的段落
|
||||
const $nodes: DomElement[] = editor.selection.getSelectionRangeTopNodes()
|
||||
|
||||
// 获取开始段落标签名
|
||||
const startNodeName = $startElem?.getNodeName()
|
||||
|
||||
// 弹出 开头序列
|
||||
$nodes.shift()
|
||||
|
||||
// 上序列元素数组
|
||||
const upperListElems: DomElement[] = []
|
||||
// 获取起点元素
|
||||
let $startDom: DomElement = getStartPoint($startElem)
|
||||
// 获取上半序列中的选中内容,并添加到文档片段中
|
||||
while ($startDom.length) {
|
||||
upperListElems.push($startDom)
|
||||
$startDom = $startDom.next()
|
||||
}
|
||||
|
||||
// =====================================
|
||||
// 当前序列类型和开头序列的类型 一致
|
||||
// 代表当前是一个 融合(把其他段落加入到开头序列中) 的操作
|
||||
// =====================================
|
||||
if (startNodeName === listType) {
|
||||
$containerFragment = createDocumentFragment()
|
||||
|
||||
upperListElems.forEach($list => $containerFragment.append($list.elems[0]))
|
||||
|
||||
// 生成 li 元属,并删除
|
||||
$containerFragment = createElementFragment(
|
||||
filterSelectionNodes($nodes), // 过滤元素节点数据
|
||||
$containerFragment
|
||||
)
|
||||
|
||||
// 插入到开始序列末尾
|
||||
this.selectionRangeElem.set($containerFragment)
|
||||
// this.selectionRangeElem.set($startElem.elems[0])
|
||||
$startElem.elems[0].append($containerFragment)
|
||||
}
|
||||
|
||||
// =====================================
|
||||
// 当前序列类型和开头序列的类型 不一致
|
||||
// 代表当前是一个 设置序列 的操作
|
||||
// =====================================
|
||||
else {
|
||||
// 创建 序列节点
|
||||
$containerFragment = createElement(listTarget)
|
||||
|
||||
upperListElems.forEach($list => $containerFragment.append($list.elems[0]))
|
||||
|
||||
// 生成 li 元素,并添加到 序列节点 当中,删除无用节点
|
||||
$containerFragment = createElementFragment(
|
||||
filterSelectionNodes($nodes), // 过滤普通节点
|
||||
$containerFragment
|
||||
)
|
||||
|
||||
// 插入到开始元素
|
||||
this.selectionRangeElem.set($containerFragment)
|
||||
$($containerFragment).insertAfter($startElem)
|
||||
|
||||
// 序列全选被掏空了后,就卸磨杀驴吧
|
||||
!$startElem.children()?.length && $startElem.remove()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
import { ContainerFragment } from '.'
|
||||
import $, { DomElement } from '../../../utils/dom-core'
|
||||
import { Exec, HandlerListOptions, ListHandle } from './ListHandle'
|
||||
import {
|
||||
insertBefore,
|
||||
createElement,
|
||||
createDocumentFragment,
|
||||
createElementFragment,
|
||||
} from '../utils'
|
||||
|
||||
/**
|
||||
* 选区在序列内的处理
|
||||
*/
|
||||
export default class WrapListHandle extends ListHandle implements Exec {
|
||||
constructor(options: HandlerListOptions) {
|
||||
super(options)
|
||||
}
|
||||
|
||||
exec(): void {
|
||||
const { listType, listTarget, $selectionElem, $startElem, $endElem } = this.options
|
||||
|
||||
let $containerFragment: ContainerFragment // 容器 - HTML 文档片段
|
||||
const $nodes: DomElement[] = [] // 获取选中的段落
|
||||
|
||||
// 获取 selectionElem 的标签名
|
||||
const containerNodeName = $selectionElem?.getNodeName()
|
||||
|
||||
// 获取开始以及结束的 li 元素
|
||||
const $start = $startElem.prior
|
||||
const $end = $endElem.prior
|
||||
|
||||
// =====================================
|
||||
// 当 开始节点 和 结束节点 没有 prior
|
||||
// 并且 开始节点 没有前一个兄弟节点
|
||||
// 并且 结束节点 没有后一个兄弟节点
|
||||
// 即代表 全选序列
|
||||
// =====================================
|
||||
if (
|
||||
(!$startElem.prior && !$endElem.prior) ||
|
||||
(!$start?.prev().length && !$end?.next().length)
|
||||
) {
|
||||
// 获取当前序列下的所有 li 标签
|
||||
; ($selectionElem?.children() as DomElement).forEach(($node: HTMLElement) => {
|
||||
$nodes.push($($node))
|
||||
})
|
||||
|
||||
// =====================================
|
||||
// 当 selectionElem 的标签名和按钮类型 一致 的时候
|
||||
// 代表着当前的操作是 取消 序列
|
||||
// =====================================
|
||||
if (containerNodeName === listType) {
|
||||
// 生成对应的段落(p)并添加到文档片段中,然后删除掉无用的 li
|
||||
$containerFragment = createElementFragment(
|
||||
$nodes,
|
||||
createDocumentFragment(), // 创建 文档片段
|
||||
'p'
|
||||
)
|
||||
}
|
||||
|
||||
// =====================================
|
||||
// 当 selectionElem 的标签名和按钮类型 不一致 的时候
|
||||
// 代表着当前的操作是 转换 序列
|
||||
// =====================================
|
||||
else {
|
||||
// 创建 序列节点
|
||||
$containerFragment = createElement(listTarget)
|
||||
|
||||
// 因为是转换,所以 li 元素可以直接使用
|
||||
$nodes.forEach($node => {
|
||||
$containerFragment.append($node.elems[0])
|
||||
})
|
||||
}
|
||||
|
||||
// 把 文档片段 或 序列节点 插入到 selectionElem 的前面
|
||||
this.selectionRangeElem.set($containerFragment)
|
||||
|
||||
// 插入到 $selectionElem 之前
|
||||
insertBefore($selectionElem, $containerFragment, $selectionElem.elems[0])
|
||||
|
||||
// 删除无用的 selectionElem 因为它被掏空了
|
||||
$selectionElem.remove()
|
||||
}
|
||||
|
||||
// =====================================
|
||||
// 当不是全选序列的时候就代表是非全选序列(废话)
|
||||
// 非全选序列的情况
|
||||
// =====================================
|
||||
else {
|
||||
// 获取选中的内容
|
||||
let $startDom: DomElement = $start as DomElement
|
||||
while ($startDom.length) {
|
||||
$nodes.push($startDom)
|
||||
$end?.equal($startDom)
|
||||
? ($startDom = $(undefined)) // 结束
|
||||
: ($startDom = $startDom.next()) // 继续
|
||||
}
|
||||
|
||||
// 获取开始节点的上一个兄弟节点
|
||||
const $prveDom: DomElement = ($start as DomElement).prev()
|
||||
// 获取结束节点的下一个兄弟节点
|
||||
let $nextDom: DomElement = ($end as DomElement).next()
|
||||
|
||||
// =====================================
|
||||
// 当 selectionElem 的标签名和按钮类型一致的时候
|
||||
// 代表着当前的操作是 取消 序列
|
||||
// =====================================
|
||||
if (containerNodeName === listType) {
|
||||
// 生成对应的段落(p)并添加到文档片段中,然后删除掉无用的 li
|
||||
$containerFragment = createElementFragment(
|
||||
$nodes,
|
||||
createDocumentFragment(), // 创建 文档片段
|
||||
'p'
|
||||
)
|
||||
}
|
||||
|
||||
// =====================================
|
||||
// 当 selectionElem 的标签名和按钮类型不一致的时候
|
||||
// 代表着当前的操作是 转换 序列
|
||||
// =====================================
|
||||
else {
|
||||
// 创建 文档片段
|
||||
$containerFragment = createElement(listTarget)
|
||||
|
||||
// 因为是转换,所以 li 元素可以直接使用
|
||||
$nodes.forEach(($node: DomElement) => {
|
||||
$containerFragment.append($node.elems[0])
|
||||
})
|
||||
}
|
||||
|
||||
// =====================================
|
||||
// 当 prveDom 和 nextDom 都存在的时候
|
||||
// 代表着当前选区是在序列的中间
|
||||
// 所以要先把 下半部分 未选择的 li 元素独立出来生成一个 序列
|
||||
// =====================================
|
||||
if ($prveDom.length && $nextDom.length) {
|
||||
// 获取尾部的元素
|
||||
const $tailDomArr: DomElement[] = []
|
||||
while ($nextDom.length) {
|
||||
$tailDomArr.push($nextDom)
|
||||
$nextDom = $nextDom.next()
|
||||
}
|
||||
|
||||
// 创建 尾部序列节点
|
||||
const $tailDocFragment = createElement(containerNodeName)
|
||||
|
||||
// 把尾部元素节点添加到尾部序列节点中
|
||||
$tailDomArr.forEach(($node: DomElement) => {
|
||||
$tailDocFragment.append($node.elems[0])
|
||||
})
|
||||
|
||||
// 把尾部序列节点插入到 selectionElem 的后面
|
||||
$($tailDocFragment).insertAfter($selectionElem)
|
||||
|
||||
// =====================================
|
||||
// 获取选区容器元素的父元素,一般就是编辑区域
|
||||
// 然后判断 selectionElem 是否还有下一个兄弟节点
|
||||
// 如果有,就把文档片段添加到 selectionElem 下一个兄弟节点前
|
||||
// 如果没有,就把文档片段添加到 编辑区域 末尾
|
||||
// =====================================
|
||||
this.selectionRangeElem.set($containerFragment)
|
||||
const $selectionNextDom: DomElement = $selectionElem.next()
|
||||
$selectionNextDom.length
|
||||
? insertBefore($selectionElem, $containerFragment, $selectionNextDom.elems[0])
|
||||
: $selectionElem.parent().elems[0].append($containerFragment)
|
||||
}
|
||||
|
||||
// =====================================
|
||||
// 不管是 取消 还是 转换 都需要重新插入节点
|
||||
//
|
||||
// prveDom.length 等于 0 即代表选区是 selectionElem 序列的上半部分
|
||||
// 上半部分的 li 元素
|
||||
// =====================================
|
||||
else if (!$prveDom.length) {
|
||||
// 文档片段插入到 selectionElem 之前
|
||||
this.selectionRangeElem.set($containerFragment)
|
||||
insertBefore($selectionElem, $containerFragment, $selectionElem.elems[0])
|
||||
}
|
||||
|
||||
// =====================================
|
||||
// 不管是 取消 还是 转换 都需要重新插入节点
|
||||
//
|
||||
// nextDom.length 等于 0 即代表选区是 selectionElem 序列的下半部分
|
||||
// 下半部分的 li 元素 if (!$nextDom.length)
|
||||
// =====================================
|
||||
else {
|
||||
// 文档片段插入到 selectionElem 之后
|
||||
this.selectionRangeElem.set($containerFragment)
|
||||
const $selectionNextDom: DomElement = $selectionElem.next()
|
||||
$selectionNextDom.length
|
||||
? insertBefore($selectionElem, $containerFragment, $selectionNextDom.elems[0])
|
||||
: $selectionElem.parent().elems[0].append($containerFragment)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
import $, { DomElement } from '../../../utils/dom-core'
|
||||
import WrapListHandle from './WrapListHandle'
|
||||
import JoinListHandle from './JoinListHandle'
|
||||
import StartJoinListHandle from './StartJoinListHandle'
|
||||
import EndJoinListHandle from './EndJoinListHandle'
|
||||
import OtherListHandle from './OtherListHandle'
|
||||
|
||||
import { HandlerListOptions } from './ListHandle'
|
||||
|
||||
// 片段类型
|
||||
export type ContainerFragment = HTMLElement | DocumentFragment
|
||||
|
||||
// 处理类
|
||||
export type ListHandleClass =
|
||||
| WrapListHandle
|
||||
| JoinListHandle
|
||||
| StartJoinListHandle
|
||||
| EndJoinListHandle
|
||||
| OtherListHandle
|
||||
|
||||
export enum ClassType {
|
||||
Wrap = 'WrapListHandle',
|
||||
Join = 'JoinListHandle',
|
||||
StartJoin = 'StartJoinListHandle',
|
||||
EndJoin = 'EndJoinListHandle',
|
||||
Other = 'OtherListHandle',
|
||||
}
|
||||
|
||||
const handle = {
|
||||
WrapListHandle,
|
||||
JoinListHandle,
|
||||
StartJoinListHandle,
|
||||
EndJoinListHandle,
|
||||
OtherListHandle,
|
||||
}
|
||||
|
||||
export function createListHandle(
|
||||
classType: ClassType,
|
||||
options: HandlerListOptions,
|
||||
range?: Range
|
||||
): ListHandleClass {
|
||||
if (classType === ClassType.Other && range === undefined) {
|
||||
throw new Error('other 类需要传入 range')
|
||||
}
|
||||
|
||||
return classType !== ClassType.Other
|
||||
? new handle[classType](options)
|
||||
: new handle[classType](options, range as Range)
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一执行的接口
|
||||
*/
|
||||
export default class ListHandleCommand {
|
||||
private handle: ListHandleClass
|
||||
|
||||
constructor(handle: ListHandleClass) {
|
||||
this.handle = handle
|
||||
this.handle.exec()
|
||||
}
|
||||
|
||||
getSelectionRangeElem(): DomElement {
|
||||
return $(this.handle.selectionRangeElem.get())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
type SelectionRangeType = HTMLElement | ChildNode[]
|
||||
type SetSelectionRangeType = SelectionRangeType | DocumentFragment
|
||||
export type SelectionRangeElemType = SelectionRangeType | null
|
||||
|
||||
/**
|
||||
* @description 选区的 Element
|
||||
* @author tonghan
|
||||
*/
|
||||
class SelectionRangeElem {
|
||||
private _element: SelectionRangeElemType
|
||||
|
||||
constructor() {
|
||||
this._element = null
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置 SelectionRangeElem 的值
|
||||
* @param { SetSelectionRangeType } data
|
||||
*/
|
||||
public set(data: SetSelectionRangeType) {
|
||||
//
|
||||
if (data instanceof DocumentFragment) {
|
||||
const childNode: ChildNode[] = []
|
||||
data.childNodes.forEach(($node: ChildNode) => {
|
||||
childNode.push($node)
|
||||
})
|
||||
data = childNode
|
||||
}
|
||||
this._element = data
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 SelectionRangeElem 的值
|
||||
* @returns { SelectionRangeType } Elem
|
||||
*/
|
||||
public get(): SelectionRangeElemType {
|
||||
return this._element
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除 SelectionRangeElem 的值
|
||||
*/
|
||||
public clear() {
|
||||
this._element = null
|
||||
}
|
||||
}
|
||||
|
||||
export default SelectionRangeElem
|
|
@ -3,11 +3,27 @@
|
|||
* @author tonghan
|
||||
*/
|
||||
|
||||
import $ from '../../utils/dom-core'
|
||||
import $, { DomElement } from '../../utils/dom-core'
|
||||
import Editor from '../../editor/index'
|
||||
import DropListMenu from '../menu-constructors/DropListMenu'
|
||||
import { MenuActive } from '../menu-constructors/Menu'
|
||||
|
||||
import { updateRange } from './utils'
|
||||
|
||||
import { HandlerListOptions } from './ListHandle/ListHandle'
|
||||
import ListHandleCommand, { createListHandle, ClassType } from './ListHandle'
|
||||
|
||||
/**
|
||||
* 列表的种类
|
||||
*/
|
||||
export enum ListType {
|
||||
OrderedList = 'OL',
|
||||
UnorderedList = 'UL',
|
||||
}
|
||||
|
||||
// 序列类型
|
||||
type ListTypeValue = ListType
|
||||
|
||||
class List extends DropListMenu implements MenuActive {
|
||||
constructor(editor: Editor) {
|
||||
const $elem = $(
|
||||
|
@ -27,7 +43,7 @@ class List extends DropListMenu implements MenuActive {
|
|||
<i class="w-e-icon-list2 w-e-drop-list-item"></i>
|
||||
${editor.i18next.t('menus.dropListMenu.list.无序列表')}
|
||||
<p>`),
|
||||
value: 'insertUnorderedList',
|
||||
value: ListType.UnorderedList,
|
||||
},
|
||||
|
||||
{
|
||||
|
@ -37,69 +53,145 @@ class List extends DropListMenu implements MenuActive {
|
|||
${editor.i18next.t('menus.dropListMenu.list.有序列表')}
|
||||
<p>`
|
||||
),
|
||||
value: 'insertOrderedList',
|
||||
value: ListType.OrderedList,
|
||||
},
|
||||
],
|
||||
clickHandler: (value: string) => {
|
||||
// 注意 this 是指向当前的 List 对象
|
||||
this.command(value)
|
||||
this.command(value as ListTypeValue)
|
||||
},
|
||||
}
|
||||
|
||||
super($elem, editor, dropListConf)
|
||||
}
|
||||
|
||||
public command(value: string): void {
|
||||
public command(type: ListTypeValue): void {
|
||||
const editor = this.editor
|
||||
const $textElem = editor.$textElem
|
||||
editor.selection.restoreSelection()
|
||||
const $selectionElem = editor.selection.getSelectionContainerElem()
|
||||
|
||||
// 判断是否已经执行了命令
|
||||
if (editor.cmd.queryCommandState(value)) {
|
||||
return
|
||||
}
|
||||
// 选区范围的 DOM 元素不存在,不执行命令
|
||||
if ($selectionElem === undefined) return
|
||||
|
||||
//禁止在table中添加列表
|
||||
let $selectionElem = $(editor.selection.getSelectionContainerElem())
|
||||
// 获取选区范围内的顶级 DOM 元素
|
||||
this.handleSelectionRangeNodes(type)
|
||||
|
||||
if (!$selectionElem?.length) return
|
||||
|
||||
let $dom = $($selectionElem.elems[0]).parentUntil('TABLE', $selectionElem.elems[0])
|
||||
if ($dom && $($dom.elems[0]).getNodeName() === 'TABLE') {
|
||||
return
|
||||
}
|
||||
|
||||
editor.cmd.do(value)
|
||||
|
||||
// 验证列表是否被包裹在 <p> 之内
|
||||
if ($selectionElem.getNodeName() === 'LI') {
|
||||
$selectionElem = $selectionElem.parent()
|
||||
}
|
||||
|
||||
if (/^ol|ul$/i.test($selectionElem.getNodeName()) === false) {
|
||||
return
|
||||
}
|
||||
|
||||
if ($selectionElem.equal($textElem)) {
|
||||
// 证明是顶级标签,没有被 <p> 包裹
|
||||
return
|
||||
}
|
||||
|
||||
const $parent = $selectionElem.parent()
|
||||
if ($parent.equal($textElem)) {
|
||||
// $parent 是顶级标签,不能删除
|
||||
return
|
||||
}
|
||||
|
||||
$selectionElem.insertAfter($parent)
|
||||
$parent.remove()
|
||||
|
||||
// 恢复选区
|
||||
editor.selection.restoreSelection()
|
||||
// 是否激活
|
||||
this.tryChangeActive()
|
||||
}
|
||||
|
||||
public tryChangeActive(): void {}
|
||||
public validator($startElem: DomElement, $endElem: DomElement, $textElem: DomElement): boolean {
|
||||
if (
|
||||
!$startElem.length ||
|
||||
!$endElem.length ||
|
||||
$textElem.equal($startElem) ||
|
||||
$textElem.equal($endElem)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private handleSelectionRangeNodes(listType: ListTypeValue): void {
|
||||
const editor = this.editor
|
||||
const selection = editor.selection
|
||||
|
||||
// 获取 序列标签
|
||||
const listTarget = listType.toLowerCase()
|
||||
|
||||
// 获取相对应的 元属节点
|
||||
let $selectionElem = selection.getSelectionContainerElem() as DomElement
|
||||
const $startElem = (selection.getSelectionStartElem() as DomElement).getNodeTop(editor)
|
||||
const $endElem = (selection.getSelectionEndElem() as DomElement).getNodeTop(editor)
|
||||
|
||||
// 验证是否执行 处理逻辑
|
||||
if (!this.validator($startElem, $endElem, editor.$textElem)) {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取选区
|
||||
const _range = selection.getRange()
|
||||
const _collapsed = _range?.collapsed
|
||||
|
||||
// 防止光标的时候判断异常
|
||||
if (!editor.$textElem.equal($selectionElem)) {
|
||||
$selectionElem = $selectionElem.getNodeTop(editor)
|
||||
}
|
||||
|
||||
const options: HandlerListOptions = {
|
||||
editor,
|
||||
listType,
|
||||
listTarget,
|
||||
$selectionElem,
|
||||
$startElem,
|
||||
$endElem,
|
||||
}
|
||||
|
||||
let classType: ClassType
|
||||
|
||||
// =====================================
|
||||
// 当 selectionElem 属于序列元素的时候
|
||||
// 代表着当前选区一定是在一个序列元素内的
|
||||
// =====================================
|
||||
if (this.isOrderElem($selectionElem as DomElement)) {
|
||||
classType = ClassType.Wrap
|
||||
}
|
||||
|
||||
// =====================================
|
||||
// 当 startElem 和 endElem 属于序列元素的时候
|
||||
// 代表着当前选区一定是在再两个序列的中间(包括两个序列)
|
||||
// =====================================
|
||||
else if (
|
||||
this.isOrderElem($startElem as DomElement) &&
|
||||
this.isOrderElem($endElem as DomElement)
|
||||
) {
|
||||
classType = ClassType.Join
|
||||
}
|
||||
|
||||
// =====================================
|
||||
// 选区开始元素为 序列 的时候
|
||||
// =====================================
|
||||
else if (this.isOrderElem($startElem as DomElement)) {
|
||||
classType = ClassType.StartJoin
|
||||
}
|
||||
|
||||
// =====================================
|
||||
// 选区结束元素为 序列 的时候
|
||||
// =====================================
|
||||
else if (this.isOrderElem($endElem as DomElement)) {
|
||||
classType = ClassType.EndJoin
|
||||
}
|
||||
|
||||
// =====================================
|
||||
// 当选区不是序列内且开头和结尾不是序列的时候
|
||||
// 直接获取所有顶级段落然后过滤
|
||||
// 代表着 设置序列 的操作
|
||||
// =====================================
|
||||
else {
|
||||
classType = ClassType.Other
|
||||
}
|
||||
|
||||
const listHandleCmd = new ListHandleCommand(
|
||||
createListHandle(classType, options, _range as Range)
|
||||
)
|
||||
|
||||
// 更新选区
|
||||
updateRange(editor, listHandleCmd.getSelectionRangeElem(), !!_collapsed)
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是序列元素节点 UL and OL
|
||||
* @param $node
|
||||
*/
|
||||
private isOrderElem($node: DomElement) {
|
||||
const nodeName = $node.getNodeName()
|
||||
if (nodeName === ListType.OrderedList || nodeName === ListType.UnorderedList) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public tryChangeActive(): void { }
|
||||
}
|
||||
|
||||
export default List
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
import { ListType } from '.'
|
||||
import Editor from '../../editor/index'
|
||||
import $, { DomElement } from '../../utils/dom-core'
|
||||
import { ContainerFragment } from './ListHandle'
|
||||
|
||||
/**
|
||||
* 过滤 选择的 node 节点
|
||||
* @returns { DomElement[] } DomElement[]
|
||||
*/
|
||||
export function filterSelectionNodes($nodes: DomElement[]): DomElement[] {
|
||||
const $listHtml: DomElement[] = []
|
||||
$nodes.forEach(($node: DomElement) => {
|
||||
const targerName = $node.getNodeName()
|
||||
if (targerName !== ListType.OrderedList && targerName !== ListType.UnorderedList) {
|
||||
// 非序列
|
||||
$listHtml.push($node)
|
||||
} else {
|
||||
// 序列
|
||||
if ($node.prior) {
|
||||
$listHtml.push($node.prior)
|
||||
} else {
|
||||
const $children = $node.children()
|
||||
$children?.forEach(($li: HTMLElement) => {
|
||||
$listHtml.push($($li))
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return $listHtml
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新选区
|
||||
* @param $node
|
||||
*/
|
||||
|
||||
export function updateRange(editor: Editor, $node: DomElement, collapsed: boolean) {
|
||||
const selection = editor.selection
|
||||
const range = document.createRange()
|
||||
|
||||
// ===============================
|
||||
// length 大于 1
|
||||
// 代表着转换是一个文档节点多段落
|
||||
// ===============================
|
||||
if ($node.length > 1) {
|
||||
range.setStart($node.elems[0], 0)
|
||||
range.setEnd($node.elems[$node.length - 1], $node.elems[$node.length - 1].childNodes.length)
|
||||
}
|
||||
|
||||
// ===============================
|
||||
// 序列节点 或 单段落
|
||||
// ===============================
|
||||
else {
|
||||
range.selectNodeContents($node.elems[0])
|
||||
}
|
||||
|
||||
// ===============================
|
||||
// collapsed 为 true 代表是光标
|
||||
// ===============================
|
||||
collapsed && range.collapse(false)
|
||||
selection.saveRange(range)
|
||||
selection.restoreSelection()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取起点元素
|
||||
* @param $startElem 开始序列节点
|
||||
*/
|
||||
export function getStartPoint($startElem: DomElement): DomElement {
|
||||
return $startElem.prior
|
||||
? $startElem.prior // 有 prior 代表不是全选序列
|
||||
: $($startElem.children()?.elems[0]) // 没有则代表全选序列
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取结束元素
|
||||
* @param $endElem 结束序列节点
|
||||
*/
|
||||
export function getEndPoint($endElem: DomElement): DomElement {
|
||||
return $endElem.prior
|
||||
? $endElem.prior // 有 prior 代表不是全选序列
|
||||
: $($endElem.children()?.last().elems[0]) // 没有则代表全选序列
|
||||
}
|
||||
|
||||
/**
|
||||
* 在您指定节点的已有子节点之前插入新的子节点。
|
||||
* @param { DomElement } $node 指定节点
|
||||
* @param { ContainerFragment } newNode 插入的新子节点
|
||||
* @param { Node | null } existingNode 指定子节点
|
||||
*/
|
||||
export function insertBefore(
|
||||
$node: DomElement,
|
||||
newNode: ContainerFragment,
|
||||
existingNode: Node | null = null
|
||||
) {
|
||||
$node.parent().elems[0].insertBefore(newNode, existingNode)
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建指定的 element 对象
|
||||
*/
|
||||
export function createElement(target: string): HTMLElement {
|
||||
return document.createElement(target)
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建文档片段
|
||||
*/
|
||||
export function createDocumentFragment(): DocumentFragment {
|
||||
return document.createDocumentFragment()
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 li 标签的元素,并返回 $fragment 文档片段
|
||||
* @param { DomElement[] } $nodes 需要转换成 li 的 dom 元素数组
|
||||
* @param { ContainerFragment } $fragment 用于存储生成后 li 元素的文档片段
|
||||
*/
|
||||
export function createElementFragment(
|
||||
$nodes: DomElement[],
|
||||
$fragment: ContainerFragment,
|
||||
tag: string = 'li'
|
||||
): ContainerFragment {
|
||||
$nodes.forEach(($node: DomElement) => {
|
||||
const $list = createElement(tag)
|
||||
$list.innerHTML = $node.html()
|
||||
$fragment.append($list)
|
||||
$node.remove()
|
||||
})
|
||||
return $fragment
|
||||
}
|
|
@ -3,7 +3,7 @@ import $, { DomElement } from '../../../utils/dom-core'
|
|||
function bindEvent(editor: Editor) {
|
||||
function quoteEnter(e: Event) {
|
||||
const $selectElem = editor.selection.getSelectionContainerElem() as DomElement
|
||||
const $topSelectElem = editor.selection.getSelectionRangeTopNodes(editor)[0]
|
||||
const $topSelectElem = editor.selection.getSelectionRangeTopNodes()[0]
|
||||
// 对quote的enter进行特殊处理
|
||||
//最后一行为<p><br></p>时再按会出跳出blockquote
|
||||
if ($topSelectElem?.getNodeName() === 'BLOCKQUOTE') {
|
||||
|
|
|
@ -27,7 +27,7 @@ class Quote extends BtnMenu implements MenuActive {
|
|||
public clickHandler(): void {
|
||||
const editor = this.editor
|
||||
const isSelectEmpty = editor.selection.isSelectionEmpty()
|
||||
const topNodeElem: DomElement[] = editor.selection.getSelectionRangeTopNodes(editor)
|
||||
const topNodeElem: DomElement[] = editor.selection.getSelectionRangeTopNodes()
|
||||
const $topNodeElem: DomElement = topNodeElem[topNodeElem.length - 1]
|
||||
const nodeName = this.getTopNodeName()
|
||||
// IE 中不支持 formatBlock <BLOCKQUOTE> ,要用其他方式兼容
|
||||
|
@ -78,7 +78,7 @@ class Quote extends BtnMenu implements MenuActive {
|
|||
*/
|
||||
public tryChangeActive(): void {
|
||||
const editor = this.editor
|
||||
const cmdValue = editor.selection.getSelectionRangeTopNodes(editor)[0]?.getNodeName()
|
||||
const cmdValue = editor.selection.getSelectionRangeTopNodes()[0]?.getNodeName()
|
||||
if (cmdValue === 'BLOCKQUOTE') {
|
||||
this.active()
|
||||
} else {
|
||||
|
@ -93,7 +93,7 @@ class Quote extends BtnMenu implements MenuActive {
|
|||
*/
|
||||
private getTopNodeName(): string {
|
||||
const editor = this.editor
|
||||
const $topNodeElem = editor.selection.getSelectionRangeTopNodes(editor)[0]
|
||||
const $topNodeElem = editor.selection.getSelectionRangeTopNodes()[0]
|
||||
const nodeName = $topNodeElem?.getNodeName()
|
||||
|
||||
return nodeName
|
||||
|
|
|
@ -18,7 +18,7 @@ function bindEvent(editor: Editor) {
|
|||
if (isAllTodo(editor)) {
|
||||
e.preventDefault()
|
||||
const selection = editor.selection
|
||||
const $topSelectElem = selection.getSelectionRangeTopNodes(editor)[0]
|
||||
const $topSelectElem = selection.getSelectionRangeTopNodes()[0]
|
||||
const $li = $topSelectElem.childNodes()?.get(0)
|
||||
const selectionNode = window.getSelection()?.anchorNode as Node
|
||||
const range = selection.getRange()
|
||||
|
@ -95,7 +95,7 @@ function bindEvent(editor: Editor) {
|
|||
function delDown(e: Event) {
|
||||
if (isAllTodo(editor)) {
|
||||
const selection = editor.selection
|
||||
const $topSelectElem = selection.getSelectionRangeTopNodes(editor)[0]
|
||||
const $topSelectElem = selection.getSelectionRangeTopNodes()[0]
|
||||
const $li = $topSelectElem.childNodes()?.getNode()
|
||||
const $p = $(`<p></p>`)
|
||||
const p = $p.getNode()
|
||||
|
@ -136,7 +136,7 @@ function bindEvent(editor: Editor) {
|
|||
*/
|
||||
function deleteUp() {
|
||||
const selection = editor.selection
|
||||
const $topSelectElem = selection.getSelectionRangeTopNodes(editor)[0]
|
||||
const $topSelectElem = selection.getSelectionRangeTopNodes()[0]
|
||||
if (isTodo($topSelectElem)) {
|
||||
if ($topSelectElem.text() === '') {
|
||||
$(`<p><br></p>`).insertAfter($topSelectElem)
|
||||
|
|
|
@ -44,7 +44,7 @@ class Todo extends BtnMenu implements MenuActive {
|
|||
*/
|
||||
private setTodo() {
|
||||
const editor = this.editor
|
||||
const topNodeElem: DomElement[] = editor.selection.getSelectionRangeTopNodes(editor)
|
||||
const topNodeElem: DomElement[] = editor.selection.getSelectionRangeTopNodes()
|
||||
topNodeElem.forEach($node => {
|
||||
const nodeName = $node?.getNodeName()
|
||||
if (nodeName === 'P') {
|
||||
|
@ -64,7 +64,7 @@ class Todo extends BtnMenu implements MenuActive {
|
|||
*/
|
||||
private cancelTodo() {
|
||||
const editor = this.editor
|
||||
const $topNodeElems: DomElement[] = editor.selection.getSelectionRangeTopNodes(editor)
|
||||
const $topNodeElems: DomElement[] = editor.selection.getSelectionRangeTopNodes()
|
||||
|
||||
$topNodeElems.forEach($topNodeElem => {
|
||||
let content = $topNodeElem.childNodes()?.childNodes()?.clone(true) as DomElement
|
||||
|
|
|
@ -13,7 +13,7 @@ function isTodo($topSelectElem: DomElement) {
|
|||
* @param editor 编辑器对象
|
||||
*/
|
||||
function isAllTodo(editor: Editor): boolean | undefined {
|
||||
const $topSelectElems = editor.selection.getSelectionRangeTopNodes(editor)
|
||||
const $topSelectElems = editor.selection.getSelectionRangeTopNodes()
|
||||
// 排除为[]的情况
|
||||
if ($topSelectElems.length === 0) return
|
||||
|
||||
|
|
|
@ -89,13 +89,15 @@ function _styleArrTrim(style: string | string[]): string[] {
|
|||
export type DomElementSelector =
|
||||
| string
|
||||
| DomElement
|
||||
| HTMLElement
|
||||
| Element
|
||||
| Document
|
||||
| HTMLCollection
|
||||
| Node
|
||||
| NodeList
|
||||
| ChildNode
|
||||
| ChildNode[]
|
||||
| Element
|
||||
| HTMLElement
|
||||
| HTMLElement[]
|
||||
| HTMLCollection
|
||||
| EventTarget
|
||||
| null
|
||||
| undefined
|
||||
|
@ -107,6 +109,7 @@ export class DomElement<T extends DomElementSelector = DomElementSelector> {
|
|||
length: number
|
||||
elems: HTMLElement[]
|
||||
dataSource: Map<string, any>
|
||||
prior?: DomElement // 通过 getNodeTop 获取顶级段落的时候,可以通过 prior 去回溯来源的子节点
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
|
@ -805,15 +808,25 @@ export class DomElement<T extends DomElementSelector = DomElementSelector> {
|
|||
* @param editor 富文本实例
|
||||
*/
|
||||
getNodeTop(editor: Editor): DomElement {
|
||||
// 异常抛出,空的 DomElement 直接返回
|
||||
if (this.length < 1) {
|
||||
return this
|
||||
}
|
||||
|
||||
// 获取父级元素,并判断是否是 编辑区域
|
||||
// 如果是则返回当前节点
|
||||
const $parent = this.parent()
|
||||
if (editor.$textElem.equal($parent)) {
|
||||
return this
|
||||
}
|
||||
|
||||
// 到了此处,即代表当前节点不是顶级段落
|
||||
// 将当前节点存放于父节点的 prior 字段下
|
||||
// 主要用于 回溯 子节点
|
||||
// 例如:ul ol 等标签
|
||||
// 实际操作的节点是 li 但是一个 ul ol 的子节点可能有多个
|
||||
// 所以需要对其进行 回溯 找到对应的子节点
|
||||
$parent.prior = this
|
||||
return $parent.getNodeTop(editor)
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* @author tonghan
|
||||
*/
|
||||
|
||||
import List from '../../../src/menus/list/index'
|
||||
import List, { ListType } from '../../../src/menus/list/index'
|
||||
import Editor from '../../../src/editor'
|
||||
import mockCmdFn from '../../helpers/command-mock'
|
||||
import createEditor from '../../helpers/create-editor'
|
||||
|
@ -24,11 +24,11 @@ test('list 菜单:dropList', () => {
|
|||
|
||||
test('list 菜单:有序', () => {
|
||||
mockCmdFn(document)
|
||||
listMenu.command('insertOrderedList')
|
||||
expect(document.execCommand).toBeCalledWith('insertOrderedList', false, undefined)
|
||||
listMenu.command(ListType.OrderedList)
|
||||
expect(editor.$textElem.html().includes('<ol>')).toBeTruthy()
|
||||
})
|
||||
|
||||
test('list 菜单:无序', () => {
|
||||
listMenu.command('insertUnorderedList')
|
||||
expect(document.execCommand).toBeCalledWith('insertUnorderedList', false, undefined)
|
||||
listMenu.command(ListType.UnorderedList)
|
||||
expect(editor.$textElem.html().includes('<ul>')).toBeTruthy()
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue