Merge pull request #8 from flarum/ds/1702-fix-toolbar-in-ie-11

Use custom JSX implementation of GitHub's markdown toolbar that works in IE
This commit is contained in:
Daniël Klabbers 2019-09-06 21:24:14 +02:00 committed by GitHub
commit 6ca55ec8ad
9 changed files with 463 additions and 77 deletions

View File

@ -979,11 +979,6 @@
"to-fast-properties": "^2.0.0"
}
},
"@github/markdown-toolbar-element": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@github/markdown-toolbar-element/-/markdown-toolbar-element-0.1.1.tgz",
"integrity": "sha512-P+wEginh59MeqKwk7oBOsFotZ1cWGdafH+lcipbaBbGN2jS58vNvHJ9UMCOGlt049Xtl7Cm607+hH11dmoBinw=="
},
"@webassemblyjs/ast": {
"version": "1.7.11",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.7.11.tgz",
@ -1138,11 +1133,6 @@
"@xtuc/long": "4.2.1"
}
},
"@webcomponents/custom-elements": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@webcomponents/custom-elements/-/custom-elements-1.2.1.tgz",
"integrity": "sha512-flmTp4rVbBkcUIF3eBO3LNoAaYvleTdhPZKzdzr6iztWLLrxCctcK+7MAQeC3/SPjc3JDdC3jYLMRF4R6C3f9g=="
},
"@xtuc/ieee754": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
@ -2322,13 +2312,13 @@
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"fsevents": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz",
"integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==",
"version": "1.2.9",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz",
"integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==",
"optional": true,
"requires": {
"nan": "^2.9.2",
"node-pre-gyp": "^0.10.0"
"nan": "^2.12.1",
"node-pre-gyp": "^0.12.0"
},
"dependencies": {
"abbrev": {
@ -2347,7 +2337,7 @@
"optional": true
},
"are-we-there-yet": {
"version": "1.1.4",
"version": "1.1.5",
"bundled": true,
"optional": true,
"requires": {
@ -2370,7 +2360,7 @@
}
},
"chownr": {
"version": "1.0.1",
"version": "1.1.1",
"bundled": true,
"optional": true
},
@ -2395,15 +2385,15 @@
"optional": true
},
"debug": {
"version": "2.6.9",
"version": "4.1.1",
"bundled": true,
"optional": true,
"requires": {
"ms": "2.0.0"
"ms": "^2.1.1"
}
},
"deep-extend": {
"version": "0.5.1",
"version": "0.6.0",
"bundled": true,
"optional": true
},
@ -2446,7 +2436,7 @@
}
},
"glob": {
"version": "7.1.2",
"version": "7.1.3",
"bundled": true,
"optional": true,
"requires": {
@ -2464,11 +2454,11 @@
"optional": true
},
"iconv-lite": {
"version": "0.4.21",
"version": "0.4.24",
"bundled": true,
"optional": true,
"requires": {
"safer-buffer": "^2.1.0"
"safer-buffer": ">= 2.1.2 < 3"
}
},
"ignore-walk": {
@ -2525,16 +2515,16 @@
"optional": true
},
"minipass": {
"version": "2.2.4",
"version": "2.3.5",
"bundled": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
}
},
"minizlib": {
"version": "1.1.0",
"version": "1.2.1",
"bundled": true,
"optional": true,
"requires": {
@ -2550,32 +2540,32 @@
}
},
"ms": {
"version": "2.0.0",
"version": "2.1.1",
"bundled": true,
"optional": true
},
"needle": {
"version": "2.2.0",
"version": "2.3.0",
"bundled": true,
"optional": true,
"requires": {
"debug": "^2.1.2",
"debug": "^4.1.0",
"iconv-lite": "^0.4.4",
"sax": "^1.2.4"
}
},
"node-pre-gyp": {
"version": "0.10.0",
"version": "0.12.0",
"bundled": true,
"optional": true,
"requires": {
"detect-libc": "^1.0.2",
"mkdirp": "^0.5.1",
"needle": "^2.2.0",
"needle": "^2.2.1",
"nopt": "^4.0.1",
"npm-packlist": "^1.1.6",
"npmlog": "^4.0.2",
"rc": "^1.1.7",
"rc": "^1.2.7",
"rimraf": "^2.6.1",
"semver": "^5.3.0",
"tar": "^4"
@ -2591,12 +2581,12 @@
}
},
"npm-bundled": {
"version": "1.0.3",
"version": "1.0.6",
"bundled": true,
"optional": true
},
"npm-packlist": {
"version": "1.1.10",
"version": "1.4.1",
"bundled": true,
"optional": true,
"requires": {
@ -2663,11 +2653,11 @@
"optional": true
},
"rc": {
"version": "1.2.7",
"version": "1.2.8",
"bundled": true,
"optional": true,
"requires": {
"deep-extend": "^0.5.1",
"deep-extend": "^0.6.0",
"ini": "~1.3.0",
"minimist": "^1.2.0",
"strip-json-comments": "~2.0.1"
@ -2695,15 +2685,15 @@
}
},
"rimraf": {
"version": "2.6.2",
"version": "2.6.3",
"bundled": true,
"optional": true,
"requires": {
"glob": "^7.0.5"
"glob": "^7.1.3"
}
},
"safe-buffer": {
"version": "5.1.1",
"version": "5.1.2",
"bundled": true,
"optional": true
},
@ -2718,7 +2708,7 @@
"optional": true
},
"semver": {
"version": "5.5.0",
"version": "5.7.0",
"bundled": true,
"optional": true
},
@ -2764,16 +2754,16 @@
"optional": true
},
"tar": {
"version": "4.4.1",
"version": "4.4.8",
"bundled": true,
"optional": true,
"requires": {
"chownr": "^1.0.1",
"chownr": "^1.1.1",
"fs-minipass": "^1.2.5",
"minipass": "^2.2.4",
"minizlib": "^1.1.0",
"minipass": "^2.3.4",
"minizlib": "^1.1.1",
"mkdirp": "^0.5.0",
"safe-buffer": "^5.1.1",
"safe-buffer": "^5.1.2",
"yallist": "^3.0.2"
}
},
@ -2783,11 +2773,11 @@
"optional": true
},
"wide-align": {
"version": "1.1.2",
"version": "1.1.3",
"bundled": true,
"optional": true,
"requires": {
"string-width": "^1.0.2"
"string-width": "^1.0.2 || 2"
}
},
"wrappy": {
@ -2796,7 +2786,7 @@
"optional": true
},
"yallist": {
"version": "3.0.2",
"version": "3.0.3",
"bundled": true,
"optional": true
}
@ -3501,9 +3491,9 @@
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
},
"nan": {
"version": "2.11.1",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz",
"integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==",
"version": "2.14.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
"integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==",
"optional": true
},
"nanomatch": {

View File

@ -2,8 +2,6 @@
"private": true,
"name": "@flarum/markdown",
"dependencies": {
"@github/markdown-toolbar-element": "^0.1.1",
"@webcomponents/custom-elements": "^1.2.1",
"flarum-webpack-config": "0.1.0-beta.10",
"mdarea": "^0.0.10",
"webpack": "^4.26.0",

View File

@ -0,0 +1,39 @@
import Component from 'flarum/Component';
import icon from 'flarum/helpers/icon';
import apply from '../util/apply';
const modifierKey = navigator.userAgent.match(/Macintosh/) ? '⌘' : 'ctrl';
export default class MarkdownButton extends Component {
config(isInitialized) {
if (isInitialized) return;
this.$().tooltip();
}
view() {
return <button className="Button Button--icon Button--link" title={this.title()} data-hotkey={this.props.hotkey}
onclick={this.click.bind(this)} onkeydown={this.keydown.bind(this)}>
{icon(this.props.icon)}
</button>;
}
keydown(event) {
if (event.key === ' ' || event.key === 'Enter') {
event.preventDefault();
this.click();
}
}
click() {
return apply(this.element, this.props.style);
}
title() {
let tooltip = this.props.title;
if (this.props.hotkey) tooltip += ` <${modifierKey}-${this.props.hotkey}>`;
return tooltip;
}
}

View File

@ -0,0 +1,30 @@
import Component from 'flarum/Component';
const modifierKey = navigator.userAgent.match(/Macintosh/) ? 'Meta' : 'Control';
export default class MarkdownToolbar extends Component {
config(isInitialized) {
if (isInitialized) return;
const field = document.getElementById(this.props.for);
field.addEventListener('keydown', this.shortcut.bind(this));
}
view() {
return <div id="MarkdownToolbar" data-for={this.props.for} style={{ display: 'inline-block' }}>
{this.props.children}
</div>;
}
shortcut(event) {
if ((event.metaKey && modifierKey === 'Meta') || (event.ctrlKey && modifierKey === 'Control')) {
const button = this.element.querySelector(`[data-hotkey="${event.key}"]`);
if (button) {
button.click();
event.preventDefault()
}
}
}
}

View File

@ -1,17 +1,12 @@
import { extend } from 'flarum/extend';
import TextEditor from 'flarum/components/TextEditor';
import icon from 'flarum/helpers/icon';
import MarkdownArea from 'mdarea';
let MarkdownArea;
if (window.Reflect) {
require('@webcomponents/custom-elements');
require('@github/markdown-toolbar-element');
MarkdownArea = require('mdarea/mdarea.js');
}
import './polyfills';
import MarkdownToolbar from './components/MarkdownToolbar';
import MarkdownButton from './components/MarkdownButton';
app.initializers.add('flarum-markdown', function(app) {
if (!MarkdownArea) return;
let index = 1;
@ -28,6 +23,7 @@ app.initializers.add('flarum-markdown', function(app) {
const editor = new MarkdownArea(element);
editor.disableInline();
editor.ignoreTab();
context.onunload = function() {
editor.destroy();
@ -35,24 +31,20 @@ app.initializers.add('flarum-markdown', function(app) {
});
extend(TextEditor.prototype, 'toolbarItems', function(items) {
const attrs = {
className: 'Button Button--icon Button--link',
config: elm => $(elm).tooltip()
};
const tooltip = name => app.translator.trans(`flarum-markdown.forum.composer.${name}_tooltip`);
items.add('markdown', (
<markdown-toolbar for={this.textareaId}>
<md-header title={tooltip('header')} {...attrs}>{icon('fas fa-heading')}</md-header>
<md-bold title={tooltip('bold')+' <cmd-b>'} {...attrs}>{icon('fas fa-bold')}</md-bold>
<md-italic title={tooltip('italic')+' <cmd-i>'} {...attrs}>{icon('fas fa-italic')}</md-italic>
<md-quote title={tooltip('quote')} {...attrs}>{icon('fas fa-quote-left')}</md-quote>
<md-code title={tooltip('code')} {...attrs}>{icon('fas fa-code')}</md-code>
<md-link title={tooltip('link')+' <cmd-k>'} {...attrs}>{icon('fas fa-link')}</md-link>
<md-unordered-list title={tooltip('unordered_list')} {...attrs}>{icon('fas fa-list-ul')}</md-unordered-list>
<md-ordered-list title={tooltip('ordered_list')} {...attrs}>{icon('fas fa-list-ol')}</md-ordered-list>
</markdown-toolbar>
<MarkdownToolbar for={this.textareaId}>
<MarkdownButton title={tooltip('header')} icon="fas fa-heading" style={{ prefix: '### ' }} />
<MarkdownButton title={tooltip('bold')} icon="fas fa-bold" style={{ prefix: '**', suffix: '**', trimFirst: true }} hotkey="b" />
<MarkdownButton title={tooltip('italic')} icon="fas fa-italic" style={{ prefix: '_', suffix: '_', trimFirst: true }} hotkey="i" />
<MarkdownButton title={tooltip('quote')} icon="fas fa-quote-left" style={{ prefix: '> ', multiline: true, surroundWithNewlines: true }} />
<MarkdownButton title={tooltip('code')} icon="fas fa-code" style={{ prefix: '`', suffix: '`', blockPrefix: '```', blockSuffix: '```' }} />
<MarkdownButton title={tooltip('link')} icon="fas fa-link" style={{ prefix: '[', suffix: '](url)', replaceNext: 'url', scanFor: 'https?://' }} />
<MarkdownButton title={tooltip('image')} icon="fas fa-image" style={{ prefix: '![', suffix: '](src)', replaceNext: 'src', scanFor: 'https?://' }} />
<MarkdownButton title={tooltip('unordered_list')} icon="fas fa-list-ul" style={{ prefix: '- ', multiline: true, surroundWithNewlines: true }} />
<MarkdownButton title={tooltip('ordered_list')} icon="fas fa-list-ol" style={{ prefix: '1. ', multiline: true, orderedList: true }} />
</MarkdownToolbar>
), 100);
});
});

View File

@ -0,0 +1,18 @@
/*! https://mths.be/startswith v0.2.0 by @mathias */
if (!String.prototype.startsWith) {
Object.defineProperty(String.prototype, 'startsWith', {
value: function(search, pos) {
pos = !pos || pos < 0 ? 0 : +pos;
return this.substring(pos, pos + search.length) === search;
}
});
}
if (!String.prototype.endsWith) {
String.prototype.endsWith = function(search, this_len) {
if (this_len === undefined || this_len > this.length) {
this_len = this.length;
}
return this.substring(this_len - search.length, this_len) === search;
};
}

View File

@ -0,0 +1,45 @@
import insertText from './insertText';
import { blockStyle, isMultipleLines, multilineStyle, orderedList } from './styles';
export const styleSelectedText = (textarea, styleArgs) => {
const text = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
let result;
if (styleArgs.orderedList) {
result = orderedList(textarea);
}
else if (styleArgs.multiline && isMultipleLines(text)) {
result = multilineStyle(textarea, styleArgs);
}
else {
result = blockStyle(textarea, styleArgs);
}
insertText(textarea, result);
};
export default (button, stylesToApply) => {
const toolbar = button.parentElement;
const defaults = {
prefix: '',
suffix: '',
blockPrefix: '',
blockSuffix: '',
multiline: false,
replaceNext: '',
prefixSpace: false,
scanFor: '',
surroundWithNewlines: false,
orderedList: false,
trimFirst: false
};
const style = Object.assign({}, defaults, stylesToApply);
const field = document.getElementById(toolbar.dataset.for);
if (field) {
field.focus();
styleSelectedText(field, style);
}
}

View File

@ -0,0 +1,49 @@
export let canInsertText = null;
export default (textarea, { text, selectionStart, selectionEnd }) => {
const originalSelectionStart = textarea.selectionStart;
const before = textarea.value.slice(0, originalSelectionStart);
const after = textarea.value.slice(textarea.selectionEnd);
if (canInsertText === null || canInsertText === true) {
textarea.contentEditable = 'true';
try {
canInsertText = document.execCommand('insertText', false, text);
}
catch (error) {
canInsertText = false;
}
textarea.contentEditable = 'false';
}
if (canInsertText && !textarea.value.slice(0, textarea.selectionStart).endsWith(text)) {
canInsertText = false;
}
if (!canInsertText) {
try {
document.execCommand('ms-beginUndoUnit');
}
catch (e) {
// Do nothing.
}
textarea.value = before + text + after;
try {
document.execCommand('ms-endUndoUnit');
}
catch (e) {
// Do nothing.
}
// fire custom event, works on IE
const event = document.createEvent('Event');
event.initEvent('input', true, true);
textarea.dispatchEvent(event);
}
if (selectionStart != null && selectionEnd != null) {
textarea.setSelectionRange(selectionStart, selectionEnd);
}
else {
textarea.setSelectionRange(originalSelectionStart, textarea.selectionEnd);
}
};

View File

@ -0,0 +1,225 @@
export function isMultipleLines(string) {
return string.trim().split('\n').length > 1;
}
export function repeat(string, n) {
return Array(n + 1).join(string);
}
export function wordSelectionStart(text, i) {
let index = i;
while (text[index] && text[index - 1] != null && !text[index - 1].match(/\s/)) {
index--;
}
return index;
}
export function wordSelectionEnd(text, i, multiline) {
let index = i;
const breakpoint = multiline ? /\n/ : /\s/;
while (text[index] && !text[index].match(breakpoint)) {
index++;
}
return index;
}
export function expandSelectedText(textarea, prefixToUse, suffixToUse) {
let multiline = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
if (textarea.selectionStart === textarea.selectionEnd) {
textarea.selectionStart = wordSelectionStart(textarea.value, textarea.selectionStart);
textarea.selectionEnd = wordSelectionEnd(textarea.value, textarea.selectionEnd, multiline);
} else {
const expandedSelectionStart = textarea.selectionStart - prefixToUse.length;
const expandedSelectionEnd = textarea.selectionEnd + suffixToUse.length;
const beginsWithPrefix = textarea.value.slice(expandedSelectionStart, textarea.selectionStart) === prefixToUse;
const endsWithSuffix = textarea.value.slice(textarea.selectionEnd, expandedSelectionEnd) === suffixToUse;
if (beginsWithPrefix && endsWithSuffix) {
textarea.selectionStart = expandedSelectionStart;
textarea.selectionEnd = expandedSelectionEnd;
}
}
return textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
}
export function newlinesToSurroundSelectedText(textarea) {
const beforeSelection = textarea.value.slice(0, textarea.selectionStart);
const afterSelection = textarea.value.slice(textarea.selectionEnd);
const breaksBefore = beforeSelection.match(/\n*$/);
const breaksAfter = afterSelection.match(/^\n*/);
const newlinesBeforeSelection = breaksBefore ? breaksBefore[0].length : 0;
const newlinesAfterSelection = breaksAfter ? breaksAfter[0].length : 0;
let newlinesToAppend;
let newlinesToPrepend;
if (beforeSelection.match(/\S/) && newlinesBeforeSelection < 2) {
newlinesToAppend = repeat('\n', 2 - newlinesBeforeSelection);
}
if (afterSelection.match(/\S/) && newlinesAfterSelection < 2) {
newlinesToPrepend = repeat('\n', 2 - newlinesAfterSelection);
}
if (newlinesToAppend == null) {
newlinesToAppend = '';
}
if (newlinesToPrepend == null) {
newlinesToPrepend = '';
}
return {
newlinesToAppend,
newlinesToPrepend
};
}
export const blockStyle = (textarea, arg) => {
let newlinesToAppend;
let newlinesToPrepend;
const { prefix, suffix, blockPrefix, blockSuffix, replaceNext, prefixSpace, scanFor, surroundWithNewlines } = arg;
const originalSelectionStart = textarea.selectionStart;
const originalSelectionEnd = textarea.selectionEnd;
let selectedText = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
let prefixToUse = isMultipleLines(selectedText) && blockPrefix.length > 0 ? `${blockPrefix}\n` : prefix;
let suffixToUse = isMultipleLines(selectedText) && blockSuffix.length > 0 ? `\n${blockSuffix}` : suffix;
if (prefixSpace) {
const beforeSelection = textarea.value[textarea.selectionStart - 1];
if (textarea.selectionStart !== 0 && beforeSelection != null && !beforeSelection.match(/\s/)) {
prefixToUse = ` ${prefixToUse}`;
}
}
selectedText = expandSelectedText(textarea, prefixToUse, suffixToUse, arg.multiline);
let selectionStart = textarea.selectionStart;
let selectionEnd = textarea.selectionEnd;
const hasReplaceNext = replaceNext.length > 0 && suffixToUse.indexOf(replaceNext) > -1 && selectedText.length > 0;
if (surroundWithNewlines) {
const ref = newlinesToSurroundSelectedText(textarea);
newlinesToAppend = ref.newlinesToAppend;
newlinesToPrepend = ref.newlinesToPrepend;
prefixToUse = newlinesToAppend + prefix;
suffixToUse += newlinesToPrepend;
}
if (selectedText.startsWith(prefixToUse) && selectedText.endsWith(suffixToUse)) {
const replacementText = selectedText.slice(prefixToUse.length, selectedText.length - suffixToUse.length);
if (originalSelectionStart === originalSelectionEnd) {
let position = originalSelectionStart - prefixToUse.length;
position = Math.max(position, selectionStart);
position = Math.min(position, selectionStart + replacementText.length);
selectionStart = selectionEnd = position;
}
else {
selectionEnd = selectionStart + replacementText.length;
}
return { text: replacementText, selectionStart, selectionEnd };
}
else if (!hasReplaceNext) {
let replacementText = prefixToUse + selectedText + suffixToUse;
selectionStart = originalSelectionStart + prefixToUse.length;
selectionEnd = originalSelectionEnd + prefixToUse.length;
const whitespaceEdges = selectedText.match(/^\s*|\s*$/g);
if (arg.trimFirst && whitespaceEdges) {
const leadingWhitespace = whitespaceEdges[0] || '';
const trailingWhitespace = whitespaceEdges[1] || '';
replacementText = leadingWhitespace + prefixToUse + selectedText.trim() + suffixToUse + trailingWhitespace;
selectionStart += leadingWhitespace.length;
selectionEnd -= trailingWhitespace.length;
}
return { text: replacementText, selectionStart, selectionEnd };
}
else if (scanFor.length > 0 && selectedText.match(scanFor)) {
suffixToUse = suffixToUse.replace(replaceNext, selectedText);
const replacementText = prefixToUse + suffixToUse;
selectionStart = selectionEnd = selectionStart + prefixToUse.length;
return { text: replacementText, selectionStart, selectionEnd };
}
else {
const replacementText = prefixToUse + selectedText + suffixToUse;
selectionStart = selectionStart + prefixToUse.length + selectedText.length + suffixToUse.indexOf(replaceNext);
selectionEnd = selectionStart + replaceNext.length;
return { text: replacementText, selectionStart, selectionEnd };
}
}
export const multilineStyle = (textarea, arg) => {
const { prefix, suffix, surroundWithNewlines } = arg;
let text = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
let selectionStart = textarea.selectionStart;
let selectionEnd = textarea.selectionEnd;
const lines = text.split('\n');
const undoStyle = lines.every(line => line.startsWith(prefix) && line.endsWith(suffix));
if (undoStyle) {
text = lines.map(line => line.slice(prefix.length, line.length - suffix.length)).join('\n');
selectionEnd = selectionStart + text.length;
}
else {
text = lines.map(line => prefix + line + suffix).join('\n');
if (surroundWithNewlines) {
const { newlinesToAppend, newlinesToPrepend } = newlinesToSurroundSelectedText(textarea);
selectionStart += newlinesToAppend.length;
selectionEnd = selectionStart + text.length;
text = newlinesToAppend + text + newlinesToPrepend;
}
}
return { text, selectionStart, selectionEnd };
}
export const orderedList = (textarea) => {
const orderedListRegex = /^\d+\.\s+/;
const noInitialSelection = textarea.selectionStart === textarea.selectionEnd;
let selectionEnd;
let selectionStart;
let text = textarea.value.slice(textarea.selectionStart, textarea.selectionEnd);
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) {
lines = linesToUnstyle.map(line => line.replace(orderedListRegex, ''));
text = lines.join('\n');
if (noInitialSelection && startOfLine && endOfLine) {
const lengthDiff = linesToUnstyle[0].length - lines[0].length;
selectionStart = selectionEnd = textarea.selectionStart - lengthDiff;
textarea.selectionStart = startOfLine;
textarea.selectionEnd = endOfLine;
}
}
else {
lines = (function () {
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;
})();
text = lines.join('\n');
const { newlinesToAppend, newlinesToPrepend } = newlinesToSurroundSelectedText(textarea);
selectionStart = textarea.selectionStart + newlinesToAppend.length;
selectionEnd = selectionStart + text.length;
if (noInitialSelection)
selectionStart = selectionEnd;
text = newlinesToAppend + text + newlinesToPrepend;
}
return { text, selectionStart, selectionEnd };
}