sdyscxyc
This commit is contained in:
@@ -1 +1 @@
|
||||
1.2.30
|
||||
1.2.31
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -2987,6 +2987,9 @@ class ApiKernel
|
||||
case 'debug.logs.read':
|
||||
$this->handleDebugLogsRead();
|
||||
break;
|
||||
case 'debug.log.write':
|
||||
$this->handleDebugLogWrite();
|
||||
break;
|
||||
case 'placeholders.status':
|
||||
$this->handlePlaceholderStatus();
|
||||
break;
|
||||
@@ -4249,6 +4252,35 @@ class ApiKernel
|
||||
$this->respond(['ok' => true, 'content' => $content]);
|
||||
}
|
||||
|
||||
private function handleDebugLogWrite(): void
|
||||
{
|
||||
$user = $this->requireAuth();
|
||||
$this->ensureDebugUser($user);
|
||||
$this->ensureDebugEnv();
|
||||
$name = trim((string)($this->in['name'] ?? 'ui_editor_dirty.log'));
|
||||
$line = $this->in['line'] ?? '';
|
||||
$append = (int)($this->in['append'] ?? 1) === 1;
|
||||
if ($name === '') {
|
||||
$this->fail('Log name required', null, 422);
|
||||
}
|
||||
$name = preg_replace('/[^a-zA-Z0-9_\.\-]/', '', $name) ?: 'debug.log';
|
||||
if (strpos($name, '..') !== false) {
|
||||
$this->fail('Invalid log name', null, 422);
|
||||
}
|
||||
$payload = $line;
|
||||
if (is_array($payload) || is_object($payload)) {
|
||||
$payload = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
} else {
|
||||
$payload = (string)$payload;
|
||||
}
|
||||
debug_log_write($name, $payload, [
|
||||
'append' => $append,
|
||||
'json' => false,
|
||||
'newline' => true,
|
||||
]);
|
||||
$this->respond(['ok' => true, 'name' => $name]);
|
||||
}
|
||||
|
||||
private function resolveBridgeConfig(?int $customerId): array
|
||||
{
|
||||
$fileConf = $this->conf['placeholders']['bridge'] ?? [];
|
||||
|
||||
Reference in New Issue
Block a user