817 lines
21 KiB
TypeScript
817 lines
21 KiB
TypeScript
import {
|
||
$,
|
||
CardEntry,
|
||
DATA_TRANSIENT_ATTRIBUTES,
|
||
isEngine,
|
||
MarkPlugin,
|
||
NodeInterface,
|
||
Parser,
|
||
Range,
|
||
RangeInterface,
|
||
SchemaGlobal,
|
||
SchemaMark,
|
||
Selection,
|
||
PluginOptions,
|
||
uuid,
|
||
EDITABLE_SELECTOR,
|
||
CARD_SELECTOR,
|
||
transformCustomTags,
|
||
} from '@aomao/engine';
|
||
import { Path } from 'sharedb';
|
||
|
||
export interface MarkRangeOptions extends PluginOptions {
|
||
keys: Array<string>;
|
||
hotkey?: string | Array<string>;
|
||
}
|
||
|
||
const PLUGIN_NAME = 'mark-range';
|
||
|
||
export default class<T extends MarkRangeOptions> extends MarkPlugin<T> {
|
||
private range?: RangeInterface;
|
||
private executeBySelf: boolean = false;
|
||
private MARK_KEY = `data-mark-key`;
|
||
private MARK_UUID = `data-mark-uuid`;
|
||
private ids: { [key: string]: Array<string> } = {};
|
||
private m_uuid = uuid(18, 24);
|
||
|
||
readonly followStyle: boolean = false;
|
||
|
||
readonly copyOnEnter: boolean = false;
|
||
|
||
#isRevoke: boolean = false;
|
||
#isPreview: boolean = false;
|
||
#isApply: boolean = false;
|
||
#previewAwating?: (value: boolean) => void;
|
||
|
||
static get pluginName() {
|
||
return PLUGIN_NAME;
|
||
}
|
||
|
||
tagName = 'span';
|
||
|
||
combineValueByWrap = true;
|
||
|
||
getIdName(key: string) {
|
||
return `data-${key}-id`;
|
||
}
|
||
|
||
getPreviewName(key: string) {
|
||
return `data-${key}-preview`;
|
||
}
|
||
|
||
init() {
|
||
super.init();
|
||
const globals: Array<SchemaGlobal> = [];
|
||
this.options.keys.forEach((key) => {
|
||
globals.push(
|
||
{
|
||
type: 'block',
|
||
attributes: {
|
||
[this.getIdName(key)]: '*',
|
||
[this.MARK_KEY]: key,
|
||
},
|
||
},
|
||
{
|
||
type: 'inline',
|
||
attributes: {
|
||
[this.getIdName(key)]: '*',
|
||
[this.MARK_KEY]: key,
|
||
},
|
||
},
|
||
);
|
||
});
|
||
this.editor.schema.add(globals);
|
||
this.editor.on('beforeCommandExecute', (name: string) => {
|
||
this.executeBySelf = name === PLUGIN_NAME;
|
||
});
|
||
this.editor.on('afterCommandExecute', (name: string) => {
|
||
this.executeBySelf = false;
|
||
});
|
||
|
||
if (isEngine(this.editor)) {
|
||
const { change } = this.editor;
|
||
this.editor.on('change', (_, trigger) => {
|
||
this.triggerChange(trigger !== 'local');
|
||
});
|
||
this.editor.on('select', () => this.onSelectionChange());
|
||
this.editor.on('parse:value', (node, atts) => {
|
||
const key = node.attributes(this.MARK_KEY);
|
||
if (!!key) {
|
||
atts[DATA_TRANSIENT_ATTRIBUTES] = this.getPreviewName(key);
|
||
}
|
||
});
|
||
this.editor.on('afterSetValue', () => {
|
||
this.range = change.range.get();
|
||
this.ids = this.getIds();
|
||
});
|
||
const keys = this.options.keys.map((key) =>
|
||
this.getPreviewName(key),
|
||
);
|
||
this.editor.history.onFilter((op) => {
|
||
if (
|
||
('od' in op || 'oi' in op) &&
|
||
keys.includes(op.p[op.p.length - 1].toString())
|
||
) {
|
||
return true;
|
||
}
|
||
return false;
|
||
});
|
||
this.editor.history.onSelf(() => {
|
||
if (this.#isPreview && !this.#previewAwating) {
|
||
return new Promise<boolean>((resolve) => {
|
||
this.#previewAwating = resolve;
|
||
this.#isPreview = false;
|
||
});
|
||
} else if (this.#isRevoke && this.#previewAwating) {
|
||
this.#previewAwating(false);
|
||
} else if (this.#isApply && this.#previewAwating) {
|
||
this.#previewAwating(true);
|
||
}
|
||
return;
|
||
});
|
||
} else {
|
||
this.editor.container.document?.addEventListener(
|
||
'selectionchange',
|
||
() => this.onSelectionChange(),
|
||
);
|
||
}
|
||
}
|
||
|
||
schema() {
|
||
const rules: Array<SchemaMark> = this.options.keys.map((key) => {
|
||
return {
|
||
name: 'span',
|
||
type: 'mark',
|
||
attributes: {
|
||
[this.MARK_KEY]: {
|
||
required: true,
|
||
value: key,
|
||
},
|
||
[this.MARK_UUID]: '*',
|
||
[this.getIdName(key)]: '*',
|
||
},
|
||
};
|
||
});
|
||
return rules;
|
||
}
|
||
/**
|
||
* 获取当前选择的标记id
|
||
* @param range 光标
|
||
* @param strict 是否严格模式
|
||
* @returns
|
||
*/
|
||
getSelectInfo(range: RangeInterface, strict?: boolean) {
|
||
const { card } = this.editor;
|
||
const cloneRange = range
|
||
.cloneRange()
|
||
.shrinkToElementNode()
|
||
.shrinkToTextNode();
|
||
const { startNode, startOffset, endNode, endOffset, collapsed } =
|
||
cloneRange;
|
||
let startMark = startNode.closest(`[${this.MARK_KEY}]`);
|
||
const startChild = startNode.children().eq(startOffset);
|
||
//如果选择是块级卡片就选择在卡片根节点
|
||
if (startNode.type === Node.ELEMENT_NODE && startChild?.isBlockCard()) {
|
||
startMark = startChild;
|
||
} else {
|
||
const cardMark = card.find(startMark);
|
||
if (
|
||
cardMark &&
|
||
!cardMark.isEditable &&
|
||
cardMark.root.isBlockCard()
|
||
) {
|
||
startMark = cardMark.root;
|
||
}
|
||
}
|
||
let key = startMark.attributes(this.MARK_KEY);
|
||
//获取节点标记ID
|
||
const startId = startMark.attributes(this.getIdName(key));
|
||
//设置当前选中的标记ID
|
||
let selectId: string | undefined = !!startId ? startId : undefined;
|
||
|
||
//不是重合状态,并且开始节点不是块级卡片
|
||
if (!collapsed && !!startId && !startMark.isBlockCard()) {
|
||
let endMark = endNode.closest(`[${this.MARK_KEY}]`);
|
||
const endKey = endMark.attributes(this.MARK_KEY);
|
||
const endChild = endNode.children().eq(endOffset);
|
||
//如果选择是块级卡片就选择在卡片根节点
|
||
if (endNode.type === Node.ELEMENT_NODE && endChild?.isBlockCard()) {
|
||
endMark = endChild;
|
||
}
|
||
const endId = endMark.attributes(this.getIdName(key));
|
||
//需要两端都是同一类型同一个id才需要显示
|
||
if (key === endKey && startId === endId) {
|
||
selectId = startId;
|
||
//严格模式,开始节点和结束节点需要在节点内的两侧
|
||
if (strict) {
|
||
const strictRange = Range.from(this.editor)?.cloneRange();
|
||
strictRange?.setStart(startMark, 0);
|
||
strictRange?.setEnd(
|
||
endMark,
|
||
endMark.isText()
|
||
? endMark.text().length
|
||
: endMark.children().length,
|
||
);
|
||
if (
|
||
!strictRange
|
||
?.shrinkToElementNode()
|
||
.shrinkToTextNode()
|
||
?.equal(cloneRange)
|
||
)
|
||
selectId = undefined;
|
||
}
|
||
} else selectId = undefined;
|
||
}
|
||
return selectId
|
||
? {
|
||
key,
|
||
id: selectId.split(',')[0],
|
||
}
|
||
: undefined;
|
||
}
|
||
/**
|
||
* 根据编号获取所有节点
|
||
* @param key 标记类型
|
||
* @param id 编号
|
||
* @returns
|
||
*/
|
||
findElements(key: string, id: string) {
|
||
const { container } = this.editor;
|
||
const elements: Array<NodeInterface> = [];
|
||
container.find(`[${this.getIdName(key)}]`).each((markNode) => {
|
||
const mark = $(markNode);
|
||
const ids = mark.attributes(this.getIdName(key)).trim().split(',');
|
||
if (ids.indexOf(id) > -1) elements.push(mark);
|
||
});
|
||
return elements;
|
||
}
|
||
/**
|
||
* 预览
|
||
* @param id 标记id,否则预览当前光标位置
|
||
*/
|
||
preview(key: string, id?: string) {
|
||
if (id) {
|
||
const elements = this.findElements(key, id);
|
||
elements.forEach((markNode) => {
|
||
markNode.attributes(
|
||
DATA_TRANSIENT_ATTRIBUTES,
|
||
this.getPreviewName(key),
|
||
);
|
||
markNode.attributes(this.getPreviewName(key), 'true');
|
||
});
|
||
} else if (this.range) {
|
||
const { block, node, card } = this.editor;
|
||
let range = this.range;
|
||
//光标重合时,选择整个block块
|
||
if (range.collapsed) {
|
||
const blockNode = block.closest(range.startNode);
|
||
if (!node.isBlock(blockNode)) return;
|
||
range.select(blockNode, true);
|
||
const selection = window.getSelection();
|
||
selection?.removeAllRanges();
|
||
selection?.addRange(range.toRange());
|
||
}
|
||
const selectInfo = this.getSelectInfo(range, true);
|
||
//当前光标已存在标记
|
||
if (selectInfo && selectInfo.key === key) {
|
||
//触发选择
|
||
this.editor.trigger(`${PLUGIN_NAME}:select`, range, selectInfo);
|
||
return;
|
||
}
|
||
//包裹标记预览样式
|
||
this.editor.mark.wrap(
|
||
`<${this.tagName} ${
|
||
this.MARK_KEY
|
||
}="${key}" ${DATA_TRANSIENT_ATTRIBUTES}="${this.getPreviewName(
|
||
key,
|
||
)}" ${this.MARK_UUID}="${this.m_uuid}" ${this.getPreviewName(
|
||
key,
|
||
)}="true" />`,
|
||
range,
|
||
);
|
||
//遍历当前光标选择节点,拼接选择的文本
|
||
let text = '';
|
||
const subRanges = range.getSubRanges(true, false);
|
||
subRanges.forEach((subRange) => {
|
||
//如果是卡片,就给卡片加上预览样式
|
||
const cardComponent = card.find(subRange.startNode);
|
||
if (cardComponent && !cardComponent.executeMark) {
|
||
text += `[card:${
|
||
(cardComponent.constructor as CardEntry).cardName
|
||
},${cardComponent.id}]`;
|
||
if (cardComponent.root.attributes(this.getIdName(key)))
|
||
return;
|
||
cardComponent.root.attributes(this.MARK_KEY, key);
|
||
cardComponent.root.attributes(
|
||
this.getPreviewName(key),
|
||
'true',
|
||
);
|
||
} else {
|
||
text += subRange.getText();
|
||
}
|
||
});
|
||
this.#isPreview = true;
|
||
return text;
|
||
}
|
||
return;
|
||
}
|
||
|
||
/**
|
||
* 应用标记样式到编辑器
|
||
* @param 标记类型
|
||
* @param id
|
||
*/
|
||
apply(key: string, id: string) {
|
||
//遍历预览节点
|
||
this.editor.container
|
||
.find(`[${this.getPreviewName(key)}]`)
|
||
.each((markNode) => {
|
||
const mark = $(markNode);
|
||
//获取旧id
|
||
const oldIds = mark
|
||
.attributes(this.getIdName(key))
|
||
.trim()
|
||
.split(',');
|
||
//组合新的id串
|
||
let ids: Array<string> = [];
|
||
if (oldIds[0] === '') oldIds.splice(0, 1);
|
||
//范围大的id放在后面
|
||
if (oldIds.length > 0) {
|
||
for (let i = 0; i < oldIds.length; i++) {
|
||
const oldId = oldIds[i];
|
||
//判断之前旧的id对应光标位置是否包含当前节点
|
||
const parent = markNode.parentElement;
|
||
if (
|
||
parent &&
|
||
oldIds.indexOf(id) < 0 &&
|
||
ids.indexOf(id) < 0
|
||
) {
|
||
const elements = this.findElements(key, oldId);
|
||
const oldRange = Range.from(
|
||
this.editor,
|
||
)?.cloneRange();
|
||
if (!oldRange || elements.length === 0) continue;
|
||
const oldBegin = oldRange
|
||
.select(elements[0], true)
|
||
.collapse(true)
|
||
.cloneRange();
|
||
const oldEnd = oldRange
|
||
.select(elements[elements.length - 1], true)
|
||
.collapse(false)
|
||
.cloneRange();
|
||
oldRange.setStart(
|
||
oldBegin.startContainer,
|
||
oldBegin.startOffset,
|
||
);
|
||
oldRange.setEnd(
|
||
oldEnd.endContainer,
|
||
oldEnd.endOffset,
|
||
);
|
||
const reuslt = oldRange.comparePoint(
|
||
parent,
|
||
mark.index(),
|
||
);
|
||
if (reuslt >= 0) {
|
||
ids.push(id);
|
||
ids = ids.concat(oldIds.slice(i));
|
||
break;
|
||
}
|
||
}
|
||
ids.push(oldId);
|
||
}
|
||
//未增加就驾到末尾
|
||
if (
|
||
ids.length === oldIds.length &&
|
||
oldIds.indexOf(id) < 0
|
||
) {
|
||
ids.push(id);
|
||
}
|
||
} else {
|
||
ids.push(id);
|
||
}
|
||
mark.attributes(
|
||
DATA_TRANSIENT_ATTRIBUTES,
|
||
this.getPreviewName(key),
|
||
);
|
||
//设置新的id串
|
||
mark.attributes(this.getIdName(key), ids.join(','));
|
||
mark.removeAttributes(this.getPreviewName(key));
|
||
const editableCard = mark.closest(EDITABLE_SELECTOR);
|
||
if (editableCard.length > 0) {
|
||
const cardComponent = this.editor.card.find(
|
||
editableCard,
|
||
true,
|
||
);
|
||
if (cardComponent && cardComponent.onChange)
|
||
cardComponent.onChange('local', cardComponent.root);
|
||
}
|
||
});
|
||
this.#isApply = true;
|
||
}
|
||
/**
|
||
* 遗弃预览项
|
||
* @param key 标记类型
|
||
* @param id 编号,不传编号则遗弃所有预览项
|
||
*/
|
||
revoke(key: string, id?: string) {
|
||
const { node } = this.editor;
|
||
let elements: Array<NodeInterface | Node> = [];
|
||
if (id) elements = this.findElements(key, id);
|
||
else
|
||
elements = this.editor.container
|
||
.find(`[${this.getPreviewName(key)}]`)
|
||
.toArray();
|
||
//遍历预览节点
|
||
elements.forEach((markNode) => {
|
||
const mark = $(markNode);
|
||
//获取旧id传
|
||
const oldIds = mark
|
||
.attributes(this.getIdName(key))
|
||
.trim()
|
||
.split(',');
|
||
if (oldIds[0] === '') oldIds.splice(0, 1);
|
||
//如果没有id,移除标记样式包裹
|
||
if (oldIds.length === 0) {
|
||
if (mark.isCard()) {
|
||
mark.removeAttributes(this.MARK_KEY);
|
||
mark.removeAttributes(this.getPreviewName(key));
|
||
} else {
|
||
node.unwrap(mark);
|
||
}
|
||
} else {
|
||
//移除预览样式
|
||
mark.removeAttributes(this.getPreviewName(key));
|
||
}
|
||
});
|
||
if (!id && elements.length > 0 && isEngine(this.editor)) {
|
||
this.#isRevoke = true;
|
||
}
|
||
}
|
||
/**
|
||
* 移除标识
|
||
* @param key 标记类型
|
||
* @param id 编号
|
||
*/
|
||
remove(key: string, id: string) {
|
||
const { node } = this.editor;
|
||
|
||
const elements: Array<NodeInterface | Node> = this.findElements(
|
||
key,
|
||
id,
|
||
);
|
||
|
||
//遍历节点
|
||
elements.forEach((markNode) => {
|
||
const mark = $(markNode);
|
||
//获取旧id传
|
||
const oldIds = mark
|
||
.attributes(this.getIdName(key))
|
||
.trim()
|
||
.split(',');
|
||
if (oldIds[0] === '') oldIds.splice(0, 1);
|
||
//移除标记样式包裹
|
||
const editableCard = mark.closest(EDITABLE_SELECTOR);
|
||
if (oldIds.length === 1 && !!oldIds.find((i) => i === id)) {
|
||
if (mark.isCard()) {
|
||
mark.removeAttributes(this.MARK_KEY);
|
||
mark.removeAttributes(this.getIdName(key));
|
||
mark.removeAttributes(this.getPreviewName(key));
|
||
} else {
|
||
node.unwrap(mark);
|
||
}
|
||
} else {
|
||
//移除预览样式
|
||
mark.removeAttributes(this.getPreviewName(key));
|
||
//移除id
|
||
const index = oldIds.findIndex((i) => i === id);
|
||
oldIds.splice(index, 1);
|
||
mark.attributes(this.getIdName(key), oldIds.join(','));
|
||
}
|
||
if (editableCard.length > 0) {
|
||
const cardComponent = this.editor.card.find(editableCard, true);
|
||
if (cardComponent && cardComponent.onChange)
|
||
cardComponent.onChange('local', cardComponent.root);
|
||
}
|
||
});
|
||
}
|
||
|
||
hotkey() {
|
||
return this.options.hotkey || '';
|
||
}
|
||
|
||
execute() {}
|
||
|
||
action(key: string, action: string, ...args: any): any {
|
||
const id = args[0];
|
||
switch (action) {
|
||
case 'preview':
|
||
const reuslt = this.preview(key, id);
|
||
return reuslt;
|
||
case 'apply':
|
||
if (!id) return;
|
||
this.apply(key, id);
|
||
break;
|
||
case 'revoke':
|
||
this.revoke(key, id);
|
||
break;
|
||
case 'find':
|
||
if (!id) return [];
|
||
return this.findElements(key, id);
|
||
case 'remove':
|
||
if (!id) return;
|
||
this.remove(key, id);
|
||
break;
|
||
case 'filter':
|
||
return this.filterValue(key, id);
|
||
case 'wrap':
|
||
const value = args[1];
|
||
return this.wrapFromPath(key, id, value);
|
||
}
|
||
}
|
||
|
||
getIds() {
|
||
const ids: { [key: string]: Array<string> } = {};
|
||
this.editor.container.find(`[${this.MARK_KEY}]`).each((markNode) => {
|
||
const mark = $(markNode);
|
||
const key = mark.attributes(this.MARK_KEY);
|
||
const idArray = mark.attributes(this.getIdName(key)).split(',');
|
||
idArray.forEach((id) => {
|
||
if (!!id) {
|
||
if (!ids[key]) ids[key] = [];
|
||
if (ids[key].indexOf(id) < 0) ids[key].push(id);
|
||
}
|
||
});
|
||
});
|
||
return ids;
|
||
}
|
||
|
||
/**
|
||
* 光标选择改变触发
|
||
* @returns
|
||
*/
|
||
onSelectionChange() {
|
||
if (this.executeBySelf) return;
|
||
const { window } = this.editor.container;
|
||
const selection = window?.getSelection();
|
||
|
||
if (!selection) return;
|
||
const range = Range.from(this.editor, selection);
|
||
if (!range) return;
|
||
|
||
//不在编辑器内
|
||
if (
|
||
!$(range.getStartOffsetNode()).inEditor() ||
|
||
!$(range.getEndOffsetNode()).inEditor()
|
||
) {
|
||
this.editor.trigger(`${PLUGIN_NAME}:select`, range);
|
||
this.range = undefined;
|
||
return;
|
||
}
|
||
|
||
this.triggerChange();
|
||
|
||
const selectInfo = this.getSelectInfo(range, true);
|
||
this.editor.trigger(`${PLUGIN_NAME}:select`, range, selectInfo);
|
||
this.range = range;
|
||
}
|
||
|
||
triggerChange(remote: boolean = false) {
|
||
const addIds: { [key: string]: Array<string> } = {};
|
||
const removeIds: { [key: string]: Array<string> } = {};
|
||
const ids = this.getIds();
|
||
|
||
this.options.keys.forEach((key) => {
|
||
const prevIds = this.ids[key] || [];
|
||
const curIds = ids[key] || [];
|
||
curIds.forEach((id) => {
|
||
if (prevIds.indexOf(id) < 0) {
|
||
if (!addIds[key]) addIds[key] = [];
|
||
addIds[key].push(id);
|
||
}
|
||
});
|
||
prevIds.forEach((id) => {
|
||
if (curIds.indexOf(id) < 0) {
|
||
if (!removeIds[key]) removeIds[key] = [];
|
||
removeIds[key].push(id);
|
||
}
|
||
});
|
||
});
|
||
if (remote) {
|
||
const currentElements = this.editor.container.find(
|
||
`[${this.MARK_UUID}="${this.m_uuid}"]`,
|
||
);
|
||
currentElements.each((_, index) => {
|
||
const child = currentElements.eq(index);
|
||
const attributes = child?.attributes() || {};
|
||
const key = attributes[this.MARK_KEY];
|
||
// 如果这个元素没有被标记,并且没有创建、没有预览选项就增加预览样式
|
||
const previewName = this.getPreviewName(key);
|
||
if (
|
||
key &&
|
||
!attributes[this.getIdName(key)] &&
|
||
!attributes[previewName]
|
||
) {
|
||
child!.attributes(previewName, 'true');
|
||
}
|
||
});
|
||
}
|
||
this.ids = ids;
|
||
this.editor.trigger(`${PLUGIN_NAME}:change`, addIds, removeIds, ids);
|
||
}
|
||
|
||
/**
|
||
* 过滤标记样式,并返回每个样式的路径
|
||
* @param value 编辑器值
|
||
* @returns 过滤后的值和路径
|
||
*/
|
||
filterValue(
|
||
key: string,
|
||
value?: string,
|
||
): {
|
||
value: string;
|
||
paths: Array<{ id: Array<string>; path: Array<Path> }>;
|
||
} {
|
||
const { node, card } = this.editor;
|
||
const container = this.editor.container.clone(value ? false : true);
|
||
container.css({
|
||
position: 'fixed',
|
||
top: 0,
|
||
clip: 'rect(0, 0, 0, 0)',
|
||
});
|
||
$(document.body).append(container);
|
||
if (value) container.html(transformCustomTags(value));
|
||
|
||
card.render(container, undefined, false);
|
||
|
||
const selection = container.window?.getSelection();
|
||
const range = (
|
||
selection
|
||
? Range.from(this.editor, selection) ||
|
||
Range.create(this.editor)
|
||
: Range.create(this.editor)
|
||
).cloneRange();
|
||
|
||
const parser = new Parser(container, this.editor, undefined, false);
|
||
const { schema, conversion } = this.editor;
|
||
if (!range) {
|
||
container.remove();
|
||
return {
|
||
value: value ? value : parser.toValue(schema, conversion),
|
||
paths: [],
|
||
};
|
||
}
|
||
range.select(container, true).collapse(true);
|
||
|
||
const paths: Array<{ id: Array<string>; path: Array<Path> }> = [];
|
||
container.traverse(
|
||
(childNode) => {
|
||
const id = childNode.attributes(this.getIdName(key));
|
||
if (!!id) {
|
||
const rangeClone = range.cloneRange();
|
||
|
||
if (childNode.isCard()) {
|
||
const cardComponent = card.find(childNode);
|
||
if (cardComponent && cardComponent.executeMark) {
|
||
const idName = this.getIdName(key);
|
||
const markNode = cardComponent.root.find(
|
||
`[${idName}="${id}"]`,
|
||
);
|
||
cardComponent.executeMark(markNode.clone(), false);
|
||
} else {
|
||
childNode.removeAttributes(this.getIdName(key));
|
||
}
|
||
rangeClone.select(childNode);
|
||
} else {
|
||
rangeClone.select(childNode, true);
|
||
const selection = rangeClone.createSelection();
|
||
node.unwrap(childNode);
|
||
selection.move();
|
||
}
|
||
const rangePath = rangeClone
|
||
.shrinkToElementNode()
|
||
.shrinkToTextNode()
|
||
.toPath(undefined, container);
|
||
paths.push({
|
||
id: id.split(','),
|
||
path: rangePath
|
||
? [rangePath.start.path, rangePath.end.path]
|
||
: [],
|
||
});
|
||
}
|
||
},
|
||
false,
|
||
true,
|
||
);
|
||
const cardNodes = container.find(CARD_SELECTOR);
|
||
cardNodes.each((_, index) => {
|
||
const cardNode = cardNodes.eq(index);
|
||
if (cardNode?.isEditableCard()) {
|
||
const card = this.editor.card.find(cardNode);
|
||
if (card) {
|
||
const value = card.getValue();
|
||
card.setValue(value || {});
|
||
}
|
||
}
|
||
});
|
||
value = parser.toValue(schema, conversion);
|
||
container.remove();
|
||
return {
|
||
value,
|
||
paths,
|
||
};
|
||
}
|
||
/**
|
||
* 从标记样式路径还原包裹编辑器值
|
||
* @param paths 标记样式路径
|
||
* @param value 编辑器值
|
||
* @returns
|
||
*/
|
||
wrapFromPath(
|
||
key: string,
|
||
paths: Array<{ id: Array<string>; path: Array<Path> }>,
|
||
value?: string,
|
||
): string {
|
||
const { card } = this.editor;
|
||
const container = this.editor.container.clone(value ? false : true);
|
||
if (value) value = Selection.removeTags(value);
|
||
container.css({
|
||
position: 'fixed',
|
||
top: 0,
|
||
clip: 'rect(0, 0, 0, 0)',
|
||
});
|
||
$(document.body).append(container);
|
||
if (value) container.html(transformCustomTags(value));
|
||
|
||
card.render(container, undefined, false);
|
||
const selection = container.window?.getSelection();
|
||
const range = (
|
||
selection
|
||
? Range.from(this.editor, selection) ||
|
||
Range.create(this.editor)
|
||
: Range.create(this.editor)
|
||
).cloneRange();
|
||
|
||
const parser = new Parser(container, this.editor, undefined, false);
|
||
const { schema, conversion } = this.editor;
|
||
if (!range) {
|
||
container.remove();
|
||
return value ? value : parser.toValue(schema, conversion);
|
||
}
|
||
|
||
range.select(container, true).collapse(true);
|
||
|
||
(paths || []).forEach(({ id, path }) => {
|
||
const pathRange = Range.fromPath(
|
||
this.editor,
|
||
{
|
||
start: { path: path[0] as number[], id: '', bi: -1 },
|
||
end: { path: path[1] as number[], id: '', bi: -1 },
|
||
},
|
||
undefined,
|
||
container,
|
||
);
|
||
const elements = pathRange.findElements();
|
||
elements.forEach((element) => {
|
||
const node = $(element);
|
||
if (node.isCard()) {
|
||
const cardComponent = card.find(node);
|
||
if (cardComponent && cardComponent.executeMark) {
|
||
cardComponent.executeMark(
|
||
$(
|
||
`<${this.tagName} ${
|
||
this.MARK_KEY
|
||
}="${key}" ${this.getIdName(key)}="${id.join(
|
||
',',
|
||
)}" />`,
|
||
),
|
||
true,
|
||
);
|
||
} else {
|
||
node.attributes(this.getIdName(key), id.join(','));
|
||
}
|
||
}
|
||
});
|
||
this.editor.mark.wrap(
|
||
`<${this.tagName} ${this.MARK_KEY}="${key}" ${this.getIdName(
|
||
key,
|
||
)}="${id.join(',')}" />`,
|
||
pathRange,
|
||
);
|
||
});
|
||
const cardNodes = container.find(CARD_SELECTOR);
|
||
cardNodes.each((_, index) => {
|
||
const cardNode = cardNodes.eq(index);
|
||
if (cardNode?.isEditableCard()) {
|
||
const card = this.editor.card.find(cardNode);
|
||
if (card) {
|
||
const value = card.getValue();
|
||
card.setValue(value || {});
|
||
}
|
||
}
|
||
});
|
||
value = parser.toValue(schema, conversion);
|
||
container.remove();
|
||
return value;
|
||
}
|
||
}
|