tortellini
Version:
A minimal code editor 🐢
619 lines (600 loc) • 34.7 kB
JavaScript
const languages = {
javascript: {
colors: [
{
regex: {
string: "(class|this|var|const|let|if|else|document|window|do|forEach|for|return|switch|try|catch|function|continue|debugger|break|while|true|null|undefined|false|new|await|async|of)(?=( |;|\\.|{|}|\\=|\\(|\\)))(?!(| )<\\/(.*)>)",
flags: "gi",
},
class: "||general"
},
{
regex: { string: "('(([a-zA-Z\\.,!\"§$%&/()-=?\\[\\]#`_ ])*?)')", flags: "gi", },
class: "||strings"
},
{
regex: { string: "(`([a-zA-Z\\.,!\"§$%&/()-=?\\[\\]#`_ ]*?)`)", flags: "gi", },
class: "||strings"
},
{
regex: { string: "(\"([a-zA-Z\\.,!\"§$%&/()-=?\\[\\]#`_ ]*?)\")", flags: "gi", },
class: "||strings"
},
{
regex: { string: "((\\.([a-zA-Z0-9]*))\\()", flags: "gi", },
class: "||functions"
},
{
regex: { string: "([\\(\\)])", flags: "gi", },
class: "||functions"
},
{
regex: { string: "([{}\\[\\]])", flags: "g", },
class: "||brackets"
},
{
regex: { string: "(\\/\\*((.|\\n|\\r)*)\\*\\/|\\/\\/(.*))", flags: "g", },
class: "||comments"
}
],
suggestions: [
"const", "let", "for", "while", "of", "new",
"document", "Document", "String", "Number", "Array", "Object",
...Object.getOwnPropertyNames(Document.prototype).filter((property) => !property.includes("queryCommand")),
...Object.getOwnPropertyNames(HTMLElement.prototype),
...Object.getOwnPropertyNames(String.prototype),
...Object.getOwnPropertyNames(Number.prototype),
...Object.getOwnPropertyNames(Array.prototype),
...Object.getOwnPropertyNames(Object.prototype)
],
variableDeclarers: ["const", "let", "var"]
},
html: {
colors: [{
regex: { string: "([a-z]*=(.*?)(?=>))", flags: "g", },
class: "||attributes"
},
{
regex: { string: "(<(\/|)[a-zA-Z0-9]*)", flags: "g" },
class: "||innerBrackets"
},
{
regex: { string: "(>)", flags: "g", },
class: "||lesserOrGreaterBrackets"
},
{
regex: { string: "(<)", flags: "g", },
class: "||lesserOrGreaterBrackets"
}],
suggestions: ["a", "abbr", "acronym", "address", "applet", "area", "article", "aside", "audio", "b", "base", "basefont", "bdi", "bdo", "big", "blockquote", "body", "br", "button", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "data", "datalist", "dd", "del", "details", "dfn", "dialog", "dir", "div", "dl", "dt", "em", "embed", "fieldset", "figcaption", "figure", "font", "footer", "form", "frame", "frameset", "h1 to h6", "head", "header", "hr", "html", "i", "iframe", "img", "input", "ins", "kbd", "label", "legend", "li", "link", "main", "map", "mark", "meta", "meter", "nav", "noframes", "noscript", "object", "ol", "optgroup", "option", "output", "p", "param", "picture", "pre", "progress", "q", "rp", "rt", "ruby", "s", "samp", "script", "section", "select", "small", "source", "span", "strike", "strong", "style", "sub", "summary", "sup", "svg", "table", "tbody", "td", "template", "textarea", "tfoot", "th", "thead", "time", "title", "tr", "track", "tt", "u", "ul", "var", "video", "wbr", "accept", "accept-charset", "accesskey", "action", "align", "alt", "async", "autocomplete", "autofocus", "autoplay", "autosave", "bgcolor", "border", "buffered", "challenge", "charset", "checked", "cite", "class", "code", "codebase", "color", "cols", "colspan", "content", "contenteditable", "contextmenu", "controls", "coords", "crossorigin", "data", "data-*", "datetime", "default", "defer", "dir", "dirname", "disabled", "download", "draggable", "dropzone", "enctype", "for", "form", "formaction", "headers", "height", "hidden", "high", "href", "hreflang", "http-equiv", "icon", "id", "integrity", "ismap", "itemprop", "keytype", "kind", "label", "lang", "language", "list", "loop", "low", "manifest", "max", "maxlength", "media", "method", "min", "multiple", "muted", "name", "novalidate", "open", "optimum", "pattern", "ping", "placeholder", "poster", "preload", "radiogroup", "readonly", "rel", "required", "reversed", "rows", "rowspan", "sandbox", "scope", "scoped", "seamless", "selected", "shape", "size", "sizes", "slot", "span", "spellcheck", "src", "srcdoc", "srclang", "srcset", "start", "step", "style", "summary", "tabindex", "target", "title", "type", "value", "width", "wrap"]
},
css: {
colors: [{
regex: { string: "([{}])", flags: "g", },
class: "||brackets"
},
{
regex: { string: "([a-zA-Z0-9-]*:)", flags: "g", },
class: "||selectors"
},
{
regex: { string: "(#[a-zA-Z0-9]*)(?=(;| |\\r|\\n))", flags: "g", },
class: "||numbers"
}],
suggestions: ["align-content", "align-items", "align-self", "all", "animation", "animation-delay", "animation-direction", "animation-duration",
"animation-fill-mode", "animation-iteration-count", "animation-name", "animation-play-state", "animation-timing-function", "backface-visibility",
"background", "background-attachment", "background-blend-mode", "background-clip", "background-color", "background-image", "background-origin",
"background-position", "background-repeat", "background-size", "border", "border-bottom", "border-bottom-color", "border-bottom-left-radius",
"border-bottom-right-radius", "border-bottom-style", "border-bottom-width", "border-collapse", "border-color", "border-image", "border-image-outset",
"border-image-repeat", "border-image-slice", "border-image-source", "border-image-width", "border-left", "border-left-color", "border-left-style",
"border-left-width", "border-radius", "border-right", "border-right-color", "border-right-style", "border-right-width", "border-spacing", "border-style",
"border-top", "border-top-color", "border-top-left-radius", "border-top-right-radius", "border-top-style", "border-top-width", "border-width", "bottom",
"box-decoration-break", "box-shadow", "box-sizing", "caption-side", "caret-color", "@charset", "clear", "clip", "color", "column-count", "column-fill",
"column-gap", "column-rule", "column-rule-color", "column-rule-style", "column-rule-width", "column-span", "column-width", "columns", "content", "counter-increment",
"counter-reset", "cursor", "direction", "display", "empty-cells", "filter", "flex", "flex-basis", "flex-direction", "flex-flow", "flex-grow", "flex-shrink", "flex-wrap",
"float", "font", "@font-face", "font-family", "font-kerning", "font-size", "font-size-adjust", "font-stretch", "font-style", "font-variant", "font-weight", "grid",
"grid-area", "grid-auto-columns", "grid-auto-flow", "grid-auto-rows", "grid-column", "grid-column-end", "grid-column-gap", "grid-column-start", "grid-gap", "grid-row",
"grid-row-end", "grid-row-gap", "grid-row-start", "grid-template", "grid-template-areas", "grid-template-columns", "grid-template-rows", "hanging-punctuation", "height", "hyphens", "@import", "isolation", "justify-content", "@keyframes", "left", "letter-spacing", "line-height", "list-style", "list-style-image", "list-style-position", "list-style-type", "margin", "margin-bottom", "margin-left", "margin-right", "margin-top", "max-height", "max-width", "@media", "min-height", "min-width", "mix-blend-mode", "object-fit", "object-position", "opacity", "order", "outline", "outline-color", "outline-offset", "outline-style", "outline-width", "overflow", "overflow-x", "overflow-y", "padding", "padding-bottom", "padding-left", "padding-right", "padding-top", "page-break-after", "page-break-before", "page-break-inside", "perspective", "perspective-origin", "pointer-events", "position", "quotes", "resize", "right", "scroll-behavior", "tab-size", "table-layout", "text-align", "text-align-last", "text-decoration", "text-decoration-color", "text-decoration-line", "text-decoration-style", "text-indent", "text-justify", "text-overflow", "text-shadow", "text-transform", "top", "transform", "transform-origin", "transform-style", "transition", "transition-delay", "transition-duration", "transition-property", "transition-timing-function", "unicode-bidi", "direction", "user-select", "vertical-align", "visibility", "white-space", "width", "word-break", "word-spacing", "word-wrap", "writing-mode", "z-index"]
}
};
export class Tortellini {
constructor(base, language, { caretColor, indentSize } = {}) {
this.singleSpaceCharacter = "\u00a0";
this.isAdviserOpen = false;
if (!base)
throw Error("Please provide a base element as the first argument.");
if (!language || (language !== "javascript" && language !== "html" && language !== "css"))
throw Error("Please define the programming language (javascript, html or css) as the second argument.");
const styles = `
* {
box-sizing: border-box;
}
.\\|\\|general {
color: #de45de;
}
.\\|\\|brackets {
color: orange;
}
.\\|\\|functions {
color: #9393d4;
}
.\\|\\|strings {
color: #6bd46b;
}
.\\|\\|tags {
color: #d45656;
}
.\\|\\|lesserOrGreaterBrackets {
color: #77bdc1;
}
.\\|\\|innerBrackets {
color: #d26e6e;
}
.\\|\\|attributes {
color: #ffeba0;
}
.\\|\\|selectors {
color: grey;
}
.\\|\\|numbers {
color: lightcoral;
}
.\\|\\|comments {
color: #777;
}
[data-tortellini-editor], [data-tortellini-highlights] {
white-space: pre-wrap;
min-height: 100%;
}
[data-tortellini-editor] {
color: transparent;
caret-color: ${caretColor || "#fff"};
z-index: 10;
position: sticky;
outline: none;
width: 100%;
}
[data-tortellini-highlights] {
position: absolute;
width: 100%;
top: 0;
left: 0;
right: 0;
padding: inherit;
}
[data-tortellini-adviser] {
background: #152248;
position: fixed;
bottom: unset;
right: unset;
padding: 0;
color: #fff;
border-radius: 3px;
}
[data-tortellini-adviser]:empty, [data-tortellini-adviser] > a:nth-child(1n + 7) {
display: none;
}
[data-tortellini-adviser] > a {
width: 100%;
padding: 5px;
display: block;
font-weight: normal;
color: #fff;
}
[data-tortellini-adviser] > a[data-active] {
position: relative;
}
[data-tortellini-adviser] > a[data-active]::after {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #fff;
opacity: 0.2;
}
`;
const styleElement = document.createElement("style");
styleElement.appendChild(document.createTextNode(styles));
document.querySelector("head").appendChild(styleElement);
this.tabCharacter = this.singleSpaceCharacter.repeat(indentSize || 4);
this.base = base;
if (getComputedStyle(this.base).position === "static")
this.base.style.position = "relative";
this.initialHTML = this.base.innerHTML;
this.language = language;
this.currentTabOffset = 0;
this.openedAdviserByInteraction = true;
this.editor = document.createElement("div");
this.editor.setAttribute("contenteditable", "true");
this.editor.setAttribute("spellcheck", "false");
this.editor.setAttribute("data-language", language);
this.editor.setAttribute("data-tortellini-editor", "");
this.editor.innerHTML = this.base.innerHTML;
this.base.innerHTML = "";
this.base.appendChild(this.editor);
this.highlights = document.createElement("div");
this.highlights.setAttribute("data-tortellini-highlights", "");
this.base.appendChild(this.highlights);
this.adviser = document.createElement("div");
this.adviser.setAttribute("data-tortellini-adviser", "");
this.base.appendChild(this.adviser);
setInterval(() => {
this.colorCode();
}, 50);
setInterval(() => {
this.setOffset();
}, 250);
this.editor.onkeyup = this.handleAdviserBehavior.bind(this);
this.base.onkeydown = this.handleKeys.bind(this);
}
remove() {
this.base.innerHTML = this.initialHTML;
}
get code() {
return this.editor.innerText;
}
getSuggestions(focusWord, { object = null, showOnlyGoodMatching = false } = {}) {
var _a;
const availableSuggestions = languages[this.language].suggestions.reduce((properties, property) => {
if (properties.includes(property))
return properties;
return [
...properties,
property
];
}, []);
if (object && window[object] && Object.getOwnPropertyNames(window[object])) {
availableSuggestions.unshift(...Object.getOwnPropertyNames(window[object]));
if (window[object].__proto__) {
availableSuggestions.unshift(...Object.getOwnPropertyNames(window[object].__proto__));
}
}
const firstLevelSuggestions = availableSuggestions.filter((suggestion) => suggestion === focusWord);
const secondLevelSuggestions = availableSuggestions.filter((suggestion) => suggestion.startsWith(focusWord) && firstLevelSuggestions.indexOf(suggestion) === -1);
const thirdLevelSuggestions = !showOnlyGoodMatching ?
availableSuggestions.filter((suggestion) => suggestion.includes(focusWord) && secondLevelSuggestions.indexOf(suggestion) === -1)
: [];
const declaredVariables = (_a = languages[this.language].variableDeclarers) === null || _a === void 0 ? void 0 : _a.reduce((declaredVariables, declarer) => {
var _a;
const foundDeclaredVariables = (_a = this.editor.innerText
.match(new RegExp(`${declarer} ([a-zA-Z0-9]*)`, "g"))) === null || _a === void 0 ? void 0 : _a.map(declaredVariable => declaredVariable.replace(declarer, "").trim());
if (foundDeclaredVariables) {
return [
...declaredVariables,
...foundDeclaredVariables
];
}
return declaredVariables;
}, []).filter((declaredVariable) => declaredVariable.startsWith(focusWord) && declaredVariable !== focusWord);
return [...declaredVariables, ...firstLevelSuggestions, ...secondLevelSuggestions, ...thirdLevelSuggestions]
.reduce((suggestions, suggestion) => suggestions.includes(suggestion) ? suggestions : [...suggestions, suggestion], []);
}
insertSuggestion(selectedElement) {
if (!selectedElement)
return;
this.editor.focus();
this.adviser.innerHTML = "";
const suggestion = selectedElement.innerText;
const focusWord = selectedElement.dataset.focusword;
if (suggestion.length === 0)
return;
const selection = window.getSelection();
const range = selection.getRangeAt(0);
const suggestionText = document.createTextNode(suggestion);
range.insertNode(suggestionText);
range.setStartAfter(suggestionText);
range.setEndAfter(suggestionText);
selection.removeAllRanges();
selection.addRange(range);
this.editor.innerHTML = this.editor.innerHTML.replace(focusWord + suggestion, `<span data-inserted>${suggestion}</span>`);
range.setStartAfter(this.editor.querySelector("[data-inserted]"));
range.setEndAfter(this.editor.querySelector("[data-inserted]"));
selection.removeAllRanges();
selection.addRange(range);
const spaceOutsideInsertedSpan = document.createTextNode("");
range.insertNode(spaceOutsideInsertedSpan);
range.setStartAfter(spaceOutsideInsertedSpan);
range.setEndAfter(spaceOutsideInsertedSpan);
selection.removeAllRanges();
selection.addRange(range);
this.editor.querySelector("[data-inserted]").outerHTML = this.editor.querySelector("[data-inserted]").innerHTML;
setTimeout(() => {
this.isAdviserOpen = false;
}, 10);
}
showSuggestions({ askedForSuggestions = true } = {}) {
var _a;
this.isAdviserOpen = false;
this.openedAdviserByInteraction = askedForSuggestions;
const position = document.getSelection().getRangeAt(0).getBoundingClientRect();
const enteredCode = window.getSelection().getRangeAt(0).startContainer.textContent.substring(0, window.getSelection().getRangeAt(0).startOffset);
const object = enteredCode.match(/([a-zA-Z]*)\.(\w+)$/) && enteredCode.match(/([a-zA-Z]*)\.(\w+)$/)[0] && enteredCode.match(/([a-zA-Z]*)\.(\w+)$/)[0].split(".") ? enteredCode.match(/([a-zA-Z]*)\.(\w+)$/)[0].split(".")[0] : null;
const focusWord = enteredCode.match(/([a-zA-Z-]*)$/g) ? enteredCode.match(/([a-zA-Z-]*)$/g)[0] : "";
const suggestions = this.getSuggestions(focusWord, {
object,
showOnlyGoodMatching: !askedForSuggestions
});
if (position.left === 0 || position.top === 0)
return;
if ((suggestions.length > 7) && !askedForSuggestions || !askedForSuggestions && suggestions[0] === "No results") {
this.adviser.innerHTML = "";
this.isAdviserOpen = false;
return;
}
this.isAdviserOpen = true;
this.adviser.style.left = `${position.left}px`;
this.adviser.style.top = `${position.top}px`;
this.adviser.innerHTML = "";
suggestions.map((suggestion) => this.adviser.innerHTML += `<a data-focusword="${focusWord}">${suggestion}</a>`);
(_a = this.adviser.querySelector("a")) === null || _a === void 0 ? void 0 : _a.setAttribute("data-active", "");
}
getCharactersBeforeCursor({ escapeElementTags = false } = {}) {
try {
return escapeElementTags
? window.getSelection().getRangeAt(0).startContainer.textContent.substring(0, window.getSelection().getRangeAt(0).startOffset + 1).replace(/</g, "<").replace(/>/g, ">")
: window.getSelection().getRangeAt(0).startContainer.textContent.substring(0, window.getSelection().getRangeAt(0).startOffset + 1);
}
catch (e) {
}
}
getCharactersBehindCursor() {
const selection = window.getSelection();
const range = selection.getRangeAt(0);
const cursorPosition = document.createElement("span");
cursorPosition.setAttribute("data-cursorposition", "");
range.insertNode(cursorPosition);
range.setStartAfter(cursorPosition);
range.setEndAfter(cursorPosition);
selection.removeAllRanges();
selection.addRange(range);
const charactersBehind = this.editor.innerHTML.split("</span>")[1].split("\n")
? this.editor.innerHTML.split("</span>")[1].split("\n")[0]
: this.editor.innerHTML.split("</span>")[1];
const spaceOutsideInsertedSpan = document.createTextNode("");
range.insertNode(spaceOutsideInsertedSpan);
range.setStartAfter(spaceOutsideInsertedSpan);
range.setEndAfter(spaceOutsideInsertedSpan);
selection.removeAllRanges();
selection.addRange(range);
this.editor.querySelector("[data-cursorposition]").outerHTML = this.editor.querySelector("[data-cursorposition]").innerHTML;
return charactersBehind;
}
colorCode() {
let editorHTML = this.editor.innerText.replace(/</g, "<").replace(/>/g, ">");
languages[this.language].colors.map((colorInformation) => {
const replacement = colorInformation.regex.replacement || "$1";
editorHTML = editorHTML.replace(new RegExp(colorInformation.regex.string, colorInformation.regex.flags), `<span class="${colorInformation.class}">${replacement}</span>`);
});
this.highlights.innerHTML = `${editorHTML}`;
}
handleAdviserBehavior(e) {
if (e.code.includes("Key") && !e.ctrlKey) {
this.showSuggestions({ askedForSuggestions: false });
}
else if (!this.openedAdviserByInteraction && e.code !== "ArrowDown" && e.code !== "ArrowUp") {
this.adviser.innerHTML = "";
this.isAdviserOpen = false;
}
}
handleEditorSpecificKeys(e) {
switch (e.key) {
case "{": {
const selection = window.getSelection();
const range = selection.getRangeAt(0);
range.insertNode(document.createTextNode("}"));
document.getSelection().modify("move", "backward", "character");
break;
}
case "[": {
const selection = window.getSelection();
const range = selection.getRangeAt(0);
range.insertNode(document.createTextNode("]"));
document.getSelection().modify("move", "backward", "character");
break;
}
case "(": {
document.execCommand("insertHTML", false, ")");
document.getSelection().modify("move", "backward", "character");
break;
}
case ")": {
if (!this.getCharactersBeforeCursor().endsWith(")"))
break;
e.preventDefault();
document.getSelection().modify("move", "forward", "character");
break;
}
case "`": {
document.execCommand("insertHTML", false, "`");
document.getSelection().modify("move", "backward", "character");
break;
}
case "'": {
document.execCommand("insertHTML", false, "'");
document.getSelection().modify("move", "backward", "character");
break;
}
case `"`: {
document.execCommand("insertHTML", false, `"`);
document.getSelection().modify("move", "backward", "character");
break;
}
case ">": {
if (!e.shiftKey || this.language !== "html")
return;
const selection = window.getSelection();
const range = selection.getRangeAt(0);
const tag = this.getCharactersBeforeCursor().match("\n") && !this.getCharactersBeforeCursor().endsWith("\n")
? this.getCharactersBeforeCursor().split("\n")[this.getCharactersBeforeCursor().split("\n").length - 1].match(/<([a-zA-Z0-9]*)/)[0].replace(/[ <>]/g, "")
: this.getCharactersBeforeCursor().match("\n")
? this.getCharactersBeforeCursor().split("\n")[this.getCharactersBeforeCursor().split("\n").length - 2].match(/<([a-zA-Z0-9]*)/)[0].replace(/[ <>]/g, "")
: this.getCharactersBeforeCursor().match(/<([a-zA-Z0-9]*)/)[0].replace(/[ <>]/g, "");
const closingTag = document.createTextNode(`</${tag}>`);
range.insertNode(closingTag);
range.setStartBefore(closingTag);
range.setEndBefore(closingTag);
selection.removeAllRanges();
selection.addRange(range);
document.getSelection().modify("move", "backward", "character");
document.getSelection().modify("move", "forward", "character");
break;
}
case "Tab": {
e.preventDefault();
const selection = document.getSelection();
const range = selection.getRangeAt(0);
if (e.shiftKey) {
const cursorPosition = document.createElement("span");
cursorPosition.setAttribute("data-cursorposition", "");
range.insertNode(cursorPosition);
range.setStartAfter(cursorPosition);
range.setEndAfter(cursorPosition);
selection.removeAllRanges();
selection.addRange(range);
this.editor.innerHTML = this.editor.innerHTML.replace(" <span data-cursorposition", "<span data-cursorposition");
range.setStartBefore(this.editor.querySelector("[data-cursorposition]"));
range.setEndBefore(this.editor.querySelector("[data-cursorposition]"));
selection.removeAllRanges();
selection.addRange(range);
const spaceOutsideInsertedSpan = document.createTextNode("");
range.insertNode(spaceOutsideInsertedSpan);
range.setStartAfter(spaceOutsideInsertedSpan);
range.setEndAfter(spaceOutsideInsertedSpan);
selection.removeAllRanges();
selection.addRange(range);
document.getSelection().modify("move", "backward", "character");
document.getSelection().modify("move", "forward", "character");
this.editor.querySelector("[data-cursorposition]").outerHTML = this.editor.querySelector("[data-cursorposition]").innerHTML;
return;
}
document.execCommand("insertHTML", false, this.tabCharacter);
break;
}
case " ": {
if (!e.ctrlKey || e.code !== "Space")
return;
this.showSuggestions();
break;
}
case "ArrowDown": {
this.currentTabOffset = 0;
break;
}
case "ArrowUp": {
this.currentTabOffset = 0;
break;
}
case "Enter": {
e.preventDefault();
const selection = document.getSelection();
const range = selection.getRangeAt(0);
let offset = this.tabCharacter.repeat(this.currentTabOffset);
if (this.getCharactersBeforeCursor().match(/(\(([a-zA-Z]*)|\(\((.*)) => {$/)) {
const charactersBehind = this.getCharactersBehindCursor();
document.execCommand("insertHTML", false, `\n<span data-spacebetween></span>\n${offset}});REPLACE-`);
this.editor.innerHTML = this.editor.innerHTML.replace(`REPLACE-${charactersBehind}`, "");
range.setStartAfter(this.editor.querySelector("[data-spacebetween]"));
range.setEndAfter(this.editor.querySelector("[data-spacebetween]"));
const spaceOutsideInsertedSpan = document.createTextNode(this.tabCharacter + offset);
this.editor.insertBefore(spaceOutsideInsertedSpan, this.editor.querySelector("[data-spacebetween]"));
range.setStartAfter(spaceOutsideInsertedSpan);
range.setEndAfter(spaceOutsideInsertedSpan);
selection.removeAllRanges();
selection.addRange(range);
document.getSelection().modify("move", "backward", "character");
document.getSelection().modify("move", "forward", "character");
this.editor.querySelector("[data-spacebetween]").outerHTML = this.editor.querySelector("[data-spacebetween]").innerHTML;
return;
}
if (this.getCharactersBeforeCursor().endsWith("{")
|| this.getCharactersBeforeCursor().endsWith("[")
|| this.getCharactersBeforeCursor().match(/<([a-zA-Z]*?)>$/)) {
const charactersBehind = this.getCharactersBehindCursor();
document.execCommand("insertHTML", false, `\n<span data-spacebetween></span>\n${offset}${charactersBehind}REPLACE-`);
this.editor.innerHTML = this.editor.innerHTML.replace(`REPLACE-${charactersBehind}`, "");
range.setStartAfter(this.editor.querySelector("[data-spacebetween]"));
range.setEndAfter(this.editor.querySelector("[data-spacebetween]"));
const spaceOutsideInsertedSpan = document.createTextNode(this.tabCharacter + offset);
this.editor.insertBefore(spaceOutsideInsertedSpan, this.editor.querySelector("[data-spacebetween]"));
range.setStartAfter(spaceOutsideInsertedSpan);
range.setEndAfter(spaceOutsideInsertedSpan);
selection.removeAllRanges();
selection.addRange(range);
document.getSelection().modify("move", "backward", "character");
document.getSelection().modify("move", "forward", "character");
this.editor.querySelector("[data-spacebetween]").outerHTML = this.editor.querySelector("[data-spacebetween]").innerHTML;
return;
}
document.execCommand("insertHTML", false, `\n${offset + "​"}`);
document.getSelection().modify("move", "backward", "character");
}
}
}
handleKeys(e) {
let suggestionKeyPressed = false;
switch (e.code) {
case "Enter": {
if (!this.isAdviserOpen)
break;
e.preventDefault();
this.insertSuggestion(this.adviser.querySelector("[data-active]"));
suggestionKeyPressed = true;
return;
}
case "Tab": {
if (!this.isAdviserOpen)
break;
e.preventDefault();
this.insertSuggestion(this.adviser.querySelector("[data-active]"));
suggestionKeyPressed = true;
return;
}
case "ArrowUp": {
if (!this.isAdviserOpen)
break;
e.preventDefault();
const currentActiveItem = Array.from(this.adviser.children).indexOf(this.adviser.querySelector("a[data-active]"));
if (!this.adviser.children[currentActiveItem - 1])
return;
this.adviser.querySelectorAll("a").forEach(element => element.removeAttribute("data-active"));
this.adviser.children[currentActiveItem - 1].setAttribute("data-active", "");
suggestionKeyPressed = true;
return;
}
case "ArrowDown": {
if (!this.isAdviserOpen)
break;
e.preventDefault();
const currentActiveItem = Array.from(this.adviser.children).indexOf(this.adviser.querySelector("a[data-active]"));
if (!this.adviser.children[currentActiveItem + 1])
return;
this.adviser.querySelectorAll("a").forEach(element => element.removeAttribute("data-active"));
this.adviser.children[currentActiveItem + 1].setAttribute("data-active", "");
suggestionKeyPressed = true;
return;
}
}
if (!suggestionKeyPressed && this.openedAdviserByInteraction) {
this.adviser.innerHTML = "";
this.isAdviserOpen = false;
}
if ((!suggestionKeyPressed && e.target.dataset.adviser) || e.target.hasAttribute("data-tortellini-editor"))
this.handleEditorSpecificKeys(e);
}
setOffset() {
if (!this.getCharactersBeforeCursor())
return;
const escapedCharactersBeforeCursor = this.getCharactersBeforeCursor({ escapeElementTags: true }).replace(/([()\[\]"'`])/g, "\\$1");
const offset = !this.getCharactersBeforeCursor().startsWith("\n") && !this.getCharactersBeforeCursor().match(" ") && this.editor.innerHTML.match(new RegExp(`\n(.*)${escapedCharactersBeforeCursor}`))
? this.editor.innerHTML.match(new RegExp(`\n(.*)${escapedCharactersBeforeCursor}`))[0].replace(/ /g, " ")
: this.getCharactersBeforeCursor().match("\n") && !this.getCharactersBeforeCursor().endsWith("\n")
? this.getCharactersBeforeCursor().split("\n")[this.getCharactersBeforeCursor().split("\n").length - 1].replace(/[a-zA-Z{}\[\]()](.*)/g, "")
: this.getCharactersBeforeCursor().match("\n")
? this.getCharactersBeforeCursor().split("\n")[this.getCharactersBeforeCursor().split("\n").length - 2].replace(/[a-zA-Z{}\[\]()](.*)/g, "")
: this.getCharactersBeforeCursor().replace(/[a-zA-Z{}\[\]()](.*)/g, "");
this.currentTabOffset = offset.match(/\s{4}/g) ? offset.match(/\s{4}/g).length : 0;
}
}
//# sourceMappingURL=index.js.map