function getSelectionRange(root: HTMLElement | null): Range | null {
if (!root || typeof window === "undefined") {
return null;
}
const selection = window.getSelection();
if (!selection || selection.rangeCount === 0) {
return null;
}
const range = selection.getRangeAt(0);
const anchorNode = selection.anchorNode;
if (!anchorNode || !root.contains(anchorNode)) {
return null;
}
return range;
}
function collapseRangeAtEnd(selection: Selection, node: Node): void {
const next = document.createRange();
next.selectNodeContents(node);
next.collapse(false);
selection.removeAllRanges();
selection.addRange(next);
}
function insertList(root: HTMLElement | null, ordered: boolean): boolean {
const range = getSelectionRange(root);
if (!range || typeof window === "undefined") {
return false;
}
const selection = window.getSelection();
if (!selection) {
return false;
}
const text = range.toString().trim();
const list = document.createElement(ordered ? "ol" : "ul");
const item = document.createElement("li");
item.innerHTML = text.length > 0 ? text : "
";
list.appendChild(item);
range.deleteContents();
range.insertNode(list);
collapseRangeAtEnd(selection, item);
return true;
}
function insertParagraph(root: HTMLElement | null): boolean {
const range = getSelectionRange(root);
if (!range || typeof window === "undefined") {
return false;
}
const selection = window.getSelection();
if (!selection) {
return false;
}
const paragraph = document.createElement("p");
paragraph.innerHTML = "
";
range.insertNode(paragraph);
collapseRangeAtEnd(selection, paragraph);
return true;
}
function formatBlock(root: HTMLElement | null, value?: string): boolean {
const range = getSelectionRange(root);
if (!range || !value || typeof window === "undefined") {
return false;
}
const selection = window.getSelection();
if (!selection) {
return false;
}
const normalized = value.replace(/[<>]/g, "").trim().toLowerCase();
const tagName = normalized === "p" ? "p" : normalized;
const block = document.createElement(tagName);
const text = range.toString();
block.innerHTML = text.length > 0 ? text : "
";
range.deleteContents();
range.insertNode(block);
collapseRangeAtEnd(selection, block);
return true;
}
export function runContentEditableCommand({
command,
value,
root,
}: {
command: string;
value?: string;
root: HTMLElement | null;
}): boolean {
if (!root || typeof document === "undefined") {
return false;
}
root.focus();
const hasExec = typeof document.execCommand === "function";
if (hasExec) {
const ok = document.execCommand(command, false, value);
if (ok) {
return true;
}
}
if (command === "insertUnorderedList") {
return insertList(root, false);
}
if (command === "insertOrderedList") {
return insertList(root, true);
}
if (command === "insertParagraph") {
return insertParagraph(root);
}
if (command === "formatBlock") {
return formatBlock(root, value);
}
return false;
}