- 表格中不能导出未渲染卡片的html
- 优化标记插件协同交互
- 协同交互影响了当前编辑者的光标
- 浏览器会响应默认快捷键命令与当前绑定的快捷键会重复执行命令
This commit is contained in:
yanmao 2021-11-25 19:11:52 +08:00
parent f299f6a704
commit 8da00f1ba9
22 changed files with 365 additions and 277 deletions

View File

@ -126,7 +126,7 @@ export const pluginConfig: { [key: string]: PluginOptions } = {
},
[Image.pluginName]: {
onBeforeRender: (status: string, url: string) => {
if (!url) return url;
if (!url || url.indexOf('http') === 0) return url;
return url + `?token=12323`;
},
},
@ -138,7 +138,8 @@ export const pluginConfig: { [key: string]: PluginOptions } = {
remote: {
action: `${DOMAIN}/upload/image`,
},
isRemote: (src: string) => src.indexOf(DOMAIN) < 0,
isRemote: (src: string) =>
src.indexOf(DOMAIN) < 0 && src.indexOf('192.168') < 0,
},
[FileUploader.pluginName]: {
action: `${DOMAIN}/upload/file`,

View File

@ -627,16 +627,20 @@ class CardModel implements CardModelInterface {
/**
*
* @param container
* @param options
* @param callback
* @param lazyRender editor的lazyRender属性
*/
render(container?: NodeInterface, callback?: (count: number) => void) {
render(
container?: NodeInterface,
callback?: (count: number) => void,
lazyRender = this.lazyRender,
) {
const cards = container
? container.isCard()
? container
: container.find(`${READY_CARD_SELECTOR}`)
: this.editor.container.find(READY_CARD_SELECTOR);
this.gc();
let setp = 0;
const asyncRenderCards: Array<CardInterface> = [];
cards.each((node) => {
@ -686,8 +690,8 @@ class CardModel implements CardModelInterface {
}
});
asyncRenderCards.forEach(async (card) => {
if (this.lazyRender && (card.constructor as CardEntry).lazyRender) {
asyncRenderCards.forEach((card) => {
if (lazyRender && (card.constructor as CardEntry).lazyRender) {
if (card.beforeRender) {
const result = card.beforeRender();
const center = card.getCenter();
@ -702,15 +706,9 @@ class CardModel implements CardModelInterface {
} else {
this.renderComponent(card);
}
setp++;
if (setp === asyncRenderCards.length) {
if (callback) callback(asyncRenderCards.length);
}
});
if (asyncRenderCards.length === 0) {
if (callback) callback(0);
}
if (asyncRenderCards.length > 0) {
if (callback) callback(asyncRenderCards.length);
if (this.asyncComponents.length > 0) {
// 触发当前在视图内的卡片渲染
this.renderAsnycComponents();
}
@ -718,7 +716,7 @@ class CardModel implements CardModelInterface {
renderComponent(card: CardInterface, ...args: any) {
const center = card.getCenter();
const result = card.render();
const result = card.render(...args);
if (result !== undefined) {
center.append(typeof result === 'string' ? $(result) : result);
}

View File

@ -159,7 +159,13 @@ class ChangeEvent implements ChangeEventInterface {
const commandName = inputType
.substring(type.length)
.toLowerCase();
if (this.engine.command.queryEnabled(commandName)) {
this.engine.hotkey.disable();
this.engine.command.execute(commandName);
setTimeout(() => {
this.engine.hotkey.enable();
}, 0);
}
}
});
});

View File

@ -133,7 +133,7 @@ class ChangeRange implements ChangeRangeInterface {
return range;
}
select(range: RangeInterface) {
select(range: RangeInterface, triggerSelect: boolean = true) {
const { container, inline, node, change } = this.engine;
const { window } = container;
const selection = window?.getSelection();
@ -321,7 +321,7 @@ class ChangeRange implements ChangeRangeInterface {
selection.addRange(range.toRange());
}
const { onSelect } = this.#otpions;
if (onSelect) onSelect(range);
if (onSelect && triggerSelect) onSelect(range);
}
/**

View File

@ -26,7 +26,7 @@ import Request, {
} from './request';
import Scrollbar from './scrollbar';
import Position from './position';
import { $, getHashId } from './node';
import { $, getHashId, uuid } from './node';
export * from './types';
export * from './utils';
@ -39,6 +39,7 @@ export default Engine;
export {
$,
uuid,
getHashId,
Selection,
Range,

View File

@ -23,7 +23,7 @@ import {
} from '../constants';
import { getDocument, getStyleMap, isEngine } from '../utils';
import $ from './query';
import getHashId from './hash';
import getHashId, { uuid } from './hash';
import { isNode, isNodeEntry } from './utils';
class NodeModel implements NodeModelInterface {
@ -1027,4 +1027,4 @@ class NodeModel implements NodeModelInterface {
export default NodeModel;
export { Entry as NodeEntry, Event, $, getHashId };
export { Entry as NodeEntry, Event, $, getHashId, uuid };

View File

@ -453,7 +453,7 @@ class Consumer implements ConsumerInterface {
if (endInfo && endInfo.container) {
range.setEnd(endInfo.container, endInfo.offset);
}
this.engine.change.range.select(range);
this.engine.change.range.select(range, false);
} catch (error) {
console.error(error);
}

View File

@ -9,12 +9,7 @@ import {
ConversionRule,
SchemaRule,
} from '../types';
import {
CARD_ELEMENT_KEY,
CARD_KEY,
CARD_SELECTOR,
READY_CARD_KEY,
} from '../constants';
import { CARD_ELEMENT_KEY, CARD_SELECTOR } from '../constants';
import {
escape,
unescape,

View File

@ -530,9 +530,14 @@ export interface CardModelInterface {
/**
*
* @param container
* @param callback
* @param callback
* @param lazyRender editor的lazyRender属性
*/
render(container?: NodeInterface, callback?: (count: number) => void): void;
render(
container?: NodeInterface,
callback?: (count: number) => void,
lazyRender?: boolean,
): void;
/**
*
* @param cards

View File

@ -137,8 +137,9 @@ export interface ChangeRangeInterface {
/**
*
* @param range
* @param triggerSelect onSelect事件
*/
select(range: RangeInterface): void;
select(range: RangeInterface, triggerSelect?: boolean): void;
/**
*
* @param toStart true:,false:

View File

@ -23,7 +23,7 @@ import { BlockModelInterface } from './block';
import { RequestInterface } from './request';
import { RangeInterface } from './range';
import { Op } from 'sharedb';
import { NodeIdInterface } from './';
import { HotkeyInterface, NodeIdInterface } from './';
/**
*
@ -508,6 +508,10 @@ export interface EngineInterface extends EditorInterface {
*
*/
history: HistoryInterface;
/**
*
*/
hotkey: HotkeyInterface;
/**
*
*/

View File

@ -12,6 +12,8 @@ import {
unescape,
CARD_TYPE_KEY,
PluginOptions,
READY_CARD_KEY,
decodeCardValue,
} from '@aomao/engine';
import CodeBlockComponent, { CodeBlockEditor } from './component';
@ -298,11 +300,14 @@ export default class extends Plugin<Options> {
parseHtml(root: NodeInterface) {
if (isServer) return;
root.find(`[${CARD_KEY}=${CodeBlockComponent.cardName}`).each(
(cardNode) => {
root.find(
`[${CARD_KEY}="${CodeBlockComponent.cardName}"],[${READY_CARD_KEY}="${CodeBlockComponent.cardName}]"`,
).each((cardNode) => {
const node = $(cardNode);
const card = this.editor.card.find(node) as CodeBlockComponent;
const value = card?.getValue();
const value =
card?.getValue() ||
decodeCardValue(node.attributes(CARD_VALUE_KEY));
if (value && value.code) {
node.empty();
const synatxMap: { [key: string]: string } = {};
@ -327,20 +332,16 @@ export default class extends Plugin<Options> {
content.traverse((node) => {
if (
node.type === Node.ELEMENT_NODE &&
(node.get<HTMLElement>()?.classList?.length || 0) >
0
(node.get<HTMLElement>()?.classList?.length || 0) > 0
) {
const element = node.get<HTMLElement>()!;
const style = window.getComputedStyle(element);
[
'color',
'margin',
'padding',
'background',
].forEach((attr) => {
['color', 'margin', 'padding', 'background'].forEach(
(attr) => {
(element.style as any)[attr] =
style.getPropertyValue(attr);
});
},
);
}
});
content.show();
@ -352,8 +353,7 @@ export default class extends Plugin<Options> {
node.attributes('data-syntax', value.mode || 'plain');
content.removeClass('am-engine-view');
} else node.remove();
},
);
});
}
}
export { CodeBlockComponent };

View File

@ -12,6 +12,8 @@ import {
unescape,
CARD_TYPE_KEY,
PluginOptions,
READY_CARD_KEY,
decodeCardValue,
} from '@aomao/engine';
import CodeBlockComponent, { CodeBlockEditor } from './component';
@ -299,11 +301,14 @@ export default class extends Plugin<Options> {
parseHtml(root: NodeInterface) {
if (isServer) return;
root.find(`[${CARD_KEY}=${CodeBlockComponent.cardName}`).each(
(cardNode) => {
root.find(
`[${CARD_KEY}="${CodeBlockComponent.cardName}"],[${READY_CARD_KEY}="${CodeBlockComponent.cardName}"]`,
).each((cardNode) => {
const node = $(cardNode);
const card = this.editor.card.find(node) as CodeBlockComponent;
const value = card?.getValue();
const value =
card?.getValue() ||
decodeCardValue(node.attributes(CARD_VALUE_KEY));
if (value) {
node.empty();
const synatxMap = {};
@ -328,20 +333,16 @@ export default class extends Plugin<Options> {
content.traverse((node) => {
if (
node.type === Node.ELEMENT_NODE &&
(node.get<HTMLElement>()?.classList?.length || 0) >
0
(node.get<HTMLElement>()?.classList?.length || 0) > 0
) {
const element = node.get<HTMLElement>()!;
const style = window.getComputedStyle(element);
[
'color',
'margin',
'padding',
'background',
].forEach((attr) => {
['color', 'margin', 'padding', 'background'].forEach(
(attr) => {
element.style[attr] =
style.getPropertyValue(attr);
});
},
);
}
});
content.show();
@ -353,8 +354,7 @@ export default class extends Plugin<Options> {
node.attributes('data-syntax', value.mode || 'plain');
content.removeClass('am-engine-view');
} else node.remove();
},
);
});
}
}
export { CodeBlockComponent };

View File

@ -3,12 +3,14 @@ import {
CardEntry,
CardInterface,
CARD_KEY,
CARD_VALUE_KEY,
decodeCardValue,
encodeCardValue,
isEngine,
NodeInterface,
Plugin,
PluginEntry,
READY_CARD_KEY,
SchemaInterface,
} from '@aomao/engine';
import FileComponent, { FileValue } from './component';
@ -154,10 +156,14 @@ export default class extends Plugin {
}
parseHtml(root: NodeInterface) {
root.find(`[${CARD_KEY}=${FileComponent.cardName}`).each((cardNode) => {
root.find(
`[${CARD_KEY}="${FileComponent.cardName}"],[${READY_CARD_KEY}="${FileComponent.cardName}"`,
).each((cardNode) => {
const node = $(cardNode);
const card = this.editor.card.find(node) as FileComponent;
const value = card?.getValue();
const value =
card?.getValue() ||
decodeCardValue(node.attributes(CARD_VALUE_KEY));
if (value?.url && value.status === 'done') {
const html = `<a data-type="${
FileComponent.cardName

View File

@ -544,6 +544,11 @@ class Image {
width: width + 'px',
height: height + 'px',
});
const { onChange } = this.options;
if (width > 0 && height > 0) {
this.size = { ...this.size, width, height };
if (onChange) onChange(this.size);
}
}
this.bg.css({
width: width + 'px',

View File

@ -4,9 +4,13 @@ import {
CardInterface,
CardType,
CARD_KEY,
CARD_TYPE_KEY,
CARD_VALUE_KEY,
decodeCardValue,
NodeInterface,
Plugin,
PluginEntry,
READY_CARD_KEY,
} from '@aomao/engine';
import ImageComponent, { ImageValue } from './component';
import ImageUploader from './uploader';
@ -108,40 +112,41 @@ export default class extends Plugin<{
}
parseHtml(root: NodeInterface) {
root.find(`[${CARD_KEY}=${ImageComponent.cardName}`).each(
(cardNode) => {
root.find(
`[${CARD_KEY}="${ImageComponent.cardName}"],[${READY_CARD_KEY}="${ImageComponent.cardName}"]`,
).each((cardNode) => {
const node = $(cardNode);
const card = this.editor.card.find(node) as ImageComponent;
const value = card?.getValue();
const value =
card?.getValue() ||
decodeCardValue(node.attributes(CARD_VALUE_KEY));
if (value?.src && value.status === 'done') {
const img = node.find('.data-image-meta > img');
let img = $('<img />');
node.empty();
let src = value.src;
const { onBeforeRender } = this.options;
if (onBeforeRender) {
src = onBeforeRender(value.status, value.src);
}
const type = node.attributes(CARD_TYPE_KEY);
img.attributes('src', src);
img.css('visibility', 'visible');
img.css('background', '');
img.css('background-color', '');
img.css('background-repeat', '');
img.css('background-position', '');
img.css('background-image', '');
const size = value.size;
if (size.width) img.css('width', `${size.width}px`);
if (size.height) img.css('height', `${size.height}px`);
img.removeAttributes('class');
img.attributes('data-type', type);
if (img.length > 0) {
node.replaceWith(img);
if (card.type === CardType.BLOCK) {
this.editor.node.wrap(
if (type === CardType.BLOCK) {
img = this.editor.node.wrap(
img,
$(`<p style="text-align:center;"></p>`),
);
}
node.replaceWith(img);
}
} else node.remove();
},
);
});
}
}

View File

@ -14,6 +14,7 @@ import {
decodeCardValue,
encodeCardValue,
removeUnit,
CardType,
} from '@aomao/engine';
import ImageComponent, { ImageValue } from './component';
@ -476,15 +477,28 @@ export default class extends Plugin<Options> {
} */
//图片
if (node.name === 'img') {
const src = node.attributes('src') || node.attributes('data-src');
const alt = node.attributes('alt');
const attributes = node.attributes();
const src = attributes['src'] || attributes['data-src'];
const alt = attributes['alt'];
if (!src) {
node.remove();
return;
}
const width = node.css('width');
const height = node.css('height');
const dataTypeValue = attributes['data-type'];
let type = CardType.INLINE;
if (dataTypeValue === 'block') {
const parent = node.parent();
// 移除转换为html的时候加载的额外p标签
if (parent && parent.name === 'p') {
this.editor.node.unwrap(node);
}
type = CardType.BLOCK;
}
this.editor.card.replaceNode(node, 'image', {
type,
src,
status:
(isRemote && isRemote(src)) || /^data:image\//i.test(src)

View File

@ -12,6 +12,7 @@ import {
SchemaMark,
Selection,
PluginOptions,
uuid,
} from '@aomao/engine';
import { Path } from 'sharedb';
@ -26,7 +27,9 @@ export default class extends MarkPlugin<Options> {
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;
@ -84,8 +87,8 @@ export default class extends MarkPlugin<Options> {
if (isEngine(this.editor)) {
const { change } = this.editor;
this.editor.on('change', () => {
this.triggerChange();
this.editor.on('change', (_, trigger) => {
this.triggerChange(trigger !== 'local');
});
this.editor.on('select', () => this.onSelectionChange());
this.editor.on('parse:value', (node, atts) => {
@ -141,6 +144,7 @@ export default class extends MarkPlugin<Options> {
required: true,
value: key,
},
[this.MARK_UUID]: '*',
[this.getIdName(key)]: '*',
},
};
@ -277,7 +281,9 @@ export default class extends MarkPlugin<Options> {
this.MARK_KEY
}="${key}" ${DATA_TRANSIENT_ATTRIBUTES}="${this.getPreviewName(
key,
)}" ${this.getPreviewName(key)}="true" />`,
)}" ${this.MARK_UUID}="${this.m_uuid}" ${this.getPreviewName(
key,
)}="true" />`,
range,
);
//遍历当前光标选择节点,拼接选择的文本
@ -478,7 +484,6 @@ export default class extends MarkPlugin<Options> {
execute() {}
action(key: string, action: string, ...args: any): any {
const history = isEngine(this.editor) ? this.editor.history : undefined;
const id = args[0];
switch (action) {
case 'preview':
@ -552,10 +557,11 @@ export default class extends MarkPlugin<Options> {
this.range = range;
}
triggerChange() {
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] || [];
@ -572,6 +578,25 @@ export default class extends MarkPlugin<Options> {
}
});
});
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);
}

View File

@ -12,6 +12,8 @@ import {
decodeCardValue,
encodeCardValue,
AjaxInterface,
READY_CARD_KEY,
CARD_VALUE_KEY,
} from '@aomao/engine';
import MathComponent from './component';
import locales from './locales';
@ -241,9 +243,10 @@ export default class Math extends Plugin<Options> {
pasteHtml(node: NodeInterface) {
if (!isEngine(this.editor)) return;
if (node.isElement()) {
const type = node.attributes('data-type');
const attributes = node.attributes();
const type = attributes['data-type'];
if (type === MathComponent.cardName) {
const value = node.attributes('data-value');
const value = attributes['data-value'];
const cardValue = decodeCardValue(value);
if (!cardValue.url) return;
this.editor.card.replaceNode(
@ -259,10 +262,14 @@ export default class Math extends Plugin<Options> {
}
parseHtml(root: NodeInterface) {
root.find(`[${CARD_KEY}=${MathComponent.cardName}`).each((cardNode) => {
root.find(
`[${CARD_KEY}="${MathComponent.cardName}"],[${READY_CARD_KEY}="${MathComponent.cardName}"]`,
).each((cardNode) => {
const node = $(cardNode);
const card = this.editor.card.find(node) as MathComponent;
const value = card?.getValue();
const value =
card?.getValue() ||
decodeCardValue(node.attributes(CARD_VALUE_KEY));
if (value) {
const img = node.find('img');
node.empty();

View File

@ -14,6 +14,8 @@ import {
encodeCardValue,
CardInterface,
AjaxInterface,
READY_CARD_KEY,
CARD_VALUE_KEY,
} from '@aomao/engine';
import MentionComponent from './component';
import locales from './locales';
@ -229,9 +231,10 @@ class MentionPlugin extends Plugin<Options> {
pasteHtml(node: NodeInterface) {
if (!isEngine(this.editor)) return;
if (node.isElement()) {
const type = node.attributes('data-type');
const attributes = node.attributes();
const type = attributes['data-type'];
if (type === MentionComponent.cardName) {
const value = node.attributes('data-value');
const value = attributes['data-value'];
const cardValue = decodeCardValue(value);
if (!cardValue.name) return;
this.editor.card.replaceNode(
@ -247,11 +250,14 @@ class MentionPlugin extends Plugin<Options> {
}
parseHtml(root: NodeInterface) {
root.find(`[${CARD_KEY}=${MentionComponent.cardName}`).each(
(cardNode) => {
root.find(
`[${CARD_KEY}="${MentionComponent.cardName}"],[${READY_CARD_KEY}="${MentionComponent.cardName}"]`,
).each((cardNode) => {
const node = $(cardNode);
const card = this.editor.card.find(node) as MentionComponent;
const value = card?.getValue();
const value =
card?.getValue() ||
decodeCardValue(node.attributes(CARD_VALUE_KEY));
if (value?.id && value.name) {
const html = `<span data-type="${
MentionComponent.cardName
@ -261,8 +267,7 @@ class MentionPlugin extends Plugin<Options> {
node.empty();
node.replaceWith($(html));
} else node.remove();
},
);
});
}
execute() {}

View File

@ -9,6 +9,10 @@ import {
PluginOptions,
SchemaInterface,
getDocument,
Parser,
READY_CARD_KEY,
decodeCardValue,
CARD_VALUE_KEY,
} from '@aomao/engine';
import TableComponent, { Template } from './component';
import locales from './locale';
@ -355,22 +359,27 @@ class Table extends Plugin<Options> {
}
parseHtml(root: NodeInterface) {
root.find(`[${CARD_KEY}=${TableComponent.cardName}`).each(
(tableNode) => {
root.find(
`[${CARD_KEY}="${TableComponent.cardName}"],[${READY_CARD_KEY}="${TableComponent.cardName}"]`,
).each((tableNode) => {
const node = $(tableNode);
const card = this.editor.card.find(node) as TableComponent;
const value = card?.getValue();
const value =
card?.getValue() ||
decodeCardValue(node.attributes(CARD_VALUE_KEY));
if (value && value.html) {
let table = node.find('table');
if (table.length === 0) {
// 表格值里面的卡片都是没有被转换过的,所以需要先把卡片转换过来
table = $(value.html);
if (table.length === 0) {
node.remove();
return;
} else {
table = $(new Parser(table, this.editor).toHTML());
}
}
const width =
table.attributes('width') || table.css('width');
const width = table.attributes('width') || table.css('width');
table.css({
outline: 'none',
'border-collapse': 'collapse',
@ -395,15 +404,12 @@ class Table extends Plugin<Options> {
});
});
table.find(Template.TABLE_TD_BG_CLASS).remove();
table
.find(Template.TABLE_TD_CONTENT_CLASS)
.each((content) => {
table.find(Template.TABLE_TD_CONTENT_CLASS).each((content) => {
this.editor.node.unwrap($(content));
});
node.replaceWith(table);
}
},
);
});
}
getMarkdownCell(match: RegExpExecArray, count?: number) {

View File

@ -3,12 +3,14 @@ import {
CardEntry,
CardInterface,
CARD_KEY,
CARD_VALUE_KEY,
decodeCardValue,
encodeCardValue,
isEngine,
NodeInterface,
Plugin,
PluginEntry,
READY_CARD_KEY,
sanitizeUrl,
SchemaInterface,
} from '@aomao/engine';
@ -162,11 +164,14 @@ export default class VideoPlugin extends Plugin<{
}
parseHtml(root: NodeInterface) {
root.find(`[${CARD_KEY}=${VideoComponent.cardName}`).each(
(cardNode) => {
root.find(
`[${CARD_KEY}="${VideoComponent.cardName}"],[${READY_CARD_KEY}="${VideoComponent.cardName}"]`,
).each((cardNode) => {
const node = $(cardNode);
const card = this.editor.card.find(node) as VideoComponent;
const value = card?.getValue();
const value =
card?.getValue() ||
decodeCardValue(node.attributes(CARD_VALUE_KEY));
if (value?.url && value.status === 'done') {
const { onBeforeRender } = this.options;
const { cover, url } = value;
@ -188,8 +193,7 @@ export default class VideoPlugin extends Plugin<{
node.empty();
node.replaceWith($(html));
} else node.remove();
},
);
});
}
}