@anywhichway/nerd-editor
Version:
A JavaScript rich text editor based on and with support for custom elements.
332 lines (329 loc) • 12 kB
JavaScript
const controls = {
file: {
type: "select",
innerHTML: [
'<option disabled selected default hidden>File</option>',
'<option value=open">Open</option>',
'<option value="preview">Preview</option>',
'<option value="save">Save</option>',
].join(""),
title: "File options",
onclick({path}) {
const select = path[0],
f = this.editor[select.value],
done = () => {
select.style.height = "";
select.removeEventListener("blur", done);
select.value = "File";
}
select.style.height = "0px";
select.addEventListener("blur", done);
if (typeof (f) === "function") {
f.call(this.editor);
done();
}
}
},
edit: {
type: "select",
innerHTML: [
'<option disabled selected default hidden>Edit</option>',
'<option value="undo">Undo</option>',
'<option value="redo">Redo</option>',
'<option value="find">Find</option>',
'<option value="replace">Replace</option>',
].join(""),
title: "Edit options",
onclick({path}) {
const select = path[0],
f = this.editor[select.value],
done = () => {
select.style.height = "";
select.removeEventListener("blur", done);
select.value = "Edit";
}
select.style.height = "0px";
select.addEventListener("blur", done);
if (typeof (f) === "function") {
f.call(this.editor);
done();
}
}
},
heading: {
type: "select",
innerHTML: [
"<option disabled selected default hidden>Heading</option>",
'<option>H1</option>',
'<option>H2</option>',
'<option>H3</option>',
'<option>H4</option>',
'<option>H5</option>',
'<option>H6</option>',
].join(""),
title: "Format Heading",
onclick({path}) {
const select = path[0],
level = parseInt(select.value[1]),
done = () => {
select.style.height = "";
select.removeEventListener("blur", done);
select.value = "Heading";
};
select.style.height = "0px";
select.addEventListener("blur", done);
if (!isNaN(level)) {
this.editor.wrapSelection(select.value);
done();
}
}
},
i: {
innerHTML: '<i>I</i>', title: "Italic", onclick() {
this.editor.wrapSelection('i')
}
},
b: {
innerHTML: '<b>B</b>', title: "Bold", onclick() {
this.editor.wrapSelection('b')
}
},
u: {
innerHTML: '<u>U</u>', title: "Underline", onclick() {
this.editor.wrapSelection('u')
}
},
del: {
innerHTML: '<del>Del</del>', title: "Deleted", onclick() {
this.editor.wrapSelection('del')
}
},
ins: {
innerHTML: '<ins>Ins</ins>',
title: "Inserted",
css: "ins { text-decoration-line: underline; text-decoration-style: wavy; }",
onclick() {
this.editor.wrapSelection('ins')
}
},
kbd: {
innerHTML: '<kbd>Key</kbd>',
icon: '<i class="fa-light fa-keyboard"></i>',
title: "Keyboard Key",
onclick() {
this.editor.wrapSelection('kbd')
}
},
aside: {
innerHTML: 'Aside',
icon: '<i class="fa-light fa-sidebar-flip"></i>',
title: "Aside",
onclick() {
this.editor.wrapSelection('aside');
}
},
details: {
innerHTML: 'Details',
icon: 'Details <i class="fa-solid fa-caret-down"></i>',
title: "Details",
onclick() {
this.editor.wrapSelection('details');
const {anchorNode} = window.getSelection(),
summary = document.createElement("summary"),
title = anchorNode.firstChild.textContent.substring(0,15) + " ...";
summary.innerHTML = title;
anchorNode.insertBefore(summary,anchorNode.firstChild);
}
},
q: {
innerHTML: '<q>Quote</q>', icon: '<i class="fa-solid fa-quotes"></i>', title: "Quote", onclick() {
this.editor.wrapSelection('q')
}
},
abbr: {
innerHTML: '<abbr>Abrv</abbr>',
title: "Abbreviation",
css: "abbr { font-style: italic; font-weight: 100;}",
onclick() {
this.editor.wrapSelection('abbr')
}
},
samp: {
innerHTML: '<samp>Sample</samp',
title: "Sample",
icon: '<i class="fa-solid fa-laptop-code"></i>',
onclick() {
this.editor.wrapSelection('samp')
}
},
blockquote: {
innerHTML: '<blockquote>Quote</blockquote>', icon: '<i class="fa-regular fa-block-quote"></i>', title: "Block Quote", onclick() {
this.editor.wrapSelection('blockquote')
}
},
code: {
innerHTML: '</>', title: "Code", onclick() {
this.editor.wrapSelection('code')
}
},
sup: {
innerHTML: '<span style="font-size:80%">x<sup>y</sup></span>', title: "Superscript", onclick() {
this.editor.wrapSelection('sup')
}
},
sub: {
innerHTML: '<span style="font-size:80%;vertical-align:top;">x<sub>y</sub></span>',
title: "Subscript",
onclick() {
this.editor.wrapSelection('sub')
}
},
var: {
innerHTML: '<var>Var</var>', title: "Variable", onclick() {
this.editor.wrapSelection('var')
}
},
function: {
innerHTML: '<i>f(x)</i>', title: "Inline Function",
onclick() {
const node = document.createElement('inline-function'),
expression = document.createElement("expression");
expression.innerHTML = window.getSelection().toString();
node.innerHTML = `
<script src="https://cdn.jsdelivr.net/npm/mathjs@11.1.0/lib/browser/math.min.js"></${"script"}>
<script>
</${"script"}>`;
node.children[1].innerHTML = `(value) => {
let result;
try {
result = Function("math","with(math) { return " + value + "}")(math);
} catch(e) {
try {
result = math.evaluate(value)
} catch(e) {
return e + "";
}
}
if(result && result.toString) {
return result.toString();
}
return result;
}`;
node.appendChild(expression);
this.editor.replaceSelection(node);
}
},
formula: {
innerHTML: 'Σ', title: "Math/Science Formula", onclick() {
this.editor.wrapSelection('math-science-formula')
}
},
datablock: {
innerHTML: 'Data',
title: "Data Block",
icon: '<i class="fa-light fa-database"></i>',
onclick() {
this.editor.wrapSelection('data-block',{asText:true});
}
},
music: {
innerHTML: 'Music',
title: "Sheet Music (ABC Notation)",
icon: '<i class="fa-regular fa-music"></i>',
onclick() {
this.editor.replaceSelection('sheet-music')
}
},
chart: {
innerHTML: 'Chart', title: "Chart", icon: '<i class="fa-regular fa-chart-pie"></i>', onclick() {
this.editor.wrapSelection('plotly-chart',{asText:true})
}
},
img: {
innerHTML: 'Img', title: "Image", icon: '<i class="fa-light fa-image"></i>', onclick() {
const img = this.editor.replaceSelection('img');
img.setAttribute("src", "test");
this.editor.edit(img)
}
},
video: {
innerHTML: 'Video', title: "Video File", icon: '<i class="fa-light fa-video"></i>', onclick() {
const video = this.editor.replaceSelection('video');
img.setAttribute("src", "test");
this.editor.edit(video)
}
},
audio: {
innerHTML: 'Audio', title: "Audio File", icon: '<i class="fa-light fa-headphones"></i>', onclick() {
const audio = this.editor.replaceSelection('audio');
img.setAttribute("src", "test");
this.editor.edit(audio)
}
},
link: {
innerHTML: '<a>Link</a>', title: "Link", icon: '<i class="fa-light fa-link-horizontal"></i>', onclick() {
const a = this.editor.replaceSelection('a');
a.setAttribute("href", "test");
this.editor.edit(a)
}
},
table: {
innerHTML: 'Table', title: "Table", icon: '<i class="fa-light fa-table"></i>',
onclick() {
const table = this.editor.createElement({tagName: 'table', innerHTML: '<tr><td>Test</td></tr>'});
this.editor.replaceSelection(table);
this.editor.edit(table);
}
},
ul: {
innerHTML: 'UL', title: "Unordered List", icon: '<i class="fa-light fa-list-ul"></i>',
onclick() {
this.editor.replaceSelection(this.editor.createElement({tagName: 'ul', innerHTML: '<li> </li>'}));
}
},
ol: {
innerHTML: 'OL', title: "Ordered List", icon: '<i class="fa-light fa-list-ol"></i>',
onclick() {
this.editor.replaceSelection(this.editor.createElement({tagName: 'ol', innerHTML: '<li> </li>'}));
}
},
unformat: {
innerHTML: 'Unformat', title: "Unformat", icon: '<i class="fa-regular fa-eraser"></i>', onclick() {
this.editor.unformatSelection()
}
},
"editor-dialog": {
innerHTML: 'Edit', title: "Edit element", icon: '<i class="fa-regular fa-pencil"></i>',
onclick() {
this.editor.editSelection()
}
},
delete: {
innerHTML: 'Delete', title: "Delete selection", icon: '<i class="fa-regular fa-trash"></i>',
onclick() {
this.editor.deleteSelection()
}
},
undo: {
innerHTML: 'Undo', title: "Undo", icon: '<i class="fa-regular fa-rotate-left"></i>', onclick() {
this.editor.undo()
}
},
redo: {
innerHTML: 'Redo', title: "Redo", icon: '<i class="fa-regular fa-rotate-right"></i>', onclick() {
this.editor.redo()
}
},
repl: {
innerHTML: "</<blink>_</blink>>",
title: "REPL",
css: "@keyframes condemned_blink_effect { 0% { visibility: hidden; } 50% { visibility: hidden; } 100% { visibility: visible; } } blink { animation: 2s linear infinite condemned_blink_effect; }",
onclick() {
this.editor.replaceSelection(this.editor.createElement({
tagName: 'repl-host',
attributes: {head: "", css: "", body: "", javascript: "", contenteditable: false}
}));
}
}
}
export {controls}