@anywhichway/nerd-editor
Version:
A JavaScript rich text editor based on and with support for custom elements.
61 lines (59 loc) • 2.99 kB
JavaScript
import {bind} from "./bind";
import {createElement} from "./create-element";
import {typeAheads} from "./type-aheads";
const cursor = createElement({tagName:"cursor",attributes:{contenteditable: false,display:"none"}}),
popper = createElement({tagName:"popper",attributes:{style:{position:"absolute",display:"none","zIndex":100,"backgroundColor":"whitesmoke"}}});
document.body.appendChild(popper);
const showPopper = (target,content,{position= {},offset={}}={}) => {
const rect = target.getBoundingClientRect(),
vertical = rect[position.vertical||"bottom"],
horizontal = rect[position.horizontal||"right"]
popper.innerHTML = "";
popper.appendChild(content);
popper.style.top = `calc(${vertical}px + ${offset.vertical||"0px"})`;
popper.style.left = `calc(${horizontal}px + ${offset.horizontal||"0px"})`;
popper.style.display = "";
}
const hidePopper = () => {
popper.style.display = "none";
}
const typeAhead = (editor) => {
popper.style.display = "none";
const selection = window.getSelection(),
{anchorNode,anchorOffset,focusNode,focusOffset} = selection,
node = selection.anchorNode;
if(node.nodeType===Node.TEXT_NODE) {
const text = node.textContent.substring(0,selection.anchorOffset);
Object.entries(typeAheads).some(([name,typeAhead]) => {
const {match,matchEnd=/.+\s/s,options,template,content} = bind(typeAhead),
matched = (text.match(match)||[])[1],
end = matched ? matched.match(matchEnd) : false;
if(matched) {
if(end) {
selection.setBaseAndExtent(anchorNode,focusOffset-matched.length,focusNode,anchorOffset);
editor.replaceSelection(content(matched))
} else {
const use = (items) => {
const div = document.createElement("div");
items = items.map((item) => {
const el = template(item);
el.onclick = () => {
hidePopper();
selection.setBaseAndExtent(anchorNode,focusOffset-matched.length,focusNode,anchorOffset);
editor.replaceSelection(content(item));
};
return el;
});
items.forEach((item) => div.appendChild(item));
showPopper(cursor,div,{position:{horizontal:"left"},offset:{horizontal:(-(matched.length-2))+"ch"}});
selection.setBaseAndExtent(anchorNode,anchorOffset,focusNode,focusOffset);
};
selection.getRangeAt(0).surroundContents(cursor);
options(matched,use);
}
return true;
}
})
}
}
export {typeAhead}