190 lines
5.4 KiB
JavaScript
190 lines
5.4 KiB
JavaScript
/* Add callbacks on text formatting buttons */
|
|
|
|
/* Locate the editor associated to an edition event.
|
|
event: Global event emitted by one of the editor buttons
|
|
Returns [the div.editor, the button, the textarea] */
|
|
function editor_event_source(event)
|
|
{
|
|
let button = undefined;
|
|
let editor = undefined;
|
|
|
|
/* Grab the button and the parent editor block. The onclick event itself
|
|
usually reports the SVG in the button as the source */
|
|
let node = event.target || event.srcElement;
|
|
while(node != document.body) {
|
|
if(node.tagName == "BUTTON" && !button) {
|
|
button = node;
|
|
}
|
|
if(node.classList.contains("editor") && !editor) {
|
|
editor = node;
|
|
break;
|
|
}
|
|
node = node.parentNode;
|
|
}
|
|
if(!button || !editor) return;
|
|
|
|
const ta = editor.querySelector("textarea");
|
|
return [editor, button, ta];
|
|
}
|
|
|
|
/* Replace the range [start:end) with the new contents, and returns the new
|
|
interval [start:end) (ie. the range where the contents are now located). */
|
|
function editor_replace_range(textarea, start, end, contents)
|
|
{
|
|
ta.value = ta.value.substring(0, start)
|
|
+ contents
|
|
+ ta.value.substring(end);
|
|
|
|
return [start, start + contents.length];
|
|
}
|
|
|
|
/* Event handler that inserts the button's data-before and data-after
|
|
attributes around the selection. */
|
|
function editor_insert_around(event)
|
|
{
|
|
const [editor, button, ta] = editor_event_source(event);
|
|
ta.focus();
|
|
let indexStart = ta.selectionStart;
|
|
let indexEnd = ta.selectionEnd;
|
|
|
|
const before = button.dataset.before || "";
|
|
const after = button.dataset.after || "";
|
|
|
|
let [start, end] = editor_replace_range(ta, indexStart, indexEnd,
|
|
before + ta.value.substring(indexStart, indexEnd) + after);
|
|
|
|
/* Restore selection */
|
|
if(indexStart != indexEnd) {
|
|
ta.selectionStart = start;
|
|
ta.selectionEnd = end;
|
|
}
|
|
else {
|
|
ta.selectionStart = ta.selectionEnd =start + before.length;
|
|
}
|
|
}
|
|
|
|
/* Event handler that modifies each line within the selection through a
|
|
generic function. */
|
|
function editor_act_on_lines(event, fn)
|
|
{
|
|
const [editor, button, ta] = editor_event_source(event);
|
|
ta.focus();
|
|
let indexStart = ta.selectionStart;
|
|
let indexEnd = ta.selectionEnd;
|
|
|
|
let firstLineIndex = ta.value.substring(0, indexStart).lastIndexOf('\n');
|
|
if(firstLineIndex < 0)
|
|
firstLineIndex = 0;
|
|
else
|
|
firstLineIndex += 1;
|
|
|
|
let lastLineIndex = ta.value.substring(indexEnd).indexOf('\n');
|
|
if(lastLineIndex < 0)
|
|
lastLineIndex = ta.value.length;
|
|
else
|
|
lastLineIndex += indexEnd;
|
|
|
|
let lines = ta.value.substring(firstLineIndex, lastLineIndex).split('\n');
|
|
|
|
for(let i = 0; i < lines.length; i++)
|
|
lines[i] = fn(lines[i]);
|
|
|
|
let [start, end] = editor_replace_range(ta, firstLineIndex, lastLineIndex,
|
|
lines.join('\n'));
|
|
|
|
ta.selectionStart = start;
|
|
ta.selectionEnd = end;
|
|
}
|
|
|
|
function editor_set_title(event, level, diff)
|
|
{
|
|
editor_act_on_lines(event, function(line) {
|
|
/* Strip all the initial # (and count them) */
|
|
let count = 0;
|
|
while(count < line.length && line[count] == '#') count++;
|
|
|
|
let contents_index = count;
|
|
if(count < line.length && line[count] == ' ') contents_index++;
|
|
let contents = line.slice(contents_index);
|
|
|
|
if(level > 0) {
|
|
/* Remove the title if the corresponding level is re-requested */
|
|
if(count == level)
|
|
return contents;
|
|
/* Otherwise, add it */
|
|
else
|
|
return '#'.repeat(level) + ' ' + contents;
|
|
}
|
|
else if(count > 0) {
|
|
/* Apply the difference */
|
|
let new_level = Math.max(1, Math.min(6, count + diff));
|
|
return '#'.repeat(new_level) + ' ' + contents;
|
|
}
|
|
return line;
|
|
});
|
|
}
|
|
|
|
previewTimeout = null;
|
|
ta = document.querySelector(".editor textarea");
|
|
ta.addEventListener('keydown', function(e) {
|
|
// Tab insert some spaces
|
|
// Ctrl+Enter send the form
|
|
let keyCode = e.keyCode || e.which;
|
|
if (keyCode == 9) {
|
|
// TODO Add one tab to selected text without replacing it
|
|
e.preventDefault();
|
|
|
|
let start = e.target.selectionStart;
|
|
let end = e.target.selectionEnd;
|
|
// set textarea value to: text before caret + tab + text after caret
|
|
e.target.value = e.target.value.substring(0, start) + "\t" + e.target.value.substring(end);
|
|
e.target.selectionEnd = start + 1;
|
|
}
|
|
if (e.ctrlKey && keyCode == 13) {
|
|
let t = e.target;
|
|
while(! (t instanceof HTMLFormElement)) {
|
|
t = t.parentNode;
|
|
}
|
|
try {
|
|
t.submit();
|
|
} catch(exception) {
|
|
t.submit.click();
|
|
}
|
|
}
|
|
|
|
// Set a timeout for refreshing the preview
|
|
if (previewTimeout != null) {
|
|
clearTimeout(previewTimeout);
|
|
}
|
|
previewTimeout = setTimeout(preview, 1000);
|
|
});
|
|
|
|
|
|
function preview() {
|
|
const previewArea = document.querySelector("#editor_content_preview");
|
|
|
|
const textarea = document.querySelector(".editor textarea");
|
|
const payload = {text: ta.value};
|
|
|
|
const headers = new Headers();
|
|
headers.append("Content-Type", "application/json");
|
|
|
|
const params = {
|
|
method: "POST",
|
|
body: JSON.stringify(payload),
|
|
headers
|
|
};
|
|
|
|
console.log(payload);
|
|
|
|
fetch("/api/markdown", params).then(
|
|
(response) => {
|
|
response.text().then(
|
|
(text) => {
|
|
previewArea.innerHTML = text;
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|