mirror of
https://github.com/flarum/core.git
synced 2025-05-08 16:35:26 +02:00
Sync with v2.1 of markdown-toolbar-element
This commit is contained in:
parent
fb82afa97f
commit
080442d085
@ -16,6 +16,7 @@ interface StyleArgs {
|
|||||||
scanFor: string;
|
scanFor: string;
|
||||||
surroundWithNewlines: boolean;
|
surroundWithNewlines: boolean;
|
||||||
orderedList: boolean;
|
orderedList: boolean;
|
||||||
|
unorderedList: boolean;
|
||||||
trimFirst: boolean;
|
trimFirst: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,6 +31,7 @@ const defaults: StyleArgs = {
|
|||||||
scanFor: '',
|
scanFor: '',
|
||||||
surroundWithNewlines: false,
|
surroundWithNewlines: false,
|
||||||
orderedList: false,
|
orderedList: false,
|
||||||
|
unorderedList: false,
|
||||||
trimFirst: false,
|
trimFirst: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -41,8 +43,8 @@ export default function styleSelectedText(textarea: HTMLTextAreaElement, styleAr
|
|||||||
const text = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
|
const text = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
|
||||||
|
|
||||||
let result;
|
let result;
|
||||||
if (styleArgs.orderedList) {
|
if (styleArgs.orderedList || styleArgs.unorderedList) {
|
||||||
result = orderedList(textarea);
|
result = listStyle(textarea, styleArgs);
|
||||||
} else if (styleArgs.multiline && isMultipleLines(text)) {
|
} else if (styleArgs.multiline && isMultipleLines(text)) {
|
||||||
result = multilineStyle(textarea, styleArgs);
|
result = multilineStyle(textarea, styleArgs);
|
||||||
} else {
|
} else {
|
||||||
@ -77,6 +79,21 @@ function wordSelectionEnd(text: string, i: number, multiline: boolean): number {
|
|||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function expandSelectionToLine(textarea: HTMLTextAreaElement) {
|
||||||
|
const lines = textarea.value.split('\n');
|
||||||
|
let counter = 0;
|
||||||
|
for (let index = 0; index < lines.length; index++) {
|
||||||
|
const lineLength = lines[index].length + 1;
|
||||||
|
if (textarea.selectionStart >= counter && textarea.selectionStart < counter + lineLength) {
|
||||||
|
textarea.selectionStart = counter;
|
||||||
|
}
|
||||||
|
if (textarea.selectionEnd >= counter && textarea.selectionEnd < counter + lineLength) {
|
||||||
|
textarea.selectionEnd = counter + lineLength - 1;
|
||||||
|
}
|
||||||
|
counter += lineLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function expandSelectedText(textarea: HTMLTextAreaElement, prefixToUse: string, suffixToUse: string, multiline = false): string {
|
function expandSelectedText(textarea: HTMLTextAreaElement, prefixToUse: string, suffixToUse: string, multiline = false): string {
|
||||||
if (textarea.selectionStart === textarea.selectionEnd) {
|
if (textarea.selectionStart === textarea.selectionEnd) {
|
||||||
textarea.selectionStart = wordSelectionStart(textarea.value, textarea.selectionStart);
|
textarea.selectionStart = wordSelectionStart(textarea.value, textarea.selectionStart);
|
||||||
@ -140,7 +157,9 @@ function blockStyle(textarea: HTMLTextAreaElement, arg: StyleArgs): SelectionRan
|
|||||||
|
|
||||||
let selectedText = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
|
let selectedText = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
|
||||||
let prefixToUse = isMultipleLines(selectedText) && blockPrefix.length > 0 ? `${blockPrefix}\n` : prefix;
|
let prefixToUse = isMultipleLines(selectedText) && blockPrefix.length > 0 ? `${blockPrefix}\n` : prefix;
|
||||||
|
// CHANGED
|
||||||
let suffixToUse = isMultipleLines(selectedText) && blockSuffix.length > 0 ? `\n${blockSuffix}` : prefixToUse === prefix ? suffix : '';
|
let suffixToUse = isMultipleLines(selectedText) && blockSuffix.length > 0 ? `\n${blockSuffix}` : prefixToUse === prefix ? suffix : '';
|
||||||
|
// END CHANGED
|
||||||
|
|
||||||
if (prefixSpace) {
|
if (prefixSpace) {
|
||||||
const beforeSelection = textarea.value[textarea.selectionStart - 1];
|
const beforeSelection = textarea.value[textarea.selectionStart - 1];
|
||||||
@ -203,16 +222,20 @@ function multilineStyle(textarea: HTMLTextAreaElement, arg: StyleArgs) {
|
|||||||
let selectionStart = textarea.selectionStart;
|
let selectionStart = textarea.selectionStart;
|
||||||
let selectionEnd = textarea.selectionEnd;
|
let selectionEnd = textarea.selectionEnd;
|
||||||
const lines = text.split('\n');
|
const lines = text.split('\n');
|
||||||
|
// CHANGED
|
||||||
let prefixToUse = blockPrefix.length > 0 ? blockPrefix : prefix;
|
let prefixToUse = blockPrefix.length > 0 ? blockPrefix : prefix;
|
||||||
let suffixToUse = blockSuffix.length > 0 ? blockSuffix : prefixToUse == prefix ? suffix : '';
|
let suffixToUse = blockSuffix.length > 0 ? blockSuffix : prefixToUse == prefix ? suffix : '';
|
||||||
const undoStyle = lines.every((line) => line.startsWith(prefixToUse) && line.endsWith(suffixToUse));
|
const undoStyle = lines.every((line) => line.startsWith(prefixToUse) && line.endsWith(suffixToUse));
|
||||||
|
// END CHANGED
|
||||||
|
|
||||||
if (undoStyle) {
|
if (undoStyle) {
|
||||||
text = lines.map((line) => line.slice(prefixToUse.length, line.length - suffixToUse.length)).join('\n');
|
text = lines.map((line) => line.slice(prefixToUse.length, line.length - suffixToUse.length)).join('\n');
|
||||||
selectionEnd = selectionStart + text.length;
|
selectionEnd = selectionStart + text.length;
|
||||||
} else {
|
} else {
|
||||||
|
// CHANGED
|
||||||
text = lines.map((line) => prefixToUse + line + suffixToUse).join('\n');
|
text = lines.map((line) => prefixToUse + line + suffixToUse).join('\n');
|
||||||
if (surroundWithNewlines || suffixToUse === '') {
|
if (surroundWithNewlines || suffixToUse === '') {
|
||||||
|
// END CHANGED
|
||||||
const { newlinesToAppend, newlinesToPrepend } = newlinesToSurroundSelectedText(textarea);
|
const { newlinesToAppend, newlinesToPrepend } = newlinesToSurroundSelectedText(textarea);
|
||||||
selectionStart += newlinesToAppend.length;
|
selectionStart += newlinesToAppend.length;
|
||||||
selectionEnd = selectionStart + text.length;
|
selectionEnd = selectionStart + text.length;
|
||||||
@ -223,54 +246,116 @@ function multilineStyle(textarea: HTMLTextAreaElement, arg: StyleArgs) {
|
|||||||
return { text, selectionStart, selectionEnd };
|
return { text, selectionStart, selectionEnd };
|
||||||
}
|
}
|
||||||
|
|
||||||
function orderedList(textarea: HTMLTextAreaElement): SelectionRange {
|
interface UndoResult {
|
||||||
|
text: string;
|
||||||
|
processed: boolean;
|
||||||
|
}
|
||||||
|
function undoOrderedListStyle(text: string): UndoResult {
|
||||||
|
const lines = text.split('\n');
|
||||||
const orderedListRegex = /^\d+\.\s+/;
|
const orderedListRegex = /^\d+\.\s+/;
|
||||||
const noInitialSelection = textarea.selectionStart === textarea.selectionEnd;
|
const shouldUndoOrderedList = lines.every((line) => orderedListRegex.test(line));
|
||||||
let selectionEnd;
|
let result = lines;
|
||||||
let selectionStart;
|
if (shouldUndoOrderedList) {
|
||||||
let text = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
|
result = lines.map((line) => line.replace(orderedListRegex, ''));
|
||||||
let textToUnstyle = text;
|
|
||||||
let lines = text.split('\n');
|
|
||||||
let startOfLine, endOfLine;
|
|
||||||
if (noInitialSelection) {
|
|
||||||
const linesBefore = textarea.value.slice(0, textarea.selectionStart).split(/\n/);
|
|
||||||
startOfLine = textarea.selectionStart - linesBefore[linesBefore.length - 1].length;
|
|
||||||
endOfLine = wordSelectionEnd(textarea.value, textarea.selectionStart, true);
|
|
||||||
textToUnstyle = textarea.value.slice(startOfLine, endOfLine);
|
|
||||||
}
|
}
|
||||||
const linesToUnstyle = textToUnstyle.split('\n');
|
|
||||||
const undoStyling = linesToUnstyle.every((line) => orderedListRegex.test(line));
|
|
||||||
|
|
||||||
if (undoStyling) {
|
return {
|
||||||
lines = linesToUnstyle.map((line) => line.replace(orderedListRegex, ''));
|
text: result.join('\n'),
|
||||||
text = lines.join('\n');
|
processed: shouldUndoOrderedList,
|
||||||
if (noInitialSelection && startOfLine && endOfLine) {
|
};
|
||||||
const lengthDiff = linesToUnstyle[0].length - lines[0].length;
|
}
|
||||||
selectionStart = selectionEnd = textarea.selectionStart - lengthDiff;
|
|
||||||
textarea.selectionStart = startOfLine;
|
function undoUnorderedListStyle(text: string): UndoResult {
|
||||||
textarea.selectionEnd = endOfLine;
|
const lines = text.split('\n');
|
||||||
|
const unorderedListPrefix = '- ';
|
||||||
|
const shouldUndoUnorderedList = lines.every((line) => line.startsWith(unorderedListPrefix));
|
||||||
|
let result = lines;
|
||||||
|
if (shouldUndoUnorderedList) {
|
||||||
|
result = lines.map((line) => line.slice(unorderedListPrefix.length, line.length));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
text: result.join('\n'),
|
||||||
|
processed: shouldUndoUnorderedList,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function makePrefix(index: number, unorderedList: boolean): string {
|
||||||
|
if (unorderedList) {
|
||||||
|
return '- ';
|
||||||
} else {
|
} else {
|
||||||
lines = numberedLines(lines);
|
return `${index + 1}. `;
|
||||||
text = lines.join('\n');
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearExistingListStyle(style: StyleArgs, selectedText: string): [UndoResult, UndoResult, string] {
|
||||||
|
let undoResultOpositeList: UndoResult;
|
||||||
|
let undoResult: UndoResult;
|
||||||
|
let pristineText;
|
||||||
|
if (style.orderedList) {
|
||||||
|
undoResult = undoOrderedListStyle(selectedText);
|
||||||
|
undoResultOpositeList = undoUnorderedListStyle(undoResult.text);
|
||||||
|
pristineText = undoResultOpositeList.text;
|
||||||
|
} else {
|
||||||
|
undoResult = undoUnorderedListStyle(selectedText);
|
||||||
|
undoResultOpositeList = undoOrderedListStyle(undoResult.text);
|
||||||
|
pristineText = undoResultOpositeList.text;
|
||||||
|
}
|
||||||
|
return [undoResult, undoResultOpositeList, pristineText];
|
||||||
|
}
|
||||||
|
|
||||||
|
function listStyle(textarea: HTMLTextAreaElement, style: StyleArgs): SelectionRange {
|
||||||
|
const noInitialSelection = textarea.selectionStart === textarea.selectionEnd;
|
||||||
|
let selectionStart = textarea.selectionStart;
|
||||||
|
let selectionEnd = textarea.selectionEnd;
|
||||||
|
|
||||||
|
// Select whole line
|
||||||
|
expandSelectionToLine(textarea);
|
||||||
|
|
||||||
|
const selectedText = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
|
||||||
|
|
||||||
|
// If the user intent was to do an undo, we will stop after this.
|
||||||
|
// Otherwise, we will still undo to other list type to prevent list stacking
|
||||||
|
const [undoResult, undoResultOpositeList, pristineText] = clearExistingListStyle(style, selectedText);
|
||||||
|
|
||||||
|
const prefixedLines = pristineText.split('\n').map((value, index) => {
|
||||||
|
return `${makePrefix(index, style.unorderedList)}${value}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalPrefixLength = prefixedLines.reduce((previousValue, _currentValue, currentIndex) => {
|
||||||
|
return previousValue + makePrefix(currentIndex, style.unorderedList).length;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
const totalPrefixLengthOpositeList = prefixedLines.reduce((previousValue, _currentValue, currentIndex) => {
|
||||||
|
return previousValue + makePrefix(currentIndex, !style.unorderedList).length;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
if (undoResult.processed) {
|
||||||
|
if (noInitialSelection) {
|
||||||
|
selectionStart = Math.max(selectionStart - makePrefix(0, style.unorderedList).length, 0);
|
||||||
|
selectionEnd = selectionStart;
|
||||||
|
} else {
|
||||||
|
selectionStart = textarea.selectionStart;
|
||||||
|
selectionEnd = textarea.selectionEnd - totalPrefixLength;
|
||||||
|
}
|
||||||
|
return { text: pristineText, selectionStart, selectionEnd };
|
||||||
|
}
|
||||||
|
|
||||||
const { newlinesToAppend, newlinesToPrepend } = newlinesToSurroundSelectedText(textarea);
|
const { newlinesToAppend, newlinesToPrepend } = newlinesToSurroundSelectedText(textarea);
|
||||||
selectionStart = textarea.selectionStart + newlinesToAppend.length;
|
const text = newlinesToAppend + prefixedLines.join('\n') + newlinesToPrepend;
|
||||||
selectionEnd = selectionStart + text.length;
|
|
||||||
if (noInitialSelection) selectionStart = selectionEnd;
|
if (noInitialSelection) {
|
||||||
text = newlinesToAppend + text + newlinesToPrepend;
|
selectionStart = Math.max(selectionStart + makePrefix(0, style.unorderedList).length + newlinesToAppend.length, 0);
|
||||||
|
selectionEnd = selectionStart;
|
||||||
|
} else {
|
||||||
|
if (undoResultOpositeList.processed) {
|
||||||
|
selectionStart = Math.max(textarea.selectionStart + newlinesToAppend.length, 0);
|
||||||
|
selectionEnd = textarea.selectionEnd + newlinesToAppend.length + totalPrefixLength - totalPrefixLengthOpositeList;
|
||||||
|
} else {
|
||||||
|
selectionStart = Math.max(textarea.selectionStart + newlinesToAppend.length, 0);
|
||||||
|
selectionEnd = textarea.selectionEnd + newlinesToAppend.length + totalPrefixLength;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { text, selectionStart, selectionEnd };
|
return { text, selectionStart, selectionEnd };
|
||||||
}
|
}
|
||||||
|
|
||||||
function numberedLines(lines: string[]) {
|
|
||||||
let i;
|
|
||||||
let len;
|
|
||||||
let index;
|
|
||||||
const results = [];
|
|
||||||
for (index = i = 0, len = lines.length; i < len; index = ++i) {
|
|
||||||
const line = lines[index];
|
|
||||||
results.push(`${index + 1}. ${line}`);
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user