editor: implement heading insertion and level changes

This commit is contained in:
Lephe 2022-04-27 20:07:55 +01:00
parent d1893bb012
commit 64448f9416
Signed by: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
2 changed files with 100 additions and 21 deletions

View File

@ -1,12 +1,16 @@
/* Add callbacks on text formatting buttons */
function editor_insert_around(e) {
/* 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 = e.target || e.srcElement;
let node = event.target || event.srcElement;
while(node != document.body) {
if(node.tagName == "BUTTON" && !button)
button = node;
@ -18,8 +22,26 @@ function editor_insert_around(e) {
}
if(!button || !editor) return;
/* Find the textarea */
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;
@ -27,23 +49,80 @@ function editor_insert_around(e) {
const before = button.dataset.before || "";
const after = button.dataset.after || "";
ta.value = ta.value.substring(0, indexStart)
+ before
+ ta.value.substring(indexStart, indexEnd)
+ after
+ ta.value.substring(indexEnd);
let [start, end] = editor_replace_range(ta, indexStart, indexEnd,
before + ta.value.substring(indexStart, indexEnd) + after);
/* Restore selection */
if(indexStart != indexEnd) {
ta.selectionStart = indexStart;
ta.selectionEnd = indexEnd + before.length + after.length;
ta.selectionStart = start;
ta.selectionEnd = end;
}
else {
ta.selectionStart = indexStart + before.length;
ta.selectionEnd = ta.selectionStart;
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;
});
}
// Tab insert some spaces
// Ctrl+Enter send the form
ta = document.querySelector(".editor textarea");

View File

@ -3,22 +3,22 @@
<!-- Buttons for the text editor -->
<div class="btn-group">
<!-- Underline, Bold, Italic, Strikethrough -->
<button type="button" onclick="editor_insert_around(event)" data-before="__" data-after="__">
<button type="button" onclick="editor_insert_around(event)" data-before="__" data-after="__" title="__Souligné__">
<svg viewBox="0 0 24 24">
<path d="M12 17c3.31 0 6-2.69 6-6V3h-2.5v8c0 1.93-1.57 3.5-3.5 3.5S8.5 12.93 8.5 11V3H6v8c0 3.31 2.69 6 6 6zm-7 2v2h14v-2H5z"/>
</svg>
</button>
<button type="button" onclick="editor_insert_around(event)" data-before="**" data-after="**">
<button type="button" onclick="editor_insert_around(event)" data-before="**" data-after="**" title="**Gras**">
<svg viewBox="0 0 24 24">
<path d="M15.6 10.79c.97-.67 1.65-1.77 1.65-2.79 0-2.26-1.75-4-4-4H7v14h7.04c2.09 0 3.71-1.7 3.71-3.79 0-1.52-.86-2.82-2.15-3.42zM10 6.5h3c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5h-3v-3zm3.5 9H10v-3h3.5c.83 0 1.5.67 1.5 1.5s-.67 1.5-1.5 1.5z"/>
</svg>
</button>
<button type="button" onclick="editor_insert_around(event)" data-before="*" data-after="*">
<button type="button" onclick="editor_insert_around(event)" data-before="*" data-after="*" title="*Italique*">
<svg viewBox="0 0 24 24">
<path d="M10 4v3h2.21l-3.42 8H6v3h8v-3h-2.21l3.42-8H18V4z"/>
</svg>
</button>
<button type="button" onclick="editor_insert_around(event)" data-before="~~" data-after="~~">
<button type="button" onclick="editor_insert_around(event)" data-before="~~" data-after="~~" title="~~Barré~~">
<svg viewBox="0 0 24 24">
<path d="M6.85,7.08C6.85,4.37,9.45,3,12.24,3c1.64,0,3,0.49,3.9,1.28c0.77,0.65,1.46,1.73,1.46,3.24h-3.01 c0-0.31-0.05-0.59-0.15-0.85c-0.29-0.86-1.2-1.28-2.25-1.28c-1.86,0-2.34,1.02-2.34,1.7c0,0.48,0.25,0.88,0.74,1.21 C10.97,8.55,11.36,8.78,12,9H7.39C7.18,8.66,6.85,8.11,6.85,7.08z M21,12v-2H3v2h9.62c1.15,0.45,1.96,0.75,1.96,1.97 c0,1-0.81,1.67-2.28,1.67c-1.54,0-2.93-0.54-2.93-2.51H6.4c0,0.55,0.08,1.13,0.24,1.58c0.81,2.29,3.29,3.3,5.67,3.3 c2.27,0,5.3-0.89,5.3-4.05c0-0.3-0.01-1.16-0.48-1.94H21V12z"/>
</svg>
@ -26,27 +26,27 @@
<span class="separator"></span>
<!-- Headers/Titles -->
<button type="button" onclick="editor_btn_type()">
<button type="button" onclick="editor_set_title(event, 1, 0)" title="# Titre 1">
<svg viewBox="0 0 24 24">
<path d="M3,4H5V10H9V4H11V18H9V12H5V18H3V4M14,18V16H16V6.31L13.5,7.75V5.44L16,4H18V16H20V18H14Z" />
</svg>
</button>
<button type="button" onclick="editor_btn_type()">
<button type="button" onclick="editor_set_title(event, 2, 0)" title="## Titre 2">
<svg viewBox="0 0 24 24">
<path d="M3,4H5V10H9V4H11V18H9V12H5V18H3V4M21,18H15A2,2 0 0,1 13,16C13,15.47 13.2,15 13.54,14.64L18.41,9.41C18.78,9.05 19,8.55 19,8A2,2 0 0,0 17,6A2,2 0 0,0 15,8H13A4,4 0 0,1 17,4A4,4 0 0,1 21,8C21,9.1 20.55,10.1 19.83,10.83L15,16H21V18Z"/>
</svg>
</button>
<button type="button" onclick="editor_btn_type()">
<button type="button" onclick="editor_set_title(event, 3, 0)" title="### Titre 3">
<svg viewBox="0 0 24 24">
<path d="M3,4H5V10H9V4H11V18H9V12H5V18H3V4M15,4H19A2,2 0 0,1 21,6V16A2,2 0 0,1 19,18H15A2,2 0 0,1 13,16V15H15V16H19V12H15V10H19V6H15V7H13V6A2,2 0 0,1 15,4Z"/>
</svg>
</button>
<button type="button" onclick="editor_btn_type()">
<button type="button" onclick="editor_set_title(event, 0, -1)" title="Réduire le niveau de titre">
<svg viewBox="0 0 24 24">
<path d="M4,4H6V10H10V4H12V18H10V12H6V18H4V4M20.42,7.41L16.83,11L20.42,14.59L19,16L14,11L19,6L20.42,7.41Z"/>
</svg>
</button>
<button type="button" onclick="editor_btn_type()">
<button type="button" onclick="editor_set_title(event, 0, +1)" title="Augmenter le niveau de titre">
<svg viewBox="0 0 24 24">
<path d="M4,4H6V10H10V4H12V18H10V12H6V18H4V4M14.59,7.41L18.17,11L14.59,14.59L16,16L21,11L16,6L14.59,7.41Z"/>
</svg>