sdyscxyc
This commit is contained in:
@@ -63,6 +63,42 @@ export function initEditor() {
|
||||
const ok = (m) => toast(m, true);
|
||||
const err = (m) => toast(m, false);
|
||||
|
||||
let lastDebugTs = 0;
|
||||
let lastDebugKey = '';
|
||||
let lastSelectionInfo = null;
|
||||
|
||||
const debugUiLog = (payload) => {
|
||||
const now = Date.now();
|
||||
const key = payload && payload.event ? payload.event : 'log';
|
||||
if (now - lastDebugTs < 400 && key === lastDebugKey) return;
|
||||
lastDebugTs = now;
|
||||
lastDebugKey = key;
|
||||
apiAction('debug.log.write', {
|
||||
method: 'POST',
|
||||
data: {
|
||||
name: 'ui_editor_dirty.log',
|
||||
append: 1,
|
||||
line: JSON.stringify({
|
||||
time: new Date().toISOString(),
|
||||
...payload,
|
||||
}),
|
||||
},
|
||||
}).catch(() => {});
|
||||
};
|
||||
|
||||
const isLibRefModel = (model) => {
|
||||
if (!model) return false;
|
||||
const attrs = typeof model.getAttributes === 'function'
|
||||
? model.getAttributes()
|
||||
: (model.attributes && model.attributes.attributes) ? model.attributes.attributes : {};
|
||||
if (attrs && (attrs['data-lib-ref'] || attrs['data-lib-kind'] || attrs['data-lib-id'])) return true;
|
||||
try {
|
||||
const classes = typeof model.getClasses === 'function' ? model.getClasses() : [];
|
||||
if (Array.isArray(classes) && classes.includes('lib-ref-wrapper')) return true;
|
||||
} catch {}
|
||||
return false;
|
||||
};
|
||||
|
||||
const showConfirmDialog = (() => {
|
||||
let dialog;
|
||||
let titleEl;
|
||||
@@ -216,9 +252,57 @@ export function initEditor() {
|
||||
};
|
||||
}
|
||||
|
||||
function markDirty() {
|
||||
if (suppressDirty || !baselineReady) return;
|
||||
function getModelInfo(model) {
|
||||
if (!model) return null;
|
||||
const info = {};
|
||||
try {
|
||||
info.id = (typeof model.getId === 'function' ? model.getId() : null)
|
||||
?? (typeof model.get === 'function' ? model.get('id') : null)
|
||||
?? model.id
|
||||
?? null;
|
||||
info.type = (typeof model.get === 'function' ? model.get('type') : null)
|
||||
?? (typeof model.get === 'function' ? model.get('tagName') : null)
|
||||
?? (model.attributes ? model.attributes.type : null)
|
||||
?? null;
|
||||
info.libRef = isLibRefModel(model);
|
||||
if (info.libRef) {
|
||||
const attrs = typeof model.getAttributes === 'function'
|
||||
? model.getAttributes()
|
||||
: (model.attributes && model.attributes.attributes) ? model.attributes.attributes : {};
|
||||
if (attrs) {
|
||||
info.lib = {
|
||||
ref: attrs['data-lib-ref'] ?? null,
|
||||
kind: attrs['data-lib-kind'] ?? null,
|
||||
id: attrs['data-lib-id'] ?? null,
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
return info;
|
||||
}
|
||||
|
||||
function markDirty(reason = 'unknown', model = null, meta = {}) {
|
||||
const blocked = suppressDirty || !baselineReady;
|
||||
if (blocked) {
|
||||
debugUiLog({
|
||||
event: 'dirty:ignored',
|
||||
reason,
|
||||
suppressDirty,
|
||||
baselineReady,
|
||||
model: getModelInfo(model),
|
||||
meta,
|
||||
selection: lastSelectionInfo,
|
||||
});
|
||||
return;
|
||||
}
|
||||
isDirty = true;
|
||||
debugUiLog({
|
||||
event: 'dirty:set',
|
||||
reason,
|
||||
model: getModelInfo(model),
|
||||
meta,
|
||||
selection: lastSelectionInfo,
|
||||
});
|
||||
}
|
||||
|
||||
function clearDirty() {
|
||||
@@ -238,53 +322,86 @@ export function initEditor() {
|
||||
if (!editor || typeof editor.on !== 'function') return () => {};
|
||||
let selectionJustChanged = false;
|
||||
let selectionTimer = null;
|
||||
const isLibRef = (model) => {
|
||||
if (!model) return false;
|
||||
const attrs = typeof model.getAttributes === 'function'
|
||||
? model.getAttributes()
|
||||
: (model.attributes && model.attributes.attributes) ? model.attributes.attributes : {};
|
||||
if (attrs && (attrs['data-lib-ref'] || attrs['data-lib-kind'] || attrs['data-lib-id'])) return true;
|
||||
try {
|
||||
const classes = typeof model.getClasses === 'function' ? model.getClasses() : [];
|
||||
if (Array.isArray(classes) && classes.includes('lib-ref-wrapper')) return true;
|
||||
} catch {}
|
||||
return false;
|
||||
};
|
||||
const onSelect = () => {
|
||||
const onSelect = (model) => {
|
||||
selectionJustChanged = true;
|
||||
lastSelectionInfo = getModelInfo(model);
|
||||
debugUiLog({
|
||||
event: 'component:selected',
|
||||
selection: lastSelectionInfo,
|
||||
});
|
||||
if (selectionTimer) clearTimeout(selectionTimer);
|
||||
selectionTimer = setTimeout(() => {
|
||||
selectionJustChanged = false;
|
||||
selectionTimer = null;
|
||||
}, 800);
|
||||
};
|
||||
const onUpdate = () => {
|
||||
if (selectionJustChanged) return;
|
||||
markDirty();
|
||||
const onUpdate = (reason, model) => {
|
||||
if (selectionJustChanged) {
|
||||
debugUiLog({
|
||||
event: 'dirty:skip',
|
||||
reason,
|
||||
note: 'selectionJustChanged',
|
||||
model: getModelInfo(model),
|
||||
});
|
||||
return;
|
||||
}
|
||||
markDirty(reason, model);
|
||||
};
|
||||
const onComponentUpdate = (model) => {
|
||||
if (isLibRef(model)) return;
|
||||
if (isLibRefModel(model)) {
|
||||
debugUiLog({
|
||||
event: 'component:update:skip',
|
||||
reason: 'lib-ref',
|
||||
model: getModelInfo(model),
|
||||
});
|
||||
return;
|
||||
}
|
||||
const changed = (model && typeof model.changedAttributes === 'function')
|
||||
? model.changedAttributes()
|
||||
: (model && model.changed) ? model.changed : null;
|
||||
const keys = changed ? Object.keys(changed) : [];
|
||||
if (!keys.length && selectionJustChanged) return;
|
||||
if (!keys.length && selectionJustChanged) {
|
||||
debugUiLog({
|
||||
event: 'component:update:skip',
|
||||
reason: 'selectionJustChanged',
|
||||
model: getModelInfo(model),
|
||||
});
|
||||
return;
|
||||
}
|
||||
const safeKeys = new Set(['status', 'toolbar', 'selected', 'hovered', 'highlighted', 'hoverable', 'selectable', 'editable', 'draggable', 'droppable', 'copyable', 'removable', 'locked']);
|
||||
if (keys.length && keys.every(k => safeKeys.has(k))) return;
|
||||
if (selectionJustChanged && keys.length === 0) return;
|
||||
markDirty();
|
||||
if (keys.length && keys.every(k => safeKeys.has(k))) {
|
||||
debugUiLog({
|
||||
event: 'component:update:skip',
|
||||
reason: 'safeKeysOnly',
|
||||
keys,
|
||||
model: getModelInfo(model),
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (selectionJustChanged && keys.length === 0) {
|
||||
debugUiLog({
|
||||
event: 'component:update:skip',
|
||||
reason: 'selectionJustChangedNoKeys',
|
||||
model: getModelInfo(model),
|
||||
});
|
||||
return;
|
||||
}
|
||||
markDirty('component:update', model, { keys });
|
||||
};
|
||||
const onComponentAdd = (model) => onUpdate('component:add', model);
|
||||
const onComponentRemove = (model) => onUpdate('component:remove', model);
|
||||
const onStyleUpdate = (model) => onUpdate('style:property:update', model);
|
||||
editor.on('component:update', onComponentUpdate);
|
||||
editor.on('component:add', onUpdate);
|
||||
editor.on('component:remove', onUpdate);
|
||||
editor.on('style:property:update', onUpdate);
|
||||
editor.on('component:add', onComponentAdd);
|
||||
editor.on('component:remove', onComponentRemove);
|
||||
editor.on('style:property:update', onStyleUpdate);
|
||||
editor.on('component:selected', onSelect);
|
||||
return () => {
|
||||
try {
|
||||
editor.off('component:update', onComponentUpdate);
|
||||
editor.off('component:add', onUpdate);
|
||||
editor.off('component:remove', onUpdate);
|
||||
editor.off('style:property:update', onUpdate);
|
||||
editor.off('component:add', onComponentAdd);
|
||||
editor.off('component:remove', onComponentRemove);
|
||||
editor.off('style:property:update', onStyleUpdate);
|
||||
editor.off('component:selected', onSelect);
|
||||
} catch {}
|
||||
};
|
||||
@@ -293,7 +410,7 @@ export function initEditor() {
|
||||
function attachCraftDirtyTracker() {
|
||||
const host = document.getElementById('craftEditor');
|
||||
if (!host) return () => {};
|
||||
const handler = () => markDirty();
|
||||
const handler = () => markDirty('craft:input');
|
||||
const events = ['input', 'keydown', 'paste', 'drop'];
|
||||
events.forEach(evt => host.addEventListener(evt, handler, true));
|
||||
return () => {
|
||||
|
||||
Reference in New Issue
Block a user