diff --git a/extensions/markdown/js/package-lock.json b/extensions/markdown/js/package-lock.json
index 1730006b1..87e2959c4 100644
--- a/extensions/markdown/js/package-lock.json
+++ b/extensions/markdown/js/package-lock.json
@@ -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": {
diff --git a/extensions/markdown/js/package.json b/extensions/markdown/js/package.json
index b76b407f6..b32885d0f 100644
--- a/extensions/markdown/js/package.json
+++ b/extensions/markdown/js/package.json
@@ -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",
diff --git a/extensions/markdown/js/src/forum/components/MarkdownButton.js b/extensions/markdown/js/src/forum/components/MarkdownButton.js
new file mode 100644
index 000000000..bff1fc194
--- /dev/null
+++ b/extensions/markdown/js/src/forum/components/MarkdownButton.js
@@ -0,0 +1,47 @@
+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 ;
+ }
+
+ keydown(event) {
+ if (event.key === ' ' || event.key === 'Enter') {
+ event.preventDefault();
+ this.click();
+ }
+ }
+
+ click() {
+ return apply(this.element, this.styles());
+ }
+
+ title() {
+ let tooltip = this.props.title;
+
+ if (this.props.hotkey) tooltip += ` <${modifierKey}-${this.props.hotkey}>`;
+
+ return tooltip;
+ }
+
+ icon() {
+ return this.props.icon;
+ }
+
+ styles() {
+ return this.props.style;
+ }
+}
diff --git a/extensions/markdown/js/src/forum/components/MarkdownToolbar.js b/extensions/markdown/js/src/forum/components/MarkdownToolbar.js
new file mode 100644
index 000000000..525fdc2bc
--- /dev/null
+++ b/extensions/markdown/js/src/forum/components/MarkdownToolbar.js
@@ -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
+ {this.props.children}
+
;
+ }
+
+ 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()
+ }
+ }
+ }
+}
diff --git a/extensions/markdown/js/src/forum/index.js b/extensions/markdown/js/src/forum/index.js
index 047fefcc8..6f5f95c04 100644
--- a/extensions/markdown/js/src/forum/index.js
+++ b/extensions/markdown/js/src/forum/index.js
@@ -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 './pollyfills';
+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,19 @@ 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', (
-
- {icon('fas fa-heading')}
- '} {...attrs}>{icon('fas fa-bold')}
- '} {...attrs}>{icon('fas fa-italic')}
- {icon('fas fa-quote-left')}
- {icon('fas fa-code')}
- '} {...attrs}>{icon('fas fa-link')}
- {icon('fas fa-list-ul')}
- {icon('fas fa-list-ol')}
-
+
+
+
+
+ ', multiline: true, surroundWithNewlines: true }} />
+
+
+
+
+
), 100);
});
});
diff --git a/extensions/markdown/js/src/forum/pollyfills.js b/extensions/markdown/js/src/forum/pollyfills.js
new file mode 100644
index 000000000..918827b58
--- /dev/null
+++ b/extensions/markdown/js/src/forum/pollyfills.js
@@ -0,0 +1,17 @@
+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;
+ };
+}
diff --git a/extensions/markdown/js/src/forum/util/apply.js b/extensions/markdown/js/src/forum/util/apply.js
new file mode 100644
index 000000000..534463e2a
--- /dev/null
+++ b/extensions/markdown/js/src/forum/util/apply.js
@@ -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);
+ }
+}
diff --git a/extensions/markdown/js/src/forum/util/insertText.js b/extensions/markdown/js/src/forum/util/insertText.js
new file mode 100644
index 000000000..c659a40b9
--- /dev/null
+++ b/extensions/markdown/js/src/forum/util/insertText.js
@@ -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);
+ }
+};
diff --git a/extensions/markdown/js/src/forum/util/styles.js b/extensions/markdown/js/src/forum/util/styles.js
new file mode 100644
index 000000000..5da1dfa04
--- /dev/null
+++ b/extensions/markdown/js/src/forum/util/styles.js
@@ -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 };
+}