From af002f3d59c5054ee61ad814d0cdec5e26a7100b Mon Sep 17 00:00:00 2001
From: Pouria Hadjibagheri
Date: Fri, 24 Mar 2017 20:01:58 +0000
Subject: [PATCH] Updated docs and interfaces.
---
markdownx/.static/markdownx/js/markdownx.ts | 429 ++++++++++++------
markdownx/.static/markdownx/js/utils.ts | 11 +-
markdownx/static/markdownx/js/markdownx.js | 227 ++++++---
.../static/markdownx/js/markdownx.min.js | 2 +-
4 files changed, 454 insertions(+), 215 deletions(-)
diff --git a/markdownx/.static/markdownx/js/markdownx.ts b/markdownx/.static/markdownx/js/markdownx.ts
index 873295f..3ac8c76 100644
--- a/markdownx/.static/markdownx/js/markdownx.ts
+++ b/markdownx/.static/markdownx/js/markdownx.ts
@@ -21,6 +21,51 @@
declare function docReady(args: any): any;
+interface ImageUploadResponse {
+ image_code?: string,
+ image_path?: string,
+ [propName: string]: any;
+}
+
+interface HandlerFunction {
+ (properties: {
+ start: number,
+ end: number,
+ value: string
+ }): string
+}
+
+interface KeyboardEvents {
+ keys: {
+ TAB: string,
+ DUPLICATE: string,
+ UNINDENT: string,
+ INDENT: string
+ },
+ handlers: {
+ _multiLineIndentation: HandlerFunction,
+ applyTab: HandlerFunction,
+ applyIndentation: HandlerFunction,
+ removeIndentation: HandlerFunction,
+ removeTab: HandlerFunction,
+ applyDuplication: HandlerFunction
+ },
+ hub: Function
+}
+
+interface EventHandlers {
+ inhibitDefault: Function,
+ onDragEnter: Function
+}
+
+interface MarkdownxProperties {
+ parent: HTMLElement,
+ editor: HTMLTextAreaElement,
+ preview: HTMLElement,
+ _latency: number | null,
+ _editorIsResizable: Boolean | null
+}
+
import {
Request,
mountEvents,
@@ -41,7 +86,10 @@ const UPLOAD_URL_ATTRIBUTE: string = "data-markdownx-upload-urls-path",
// ---------------------------------------------------------------------------------------------------------------------
-const EventHandlers = {
+/**
+ *
+ */
+const EventHandlers: EventHandlers = {
/**
* Routine tasks for event handlers (e.g. default preventions).
@@ -74,175 +122,273 @@ const EventHandlers = {
};
-const keyboardEvents = {
+/**
+ *
+ */
+const keyboardEvents: KeyboardEvents = {
/**
- *
+ * Custom hotkeys.
*/
keys: {
-
- TAB: "Tab",
+ TAB: "Tab",
DUPLICATE: "d",
- UNINDENT: "[",
- INDENT: "]"
-
+ UNINDENT: "[",
+ INDENT: "]"
},
/**
- *
+ * Hotkey response functions.
*/
handlers: {
/**
+ * Smart application of tab indentations under various conditions.
*
- * @param {number} start
- * @param {number} end
- * @param {string} value
* @returns {string}
- * @private
*/
- applyTab: function (start: number, end: number, value: string): string {
+ applyTab: function (properties) {
- return value.substring(0, start) + (
- value.substring(start, end).match(/\n/g) === null ?
- `\t${value.substring(start)}` :
- value.substring(start, end).replace(/^/gm, '\t') + value.substring(end)
- )
-
- },
-
-
- /**
- *
- * @param start
- * @param end
- * @param value
- * @returns {string}
- * @private
- */
- _multiLineIndentation: function (start: number, end: number, value: string): string {
-
- const endLine: string = new RegExp(`(?:\n|.){0,${end}}(^.*$)`, "m").exec(value)[1];
-
- return value.substring(
- value.indexOf(
- new RegExp(`(?:\n|.){0,${start}}(^.*$)`, "m").exec(value)[1] // Start line.
- ),
- (value.indexOf(endLine) ? value.indexOf(endLine) + endLine.length : end)
- );
+ // Do not replace with variables; this
+ // feature is optimised for swift response.
+ return properties.value
+ .substring(0, properties.start) + // Preceding text.
+ (
+ properties.value
+ .substring(properties.start, properties.end) // Selected text
+ .match(/\n/gm) === null ? // Not multi line?
+ `\t${properties.value.substring(properties.start)}` : // Add `\t`.
+ properties.value // Otherwise:
+ .substring(properties.start, properties.end)
+ .replace(/^/gm, '\t') + // Add `\t` to be beginning of each line.
+ properties.value.substring(properties.end) // Succeeding text.
+ )
},
/**
+ * Smart removal of tab indentations.
*
- * @param start
- * @param end
- * @param value
+ * @param {JSON} properties
* @returns {string}
- * @private
*/
- applyIndentation: function (start: number, end: number, value: string): string {
+ removeTab: function (properties) {
- if (start === end) {
- const line: string = new RegExp(`(?:\n|.){0,${start}}(^.+$)`, "m").exec(value)[1];
- return value.replace(line, `\t${line}`)
- }
+ let substitution: string = null,
+ lineTotal: number = (
+ properties.value
+ .substring(
+ properties.start,
+ properties.end
+ ).match(/\n/g) || [] // Number of lines (\n) or empty array (zero).
+ ).length; // Length of the array is equal to the number of lines.
- const content: string = this._multiLineIndentation(start, end, value);
+ if (properties.start === properties.end) {
- return value.replace(content, content.replace(/(^.+$)\n*/gmi, "\t$&"))
+ // Replacing `\t` at a specific location
+ // (+/- 1 chars) where there is no selection.
+ properties.start =
+ properties.start > 0 &&
+ properties.value[properties.start - 1] // -1 is to account any tabs just before the cursor.
+ .match(/\t/) !== null ? // if there's no `\t`, check the preceding character.
+ properties.start - 1 : properties.start;
- },
+ substitution = properties.value
+ .substring(properties.start)
+ .replace("\t", ''); // Remove only a single `\t`.
- /**
- *
- * @param start
- * @param end
- * @param value
- * @returns {string}
- * @private
- */
- removeIndentation: function (start: number, end: number, value: string): string {
-
- if (start === end) {
- const line: string = new RegExp(`(?:\n|.){0,${start}}(^\t.+$)`, "m").exec(value)[1];
- return value.replace(line, line.substring(1))
- }
-
- const content: string = this._multiLineIndentation(start, end, value);
-
- return value.replace(content, content.replace(/^\t(.+)\n*$/gmi, "$1"))
-
- },
-
- /**
- *
- * @param {number} start
- * @param {number} end
- * @param {string} value
- * @returns {string}
- * @private
- */
- removeTab: function (start: number, end: number, value: string): string {
-
- let endString: string = null,
- lineNumbers: number = (value.substring(start, end).match(/\n/g) || []).length;
-
- if (start === end) {
-
- // Replacing `\t` at a specific location (+/- 1 chars) where there is no selection.
- start = start > 0 && value[start - 1].match(/\t/) !== null ? start - 1 : start;
- endString = value.substring(start).replace(/\t/, '');
-
- } else if (!lineNumbers) {
+ } else if (!lineTotal) {
// Replacing `\t` within a single line selection.
- endString = value.substring(start).replace(/\t/, '')
-
+ substitution =
+ properties.value
+ .substring(properties.start)
+ .replace("\t", '')
} else {
- // Replacing `\t` in the beginning of each line in a multi-line selection.
- endString = value.substring(start, end).replace(/^\t/gm, '') + value.substring(end, value.length);
+ // Replacing `\t` in the beginning of each line
+ // in a multi-line selection.
+ substitution =
+ properties.value.substring(
+ properties.start,
+ properties.end
+ ).replace(/^\t/gm, '') + // Selection.
+ properties.value.substring(properties.end); // After the selection
}
- return value.substring(0, start) + endString;
+ return properties.value
+ .substring(0, properties.start) + // Text preceding to selection / cursor.
+ substitution
},
/**
+ * Handles multi line indentations.
*
- * @param {number} start
- * @param {number} end
- * @param {string} value
+ * @param properties
* @returns {string}
* @private
*/
- applyDuplication: function (start: number, end: number, value: string): string {
+ _multiLineIndentation: function (properties) {
- // Selected.
- if (start !== end) return (
- value.substring(0, start) +
- value.substring(start, end) +
- (~value.charAt(start - 1).indexOf('\n') || ~value.charAt(start).indexOf('\n') ? '\n' : '') +
- value.substring(start, end) +
- value.substring(end)
+ // Last line in the selection; regardless of
+ // where of not the entire line is selected.
+ const endLine: string =
+ new RegExp(`(?:\n|.){0,${properties.end}}(^.*$)`, "m")
+ .exec(properties.value)[1];
+
+ // Do not replace with variables; this
+ // feature is optimised for swift response.
+ return properties.value.substring(
+ // First line of the selection, regardless of
+ // where or not the entire line is selected.
+ properties.value.indexOf(
+ new RegExp(`(?:\n|.){0,${properties.start}}(^.*$)`, "m")
+ .exec(properties.value)[1] // Start line.
+ ), (
+ // If there is a last line in a multi line selected
+ // value where the last line is not empty or `\n`:
+ properties.value.indexOf(endLine) ?
+ // Location where the last line finishes with
+ // respect to the entire value.
+ properties.value.indexOf(endLine) + endLine.length :
+ // Otherwise, where the selection ends.
+ properties.end
+ )
);
- // Not selected.
- let pattern: RegExp = new RegExp(`(?:.|\n){0,${end}}\n([^].+)(?:.|\n)*`, 'm'),
- line: string = '';
+ },
- value.replace(pattern, (match, p1) => line += p1);
+ /**
+ * Smart application of indentation at the beginning of the line.
+ *
+ * @returns {string}
+ */
+ applyIndentation: function (properties) {
- return value.replace(line, `${line}\n${line}`)
+ // Single line?
+ if (properties.start === properties.end) {
+ // Current line, from the beginning to the end, regardless of any selections.
+ const line: string =
+ new RegExp(`(?:\n|.){0,${properties.start}}(^.+$)`, "m")
+ .exec(properties.value)[1];
+
+ return properties.value.replace(line, `\t${line}`)
+ }
+
+ // Multi line
+ const content: string = this._multiLineIndentation({
+ start: properties.start,
+ end: properties.end,
+ value: properties.value
+ });
+
+ return properties.value
+ .replace(
+ content, // Existing contents.
+ content.replace(/(^.+$)\n*/gmi, "\t$&") // Indented contents.
+ )
+
+ },
+
+ /**
+ * Smart removal of indentation from the beginning of the line.
+ *
+ * @returns {string}
+ */
+ removeIndentation: function (properties) {
+
+ // Single Line
+ if (properties.start === properties.end) {
+ // Entire line where the line immediately begins
+ // with a one or more `\t`, regardless of any
+ // selections.
+ const line: string =
+ new RegExp(`(?:\n|.){0,${properties.start}}(^\t.+$)`, "m")
+ .exec(properties.value)[1];
+
+ return properties.value
+ .replace(
+ line, // Existing content.
+ line.substring(1) // First character (necessarily a `\t`) removed.
+ )
+ }
+
+ // Multi line
+ const content: string = this._multiLineIndentation({
+ start: properties.start,
+ end: properties.end,
+ value: properties.value
+ });
+
+ return properties.value
+ .replace(
+ content, // Existing content.
+ content.replace(/^\t(.+)\n*$/gmi, "$1") // A single `\t` removed from the beginning.
+ )
+
+ },
+
+ /**
+ * Duplication of the current or selected lines.
+ *
+ * @returns {string}
+ */
+ applyDuplication: function (properties) {
+
+ // With selection.
+ // Do not replace with variables. This
+ // feature is optimised for swift response.
+ if (properties.start !== properties.end)
+ return (
+ properties.value.substring( // Text preceding the selected area.
+ 0,
+ properties.start
+ ) +
+ properties.value.substring( // Selected area
+ properties.start,
+ properties.end
+ ) +
+ (
+ ~properties.value // First character before the cursor is linebreak?
+ .charAt(properties.start - 1)
+ .indexOf('\n') || // --> or
+ ~properties.value // Character on the cursor is linebreak?
+ .charAt(properties.start)
+ .indexOf('\n') ? '\n' : '' // If either, add linebreak, otherwise add nothing.
+ ) +
+ properties.value.substring( // Selected area (again for duplication).
+ properties.start,
+ properties.end
+ ) +
+ properties.value.substring(properties.end) // Text succeeding the selected area.
+ );
+
+ // Without selection.
+ let pattern: RegExp = // Separate lines up to the end of the current line.
+ new RegExp(`(?:.|\n){0,160}(^.*$)`, 'm'),
+ line: string = '';
+
+ // Add anything found to the `line`. Note that
+ // `replace` is used a simple hack; it functions
+ // in a similar way to `regex.search` in Python.
+ properties.value
+ .replace(pattern, (match, p1) => line += p1);
+
+ return properties.value
+ .replace(
+ line, // Existing line.
+ `${line}\n${line}` // Doubled ... magic!
+ )
},
},
/**
+ * Mapping of hotkeys from keyboard events to their corresponding functions.
*
* @param {KeyboardEvent} event
* @returns {Function | Boolean}
@@ -267,6 +413,8 @@ const keyboardEvents = {
return (event.ctrlKey || event.metaKey) ? this.handlers.removeIndentation : false;
default:
+ // default would prevent the
+ // inhibition of default settings.
return false
}
@@ -311,10 +459,13 @@ function updateHeight(editor: HTMLTextAreaElement): HTMLTextAreaElement {
/**
* @example
*
- * let editor = document.getElementById('MyMarkdownEditor'),
- * preview = document.getElementById('MyMarkdownPreview');
+ * let element = document.getElementsByClassName('markdownx');
*
- * let mdx = new MarkdownX(editor, preview)
+ * new MarkdownX(
+ * element,
+ * element.querySelector('.markdownx-editor'),
+ * element.querySelector('.markdownx-preview')
+ * )
*
* @param {HTMLElement} parent - Markdown editor element.
* @param {HTMLTextAreaElement} editor - Markdown editor element.
@@ -322,7 +473,7 @@ function updateHeight(editor: HTMLTextAreaElement): HTMLTextAreaElement {
*/
const MarkdownX = function (parent: HTMLElement, editor: HTMLTextAreaElement, preview: HTMLElement): void {
- const properties = {
+ const properties: MarkdownxProperties = {
editor: editor,
preview: preview,
@@ -380,7 +531,7 @@ const MarkdownX = function (parent: HTMLElement, editor: HTMLTextAreaElement, pr
// If `true`, the editor will expand to scrollHeight when needed.
properties._editorIsResizable =
- (properties.editor.getAttribute(RESIZABILITY_ATTRIBUTE).match(/True/i) || []).length > 0;
+ (properties.editor.getAttribute(RESIZABILITY_ATTRIBUTE).match(/true/i) || []).length > 0;
getMarkdown();
@@ -445,11 +596,11 @@ const MarkdownX = function (parent: HTMLElement, editor: HTMLTextAreaElement, pr
// Holding the start location before anything changes.
const SELECTION_START: number = properties.editor.selectionStart;
- properties.editor.value = handlerFunc(
- properties.editor.selectionStart,
- properties.editor.selectionEnd,
- properties.editor.value
- );
+ properties.editor.value = handlerFunc({
+ start: properties.editor.selectionStart,
+ end: properties.editor.selectionEnd,
+ value: properties.editor.value
+ });
_markdownify();
@@ -475,9 +626,9 @@ const MarkdownX = function (parent: HTMLElement, editor: HTMLTextAreaElement, pr
preparePostData({image: file}) // Data
);
- xhr.success = (resp: string): void => {
+ xhr.success = (resp: string): void | null => {
- const response = JSON.parse(resp);
+ const response: ImageUploadResponse = JSON.parse(resp);
if (response.image_code) {
@@ -493,16 +644,16 @@ const MarkdownX = function (parent: HTMLElement, editor: HTMLTextAreaElement, pr
} else {
console.error(XHR_RESPONSE_ERROR, response);
- triggerCustomEvent('markdownx.fileUploadError', properties.parent, [response])
+ triggerCustomEvent('markdownx.fileUploadError', properties.parent, [response]);
+ return null;
}
- properties.preview.innerHTML = this.response;
properties.editor.style.opacity = NORMAL_OPACITY;
};
- xhr.error = (response: string): void => {
+ xhr.error = (response: any): void => {
properties.editor.style.opacity = NORMAL_OPACITY;
console.error(response);
@@ -533,7 +684,7 @@ const MarkdownX = function (parent: HTMLElement, editor: HTMLTextAreaElement, pr
};
- xhr.error = (response: string): void => {
+ xhr.error = (response: any): void => {
console.error(response);
@@ -549,16 +700,16 @@ const MarkdownX = function (parent: HTMLElement, editor: HTMLTextAreaElement, pr
*
* @param textToInsert
*/
- const insertImage = (textToInsert): void => {
+ const insertImage = (textToInsert: string): void => {
- let cursorPosition = properties.editor.selectionStart,
- text = properties.editor.value,
- textBeforeCursor = text.substring(0, cursorPosition),
- textAfterCursor = text.substring(cursorPosition, text.length);
+ properties.editor.value =
+ `${properties.editor.value.substring(0, properties.editor.selectionStart)}\n\n` + // Preceding text.
+ textToInsert +
+ `\n\n${properties.editor.value.substring(properties.editor.selectionEnd)}`; // Succeeding text.
- properties.editor.value = `${textBeforeCursor}${textToInsert}${textAfterCursor}`;
- properties.editor.selectionStart = cursorPosition + textToInsert.length;
- properties.editor.selectionEnd = cursorPosition + textToInsert.length;
+ properties.editor.selectionStart =
+ properties.editor.selectionEnd =
+ properties.editor.selectionStart + textToInsert.length;
triggerEvent(properties.editor, 'keyup');
inputChanged();
diff --git a/markdownx/.static/markdownx/js/utils.ts b/markdownx/.static/markdownx/js/utils.ts
index bb4d0f0..9c705b8 100644
--- a/markdownx/.static/markdownx/js/utils.ts
+++ b/markdownx/.static/markdownx/js/utils.ts
@@ -58,12 +58,21 @@ export function zip (...rows) {
}
+interface EventListener {
+ object: Element | Document,
+ listeners: {
+ type: string,
+ listener: any,
+ capture: boolean,
+ } []
+}
+
/**
*
* @param collections
* @returns
*/
-export function mountEvents (...collections): any {
+export function mountEvents (...collections: EventListener[]): any[] {
return collections.map(events =>
events.listeners.map(series =>
diff --git a/markdownx/static/markdownx/js/markdownx.js b/markdownx/static/markdownx/js/markdownx.js
index 60aa0eb..411b6ab 100644
--- a/markdownx/static/markdownx/js/markdownx.js
+++ b/markdownx/static/markdownx/js/markdownx.js
@@ -22,6 +22,9 @@ var utils_1 = require("./utils");
var UPLOAD_URL_ATTRIBUTE = "data-markdownx-upload-urls-path", PROCESSING_URL_ATTRIBUTE = "data-markdownx-urls-path", RESIZABILITY_ATTRIBUTE = "data-markdownx-editor-resizable", LATENCY_ATTRIBUTE = "data-markdownx-latency", LATENCY_MINIMUM = 500, // microseconds.
XHR_RESPONSE_ERROR = "Invalid response", UPLOAD_START_OPACITY = "0.3", NORMAL_OPACITY = "1";
// ---------------------------------------------------------------------------------------------------------------------
+/**
+ *
+ */
var EventHandlers = {
/**
* Routine tasks for event handlers (e.g. default preventions).
@@ -44,6 +47,9 @@ var EventHandlers = {
return this.inhibitDefault(event);
}
};
+/**
+ *
+ */
var keyboardEvents = {
/**
*
@@ -60,107 +66,173 @@ var keyboardEvents = {
handlers: {
/**
*
- * @param {number} start
- * @param {number} end
- * @param {string} value
* @returns {string}
- * @private
*/
- applyTab: function (start, end, value) {
- return value.substring(0, start) + (value.substring(start, end).match(/\n/g) === null ?
- "\t" + value.substring(start) :
- value.substring(start, end).replace(/^/gm, '\t') + value.substring(end));
+ applyTab: function (properties) {
+ // Do not replace with variables; this
+ // feature is optimised for swift response.
+ return properties.value
+ .substring(0, properties.start) +
+ (properties.value
+ .substring(properties.start, properties.end) // Selected text
+ .match(/\n/gm) === null ?
+ "\t" + properties.value.substring(properties.start) :
+ properties.value // Otherwise:
+ .substring(properties.start, properties.end)
+ .replace(/^/gm, '\t') +
+ properties.value.substring(properties.end) // Succeeding text.
+ );
},
/**
*
- * @param start
- * @param end
- * @param value
* @returns {string}
- * @private
*/
- _multiLineIndentation: function (start, end, value) {
- var endLine = new RegExp("(?:\n|.){0," + end + "}(^.*$)", "m").exec(value)[1];
- return value.substring(value.indexOf(new RegExp("(?:\n|.){0," + start + "}(^.*$)", "m").exec(value)[1] // Start line.
- ), (value.indexOf(endLine) ? value.indexOf(endLine) + endLine.length : end));
+ _multiLineIndentation: function (properties) {
+ // Last line in the selection; regardless of
+ // where of not the entire line is selected.
+ var endLine = new RegExp("(?:\n|.){0," + properties.end + "}(^.*$)", "m")
+ .exec(properties.value)[1];
+ // Do not replace with variables; this
+ // feature is optimised for swift response.
+ return properties.value.substring(
+ // First line of the selection, regardless of
+ // where or not the entire line is selected.
+ properties.value.indexOf(new RegExp("(?:\n|.){0," + properties.start + "}(^.*$)", "m")
+ .exec(properties.value)[1] // Start line.
+ ), (
+ // If there is a last line in a multi line selected
+ // value where the last line is not empty or `\n`:
+ properties.value.indexOf(endLine) ?
+ // Location where the last line finishes with
+ // respect to the entire value.
+ properties.value.indexOf(endLine) + endLine.length :
+ // Otherwise, where the selection ends.
+ properties.end));
},
/**
*
- * @param start
- * @param end
- * @param value
* @returns {string}
- * @private
*/
- applyIndentation: function (start, end, value) {
- if (start === end) {
- var line = new RegExp("(?:\n|.){0," + start + "}(^.+$)", "m").exec(value)[1];
- return value.replace(line, "\t" + line);
+ applyIndentation: function (properties) {
+ // Single line?
+ if (properties.start === properties.end) {
+ // Current line, from the beginning to the end, regardless of any selections.
+ var line = new RegExp("(?:\n|.){0," + properties.start + "}(^.+$)", "m")
+ .exec(properties.value)[1];
+ return properties.value.replace(line, "\t" + line);
}
- var content = this._multiLineIndentation(start, end, value);
- return value.replace(content, content.replace(/(^.+$)\n*/gmi, "\t$&"));
+ // Multi line
+ var content = this._multiLineIndentation({
+ start: properties.start,
+ end: properties.end,
+ value: properties.value
+ });
+ return properties.value
+ .replace(content, // Existing contents.
+ content.replace(/(^.+$)\n*/gmi, "\t$&") // Indented contents.
+ );
},
/**
*
- * @param start
- * @param end
- * @param value
* @returns {string}
- * @private
*/
- removeIndentation: function (start, end, value) {
- if (start === end) {
- var line = new RegExp("(?:\n|.){0," + start + "}(^\t.+$)", "m").exec(value)[1];
- return value.replace(line, line.substring(1));
+ removeIndentation: function (properties) {
+ // Single Line
+ if (properties.start === properties.end) {
+ // Entire line where the line immediately begins
+ // with a one or more `\t`, regardless of any
+ // selections.
+ var line = new RegExp("(?:\n|.){0," + properties.start + "}(^\t.+$)", "m")
+ .exec(properties.value)[1];
+ return properties.value
+ .replace(line, // Existing content.
+ line.substring(1) // First character (necessarily a `\t`) removed.
+ );
}
- var content = this._multiLineIndentation(start, end, value);
- return value.replace(content, content.replace(/^\t(.+)\n*$/gmi, "$1"));
+ // Multi line
+ var content = this._multiLineIndentation({
+ start: properties.start,
+ end: properties.end,
+ value: properties.value
+ });
+ return properties.value
+ .replace(content, // Existing content.
+ content.replace(/^\t(.+)\n*$/gmi, "$1") // A single `\t` removed from the beginning.
+ );
},
/**
*
- * @param {number} start
- * @param {number} end
- * @param {string} value
* @returns {string}
- * @private
*/
- removeTab: function (start, end, value) {
- var endString = null, lineNumbers = (value.substring(start, end).match(/\n/g) || []).length;
- if (start === end) {
- // Replacing `\t` at a specific location (+/- 1 chars) where there is no selection.
- start = start > 0 && value[start - 1].match(/\t/) !== null ? start - 1 : start;
- endString = value.substring(start).replace(/\t/, '');
+ removeTab: function (properties) {
+ var substitution = null, lineTotal = (properties.value
+ .substring(properties.start, properties.end).match(/\n/g) || [] // Number of lines (\n) or empty array (zero).
+ ).length; // Length of the array is equal to the number of lines.
+ if (properties.start === properties.end) {
+ // Replacing `\t` at a specific location
+ // (+/- 1 chars) where there is no selection.
+ properties.start =
+ properties.start > 0 &&
+ properties.value[properties.start - 1] // -1 is to account any tabs just before the cursor.
+ .match(/\t/) !== null ?
+ properties.start - 1 : properties.start;
+ substitution = properties.value
+ .substring(properties.start)
+ .replace("\t", ''); // Remove only a single `\t`.
}
- else if (!lineNumbers) {
+ else if (!lineTotal) {
// Replacing `\t` within a single line selection.
- endString = value.substring(start).replace(/\t/, '');
+ substitution =
+ properties.value
+ .substring(properties.start)
+ .replace("\t", '');
}
else {
- // Replacing `\t` in the beginning of each line in a multi-line selection.
- endString = value.substring(start, end).replace(/^\t/gm, '') + value.substring(end, value.length);
+ // Replacing `\t` in the beginning of each line
+ // in a multi-line selection.
+ substitution =
+ properties.value.substring(properties.start, properties.end).replace(/^\t/gm, '') +
+ properties.value.substring(properties.end); // After the selection
}
- return value.substring(0, start) + endString;
+ return properties.value
+ .substring(0, properties.start) +
+ substitution;
},
/**
*
- * @param {number} start
- * @param {number} end
- * @param {string} value
* @returns {string}
- * @private
*/
- applyDuplication: function (start, end, value) {
- // Selected.
- if (start !== end)
- return (value.substring(0, start) +
- value.substring(start, end) +
- (~value.charAt(start - 1).indexOf('\n') || ~value.charAt(start).indexOf('\n') ? '\n' : '') +
- value.substring(start, end) +
- value.substring(end));
- // Not selected.
- var pattern = new RegExp("(?:.|\n){0," + end + "}\n([^].+)(?:.|\n)*", 'm'), line = '';
- value.replace(pattern, function (match, p1) { return line += p1; });
- return value.replace(line, line + "\n" + line);
+ applyDuplication: function (properties) {
+ // With selection.
+ // Do not replace with variables. This
+ // feature is optimised for swift response.
+ if (properties.start !== properties.end)
+ return (properties.value.substring(// Text preceding the selected area.
+ 0, properties.start) +
+ properties.value.substring(// Selected area
+ properties.start, properties.end) +
+ (~properties.value // First character before the cursor is linebreak?
+ .charAt(properties.start - 1)
+ .indexOf('\n') ||
+ ~properties.value // Character on the cursor is linebreak?
+ .charAt(properties.start)
+ .indexOf('\n') ? '\n' : '' // If either, add linebreak, otherwise add nothing.
+ ) +
+ properties.value.substring(// Selected area (again for duplication).
+ properties.start, properties.end) +
+ properties.value.substring(properties.end) // Text succeeding the selected area.
+ );
+ // Without selection.
+ var pattern = new RegExp("(?:.|\n){0,160}(^.*$)", 'm'), line = '';
+ // Add anything found to the `line`. Note that
+ // `replace` is used a simple hack; it functions
+ // in a similar way to `regex.search` in Python.
+ properties.value
+ .replace(pattern, function (match, p1) { return line += p1; });
+ return properties.value
+ .replace(line, // Existing line.
+ line + "\n" + line // Doubled ... magic!
+ );
},
},
/**
@@ -270,7 +342,7 @@ var MarkdownX = function (parent, editor, preview) {
Math.max(parseInt(properties.editor.getAttribute(LATENCY_ATTRIBUTE)) || 0, LATENCY_MINIMUM);
// If `true`, the editor will expand to scrollHeight when needed.
properties._editorIsResizable =
- (properties.editor.getAttribute(RESIZABILITY_ATTRIBUTE).match(/True/i) || []).length > 0;
+ (properties.editor.getAttribute(RESIZABILITY_ATTRIBUTE).match(/true/i) || []).length > 0;
getMarkdown();
utils_1.triggerCustomEvent("markdownx.init");
};
@@ -314,7 +386,11 @@ var MarkdownX = function (parent, editor, preview) {
EventHandlers.inhibitDefault(event);
// Holding the start location before anything changes.
var SELECTION_START = properties.editor.selectionStart;
- properties.editor.value = handlerFunc(properties.editor.selectionStart, properties.editor.selectionEnd, properties.editor.value);
+ properties.editor.value = handlerFunc({
+ start: properties.editor.selectionStart,
+ end: properties.editor.selectionEnd,
+ value: properties.editor.value
+ });
_markdownify();
properties.editor.focus();
// Set the cursor location to the start location of the selection.
@@ -344,8 +420,8 @@ var MarkdownX = function (parent, editor, preview) {
else {
console.error(XHR_RESPONSE_ERROR, response);
utils_1.triggerCustomEvent('markdownx.fileUploadError', properties.parent, [response]);
+ return null;
}
- properties.preview.innerHTML = _this.response;
properties.editor.style.opacity = NORMAL_OPACITY;
};
xhr.error = function (response) {
@@ -378,10 +454,13 @@ var MarkdownX = function (parent, editor, preview) {
* @param textToInsert
*/
var insertImage = function (textToInsert) {
- var cursorPosition = properties.editor.selectionStart, text = properties.editor.value, textBeforeCursor = text.substring(0, cursorPosition), textAfterCursor = text.substring(cursorPosition, text.length);
- properties.editor.value = "" + textBeforeCursor + textToInsert + textAfterCursor;
- properties.editor.selectionStart = cursorPosition + textToInsert.length;
- properties.editor.selectionEnd = cursorPosition + textToInsert.length;
+ properties.editor.value =
+ properties.editor.value.substring(0, properties.editor.selectionStart) + "\n\n" +
+ textToInsert +
+ ("\n\n" + properties.editor.value.substring(properties.editor.selectionEnd)); // Succeeding text.
+ properties.editor.selectionStart =
+ properties.editor.selectionEnd =
+ properties.editor.selectionStart + textToInsert.length;
utils_1.triggerEvent(properties.editor, 'keyup');
inputChanged();
};
diff --git a/markdownx/static/markdownx/js/markdownx.min.js b/markdownx/static/markdownx/js/markdownx.min.js
index 5c68988..74e8921 100644
--- a/markdownx/static/markdownx/js/markdownx.min.js
+++ b/markdownx/static/markdownx/js/markdownx.min.js
@@ -1 +1 @@
-(function e(t,r,n){function i(a,s){if(!r[a]){if(!t[a]){var u=typeof require=="function"&&require;if(!s&&u)return u(a,!0);if(o)return o(a,!0);var l=new Error("Cannot find module '"+a+"'");throw l.code="MODULE_NOT_FOUND",l}var c=r[a]={exports:{}};t[a][0].call(c.exports,function(e){var r=t[a][1][e];return i(r?r:e)},c,c.exports,e,t,r,n)}return r[a].exports}var o=typeof require=="function"&&require;for(var a=0;a0&&r[e-1].match(/\t/)!==null?e-1:e;n=r.substring(e).replace(/\t/,"")}else if(!i){n=r.substring(e).replace(/\t/,"")}else{n=r.substring(e,t).replace(/^\t/gm,"")+r.substring(t,r.length)}return r.substring(0,e)+n},applyDuplication:function(e,t,r){if(e!==t)return r.substring(0,e)+r.substring(e,t)+(~r.charAt(e-1).indexOf("\n")||~r.charAt(e).indexOf("\n")?"\n":"")+r.substring(e,t)+r.substring(t);var n=new RegExp("(?:.|\n){0,"+t+"}\n([^].+)(?:.|\n)*","m"),i="";r.replace(n,function(e,t){return i+=t});return r.replace(i,i+"\n"+i)}},hub:function(e){switch(e.key){case this.keys.TAB:return e.shiftKey?this.handlers.removeTab:this.handlers.applyTab;case this.keys.DUPLICATE:return e.ctrlKey||e.metaKey?this.handlers.applyDuplication:false;case this.keys.INDENT:return e.ctrlKey||e.metaKey?this.handlers.applyIndentation:false;case this.keys.UNINDENT:return e.ctrlKey||e.metaKey?this.handlers.removeIndentation:false;default:return false}}};function h(e){return Math.max(parseInt(window.getComputedStyle(e).height),parseInt(e.style.height)||0)}function v(e){if(e.scrollTop)e.style.height=e.scrollTop+h(e)+"px";return e}var m=function(e,t,r){var h=this;var m={editor:t,preview:r,parent:e,_latency:null,_editorIsResizable:null};var g=function(){h.timeout=null;var e={object:document,listeners:[{type:"drop",capture:false,listener:f.inhibitDefault},{type:"dragover",capture:false,listener:f.inhibitDefault},{type:"dragenter",capture:false,listener:f.inhibitDefault},{type:"dragleave",capture:false,listener:f.inhibitDefault}]},r={object:m.editor,listeners:[{type:"drop",capture:false,listener:b},{type:"input",capture:true,listener:x},{type:"keydown",capture:true,listener:w},{type:"dragover",capture:false,listener:f.onDragEnter},{type:"dragenter",capture:false,listener:f.onDragEnter},{type:"dragleave",capture:false,listener:f.inhibitDefault},{type:"compositionstart",capture:true,listener:w}]};if(!t.style.maxHeight)t.style.maxHeight=window.innerHeight*75/100+"px";n.mountEvents(r,e);m.editor.style.transition="opacity 1s ease";m.editor.style.webkitTransition="opacity 1s ease";m._latency=Math.max(parseInt(m.editor.getAttribute(s))||0,u);m._editorIsResizable=(m.editor.getAttribute(a).match(/True/i)||[]).length>0;T();n.triggerCustomEvent("markdownx.init")};var y=function(){clearTimeout(h.timeout);h.timeout=setTimeout(T,m._latency)};var x=function(){m.editor=m._editorIsResizable?v(m.editor):m.editor;return y()};var b=function(e){if(e.dataTransfer&&e.dataTransfer.files.length)Object.keys(e.dataTransfer.files).map(function(t){return E(e.dataTransfer.files[t])});f.inhibitDefault(e)};var w=function(e){var t=p.hub(e);if(typeof t!="function")return false;f.inhibitDefault(e);var r=m.editor.selectionStart;m.editor.value=t(m.editor.selectionStart,m.editor.selectionEnd,m.editor.value);y();m.editor.focus();m.editor.selectionEnd=m.editor.selectionStart=r;return false};var E=function(e){m.editor.style.opacity=c;var t=new n.Request(m.editor.getAttribute(i),n.preparePostData({image:e}));t.success=function(e){var t=JSON.parse(e);if(t.image_code){k(t.image_code);n.triggerCustomEvent("markdownx.fileUploadEnd",m.parent,[t])}else if(t.image_path){k('');n.triggerCustomEvent("markdownx.fileUploadEnd",m.parent,[t])}else{console.error(l,t);n.triggerCustomEvent("markdownx.fileUploadError",m.parent,[t])}m.preview.innerHTML=h.response;m.editor.style.opacity=d};t.error=function(e){m.editor.style.opacity=d;console.error(e);n.triggerCustomEvent("fileUploadError",m.parent,[e])};return t.send()};var T=function(){var e=new n.Request(m.editor.getAttribute(o),n.preparePostData({content:m.editor.value}));e.success=function(e){m.preview.innerHTML=e;m.editor=v(m.editor);n.triggerCustomEvent("markdownx.update",m.parent,[e])};e.error=function(e){console.error(e);n.triggerCustomEvent("markdownx.updateError",m.parent,[e])};return e.send()};var k=function(e){var t=m.editor.selectionStart,r=m.editor.value,i=r.substring(0,t),o=r.substring(t,r.length);m.editor.value=""+i+e+o;m.editor.selectionStart=t+e.length;m.editor.selectionEnd=t+e.length;n.triggerEvent(m.editor,"keyup");x()};g()};r.MarkdownX=m;(function(e,t){e=e||"docReady";t=t||window;var r=[],n=false,i=false;var o=function(){if(!n){n=true;r.map(function(e){return e.fn.call(window,e.ctx)});r=[]}};var a=function(){return document.readyState==="complete"?o():null};t[e]=function(e,t){if(n){setTimeout(function(){return e(t)},1);return}else{r.push({fn:e,ctx:t})}if(document.readyState==="complete"){setTimeout(o,1)}else if(!i){document.addEventListener("DOMContentLoaded",o,false);window.addEventListener("load",o,false);i=true}}})("docReady",window);docReady(function(){var e=document.getElementsByClassName("markdownx");return Object.keys(e).map(function(t){return new m(e[t],e[t].querySelector(".markdownx-editor"),e[t].querySelector(".markdownx-preview"))})})},{"./utils":2}],2:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:true});function n(e){if(document.cookie&&document.cookie.length){var t=document.cookie.split(";").filter(function(t){return t.indexOf(e+"=")!==-1})[0];try{return decodeURIComponent(t.trim().substring(e.length+1))}catch(t){if(t instanceof TypeError){console.info('No cookie with key "'+e+'". Wrong name?');return null}throw t}}return null}r.getCookie=n;function i(){var e=[];for(var t=0;t-1)r.splice(n,1);e.className=r.join(" ")}})}r.removeClass=f},{}]},{},[1]);
\ No newline at end of file
+(function e(t,r,n){function a(s,o){if(!r[s]){if(!t[s]){var u=typeof require=="function"&&require;if(!o&&u)return u(s,!0);if(i)return i(s,!0);var l=new Error("Cannot find module '"+s+"'");throw l.code="MODULE_NOT_FOUND",l}var c=r[s]={exports:{}};t[s][0].call(c.exports,function(e){var r=t[s][1][e];return a(r?r:e)},c,c.exports,e,t,r,n)}return r[s].exports}var i=typeof require=="function"&&require;for(var s=0;s0&&e.value[e.start-1].match(/\t/)!==null?e.start-1:e.start;t=e.value.substring(e.start).replace("\t","")}else if(!r){t=e.value.substring(e.start).replace("\t","")}else{t=e.value.substring(e.start,e.end).replace(/^\t/gm,"")+e.value.substring(e.end)}return e.value.substring(0,e.start)+t},applyDuplication:function(e){if(e.start!==e.end)return e.value.substring(0,e.start)+e.value.substring(e.start,e.end)+(~e.value.charAt(e.start-1).indexOf("\n")||~e.value.charAt(e.start).indexOf("\n")?"\n":"")+e.value.substring(e.start,e.end)+e.value.substring(e.end);var t=new RegExp("(?:.|\n){0,160}(^.*$)","m"),r="";e.value.replace(t,function(e,t){return r+=t});return e.value.replace(r,r+"\n"+r)}},hub:function(e){switch(e.key){case this.keys.TAB:return e.shiftKey?this.handlers.removeTab:this.handlers.applyTab;case this.keys.DUPLICATE:return e.ctrlKey||e.metaKey?this.handlers.applyDuplication:false;case this.keys.INDENT:return e.ctrlKey||e.metaKey?this.handlers.applyIndentation:false;case this.keys.UNINDENT:return e.ctrlKey||e.metaKey?this.handlers.removeIndentation:false;default:return false}}};function v(e){return Math.max(parseInt(window.getComputedStyle(e).height),parseInt(e.style.height)||0)}function h(e){if(e.scrollTop)e.style.height=e.scrollTop+v(e)+"px";return e}var m=function(e,t,r){var v=this;var m={editor:t,preview:r,parent:e,_latency:null,_editorIsResizable:null};var g=function(){v.timeout=null;var e={object:document,listeners:[{type:"drop",capture:false,listener:f.inhibitDefault},{type:"dragover",capture:false,listener:f.inhibitDefault},{type:"dragenter",capture:false,listener:f.inhibitDefault},{type:"dragleave",capture:false,listener:f.inhibitDefault}]},r={object:m.editor,listeners:[{type:"drop",capture:false,listener:b},{type:"input",capture:true,listener:x},{type:"keydown",capture:true,listener:w},{type:"dragover",capture:false,listener:f.onDragEnter},{type:"dragenter",capture:false,listener:f.onDragEnter},{type:"dragleave",capture:false,listener:f.inhibitDefault},{type:"compositionstart",capture:true,listener:w}]};if(!t.style.maxHeight)t.style.maxHeight=window.innerHeight*75/100+"px";n.mountEvents(r,e);m.editor.style.transition="opacity 1s ease";m.editor.style.webkitTransition="opacity 1s ease";m._latency=Math.max(parseInt(m.editor.getAttribute(o))||0,u);m._editorIsResizable=(m.editor.getAttribute(s).match(/true/i)||[]).length>0;T();n.triggerCustomEvent("markdownx.init")};var y=function(){clearTimeout(v.timeout);v.timeout=setTimeout(T,m._latency)};var x=function(){m.editor=m._editorIsResizable?h(m.editor):m.editor;return y()};var b=function(e){if(e.dataTransfer&&e.dataTransfer.files.length)Object.keys(e.dataTransfer.files).map(function(t){return E(e.dataTransfer.files[t])});f.inhibitDefault(e)};var w=function(e){var t=p.hub(e);if(typeof t!="function")return false;f.inhibitDefault(e);var r=m.editor.selectionStart;m.editor.value=t({start:m.editor.selectionStart,end:m.editor.selectionEnd,value:m.editor.value});y();m.editor.focus();m.editor.selectionEnd=m.editor.selectionStart=r;return false};var E=function(e){m.editor.style.opacity=c;var t=new n.Request(m.editor.getAttribute(a),n.preparePostData({image:e}));t.success=function(e){var t=JSON.parse(e);if(t.image_code){k(t.image_code);n.triggerCustomEvent("markdownx.fileUploadEnd",m.parent,[t])}else if(t.image_path){k('');n.triggerCustomEvent("markdownx.fileUploadEnd",m.parent,[t])}else{console.error(l,t);n.triggerCustomEvent("markdownx.fileUploadError",m.parent,[t]);return null}m.editor.style.opacity=d};t.error=function(e){m.editor.style.opacity=d;console.error(e);n.triggerCustomEvent("fileUploadError",m.parent,[e])};return t.send()};var T=function(){var e=new n.Request(m.editor.getAttribute(i),n.preparePostData({content:m.editor.value}));e.success=function(e){m.preview.innerHTML=e;m.editor=h(m.editor);n.triggerCustomEvent("markdownx.update",m.parent,[e])};e.error=function(e){console.error(e);n.triggerCustomEvent("markdownx.updateError",m.parent,[e])};return e.send()};var k=function(e){m.editor.value=m.editor.value.substring(0,m.editor.selectionStart)+"\n\n"+e+("\n\n"+m.editor.value.substring(m.editor.selectionEnd));m.editor.selectionStart=m.editor.selectionEnd=m.editor.selectionStart+e.length;n.triggerEvent(m.editor,"keyup");x()};g()};r.MarkdownX=m;(function(e,t){e=e||"docReady";t=t||window;var r=[],n=false,a=false;var i=function(){if(!n){n=true;r.map(function(e){return e.fn.call(window,e.ctx)});r=[]}};var s=function(){return document.readyState==="complete"?i():null};t[e]=function(e,t){if(n){setTimeout(function(){return e(t)},1);return}else{r.push({fn:e,ctx:t})}if(document.readyState==="complete"){setTimeout(i,1)}else if(!a){document.addEventListener("DOMContentLoaded",i,false);window.addEventListener("load",i,false);a=true}}})("docReady",window);docReady(function(){var e=document.getElementsByClassName("markdownx");return Object.keys(e).map(function(t){return new m(e[t],e[t].querySelector(".markdownx-editor"),e[t].querySelector(".markdownx-preview"))})})},{"./utils":2}],2:[function(e,t,r){"use strict";Object.defineProperty(r,"__esModule",{value:true});function n(e){if(document.cookie&&document.cookie.length){var t=document.cookie.split(";").filter(function(t){return t.indexOf(e+"=")!==-1})[0];try{return decodeURIComponent(t.trim().substring(e.length+1))}catch(t){if(t instanceof TypeError){console.info('No cookie with key "'+e+'". Wrong name?');return null}throw t}}return null}r.getCookie=n;function a(){var e=[];for(var t=0;t-1)r.splice(n,1);e.className=r.join(" ")}})}r.removeClass=f},{}]},{},[1]);
\ No newline at end of file