189 lines
5.5 KiB
JavaScript
189 lines
5.5 KiB
JavaScript
import cledit from '../editor/cledit';
|
|
import editorSvc from '../editorSvc';
|
|
import store from '../../store';
|
|
|
|
const { Keystroke } = cledit;
|
|
const indentRegexp = /^ {0,3}>[ ]*|^[ \t]*[*+-][ \t](?:\[[ xX]\][ \t])?|^([ \t]*)\d+\.[ \t](?:\[[ xX]\][ \t])?|^\s+/;
|
|
let clearNewline;
|
|
let lastSelection;
|
|
|
|
function fixNumberedList(state, indent) {
|
|
if (state.selection
|
|
|| indent === undefined
|
|
|| !store.getters['data/computedSettings'].editor.listAutoNumber
|
|
) {
|
|
return;
|
|
}
|
|
const spaceIndent = indent.replace(/\t/g, ' ');
|
|
const indentRegex = new RegExp(`^[ \\s]*$|^${spaceIndent}(\\d+\\.[ \\t])?(( )?.*)$`);
|
|
|
|
function getHits(lines) {
|
|
let hits = [];
|
|
let pendingHits = [];
|
|
|
|
function flush() {
|
|
if (!pendingHits.hasHit && pendingHits.hasNoIndent) {
|
|
return false;
|
|
}
|
|
hits = hits.concat(pendingHits);
|
|
pendingHits = [];
|
|
return true;
|
|
}
|
|
|
|
lines.some((line) => {
|
|
const match = line.replace(
|
|
/^[ \t]*/,
|
|
wholeMatch => wholeMatch.replace(/\t/g, ' '),
|
|
).match(indentRegex);
|
|
if (!match || line.match(/^#+ /)) { // Line not empty, not indented, or title
|
|
flush();
|
|
return true;
|
|
}
|
|
pendingHits.push({
|
|
line,
|
|
match,
|
|
});
|
|
if (match[2] !== undefined) {
|
|
if (match[1]) {
|
|
pendingHits.hasHit = true;
|
|
} else if (!match[3]) {
|
|
pendingHits.hasNoIndent = true;
|
|
}
|
|
} else if (!flush()) {
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
return hits;
|
|
}
|
|
|
|
function formatHits(hits) {
|
|
let num;
|
|
return hits.map((hit) => {
|
|
if (hit.match[1]) {
|
|
if (!num) {
|
|
num = parseInt(hit.match[1], 10);
|
|
}
|
|
const result = indent + num + hit.match[1].slice(-2) + hit.match[2];
|
|
num += 1;
|
|
return result;
|
|
}
|
|
return hit.line;
|
|
});
|
|
}
|
|
|
|
const before = state.before.split('\n');
|
|
before.unshift(''); // Add an extra line (fixes #184)
|
|
const after = state.after.split('\n');
|
|
let currentLine = before.pop() || '';
|
|
const currentPos = currentLine.length;
|
|
currentLine += after.shift() || '';
|
|
let lines = before.concat(currentLine).concat(after);
|
|
let idx = before.length - getHits(before.slice().reverse()).length; // Prevents starting from 0
|
|
while (idx <= before.length + 1) {
|
|
const hits = formatHits(getHits(lines.slice(idx)));
|
|
if (!hits.length) {
|
|
idx += 1;
|
|
} else {
|
|
lines = lines.slice(0, idx).concat(hits).concat(lines.slice(idx + hits.length));
|
|
idx += hits.length;
|
|
}
|
|
}
|
|
currentLine = lines[before.length];
|
|
state.before = lines.slice(1, before.length); // As we've added an extra line
|
|
state.before.push(currentLine.slice(0, currentPos));
|
|
state.before = state.before.join('\n');
|
|
state.after = [currentLine.slice(currentPos)].concat(lines.slice(before.length + 1));
|
|
state.after = state.after.join('\n');
|
|
}
|
|
|
|
function enterKeyHandler(evt, state) {
|
|
if (evt.which !== 13) {
|
|
// Not enter
|
|
clearNewline = false;
|
|
return false;
|
|
}
|
|
|
|
evt.preventDefault();
|
|
|
|
// Get the last line before the selection
|
|
const lastLf = state.before.lastIndexOf('\n') + 1;
|
|
const lastLine = state.before.slice(lastLf);
|
|
// See if the line is indented
|
|
const indentMatch = lastLine.match(indentRegexp) || [''];
|
|
if (clearNewline && !state.selection && state.before.length === lastSelection) {
|
|
state.before = state.before.substring(0, lastLf);
|
|
state.selection = '';
|
|
clearNewline = false;
|
|
fixNumberedList(state, indentMatch[1]);
|
|
return true;
|
|
}
|
|
clearNewline = false;
|
|
const indent = indentMatch[0];
|
|
if (indent.length) {
|
|
clearNewline = true;
|
|
}
|
|
|
|
editorSvc.clEditor.undoMgr.setCurrentMode('single');
|
|
|
|
state.before += `\n${indent}`;
|
|
state.selection = '';
|
|
lastSelection = state.before.length;
|
|
fixNumberedList(state, indentMatch[1]);
|
|
return true;
|
|
}
|
|
|
|
function tabKeyHandler(evt, state) {
|
|
if (evt.which !== 9 || evt.metaKey || evt.ctrlKey) {
|
|
// Not tab
|
|
return false;
|
|
}
|
|
|
|
const strSplice = (str, i, remove, add) =>
|
|
str.slice(0, i) + (add || '') + str.slice(i + (+remove || 0));
|
|
|
|
evt.preventDefault();
|
|
const isInverse = evt.shiftKey;
|
|
const lastLf = state.before.lastIndexOf('\n') + 1;
|
|
const lastLine = state.before.slice(lastLf);
|
|
const currentLine = lastLine + state.selection + state.after;
|
|
const indentMatch = currentLine.match(indentRegexp);
|
|
if (isInverse) {
|
|
const previousChar = state.before.slice(-1);
|
|
if (/\s/.test(state.before.charAt(lastLf))) {
|
|
state.before = strSplice(state.before, lastLf, 1);
|
|
if (indentMatch) {
|
|
fixNumberedList(state, indentMatch[1]);
|
|
if (indentMatch[1]) {
|
|
fixNumberedList(state, indentMatch[1].slice(1));
|
|
}
|
|
}
|
|
}
|
|
const selection = previousChar + state.selection;
|
|
state.selection = selection.replace(/\n[ \t]/gm, '\n');
|
|
if (previousChar) {
|
|
state.selection = state.selection.slice(1);
|
|
}
|
|
} else if (
|
|
// If selection is not empty
|
|
state.selection
|
|
// Or we are in an indented paragraph and the cursor is over the indentation characters
|
|
|| (indentMatch && indentMatch[0].length >= lastLine.length)
|
|
) {
|
|
state.before = strSplice(state.before, lastLf, 0, '\t');
|
|
state.selection = state.selection.replace(/\n(?=.)/g, '\n\t');
|
|
if (indentMatch) {
|
|
fixNumberedList(state, indentMatch[1]);
|
|
fixNumberedList(state, `\t${indentMatch[1]}`);
|
|
}
|
|
} else {
|
|
state.before += '\t';
|
|
}
|
|
return true;
|
|
}
|
|
|
|
editorSvc.$on('inited', () => {
|
|
editorSvc.clEditor.addKeystroke(new Keystroke(enterKeyHandler, 50));
|
|
editorSvc.clEditor.addKeystroke(new Keystroke(tabKeyHandler, 50));
|
|
});
|