1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 07:27:45 +00:00

LibWebView: Add Inspector actions to be used as context menu callbacks

These allow for triggering an edit of a DOM node (as an alternative to
double-clicking), removing a DOM node, and adding/removing DOM node
attributes.
This commit is contained in:
Timothy Flynn 2023-12-05 16:16:12 -05:00 committed by Andreas Kling
parent 9a5fe740c6
commit 0ddc2ea8c4
3 changed files with 161 additions and 30 deletions

View file

@ -136,6 +136,28 @@ inspector.clearInspectedDOMNode = () => {
}
};
inspector.editDOMNodeID = nodeID => {
if (pendingEditDOMNode === null) {
return;
}
inspector.inspectDOMNodeID(nodeID);
editDOMNode(pendingEditDOMNode);
pendingEditDOMNode = null;
};
inspector.addAttributeToDOMNodeID = nodeID => {
if (pendingEditDOMNode === null) {
return;
}
inspector.inspectDOMNodeID(nodeID);
addAttributeToDOMNode(pendingEditDOMNode);
pendingEditDOMNode = null;
};
inspector.createPropertyTables = (computedStyle, resolvedStyle, customProperties) => {
const createPropertyTable = (tableID, properties) => {
let oldTable = document.getElementById(tableID);
@ -176,62 +198,110 @@ const inspectDOMNode = domNode => {
inspector.inspectDOMNode(domNode.dataset.id, domNode.dataset.pseudoElement);
};
const editDOMNode = domNode => {
if (selectedDOMNode === null) {
return;
}
const domNodeID = selectedDOMNode.dataset.id;
const type = domNode.dataset.nodeType;
const createDOMEditor = (onHandleChange, onCancelChange) => {
selectedDOMNode.classList.remove("selected");
let input = document.createElement("input");
input.classList.add("dom-editor");
input.classList.add("selected");
input.value = domNode.innerText;
const handleChange = () => {
input.removeEventListener("change", handleChange);
input.removeEventListener("blur", cancelChange);
if (type === "text" || type === "comment") {
inspector.setDOMNodeText(domNodeID, input.value);
} else if (type === "tag") {
try {
const element = document.createElement(input.value);
inspector.setDOMNodeTag(domNodeID, input.value);
} catch {
cancelChange();
}
} else if (type === "attribute") {
let element = document.createElement("div");
element.innerHTML = `<div ${input.value}></div>`;
inspector.replaceDOMNodeAttribute(
domNodeID,
domNode.dataset.attributeName,
element.children[0].attributes
);
try {
onHandleChange(input.value);
} catch {
cancelChange();
}
};
const cancelChange = () => {
input.removeEventListener("change", handleChange);
input.removeEventListener("blur", cancelChange);
selectedDOMNode.classList.add("selected");
input.parentNode.replaceChild(domNode, input);
onCancelChange(input);
};
input.addEventListener("change", handleChange);
input.addEventListener("blur", cancelChange);
domNode.parentNode.replaceChild(input, domNode);
setTimeout(() => {
input.focus();
// FIXME: Invoke `select` when it isn't just stubbed out.
// input.select();
});
return input;
};
const parseDOMAttributes = value => {
let element = document.createElement("div");
element.innerHTML = `<div ${value}></div>`;
return element.children[0].attributes;
};
const editDOMNode = domNode => {
if (selectedDOMNode === null) {
return;
}
const domNodeID = selectedDOMNode.dataset.id;
const handleChange = value => {
const type = domNode.dataset.nodeType;
if (type === "text" || type === "comment") {
inspector.setDOMNodeText(domNodeID, value);
} else if (type === "tag") {
const element = document.createElement(value);
inspector.setDOMNodeTag(domNodeID, value);
} else if (type === "attribute") {
const attributes = parseDOMAttributes(value);
inspector.replaceDOMNodeAttribute(domNodeID, domNode.dataset.attributeName, attributes);
}
};
const cancelChange = editor => {
editor.parentNode.replaceChild(domNode, editor);
};
let editor = createDOMEditor(handleChange, cancelChange);
editor.value = domNode.innerText;
domNode.parentNode.replaceChild(editor, domNode);
};
const addAttributeToDOMNode = domNode => {
if (selectedDOMNode === null) {
return;
}
const domNodeID = selectedDOMNode.dataset.id;
const handleChange = value => {
const attributes = parseDOMAttributes(value);
inspector.addDOMNodeAttributes(domNodeID, attributes);
};
const cancelChange = () => {
container.remove();
};
let editor = createDOMEditor(handleChange, cancelChange);
editor.placeholder = 'name="value"';
let nbsp = document.createElement("span");
nbsp.innerHTML = "&nbsp;";
let container = document.createElement("span");
container.appendChild(nbsp);
container.appendChild(editor);
domNode.parentNode.insertBefore(container, domNode.parentNode.lastChild);
};
const requestContextMenu = (clientX, clientY, domNode) => {