From 8506ea179d9fcc1b9e44f73820b64915e7bf0533 Mon Sep 17 00:00:00 2001 From: Pouria Hadjibagheri Date: Sat, 18 Mar 2017 20:53:22 +0000 Subject: [PATCH] Now supports TAB indentations (including in selected sub-strings) and unindentation with SHIFT + TAB. --- markdownx/static/markdownx/js/markdownx.js | 442 ++++++------------ .../static/markdownx/js/markdownx.min.js | 2 +- markdownx/static/markdownx/js/src/_backup | 287 ++++++++++++ .../static/markdownx/js/src/markdownx.ts | 116 ++++- testapp/models.py | 1 + 5 files changed, 537 insertions(+), 311 deletions(-) create mode 100644 markdownx/static/markdownx/js/src/_backup diff --git a/markdownx/static/markdownx/js/markdownx.js b/markdownx/static/markdownx/js/markdownx.js index 81aa64c..83b580f 100644 --- a/markdownx/static/markdownx/js/markdownx.js +++ b/markdownx/static/markdownx/js/markdownx.js @@ -16,349 +16,205 @@ var utils_1 = require("./utils"); var UPLOAD_URL_ATTRIBUTE = "data-markdownx-upload-urls-path", PROCESSING_URL_ATTRIBUTE = "data-markdownx-urls-path"; // --------------------------------------------------------------------------------------------------------------------- /** + * + * @param {number} start + * @param {number} end + * @param {string} value + * @returns {string} + */ +function tabKeyEvent(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)); +} +/** + * + * @param {number} start + * @param {number} end + * @param {string} value + * @returns {string} + */ +function shiftTabKeyEvent(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/, ''); + } + else if (!lineNumbers) { + // Replacing `\t` within a single line selection. + endString = value.substring(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); + } + return value.substring(0, start) + endString; +} +/** + * @example + * + * let editor = document.getElementById('MyMarkdownEditor'), + * preview = document.getElementById('MyMarkdownPreview'); + * + * let mdx = new MarkdownX(editor, preview) * * @param {HTMLTextAreaElement} editor - Markdown editor element. * @param {HTMLElement} preview - Markdown preview element. */ -// const MarkdownX = function (editor: HTMLTextAreaElement, preview: Element) { -// -// this.editor = editor; -// this.preview = preview; -// this.editorIsResizable = this.editor.style.resize == 'none'; -// this.timeout = null; -// -// this.getEditorHeight = () => `${this.editor.scrollHeight}px`; -// -// this.markdownify = (): void => { -// -// clearTimeout(this.timeout); -// this.timeout = setTimeout(this.getMarkdown, 500) -// -// }; -// -// this.updateHeight = (): void => { -// -// this.editorIsResizable ? this.editor.style.height = this.getEditorHeight() : null -// -// }; -// -// this.inputChanged = (): void => { -// -// this.updateHeight(); -// this.markdownify() -// -// }; -// -// // ToDo: Deprecate. -// this.onHtmlEvents = (event: Event): void => this.routineEventResponse(event); -// -// this.routineEventResponse = (event: any): void => { -// -// event.preventDefault(); -// event.stopPropagation() -// -// }; -// -// this.onDragEnterEvent = (event: any): void => { -// -// event.dataTransfer.dropEffect = 'copy'; -// this.routineEventResponse(event) -// -// }; -// -// this.onDragLeaveEvent = (event: Event): void => this.routineEventResponse(event); -// -// this.onDropEvent = (event: any): void => { -// -// if (event.dataTransfer && event.dataTransfer.files.length) -// Object.keys(event.dataTransfer.files).map(fileKey => this.sendFile(event.dataTransfer.files[fileKey])); -// -// this.routineEventResponse(event); -// -// }; -// -// this.onKeyDownEvent = (event: any): Boolean | null => { -// -// const TAB_ASCII_CODE = 9; -// -// if (event.keyCode !== TAB_ASCII_CODE) return null; -// -// let start: number = this.editor.selectionStart, -// end: number = this.editor.selectionEnd, -// value: string = this.editor.value; -// -// this.editor.value = `${value.substring(0, start)}\t${value.substring(end)}`; -// this.editor.selectionStart = this.editor.selectionEnd = start++; -// -// this.markdownify(); -// -// this.editor.focus(); -// -// return false -// -// }; -// -// this.sendFile = (file: File): void => { -// -// this.editor.style.opacity = "0.3"; -// -// const xhr = new Request( -// this.editor.getAttribute(UPLOAD_URL_ATTRIBUTE), // URL -// preparePostData({image: file}) // Data -// ); -// -// xhr.success = (resp: string): void => { -// -// const response = JSON.parse(resp); -// -// if (response.image_code) { -// -// this.insertImage(response.image_code); -// triggerCustomEvent('markdownx.fileUploadEnd', [response]) -// -// } else if (response.image_path) { -// -// // ToDo: Deprecate. -// this.insertImage(`![]("${response.image_path}")`); -// triggerCustomEvent('markdownx.fileUploadEnd', [response]) -// -// } else { -// -// console.error('Wrong response', response); -// triggerCustomEvent('markdownx.fileUploadError', [response]) -// -// } -// -// this.preview.innerHTML = this.response; -// this.editor.style.opacity = "1"; -// -// }; -// -// xhr.error = (response: string): void => { -// -// this.editor.style.opacity = "1"; -// console.error(response); -// triggerCustomEvent('fileUploadError', [response]) -// -// }; -// -// xhr.send() -// -// }; -// -// this.getMarkdown = (): void => { -// -// const xhr = new Request( -// this.editor.getAttribute(PROCESSING_URL_ATTRIBUTE), // URL -// preparePostData({content: this.editor.value}) // Data -// ); -// -// xhr.success = (response: string): void => { -// this.preview.innerHTML = response; -// this.updateHeight(); -// triggerCustomEvent('markdownx.update', [response]) -// }; -// -// xhr.error = (response: string): void => { -// console.error(response); -// triggerCustomEvent('markdownx.updateError', [response]) -// }; -// -// xhr.send() -// -// }; -// -// this.insertImage = (textToInsert): void => { -// -// let cursorPosition = this.editor.selectionStart, -// text = this.editor.value, -// textBeforeCursor = text.substring(0, cursorPosition), -// textAfterCursor = text.substring(cursorPosition, text.length); -// -// this.editor.value = `${textBeforeCursor}${textToInsert}${textAfterCursor}`; -// this.editor.selectionStart = cursorPosition + textToInsert.length; -// this.editor.selectionEnd = cursorPosition + textToInsert.length; -// -// triggerEvent(this.editor, 'keyup'); -// this.inputChanged(); -// -// }; -// -// // Events -// // ---------------------------------------------------------------------------------------------- -// let documentListeners = { -// // ToDo: Deprecate. -// object: document, -// listeners: [ -// { type: 'drop' , capture: false, listener: this.onHtmlEvents }, -// { type: 'dragover' , capture: false, listener: this.onHtmlEvents }, -// { type: 'dragenter', capture: false, listener: this.onHtmlEvents }, -// { type: 'dragleave', capture: false, listener: this.onHtmlEvents } -// ] -// }, -// editorListeners = { -// object: this.editor, -// listeners: [ -// { type: 'drop', capture: false, listener: this.onDropEvent }, -// { type: 'input', capture: true , listener: this.inputChanged }, -// { type: 'keydown', capture: true , listener: this.onKeyDownEvent }, -// { type: 'dragover', capture: false, listener: this.onDragEnterEvent }, -// { type: 'dragenter', capture: false, listener: this.onDragEnterEvent }, -// { type: 'dragleave', capture: false, listener: this.onDragLeaveEvent }, -// { type: 'compositionstart', capture: true , listener: this.onKeyDownEvent } -// ] -// }; -// -// // Initialise -// // ---------------------------------------------------------------------------------------------- -// -// mountEvents(editorListeners); -// mountEvents(documentListeners); // ToDo: Deprecate. -// triggerCustomEvent('markdownx.init'); -// this.editor.style.transition = "opacity 1s ease"; -// this.editor.style.webkitTransition = "opacity 1s ease"; -// this.getMarkdown(); -// this.inputChanged() -// -// }; -var MarkdownX = (function () { - function MarkdownX(editor, preview) { - this.UPLOAD_URL_ATTRIBUTE = "data-markdownx-upload-urls-path"; - this.PROCESSING_URL_ATTRIBUTE = "data-markdownx-urls-path"; - this.editor = editor; - this.preview = preview; - this.editorIsResizable = this.editor.style.resize == 'none'; - this.timeout = null; - // Events - // ---------------------------------------------------------------------------------------------- - var documentListeners = { - // ToDo: Deprecate. - object: document, - listeners: [ - { type: 'drop', capture: false, listener: this.onHtmlEvents }, - { type: 'dragover', capture: false, listener: this.onHtmlEvents }, - { type: 'dragenter', capture: false, listener: this.onHtmlEvents }, - { type: 'dragleave', capture: false, listener: this.onHtmlEvents } - ] - }, editorListeners = { - object: this.editor, - listeners: [ - { type: 'drop', capture: false, listener: this.onDrop }, - { type: 'input', capture: true, listener: this.inputChanged }, - { type: 'keydown', capture: true, listener: this.onKeyDown }, - { type: 'dragover', capture: false, listener: this.onDragEnter }, - { type: 'dragenter', capture: false, listener: this.onDragEnter }, - { type: 'dragleave', capture: false, listener: this.onDragLeave }, - { type: 'compositionstart', capture: true, listener: this.onKeyDown } - ] - }; - // Initialise - // ---------------------------------------------------------------------------------------------- - utils_1.mountEvents(editorListeners); - utils_1.mountEvents(documentListeners); // ToDo: Deprecate. - utils_1.triggerCustomEvent('markdownx.init'); - this.editor.style.transition = "opacity 1s ease"; - this.editor.style.webkitTransition = "opacity 1s ease"; - this.getMarkdown(); - this.inputChanged(); - } - MarkdownX.prototype.getEditorHeight = function () { return this.editor.scrollHeight + "px"; }; - MarkdownX.prototype._markdownify = function () { - clearTimeout(this.timeout); - this.timeout = setTimeout(this.getMarkdown, 500); +var MarkdownX = function (editor, preview) { + var _this = this; + this.editor = editor; + this.preview = preview; + this._editorIsResizable = this.editor.style.resize == 'none'; + this.timeout = null; + this.getEditorHeight = function (editor) { return editor.scrollHeight + "px"; }; + /** + * settings for ``timeout``. + * + * @private + */ + this._markdownify = function () { + clearTimeout(_this.timeout); + _this.timeout = setTimeout(_this.getMarkdown, 500); }; - MarkdownX.prototype.updateHeight = function () { - this.editorIsResizable ? this.editor.style.height = this.getEditorHeight() : null; + this.updateHeight = function () { + _this._editorIsResizable ? _this.editor.style.height = _this.getEditorHeight(_this.editor) : null; }; - MarkdownX.prototype.inputChanged = function () { - this.updateHeight(); - this._markdownify(); + this.inputChanged = function () { + _this.updateHeight(); + _this._markdownify(); }; // ToDo: Deprecate. - MarkdownX.prototype.onHtmlEvents = function (event) { this._routineEventResponse(event); }; - MarkdownX.prototype._routineEventResponse = function (event) { + this.onHtmlEvents = function (event) { return _this._routineEventResponse(event); }; + /** + * Routine tasks for event handlers (e.g. default preventions). + * + * @param {Event} event + * @private + */ + this._routineEventResponse = function (event) { event.preventDefault(); event.stopPropagation(); }; - MarkdownX.prototype.onDragEnter = function (event) { + this.onDragEnter = function (event) { event.dataTransfer.dropEffect = 'copy'; - this._routineEventResponse(event); + _this._routineEventResponse(event); }; - MarkdownX.prototype.onDragLeave = function (event) { - this._routineEventResponse(event); - }; - MarkdownX.prototype.onDrop = function (event) { - var _this = this; + this.onDragLeave = function (event) { return _this._routineEventResponse(event); }; + this.onDrop = function (event) { if (event.dataTransfer && event.dataTransfer.files.length) Object.keys(event.dataTransfer.files).map(function (fileKey) { return _this.sendFile(event.dataTransfer.files[fileKey]); }); - this._routineEventResponse(event); + _this._routineEventResponse(event); }; - MarkdownX.prototype.onKeyDown = function (event) { - var TAB_ASCII_CODE = 9; - if (event.keyCode !== TAB_ASCII_CODE) + this.onKeyDown = function (event) { + // ASCII code references: + var TAB_KEY = 9, SELECTION_START = _this.editor.selectionStart; + if (event.keyCode !== TAB_KEY) return null; - var start = this.editor.selectionStart, end = this.editor.selectionEnd, value = this.editor.value; - this.editor.value = value.substring(0, start) + "\t" + value.substring(end); - this.editor.selectionStart = this.editor.selectionEnd = start++; - this._markdownify(); - this.editor.focus(); + event.preventDefault(); + var handlerFunc = event.shiftKey && event.keyCode === TAB_KEY ? shiftTabKeyEvent : tabKeyEvent; + _this.editor.value = handlerFunc(_this.editor.selectionStart, _this.editor.selectionEnd, _this.editor.value); + _this._markdownify(); + _this.editor.focus(); + _this.editor.selectionEnd = _this.editor.selectionStart = SELECTION_START; return false; }; - MarkdownX.prototype.sendFile = function (file) { - var preview = this.preview, editor = this.editor, url = this.UPLOAD_URL_ATTRIBUTE, xhr = new utils_1.Request(editor.getAttribute(url), // URL + /** + * + * @param file + * @returns {Request} + */ + this.sendFile = function (file) { + _this.editor.style.opacity = "0.3"; + var xhr = new utils_1.Request(_this.editor.getAttribute(UPLOAD_URL_ATTRIBUTE), // URL utils_1.preparePostData({ image: file }) // Data ); - editor.style.opacity = "0.3"; xhr.success = function (resp) { var response = JSON.parse(resp); if (response.image_code) { - this.insertImage(response.image_code); + _this.insertImage(response.image_code); utils_1.triggerCustomEvent('markdownx.fileUploadEnd', [response]); } else if (response.image_path) { // ToDo: Deprecate. - this.insertImage("![](\"" + response.image_path + "\")"); + _this.insertImage("![](\"" + response.image_path + "\")"); utils_1.triggerCustomEvent('markdownx.fileUploadEnd', [response]); } else { console.error('Wrong response', response); utils_1.triggerCustomEvent('markdownx.fileUploadError', [response]); } - preview.innerHTML = this.response; - editor.style.opacity = "1"; + _this.preview.innerHTML = _this.response; + _this.editor.style.opacity = "1"; }; xhr.error = function (response) { - editor.style.opacity = "1"; + _this.editor.style.opacity = "1"; console.error(response); utils_1.triggerCustomEvent('fileUploadError', [response]); }; - xhr.send(); + return xhr.send(); }; - MarkdownX.prototype.getMarkdown = function () { - var preview = this.preview, editor = this.editor, url = this.PROCESSING_URL_ATTRIBUTE, xhr = new utils_1.Request(editor.getAttribute(url), // URL - utils_1.preparePostData({ content: this.editor.value }) // Data + /** + * + * @returns {Request} + */ + this.getMarkdown = function () { + var xhr = new utils_1.Request(_this.editor.getAttribute(PROCESSING_URL_ATTRIBUTE), // URL + utils_1.preparePostData({ content: _this.editor.value }) // Data ); xhr.success = function (response) { - preview.innerHTML = response; - this.updateHeight(); + _this.preview.innerHTML = response; + _this.updateHeight(); utils_1.triggerCustomEvent('markdownx.update', [response]); }; xhr.error = function (response) { console.error(response); utils_1.triggerCustomEvent('markdownx.updateError', [response]); }; - xhr.send(); + return xhr.send(); }; - MarkdownX.prototype.insertImage = function (textToInsert) { - var cursorPosition = this.editor.selectionStart, text = this.editor.value, textBeforeCursor = text.substring(0, cursorPosition), textAfterCursor = text.substring(cursorPosition, text.length); - this.editor.value = "" + textBeforeCursor + textToInsert + textAfterCursor; - this.editor.selectionStart = cursorPosition + textToInsert.length; - this.editor.selectionEnd = cursorPosition + textToInsert.length; - utils_1.triggerEvent(this.editor, 'keyup'); - this.inputChanged(); + this.insertImage = function (textToInsert) { + var cursorPosition = _this.editor.selectionStart, text = _this.editor.value, textBeforeCursor = text.substring(0, cursorPosition), textAfterCursor = text.substring(cursorPosition, text.length); + _this.editor.value = "" + textBeforeCursor + textToInsert + textAfterCursor; + _this.editor.selectionStart = cursorPosition + textToInsert.length; + _this.editor.selectionEnd = cursorPosition + textToInsert.length; + utils_1.triggerEvent(_this.editor, 'keyup'); + _this.inputChanged(); }; - return MarkdownX; -}()); + // Events + // ---------------------------------------------------------------------------------------------- + var documentListeners = { + // ToDo: Deprecate. + object: document, + listeners: [ + { type: 'drop', capture: false, listener: this.onHtmlEvents }, + { type: 'dragover', capture: false, listener: this.onHtmlEvents }, + { type: 'dragenter', capture: false, listener: this.onHtmlEvents }, + { type: 'dragleave', capture: false, listener: this.onHtmlEvents } + ] + }, editorListeners = { + object: this.editor, + listeners: [ + { type: 'drop', capture: false, listener: this.onDrop }, + { type: 'input', capture: true, listener: this.inputChanged }, + { type: 'keydown', capture: true, listener: this.onKeyDown }, + { type: 'dragover', capture: false, listener: this.onDragEnter }, + { type: 'dragenter', capture: false, listener: this.onDragEnter }, + { type: 'dragleave', capture: false, listener: this.onDragLeave }, + { type: 'compositionstart', capture: true, listener: this.onKeyDown } + ] + }; + // Initialise + // ---------------------------------------------------------------------------------------------- + utils_1.mountEvents(editorListeners); + utils_1.mountEvents(documentListeners); // ToDo: Deprecate. + utils_1.triggerCustomEvent('markdownx.init'); + this.editor.style.transition = "opacity 1s ease"; + this.editor.style.webkitTransition = "opacity 1s ease"; + this.getMarkdown(); + this.inputChanged(); +}; (function (funcName, baseObj) { // The public function name defaults to window.docReady // but you can pass in your own object and own function diff --git a/markdownx/static/markdownx/js/markdownx.min.js b/markdownx/static/markdownx/js/markdownx.min.js index 550ce3d..cffd877 100644 --- a/markdownx/static/markdownx/js/markdownx.min.js +++ b/markdownx/static/markdownx/js/markdownx.min.js @@ -1 +1 @@ -!function t(e,n,r){function o(s,a){if(!n[s]){if(!e[s]){var u="function"==typeof require&&require;if(!a&&u)return u(s,!0);if(i)return i(s,!0);var c=new Error("Cannot find module '"+s+"'");throw c.code="MODULE_NOT_FOUND",c}var p=n[s]={exports:{}};e[s][0].call(p.exports,function(t){var n=e[s][1][t];return o(n?n:t)},p,p.exports,t,e,n,r)}return n[s].exports}for(var i="function"==typeof require&&require,s=0;s .markdownx-editor"),e=document.querySelectorAll(".markdownx > .markdownx-preview");return r.zip(t,e).map(function(t){return new o(t[0],t[1])})})},{"./utils":2}],2:[function(t,e,n){"use strict";function r(t){if(document.cookie&&document.cookie.length){var e=document.cookie.split(";").filter(function(e){return e.indexOf(t+"=")!==-1})[0];try{return decodeURIComponent(e.trim().substring(t.length+1))}catch(e){if(e instanceof TypeError)return console.info('No cookie with key "'+t+'". Wrong name?'),null;throw e}}return null}function o(){for(var t=[],e=0;e-1&&n.splice(r,1),t.className=n.join(" ")}})}n.__esModule=!0,n.getCookie=r,n.zip=o,n.mountEvents=i,n.preparePostData=s;var d=function(){if("XMLHttpRequest"in window)return new XMLHttpRequest;try{return new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(t){}try{return new ActiveXObject("Msxml2.XMLHTTP.3.0")}catch(t){}try{return new ActiveXObject("Microsoft.XMLHTTP")}catch(t){}throw alert("Your browser belongs to history!"),new TypeError("This browser does not support AJAX requests.")},l=function(){function t(t,e){this.xhr=d(),this.url=t,this.data=e}return t.prototype.progress=function(t){t.lengthComputable&&console.log(t.loaded/t.total*100+"% uploaded")},t.prototype.error=function(t){console.error(t)},t.prototype.success=function(t){console.info(t)},t.prototype.send=function(){var t=this,e=this.success,n=this.error,r=this.progress;this.xhr.open("POST",this.url,!0),this.xhr.setRequestHeader("X-Requested-With","XMLHttpRequest"),this.xhr.upload.onprogress=function(t){return r(t)},this.xhr.onerror=function(e){n(t.xhr.responseText)},this.xhr.onload=function(n){var r=null;t.xhr.readyState==XMLHttpRequest.DONE&&(r=t.xhr.responseType&&"text"!==t.xhr.responseType?"document"===t.xhr.responseType?t.xhr.responseXML:t.xhr.response:t.xhr.responseText),e(r)},this.xhr.send(this.data)},t}();n.Request=l,n.triggerEvent=a,n.triggerCustomEvent=u,n.addClass=c,n.removeClass=p},{}]},{},[1]); \ No newline at end of file +!function e(t,n,r){function o(s,u){if(!n[s]){if(!t[s]){var a="function"==typeof require&&require;if(!u&&a)return a(s,!0);if(i)return i(s,!0);var c=new Error("Cannot find module '"+s+"'");throw c.code="MODULE_NOT_FOUND",c}var d=n[s]={exports:{}};t[s][0].call(d.exports,function(e){var n=t[s][1][e];return o(n?n:e)},d,d.exports,e,t,n,r)}return n[s].exports}for(var i="function"==typeof require&&require,s=0;s0&&null!==n[e-1].match(/\t/)?e-1:e,r=n.substring(e).replace(/\t/,"")):r=o?n.substring(e,t).replace(/^\t/gm,"")+n.substring(t,n.length):n.substring(e).replace(/\t/,""),n.substring(0,e)+r}n.__esModule=!0;var i=e("./utils"),s=function(e,t){var n=this;this.editor=e,this.preview=t,this._editorIsResizable="none"==this.editor.style.resize,this.timeout=null,this.getEditorHeight=function(e){return e.scrollHeight+"px"},this._markdownify=function(){clearTimeout(n.timeout),n.timeout=setTimeout(n.getMarkdown,500)},this.updateHeight=function(){n._editorIsResizable&&(n.editor.style.height=n.getEditorHeight(n.editor))},this.inputChanged=function(){n.updateHeight(),n._markdownify()},this.onHtmlEvents=function(e){return n._routineEventResponse(e)},this._routineEventResponse=function(e){e.preventDefault(),e.stopPropagation()},this.onDragEnter=function(e){e.dataTransfer.dropEffect="copy",n._routineEventResponse(e)},this.onDragLeave=function(e){return n._routineEventResponse(e)},this.onDrop=function(e){e.dataTransfer&&e.dataTransfer.files.length&&Object.keys(e.dataTransfer.files).map(function(t){return n.sendFile(e.dataTransfer.files[t])}),n._routineEventResponse(e)},this.onKeyDown=function(e){var t=n.editor.selectionStart;if(9!==e.keyCode)return null;e.preventDefault();var i=e.shiftKey&&9===e.keyCode?o:r;return n.editor.value=i(n.editor.selectionStart,n.editor.selectionEnd,n.editor.value),n._markdownify(),n.editor.focus(),n.editor.selectionEnd=n.editor.selectionStart=t,!1},this.sendFile=function(e){n.editor.style.opacity="0.3";var t=new i.Request(n.editor.getAttribute("data-markdownx-upload-urls-path"),i.preparePostData({image:e}));return t.success=function(e){var t=JSON.parse(e);t.image_code?(n.insertImage(t.image_code),i.triggerCustomEvent("markdownx.fileUploadEnd",[t])):t.image_path?(n.insertImage('![]("'+t.image_path+'")'),i.triggerCustomEvent("markdownx.fileUploadEnd",[t])):(console.error("Wrong response",t),i.triggerCustomEvent("markdownx.fileUploadError",[t])),n.preview.innerHTML=n.response,n.editor.style.opacity="1"},t.error=function(e){n.editor.style.opacity="1",console.error(e),i.triggerCustomEvent("fileUploadError",[e])},t.send()},this.getMarkdown=function(){var e=new i.Request(n.editor.getAttribute("data-markdownx-urls-path"),i.preparePostData({content:n.editor.value}));return e.success=function(e){n.preview.innerHTML=e,n.updateHeight(),i.triggerCustomEvent("markdownx.update",[e])},e.error=function(e){console.error(e),i.triggerCustomEvent("markdownx.updateError",[e])},e.send()},this.insertImage=function(e){var t=n.editor.selectionStart,r=n.editor.value,o=r.substring(0,t),s=r.substring(t,r.length);n.editor.value=""+o+e+s,n.editor.selectionStart=t+e.length,n.editor.selectionEnd=t+e.length,i.triggerEvent(n.editor,"keyup"),n.inputChanged()};var s={object:document,listeners:[{type:"drop",capture:!1,listener:this.onHtmlEvents},{type:"dragover",capture:!1,listener:this.onHtmlEvents},{type:"dragenter",capture:!1,listener:this.onHtmlEvents},{type:"dragleave",capture:!1,listener:this.onHtmlEvents}]},u={object:this.editor,listeners:[{type:"drop",capture:!1,listener:this.onDrop},{type:"input",capture:!0,listener:this.inputChanged},{type:"keydown",capture:!0,listener:this.onKeyDown},{type:"dragover",capture:!1,listener:this.onDragEnter},{type:"dragenter",capture:!1,listener:this.onDragEnter},{type:"dragleave",capture:!1,listener:this.onDragLeave},{type:"compositionstart",capture:!0,listener:this.onKeyDown}]};i.mountEvents(u),i.mountEvents(s),i.triggerCustomEvent("markdownx.init"),this.editor.style.transition="opacity 1s ease",this.editor.style.webkitTransition="opacity 1s ease",this.getMarkdown(),this.inputChanged()};!function(e,t){e=e||"docReady",t=t||window;var n=[],r=!1,o=!1,i=function(){r||(r=!0,n.map(function(e){return e.fn.call(window,e.ctx)}),n=[])};t[e]=function(e,t){if(r)return void setTimeout(function(){return e(t)},1);n.push({fn:e,ctx:t}),"complete"===document.readyState?setTimeout(i,1):o||(document.addEventListener("DOMContentLoaded",i,!1),window.addEventListener("load",i,!1),o=!0)}}("docReady",window),docReady(function(){var e=document.querySelectorAll(".markdownx > .markdownx-editor"),t=document.querySelectorAll(".markdownx > .markdownx-preview");return i.zip(e,t).map(function(e){return new s(e[0],e[1])})})},{"./utils":2}],2:[function(e,t,n){"use strict";function r(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)return console.info('No cookie with key "'+e+'". Wrong name?'),null;throw t}}return null}function o(){for(var e=[],t=0;t-1&&n.splice(r,1),e.className=n.join(" ")}})}n.__esModule=!0,n.getCookie=r,n.zip=o,n.mountEvents=i,n.preparePostData=s;var l=function(){if("XMLHttpRequest"in window)return new XMLHttpRequest;try{return new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(e){}try{return new ActiveXObject("Msxml2.XMLHTTP.3.0")}catch(e){}try{return new ActiveXObject("Microsoft.XMLHTTP")}catch(e){}throw alert("Your browser belongs to history!"),new TypeError("This browser does not support AJAX requests.")},p=function(){function e(e,t){this.xhr=l(),this.url=e,this.data=t}return e.prototype.progress=function(e){e.lengthComputable&&console.log(e.loaded/e.total*100+"% uploaded")},e.prototype.error=function(e){console.error(e)},e.prototype.success=function(e){console.info(e)},e.prototype.send=function(){var e=this,t=this.success,n=this.error,r=this.progress;this.xhr.open("POST",this.url,!0),this.xhr.setRequestHeader("X-Requested-With","XMLHttpRequest"),this.xhr.upload.onprogress=function(e){return r(e)},this.xhr.onerror=function(t){n(e.xhr.responseText)},this.xhr.onload=function(n){var r=null;e.xhr.readyState==XMLHttpRequest.DONE&&(r=e.xhr.responseType&&"text"!==e.xhr.responseType?"document"===e.xhr.responseType?e.xhr.responseXML:e.xhr.response:e.xhr.responseText),t(r)},this.xhr.send(this.data)},e}();n.Request=p,n.triggerEvent=u,n.triggerCustomEvent=a,n.addClass=c,n.removeClass=d},{}]},{},[1]); \ No newline at end of file diff --git a/markdownx/static/markdownx/js/src/_backup b/markdownx/static/markdownx/js/src/_backup new file mode 100644 index 0000000..2dd102a --- /dev/null +++ b/markdownx/static/markdownx/js/src/_backup @@ -0,0 +1,287 @@ +/// +/** + * Markdownx + * + * Frontend (JavaScript) management of Django-Markdownx module. + * + * Written in JavaScript (ECMA6), compiled in (ECMA6 - 2015). + * + * Requirements: + * - Modern browser with support for HTML5 and ECMA 2011+ (IE 9+). + */ + + +/** + * Looks for a cookie, and if found, returns the values. + * + * NOTE: Only the first item in the array is returned + * to eliminate the need for array deconstruction in + * the target. + * + * @param {string} name - The name of the cookie. + * @returns {string | null} + */ +function getCookie (name: string): string | null { + if (document.cookie && document.cookie.length) { + + const cookies = document.cookie + .split(';') + .filter(cookie => cookie.indexOf(`${name}=`) !== -1)[0]; + + try{ + + return decodeURIComponent(cookies.trim().substring(name.length + 1)); + + } catch (e){ + + if (e instanceof TypeError) { + console.info(`No cookie with key "${name}". Wrong name?`); + return null + } + + throw e + } + } + + return null; +} + +/** + * + * @param data + * @param csrf + * @returns {FormData} + */ +function preparePostData(data: Object, csrf=true){ + let form: FormData = new FormData(); + + csrf ? form.append("csrfmiddlewaretoken", getCookie('csrftoken')) : null; + + Object.keys(data).map(key => form.append(key, data[key])); + + return form +} + + +($ => { + + if (!$) $ = django.jQuery; + + /** + * + * @returns {JQuery|markdownx} + */ + $.fn.markdownx = function () { + + return this.each(() => { + + /** + * + */ + let getMarkdown = () => { + + $.ajax({ + type: 'POST', + url: markdownxEditor.data("markdownxUrlsPath"), + data: preparePostData({content: markdownxEditor.val()}), + processData: false, + contentType: false, + + success: response => { + markdownxPreview.html(response); + updateHeight(); + markdownx.trigger('markdownx.update', [response]); + }, + + error: response => { + console.error(response); + markdownx.trigger('markdownx.updateError', [response]); + } + }); + + }; + + /** + * + */ + let updateHeight = () => { + isMarkdownxEditorResizable ? + markdownxEditor.innerHeight(markdownxEditor.prop('scrollHeight')) : null + }; + + /** + * + * @param textToInsert + */ + let insertImage = textToInsert => { + let cursor_pos = markdownxEditor.prop('selectionStart'), + text = markdownxEditor.val(), + textBeforeCursor = text.substring(0, cursor_pos), + textAfterCursor = text.substring(cursor_pos, text.length); + + markdownxEditor + .val(textBeforeCursor + textToInsert + textAfterCursor) + .prop('selectionStart', cursor_pos + textToInsert.length) + .prop('selectionEnd', cursor_pos + textToInsert.length) + .keyup(); + + updateHeight(); + markdownify(); + }; + + /** + * + * @param file + */ + let sendFile = file => { + + $.ajax({ + type: 'POST', + url: markdownxEditor.data("markdownxUploadUrlsPath"), + data: preparePostData({image: file}), + processData: false, + contentType: false, + + beforeSend: () => { + markdownxEditor.fadeTo("fast", 0.3); + markdownx.trigger('markdownx.fileUploadBegin'); + }, + + success: response => { + markdownxEditor.fadeTo("fast", 1); + + if (response.image_code) { + insertImage(response.image_code); + markdownx.trigger('markdownx.fileUploadEnd', [response]); + } else if (response.image_path) { + // For backwards-compatibility + insertImage(`![]("${response.image_path}")`); + markdownx.trigger('markdownx.fileUploadEnd', [response]); + } else { + console.error('Wrong response', response); + markdownx.trigger('markdownx.fileUploadError', [response]); + } + }, + + error: response => { + console.error(response); + markdownxEditor.fadeTo("fast", 1); + markdownx.trigger('markdownx.fileUploadError', [response]); + } + }); + + }; + + let timeout; + + /** + * + */ + let markdownify = () => { + clearTimeout(timeout); + timeout = setTimeout(getMarkdown, 500); + }; + + /** + * + * @param event + * @returns {boolean} + */ + let onKeyDownEvent = function(event) { + const TAB_ASCII_CODE = 9; + + if (event.keyCode !== TAB_ASCII_CODE) return null; + + let start: number = this.editor.selectionStart, + end: number = this.editor.selectionEnd, + value: string = this.editor.value; + + this.editor.value = `${value.substring(0, start)}\t${value.substring(end)}`; + + this.editor.selectionStart = this.editor.selectionEnd = start++; + + markdownify(); + + this.editor.focus(); + + return false; + + }; + + /** + * + */ + let onInputChangeEvent = () => { + updateHeight(); + markdownify(); + }; + + /** + * + * @param event + */ + let onHtmlEvents = event => { + event.preventDefault(); + event.stopPropagation(); + }; + + /** + * + * @param event + */ + let onDragEnterEvent = event => { + event.originalEvent.dataTransfer.dropEffect = 'copy'; + event.preventDefault(); + event.stopPropagation(); + }; + + /** + * + * @param event + */ + let onDragLeaveEvent = event => { + event.preventDefault(); + event.stopPropagation(); + }; + + let onDropEvent = event => { + const dataTransfer = event.originalEvent.dataTransfer; + + if (dataTransfer) + dataTransfer.files.length ? dataTransfer.files.map((file) => sendFile(file)) : null; + + event.preventDefault(); + event.stopPropagation(); + }; + + // Init + + + let markdownx = $(this), + markdownxEditor: any = $(this).find('.markdownx-editor'), + markdownxPreview: any = $(this).find('.markdownx-preview'), + isMarkdownxEditorResizable = markdownxEditor.is("[data-markdownx-editor-resizable]"); + + $('html').on('dragenter.markdownx dragover.markdownx drop.markdownx dragleave.markdownx', onHtmlEvents); + + markdownxEditor + .on('keydown.markdownx', onKeyDownEvent) + .on('input.markdownx propertychange.markdownx', onInputChangeEvent) + .on('dragenter.markdownx dragover.markdownx', onDragEnterEvent) + .on('dragleave.markdownx', onDragLeaveEvent) + .on('drop.markdownx', onDropEvent); + + markdownx.trigger('markdownx.init'); + + updateHeight(); + markdownify(); + }); + }; + + $(() => { + + $('.markdownx').markdownx(); + + }); + +})(jQuery); diff --git a/markdownx/static/markdownx/js/src/markdownx.ts b/markdownx/static/markdownx/js/src/markdownx.ts index af93898..d74b9ba 100644 --- a/markdownx/static/markdownx/js/src/markdownx.ts +++ b/markdownx/static/markdownx/js/src/markdownx.ts @@ -29,6 +29,61 @@ const UPLOAD_URL_ATTRIBUTE: string = "data-markdownx-upload-urls-path", // --------------------------------------------------------------------------------------------------------------------- + +/** + * + * @param {number} start + * @param {number} end + * @param {string} value + * @returns {string} + */ +function tabKeyEvent(start: number, end: number, value: string): string { + + 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 {number} start + * @param {number} end + * @param {string} value + * @returns {string} + */ +function shiftTabKeyEvent(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) { + + // Replacing `\t` within a single line selection. + endString = value.substring(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); + + } + + return value.substring(0, start) + endString; + +} + + /** * @example * @@ -42,13 +97,18 @@ const UPLOAD_URL_ATTRIBUTE: string = "data-markdownx-upload-urls-path", */ const MarkdownX = function (editor: HTMLTextAreaElement, preview: Element) { - this.editor = editor; - this.preview = preview; - this.editorIsResizable = this.editor.style.resize == 'none'; - this.timeout = null; + this.editor = editor; + this.preview = preview; + this._editorIsResizable = this.editor.style.resize == 'none'; + this.timeout = null; - this.getEditorHeight = () => `${this.editor.scrollHeight}px`; + this.getEditorHeight = (editor: HTMLTextAreaElement) => `${editor.scrollHeight}px`; + /** + * settings for ``timeout``. + * + * @private + */ this._markdownify = (): void => { clearTimeout(this.timeout); @@ -58,7 +118,7 @@ const MarkdownX = function (editor: HTMLTextAreaElement, preview: Element) { this.updateHeight = (): void => { - this.editorIsResizable ? this.editor.style.height = this.getEditorHeight() : null + this._editorIsResizable ? this.editor.style.height = this.getEditorHeight(this.editor) : null }; @@ -72,6 +132,12 @@ const MarkdownX = function (editor: HTMLTextAreaElement, preview: Element) { // ToDo: Deprecate. this.onHtmlEvents = (event: Event): void => this._routineEventResponse(event); + /** + * Routine tasks for event handlers (e.g. default preventions). + * + * @param {Event} event + * @private + */ this._routineEventResponse = (event: any): void => { event.preventDefault(); @@ -99,26 +165,38 @@ const MarkdownX = function (editor: HTMLTextAreaElement, preview: Element) { this.onKeyDown = (event: any): Boolean | null => { - const TAB_ASCII_CODE = 9; + // ASCII code references: + const TAB_KEY: number = 9, + SELECTION_START: number = this.editor.selectionStart; - if (event.keyCode !== TAB_ASCII_CODE) return null; + if (event.keyCode !== TAB_KEY) return null; - let start: number = this.editor.selectionStart, - end: number = this.editor.selectionEnd, - value: string = this.editor.value; + event.preventDefault(); - this.editor.value = `${value.substring(0, start)}\t${value.substring(end)}`; - this.editor.selectionStart = this.editor.selectionEnd = start++; + let handlerFunc = event.shiftKey && event.keyCode === TAB_KEY ? shiftTabKeyEvent : tabKeyEvent; + + this.editor.value = handlerFunc( + this.editor.selectionStart, + this.editor.selectionEnd, + this.editor.value + ); this._markdownify(); this.editor.focus(); + this.editor.selectionEnd = this.editor.selectionStart = SELECTION_START; + return false }; - this.sendFile = (file: File): void => { + /** + * + * @param file + * @returns {Request} + */ + this.sendFile = (file: File): Request => { this.editor.style.opacity = "0.3"; @@ -162,11 +240,15 @@ const MarkdownX = function (editor: HTMLTextAreaElement, preview: Element) { }; - xhr.send() + return xhr.send() }; - this.getMarkdown = (): void => { + /** + * + * @returns {Request} + */ + this.getMarkdown = (): Request => { const xhr = new Request( this.editor.getAttribute(PROCESSING_URL_ATTRIBUTE), // URL @@ -184,7 +266,7 @@ const MarkdownX = function (editor: HTMLTextAreaElement, preview: Element) { triggerCustomEvent('markdownx.updateError', [response]) }; - xhr.send() + return xhr.send() }; diff --git a/testapp/models.py b/testapp/models.py index 30c74b7..111c0b8 100644 --- a/testapp/models.py +++ b/testapp/models.py @@ -2,6 +2,7 @@ from django.db import models from markdownx.models import MarkdownxField + class MyModel(models.Model): markdownx_field1 = MarkdownxField() markdownx_field2 = MarkdownxField()