first commit

This commit is contained in:
2026-06-24 09:48:54 +02:00
commit 41e62ddcad
33739 changed files with 4266226 additions and 0 deletions
@@ -0,0 +1,3 @@
import type { InlineEditHost, InlineEditController } from "./types.js";
export declare function createInlineEditController(host: InlineEditHost): InlineEditController;
//# sourceMappingURL=controller.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../../src/capabilities/inline-edit/controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAYvE,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,cAAc,GACnB,oBAAoB,CA+OtB"}
@@ -0,0 +1,203 @@
import { injectFocusOutlineCSS, removeFocusOutlineCSS, selectText, shouldEnterInlineEditingMode, isStaticArrayTextElement, } from "./dom-utils.js";
import { PLUGIN_ELEMENT_ATTR } from "../../injections/utils.js";
const DEBOUNCE_MS = 500;
export function createInlineEditController(host) {
let currentEditingElement = null;
let debouncedSendTimeout = null;
let enabled = false;
const listenerAbortControllers = new WeakMap();
// --- Private helpers ---
const repositionOverlays = () => {
const selectedId = host.getSelectedElementId();
if (!selectedId)
return;
const elements = host.findElementsById(selectedId);
const overlays = host.getSelectedOverlays();
overlays.forEach((overlay, i) => {
if (i < elements.length && elements[i]) {
host.positionOverlay(overlay, elements[i]);
}
});
};
const reportEdit = (element) => {
const originalContent = element.dataset.originalTextContent;
const newContent = element.textContent;
const svgElement = element;
const rect = element.getBoundingClientRect();
const message = {
type: "inline-edit",
elementInfo: {
tagName: element.tagName,
classes: svgElement.className?.baseVal ||
element.className ||
"",
visualSelectorId: host.getSelectedElementId(),
content: newContent,
dataSourceLocation: element.dataset.sourceLocation,
isDynamicContent: element.dataset.dynamicContent === "true",
linenumber: element.dataset.linenumber,
filename: element.dataset.filename,
position: {
top: rect.top,
left: rect.left,
right: rect.right,
bottom: rect.bottom,
width: rect.width,
height: rect.height,
centerX: rect.left + rect.width / 2,
centerY: rect.top + rect.height / 2,
},
},
originalContent,
newContent,
};
if (isStaticArrayTextElement(element)) {
message.arrIndex = element.dataset.arrIndex;
message.arrVariableName = element.dataset.arrVariableName;
message.arrField = element.dataset.arrField;
}
window.parent.postMessage(message, "*");
element.dataset.originalTextContent = newContent || "";
};
const debouncedReport = (element) => {
if (debouncedSendTimeout)
clearTimeout(debouncedSendTimeout);
debouncedSendTimeout = setTimeout(() => reportEdit(element), DEBOUNCE_MS);
};
const onTextInput = (element) => {
repositionOverlays();
debouncedReport(element);
};
const handleInputEvent = function () {
onTextInput(this);
};
const makeEditable = (element) => {
injectFocusOutlineCSS();
element.dataset.originalTextContent = element.textContent || "";
element.dataset.originalCursor = element.style.cursor;
element.contentEditable = "true";
element.setAttribute(PLUGIN_ELEMENT_ATTR, "true");
const abortController = new AbortController();
listenerAbortControllers.set(element, abortController);
element.addEventListener("input", handleInputEvent, {
signal: abortController.signal,
});
element.style.cursor = "text";
selectText(element);
setTimeout(() => {
if (element.isConnected) {
element.focus();
}
}, 0);
};
const makeNonEditable = (element) => {
const abortController = listenerAbortControllers.get(element);
if (abortController) {
abortController.abort();
listenerAbortControllers.delete(element);
}
if (!element.isConnected)
return;
removeFocusOutlineCSS();
element.contentEditable = "false";
element.removeAttribute(PLUGIN_ELEMENT_ATTR);
delete element.dataset.originalTextContent;
if (element.dataset.originalCursor !== undefined) {
element.style.cursor = element.dataset.originalCursor;
delete element.dataset.originalCursor;
}
};
// --- Public API ---
return {
get enabled() {
return enabled;
},
set enabled(value) {
enabled = value;
},
isEditing() {
return currentEditingElement !== null;
},
getCurrentElement() {
return currentEditingElement;
},
canEdit(element) {
return shouldEnterInlineEditingMode(element);
},
startEditing(element) {
currentEditingElement = element;
host.getSelectedOverlays().forEach((o) => {
o.style.display = "none";
});
makeEditable(element);
window.parent.postMessage({
type: "content-editing-started",
visualSelectorId: host.getSelectedElementId(),
}, "*");
},
stopEditing() {
if (!currentEditingElement)
return;
if (debouncedSendTimeout) {
clearTimeout(debouncedSendTimeout);
debouncedSendTimeout = null;
}
const element = currentEditingElement;
makeNonEditable(element);
host.getSelectedOverlays().forEach((o) => {
o.style.display = "";
});
repositionOverlays();
window.parent.postMessage({
type: "content-editing-ended",
visualSelectorId: host.getSelectedElementId(),
}, "*");
currentEditingElement = null;
},
markElementsSelected(elements) {
elements.forEach((el) => {
if (el instanceof HTMLElement) {
el.dataset.selected = "true";
}
});
},
clearSelectedMarks(elementId) {
if (!elementId)
return;
host.findElementsById(elementId).forEach((el) => {
if (el instanceof HTMLElement) {
delete el.dataset.selected;
}
});
},
handleToggleMessage(data) {
if (!enabled)
return;
const elements = host.findElementsById(data.dataSourceLocation);
if (elements.length === 0 || !(elements[0] instanceof HTMLElement))
return;
const element = elements[0];
if (data.inlineEditingMode) {
if (!shouldEnterInlineEditingMode(element))
return;
// Select the element first if not already selected
if (host.getSelectedElementId() !== data.dataSourceLocation) {
this.stopEditing();
host.clearSelection();
this.markElementsSelected(elements);
host.createSelectionOverlays(elements, data.dataSourceLocation);
}
this.startEditing(element);
}
else {
if (currentEditingElement === element) {
this.stopEditing();
}
}
},
cleanup() {
this.stopEditing();
},
};
}
//# sourceMappingURL=controller.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,7 @@
export declare const isStaticArrayTextElement: (element: HTMLElement) => boolean;
export declare const injectFocusOutlineCSS: () => void;
export declare const removeFocusOutlineCSS: () => void;
export declare const selectText: (element: HTMLElement) => void;
export declare const isEditableTextElement: (element: Element) => boolean;
export declare const shouldEnterInlineEditingMode: (element: Element) => boolean;
//# sourceMappingURL=dom-utils.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"dom-utils.d.ts","sourceRoot":"","sources":["../../../src/capabilities/inline-edit/dom-utils.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,wBAAwB,GAAI,SAAS,WAAW,KAAG,OAE/D,CAAC;AAUF,eAAO,MAAM,qBAAqB,YAWjC,CAAC;AAEF,eAAO,MAAM,qBAAqB,YAEjC,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,SAAS,WAAW,SAM9C,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAAI,SAAS,OAAO,KAAG,OAMxD,CAAC;AAEF,eAAO,MAAM,4BAA4B,GAAI,SAAS,OAAO,KAAG,OAK/D,CAAC"}
@@ -0,0 +1,59 @@
const FOCUS_STYLE_ID = "visual-edit-focus-styles";
const EDITABLE_TAGS = [
"div", "p", "h1", "h2", "h3", "h4", "h5", "h6",
"span", "li", "td", "a", "button", "label",
];
export const isStaticArrayTextElement = (element) => {
return !!element.dataset.arrField;
};
const passesStructuralChecks = (element) => {
if (!EDITABLE_TAGS.includes(element.tagName.toLowerCase()))
return false;
if (!element.textContent?.trim())
return false;
if (element.querySelector("img, video, canvas, svg"))
return false;
if (element.children?.length > 0)
return false;
return true;
};
export const injectFocusOutlineCSS = () => {
if (document.getElementById(FOCUS_STYLE_ID))
return;
const style = document.createElement("style");
style.id = FOCUS_STYLE_ID;
style.textContent = `
[data-selected="true"][contenteditable="true"]:focus {
outline: none !important;
}
`;
document.head.appendChild(style);
};
export const removeFocusOutlineCSS = () => {
document.getElementById(FOCUS_STYLE_ID)?.remove();
};
export const selectText = (element) => {
const range = document.createRange();
range.selectNodeContents(element);
const selection = window.getSelection();
selection?.removeAllRanges();
selection?.addRange(range);
};
export const isEditableTextElement = (element) => {
if (!(element instanceof HTMLElement))
return false;
if (!passesStructuralChecks(element))
return false;
if (isStaticArrayTextElement(element))
return true;
if (element.dataset.dynamicContent === "true")
return false;
return true;
};
export const shouldEnterInlineEditingMode = (element) => {
if (!(element instanceof HTMLElement) || element.dataset.selected !== "true") {
return false;
}
return isEditableTextElement(element);
};
//# sourceMappingURL=dom-utils.js.map
@@ -0,0 +1 @@
{"version":3,"file":"dom-utils.js","sourceRoot":"","sources":["../../../src/capabilities/inline-edit/dom-utils.ts"],"names":[],"mappings":"AAAA,MAAM,cAAc,GAAG,0BAA0B,CAAC;AAElD,MAAM,aAAa,GAAG;IACpB,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI;IAC9C,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO;CAC3C,CAAC;AAEF,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,OAAoB,EAAW,EAAE;IACxE,OAAO,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC;AACpC,CAAC,CAAC;AAEF,MAAM,sBAAsB,GAAG,CAAC,OAAoB,EAAW,EAAE;IAC/D,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;QAAE,OAAO,KAAK,CAAC;IACzE,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,IAAI,EAAE;QAAE,OAAO,KAAK,CAAC;IAC/C,IAAI,OAAO,CAAC,aAAa,CAAC,yBAAyB,CAAC;QAAE,OAAO,KAAK,CAAC;IACnE,IAAI,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAC/C,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAG,EAAE;IACxC,IAAI,QAAQ,CAAC,cAAc,CAAC,cAAc,CAAC;QAAE,OAAO;IAEpD,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IAC9C,KAAK,CAAC,EAAE,GAAG,cAAc,CAAC;IAC1B,KAAK,CAAC,WAAW,GAAG;;;;GAInB,CAAC;IACF,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;AACnC,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAG,EAAE;IACxC,QAAQ,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;AACpD,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,OAAoB,EAAE,EAAE;IACjD,MAAM,KAAK,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;IACrC,KAAK,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAClC,MAAM,SAAS,GAAG,MAAM,CAAC,YAAY,EAAE,CAAC;IACxC,SAAS,EAAE,eAAe,EAAE,CAAC;IAC7B,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC7B,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,OAAgB,EAAW,EAAE;IACjE,IAAI,CAAC,CAAC,OAAO,YAAY,WAAW,CAAC;QAAE,OAAO,KAAK,CAAC;IACpD,IAAI,CAAC,sBAAsB,CAAC,OAAO,CAAC;QAAE,OAAO,KAAK,CAAC;IACnD,IAAI,wBAAwB,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IACnD,IAAI,OAAO,CAAC,OAAO,CAAC,cAAc,KAAK,MAAM;QAAE,OAAO,KAAK,CAAC;IAC5D,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,4BAA4B,GAAG,CAAC,OAAgB,EAAW,EAAE;IACxE,IAAI,CAAC,CAAC,OAAO,YAAY,WAAW,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;QAC7E,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,qBAAqB,CAAC,OAAO,CAAC,CAAC;AACxC,CAAC,CAAC"}
@@ -0,0 +1,3 @@
export { createInlineEditController } from "./controller.js";
export type { CollectionInfo, InlineEditController, InlineEditHost } from "./types.js";
//# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/capabilities/inline-edit/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAC7D,YAAY,EAAE,cAAc,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,2 @@
export { createInlineEditController } from "./controller.js";
//# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/capabilities/inline-edit/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,29 @@
export interface CollectionInfo {
id: string;
references: string[];
}
export interface InlineEditHost {
findElementsById(id: string | null): Element[];
getSelectedElementId(): string | null;
getSelectedOverlays(): HTMLDivElement[];
positionOverlay(overlay: HTMLDivElement, element: Element, isSelected?: boolean): void;
clearSelection(): void;
createSelectionOverlays(elements: Element[], elementId: string): void;
}
export interface ToggleInlineEditData {
dataSourceLocation: string;
inlineEditingMode: boolean;
}
export interface InlineEditController {
enabled: boolean;
isEditing(): boolean;
getCurrentElement(): HTMLElement | null;
canEdit(element: Element): boolean;
startEditing(element: HTMLElement): void;
stopEditing(): void;
markElementsSelected(elements: Element[]): void;
clearSelectedMarks(elementId: string | null): void;
handleToggleMessage(data: ToggleInlineEditData): void;
cleanup(): void;
}
//# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/capabilities/inline-edit/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,EAAE,CAAC;IAC/C,oBAAoB,IAAI,MAAM,GAAG,IAAI,CAAC;IACtC,mBAAmB,IAAI,cAAc,EAAE,CAAC;IACxC,eAAe,CACb,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,OAAO,EAChB,UAAU,CAAC,EAAE,OAAO,GACnB,IAAI,CAAC;IACR,cAAc,IAAI,IAAI,CAAC;IACvB,uBAAuB,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CACvE;AAED,MAAM,WAAW,oBAAoB;IACnC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,IAAI,OAAO,CAAC;IACrB,iBAAiB,IAAI,WAAW,GAAG,IAAI,CAAC;IACxC,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC;IACnC,YAAY,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IACzC,WAAW,IAAI,IAAI,CAAC;IACpB,oBAAoB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAChD,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;IACnD,mBAAmB,CAAC,IAAI,EAAE,oBAAoB,GAAG,IAAI,CAAC;IACtD,OAAO,IAAI,IAAI,CAAC;CACjB"}
@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/capabilities/inline-edit/types.ts"],"names":[],"mappings":""}