mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2026-01-12 17:08:14 +00:00
enhace liveediting
This commit is contained in:
parent
b774428a0f
commit
54242ca62a
@ -1,3 +1,4 @@
|
||||
export * from './host';
|
||||
export * from './host-view';
|
||||
export * from './renderer';
|
||||
export * from './live-editing/live-editing';
|
||||
|
||||
@ -16,39 +16,59 @@ function defaultSaveContent(content: string, prop: Prop) {
|
||||
prop.setValue(content);
|
||||
}
|
||||
|
||||
export interface EditingTarget {
|
||||
node: Node;
|
||||
rootElement: HTMLElement;
|
||||
event: MouseEvent;
|
||||
}
|
||||
|
||||
const saveHandlers: SaveHandler[] = [];
|
||||
function addLiveEditingSaveHandler(handler: SaveHandler) {
|
||||
saveHandlers.push(handler);
|
||||
}
|
||||
|
||||
const specificRules: SpecificRule[] = [];
|
||||
function addLiveEditingSpecificRule(rule: SpecificRule) {
|
||||
specificRules.push(rule);
|
||||
}
|
||||
|
||||
export class LiveEditing {
|
||||
static addLiveEditingSpecificRule = addLiveEditingSpecificRule;
|
||||
static addLiveEditingSaveHandler = addLiveEditingSaveHandler;
|
||||
|
||||
@obx.ref private _editing: Prop | null = null;
|
||||
apply(target: { node: Node; rootElement: HTMLElement; event: MouseEvent }) {
|
||||
apply(target: EditingTarget) {
|
||||
const { node, event, rootElement } = target;
|
||||
const targetElement = event.target as HTMLElement;
|
||||
const liveTextEditing = node.componentMeta.getMetadata().experimental?.liveTextEditing || [];
|
||||
const liveTextEditing = node.componentMeta.liveTextEditing;
|
||||
|
||||
let setterPropElement = getSetterPropElement(targetElement, rootElement);
|
||||
let propTarget = setterPropElement?.dataset.setterProp;
|
||||
let matched: LiveTextEditingConfig | undefined;
|
||||
if (propTarget) {
|
||||
// 已埋点命中 data-setter-prop="proptarget", 从 liveTextEditing 读取配置(mode|onSaveContent)
|
||||
matched = liveTextEditing.find(config => config.propTarget == propTarget);
|
||||
} else {
|
||||
// 执行 embedTextEditing selector 规则,获得第一个节点 是否 contains e.target,若匹配,读取配置
|
||||
matched = liveTextEditing.find(config => {
|
||||
if (!config.selector) {
|
||||
return false;
|
||||
}
|
||||
setterPropElement = config.selector === ':root' ? rootElement : rootElement.querySelector(config.selector);
|
||||
if (!setterPropElement) {
|
||||
return false;
|
||||
}
|
||||
if (!setterPropElement.contains(targetElement)) {
|
||||
// try selectorAll
|
||||
setterPropElement = Array.from(rootElement.querySelectorAll(config.selector)).find(item => item.contains(targetElement)) as HTMLElement;
|
||||
if (!setterPropElement) {
|
||||
let matched: LiveTextEditingConfig & { propElement?: HTMLElement; } | undefined;
|
||||
if (liveTextEditing) {
|
||||
if (propTarget) {
|
||||
// 已埋点命中 data-setter-prop="proptarget", 从 liveTextEditing 读取配置(mode|onSaveContent)
|
||||
matched = liveTextEditing.find(config => config.propTarget == propTarget);
|
||||
} else {
|
||||
// 执行 embedTextEditing selector 规则,获得第一个节点 是否 contains e.target,若匹配,读取配置
|
||||
matched = liveTextEditing.find(config => {
|
||||
if (!config.selector) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
setterPropElement = queryPropElement(rootElement, targetElement, config.selector);
|
||||
return setterPropElement ? true : false;
|
||||
});
|
||||
propTarget = matched?.propTarget;
|
||||
}
|
||||
} else {
|
||||
specificRules.some((rule) => {
|
||||
matched = rule(target);
|
||||
return matched ? true : false;
|
||||
});
|
||||
propTarget = matched?.propTarget;
|
||||
if (matched) {
|
||||
propTarget = matched.propTarget;
|
||||
setterPropElement = matched.propElement || queryPropElement(rootElement, targetElement, matched.selector);
|
||||
}
|
||||
}
|
||||
|
||||
if (!propTarget) {
|
||||
@ -75,7 +95,7 @@ export class LiveEditing {
|
||||
// 4. 监听 blur 事件
|
||||
// 5. 设置编辑锁定:disable hover | disable select | disable canvas drag
|
||||
|
||||
const onSaveContent = matched?.onSaveContent || this.saveHandlers.find(item => item.condition(prop))?.onSaveContent || defaultSaveContent;
|
||||
const onSaveContent = matched?.onSaveContent || saveHandlers.find(item => item.condition(prop))?.onSaveContent || defaultSaveContent;
|
||||
|
||||
setterPropElement.setAttribute('contenteditable', matched?.mode && matched.mode !== 'plaintext' ? 'true' : 'plaintext-only');
|
||||
setterPropElement.classList.add('engine-live-editing');
|
||||
@ -99,6 +119,8 @@ export class LiveEditing {
|
||||
this._editing = prop;
|
||||
}
|
||||
|
||||
// TODO: process enter | esc events & joint the FocusTracker
|
||||
|
||||
// TODO: upward testing for b/i/a html elements
|
||||
|
||||
// 非文本编辑
|
||||
@ -127,13 +149,12 @@ export class LiveEditing {
|
||||
}
|
||||
this._editing = null;
|
||||
}
|
||||
|
||||
private saveHandlers: SaveHandler[] = [];
|
||||
setSaveHandler(handler: SaveHandler) {
|
||||
this.saveHandlers.push(handler);
|
||||
}
|
||||
}
|
||||
|
||||
export type SpecificRule = (target: EditingTarget) => LiveTextEditingConfig & {
|
||||
propElement?: HTMLElement;
|
||||
};
|
||||
|
||||
export interface SaveHandler {
|
||||
condition: (prop: Prop) => boolean;
|
||||
onSaveContent: (content: string, prop: Prop) => void;
|
||||
@ -155,3 +176,22 @@ function selectRange(doc: Document, range: Range) {
|
||||
selection.addRange(range);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function queryPropElement(rootElement: HTMLElement, targetElement: HTMLElement, selector?: string) {
|
||||
if (!selector) {
|
||||
return null;
|
||||
}
|
||||
let propElement = selector === ':root' ? rootElement : rootElement.querySelector(selector);
|
||||
if (!propElement) {
|
||||
return null;
|
||||
}
|
||||
if (!propElement.contains(targetElement)) {
|
||||
// try selectorAll
|
||||
propElement = Array.from(rootElement.querySelectorAll(selector)).find(item => item.contains(targetElement)) as HTMLElement;
|
||||
if (!propElement) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return propElement as HTMLElement;
|
||||
}
|
||||
|
||||
@ -9,6 +9,8 @@ import {
|
||||
NestingFilter,
|
||||
isTitleConfig,
|
||||
I18nData,
|
||||
LiveTextEditingConfig,
|
||||
FieldConfig,
|
||||
} from '@ali/lowcode-types';
|
||||
import { computed } from '@ali/lowcode-editor-core';
|
||||
import { Node, ParentalNode } from './document';
|
||||
@ -91,6 +93,11 @@ export class ComponentMeta {
|
||||
return config?.combined || config?.props || [];
|
||||
}
|
||||
|
||||
private _liveTextEditing?: LiveTextEditingConfig[];
|
||||
get liveTextEditing() {
|
||||
return this._liveTextEditing;
|
||||
}
|
||||
|
||||
private parentWhitelist?: NestingFilter | null;
|
||||
private childWhitelist?: NestingFilter | null;
|
||||
|
||||
@ -150,6 +157,26 @@ export class ComponentMeta {
|
||||
: title;
|
||||
}
|
||||
|
||||
const liveTextEditing = this._transformedMetadata.experimental?.liveTextEditing || [];
|
||||
|
||||
function collectLiveTextEditing(items: FieldConfig[]) {
|
||||
items.forEach(config => {
|
||||
if (config.items) {
|
||||
collectLiveTextEditing(config.items);
|
||||
} else {
|
||||
const liveConfig = config.liveTextEditing || config.extraProps?.liveTextEditing;
|
||||
if (liveConfig) {
|
||||
liveTextEditing.push({
|
||||
propTarget: String(config.name),
|
||||
...liveConfig,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
collectLiveTextEditing(this.configure);
|
||||
this._liveTextEditing = liveTextEditing;
|
||||
|
||||
const { configure = {} } = this._transformedMetadata;
|
||||
this._acceptable = false;
|
||||
|
||||
|
||||
@ -45,6 +45,13 @@ export interface FieldExtraProps {
|
||||
* compatiable vision display
|
||||
*/
|
||||
display?: 'accordion' | 'inline' | 'block' | 'plain' | 'popup' | 'entry';
|
||||
liveTextEditing?: {
|
||||
selector: string;
|
||||
// 编辑模式 纯文本|段落编辑|文章编辑(默认纯文本,无跟随工具条)
|
||||
mode?: 'plaintext' | 'paragraph' | 'article';
|
||||
// 从 contentEditable 获取内容并设置到属性
|
||||
onSaveContent?: (content: string, prop: any) => any;
|
||||
}
|
||||
}
|
||||
|
||||
export interface FieldConfig extends FieldExtraProps {
|
||||
|
||||
@ -22,7 +22,7 @@ const GlobalPropsConfigure: Array<{ position: string; initials?: InitialItem[];
|
||||
const Overrides: {
|
||||
[componentName: string]: {
|
||||
initials?: InitialItem[];
|
||||
config: any;
|
||||
override: any;
|
||||
};
|
||||
} = {};
|
||||
|
||||
@ -44,14 +44,23 @@ function removeGlobalPropsConfigure(name: string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
function overridePropsConfigure(componentName: string, config: OldPropConfig | OldPropConfig[]) {
|
||||
function overridePropsConfigure(componentName: string, config: { [name: string]: OldPropConfig } | OldPropConfig[]) {
|
||||
const initials: InitialItem[] = [];
|
||||
const addInitial = (item: InitialItem) => {
|
||||
initials.push(item);
|
||||
};
|
||||
let override: any;
|
||||
if (Array.isArray(config)) {
|
||||
override = upgradeConfigure(config, addInitial);
|
||||
} else {
|
||||
override = {};
|
||||
Object.keys(config).forEach(key => {
|
||||
override[key] = upgradePropConfig(config[key], addInitial);
|
||||
});
|
||||
}
|
||||
Overrides[componentName] = {
|
||||
initials,
|
||||
config: Array.isArray(config) ? upgradeConfigure(config, addInitial) : upgradePropConfig(config, addInitial),
|
||||
override,
|
||||
};
|
||||
}
|
||||
registerMetadataTransducer(
|
||||
@ -82,18 +91,18 @@ registerMetadataTransducer(
|
||||
}
|
||||
});
|
||||
|
||||
const override = Overrides[componentName];
|
||||
const override = Overrides[componentName]?.override;
|
||||
if (override) {
|
||||
if (Array.isArray(override.config)) {
|
||||
metadata.configure.combined = override.config;
|
||||
if (Array.isArray(override)) {
|
||||
metadata.configure.combined = override;
|
||||
} else {
|
||||
let l = top.length;
|
||||
let item;
|
||||
while (l-- > 0) {
|
||||
item = top[l];
|
||||
if (item.name in override) {
|
||||
if (override.config[item.name]) {
|
||||
top.splice(l, 1, override.config[item.name]);
|
||||
if (override[item.name]) {
|
||||
top.splice(l, 1, override[item.name]);
|
||||
} else {
|
||||
top.splice(l, 1);
|
||||
}
|
||||
@ -102,7 +111,6 @@ registerMetadataTransducer(
|
||||
}
|
||||
}
|
||||
|
||||
// TODO FIXME! append override & globalConfigure initials and then unique
|
||||
return metadata;
|
||||
},
|
||||
100,
|
||||
@ -249,7 +257,7 @@ class Prototype {
|
||||
}
|
||||
|
||||
getRectSelector() {
|
||||
return this.meta.rectSelector;
|
||||
return this.meta.rootSelector;
|
||||
}
|
||||
|
||||
isContainer() {
|
||||
|
||||
@ -92,6 +92,7 @@ export interface OldPropConfig {
|
||||
slotTitle?: string;
|
||||
initialChildren?: any; // schema
|
||||
allowTextInput: boolean;
|
||||
liveTextEditing?: any;
|
||||
}
|
||||
|
||||
// from vision 5.4
|
||||
@ -205,6 +206,7 @@ export function upgradePropConfig(config: OldPropConfig, addInitial: AddIntial)
|
||||
setter,
|
||||
useVariableChange,
|
||||
supportVariable,
|
||||
liveTextEditing,
|
||||
} = config;
|
||||
|
||||
const extraProps: any = {};
|
||||
@ -451,6 +453,10 @@ export function upgradePropConfig(config: OldPropConfig, addInitial: AddIntial)
|
||||
}
|
||||
newConfig.setter = primarySetter;
|
||||
|
||||
if (liveTextEditing) {
|
||||
extraProps.liveTextEditing = liveTextEditing;
|
||||
}
|
||||
|
||||
return newConfig;
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { isJSBlock, isJSExpression, isJSSlot } from '@ali/lowcode-types';
|
||||
import { isPlainObject } from '@ali/lowcode-utils';
|
||||
import { globalContext, Editor } from '@ali/lowcode-editor-core';
|
||||
import { Designer, TransformStage, addBuiltinComponentAction } from '@ali/lowcode-designer';
|
||||
import { Designer, LiveEditing, TransformStage, addBuiltinComponentAction } from '@ali/lowcode-designer';
|
||||
import Outline, { OutlineBackupPane, getTreeMaster } from '@ali/lowcode-plugin-outline-pane';
|
||||
import { toCss } from '@ali/vu-css-style';
|
||||
|
||||
@ -160,6 +160,12 @@ skeleton.add({
|
||||
content: OutlineBackupPane,
|
||||
});
|
||||
|
||||
LiveEditing.addLiveEditingSpecificRule((target) => {
|
||||
// TODO: enhance for legao specific
|
||||
const contentValue = target.node.getPropValue('content');
|
||||
return null;
|
||||
});
|
||||
|
||||
// skeleton.add({
|
||||
// name: 'sourceEditor',
|
||||
// type: 'PanelDock',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user