205 lines
7.9 KiB
JavaScript
205 lines
7.9 KiB
JavaScript
(function () {
|
|
var textarea = document.getElementById('editor');
|
|
if (!textarea) return;
|
|
|
|
var form = textarea.closest('form');
|
|
|
|
// --- DOM helpers ---
|
|
|
|
function wrap(before, after, placeholder) {
|
|
var start = textarea.selectionStart;
|
|
var end = textarea.selectionEnd;
|
|
var selected = textarea.value.slice(start, end) || placeholder;
|
|
var replacement = before + selected + after;
|
|
textarea.value = textarea.value.slice(0, start) + replacement + textarea.value.slice(end);
|
|
if (selected === placeholder) {
|
|
textarea.selectionStart = start + before.length;
|
|
textarea.selectionEnd = start + before.length + placeholder.length;
|
|
} else {
|
|
textarea.selectionStart = start + replacement.length;
|
|
textarea.selectionEnd = start + replacement.length;
|
|
}
|
|
textarea.focus();
|
|
}
|
|
|
|
function linePrefix(prefix) {
|
|
var start = textarea.selectionStart;
|
|
var lineStart = textarea.value.lastIndexOf('\n', start - 1) + 1;
|
|
textarea.value = textarea.value.slice(0, lineStart) + prefix + textarea.value.slice(lineStart);
|
|
textarea.selectionStart = textarea.selectionEnd = start + prefix.length;
|
|
textarea.focus();
|
|
}
|
|
|
|
function insertAtCursor(s) {
|
|
var start = textarea.selectionStart;
|
|
var end = textarea.selectionEnd;
|
|
textarea.value = textarea.value.slice(0, start) + s + textarea.value.slice(end);
|
|
textarea.selectionStart = textarea.selectionEnd = start + s.length;
|
|
textarea.dispatchEvent(new Event('input'));
|
|
textarea.focus();
|
|
}
|
|
|
|
function applyResult(result) {
|
|
textarea.value = result.text;
|
|
textarea.selectionStart = textarea.selectionEnd = result.cursor;
|
|
textarea.dispatchEvent(new Event('input'));
|
|
textarea.focus();
|
|
}
|
|
|
|
function applyTableOp(fn, arg) {
|
|
var result = arg !== undefined
|
|
? fn(textarea.value, textarea.selectionStart, arg)
|
|
: fn(textarea.value, textarea.selectionStart);
|
|
if (result) applyResult(result);
|
|
}
|
|
|
|
// --- Actions ---
|
|
|
|
var T = EditorTables;
|
|
var L = EditorLists;
|
|
var D = EditorDates;
|
|
|
|
var actions = {
|
|
save: function () { form.submit(); },
|
|
bold: function () { wrap('**', '**', 'bold text'); },
|
|
italic: function () { wrap('*', '*', 'italic text'); },
|
|
h1: function () { linePrefix('# '); },
|
|
h2: function () { linePrefix('## '); },
|
|
h3: function () { linePrefix('### '); },
|
|
code: function () { wrap('`', '`', 'code'); },
|
|
codeblock: function () { wrap('```\n', '\n```', 'code'); },
|
|
quote: function () { linePrefix('> '); },
|
|
link: function () { wrap('[', '](url)', 'link text'); },
|
|
ul: function () { linePrefix('- '); },
|
|
ol: function () { linePrefix('1. '); },
|
|
hr: function () { wrap('\n\n---\n\n', '', ''); },
|
|
fmttable: function () { applyTableOp(T.formatTableText); },
|
|
tblalignleft: function () { applyTableOp(T.setColumnAlignment, 'left'); },
|
|
tblaligncenter: function () { applyTableOp(T.setColumnAlignment, 'center'); },
|
|
tblalignright: function () { applyTableOp(T.setColumnAlignment, 'right'); },
|
|
tblinsertcol: function () { applyTableOp(T.insertColumn); },
|
|
tbldeletecol: function () { applyTableOp(T.deleteColumn); },
|
|
tblinsertrow: function () { applyTableOp(T.insertRow); },
|
|
tbldeleterow: function () { applyTableOp(T.deleteRow); },
|
|
dateiso: function () { insertAtCursor(D.isoDate()); },
|
|
datelong: function () { insertAtCursor(D.longDate()); },
|
|
};
|
|
|
|
// --- Keyboard shortcut registration ---
|
|
|
|
var keyMap = {};
|
|
document.querySelectorAll('[data-action]').forEach(function (btn) {
|
|
btn.addEventListener('click', function () {
|
|
var action = actions[btn.dataset.action];
|
|
if (action) action();
|
|
});
|
|
if (btn.dataset.key) {
|
|
keyMap[btn.dataset.key] = actions[btn.dataset.action];
|
|
}
|
|
});
|
|
|
|
keyMap['T'] = actions.fmttable;
|
|
keyMap['D'] = actions.dateiso;
|
|
keyMap['W'] = actions.datelong;
|
|
|
|
document.addEventListener('keydown', function (e) {
|
|
if (!e.altKey || !e.shiftKey) return;
|
|
var action = keyMap[e.key];
|
|
if (action) {
|
|
e.preventDefault();
|
|
action();
|
|
}
|
|
});
|
|
|
|
// --- Textarea key handling ---
|
|
|
|
textarea.addEventListener('keydown', function (e) {
|
|
if (e.key === 'Delete' && e.shiftKey) {
|
|
var result = T.deleteRow(textarea.value, textarea.selectionStart)
|
|
|| L.deleteOrderedLine(textarea.value, textarea.selectionStart);
|
|
if (!result) return;
|
|
e.preventDefault();
|
|
applyResult(result);
|
|
return;
|
|
}
|
|
if (e.key === 'Enter' && e.shiftKey) {
|
|
var result = T.insertRowBelow(textarea.value, textarea.selectionStart);
|
|
if (!result) return;
|
|
e.preventDefault();
|
|
applyResult(result);
|
|
return;
|
|
}
|
|
if (e.key !== 'Enter') return;
|
|
var result = L.handleEnterKey(textarea.value, textarea.selectionStart);
|
|
if (!result) return;
|
|
e.preventDefault();
|
|
applyResult(result);
|
|
});
|
|
|
|
// --- Dropdown helper ---
|
|
|
|
var openMenus = [];
|
|
|
|
function makeDropdown(triggerBtn, items) {
|
|
var menu = document.createElement('div');
|
|
menu.className = 'toolbar-dropdown-menu';
|
|
items.forEach(function (item) {
|
|
var btn = document.createElement('button');
|
|
btn.type = 'button';
|
|
btn.className = 'btn btn-tool toolbar-dropdown-item';
|
|
btn.textContent = item.label;
|
|
btn.addEventListener('mousedown', function (e) {
|
|
e.preventDefault();
|
|
actions[item.action]();
|
|
menu.classList.remove('is-open');
|
|
});
|
|
menu.appendChild(btn);
|
|
});
|
|
triggerBtn.appendChild(menu);
|
|
openMenus.push(menu);
|
|
|
|
triggerBtn.addEventListener('click', function (e) {
|
|
if (e.target !== triggerBtn) return;
|
|
var wasOpen = menu.classList.contains('is-open');
|
|
openMenus.forEach(function (m) { m.classList.remove('is-open'); });
|
|
if (!wasOpen) menu.classList.add('is-open');
|
|
});
|
|
}
|
|
|
|
document.addEventListener('click', function (e) {
|
|
var insideAny = openMenus.some(function (m) {
|
|
return m.parentElement && m.parentElement.contains(e.target);
|
|
});
|
|
if (!insideAny) openMenus.forEach(function (m) { m.classList.remove('is-open'); });
|
|
});
|
|
document.addEventListener('keydown', function (e) {
|
|
if (e.key === 'Escape') openMenus.forEach(function (m) { m.classList.remove('is-open'); });
|
|
});
|
|
|
|
// --- Table dropdown ---
|
|
|
|
var tblDropBtn = document.querySelector('[data-action="tbldrop"]');
|
|
if (tblDropBtn) {
|
|
makeDropdown(tblDropBtn, [
|
|
{ label: 'Format table', action: 'fmttable' },
|
|
{ label: 'Align left', action: 'tblalignleft' },
|
|
{ label: 'Align center', action: 'tblaligncenter' },
|
|
{ label: 'Align right', action: 'tblalignright' },
|
|
{ label: 'Insert column', action: 'tblinsertcol' },
|
|
{ label: 'Delete column', action: 'tbldeletecol' },
|
|
{ label: 'Insert row', action: 'tblinsertrow' },
|
|
{ label: 'Delete row', action: 'tbldeleterow' },
|
|
]);
|
|
}
|
|
|
|
// --- Date dropdown ---
|
|
|
|
var dateDropBtn = document.querySelector('[data-action="datedrop"]');
|
|
if (dateDropBtn) {
|
|
makeDropdown(dateDropBtn, [
|
|
{ label: 'YYYY-MM-DD', action: 'dateiso' },
|
|
{ label: 'DE Long', action: 'datelong' },
|
|
]);
|
|
}
|
|
})();
|