]> BookStack Code Mirror - bookstack/commitdiff
Updated the markdown editor to use codemirror as editor
authorDan Brown <redacted>
Sun, 28 May 2017 15:02:46 +0000 (16:02 +0100)
committerDan Brown <redacted>
Sun, 28 May 2017 15:02:46 +0000 (16:02 +0100)
Improved scroll sync system to be smarter

resources/assets/js/code.js
resources/assets/js/directives.js
resources/assets/sass/_codemirror.scss
resources/assets/sass/_grid.scss

index cfe5ab1583309160559190b6a7d8604317494db0..872b1342658995d0f68358aacf4383f4e6435cb7 100644 (file)
@@ -37,3 +37,20 @@ module.exports.highlight = function() {
 
 };
 
+module.exports.markdownEditor = function(elem) {
+    let content = elem.textContent;
+
+    let cm = CodeMirror(function(elt) {
+        elem.parentNode.insertBefore(elt, elem);
+        elem.style.display = 'none';
+    }, {
+        value: content,
+        mode:  "markdown",
+        lineNumbers: true,
+        theme: 'base16-light',
+        lineWrapping: true
+    });
+    return cm;
+
+};
+
index 3219db8f997b294b4246a8bc8d0de4d75651ca3a..221e18b0e1559ca22a56564a9136d74d1c5ae71f 100644 (file)
@@ -2,6 +2,7 @@
 const DropZone = require("dropzone");
 const MarkdownIt = require("markdown-it");
 const mdTasksLists = require('markdown-it-task-lists');
+const code = require('./code');
 
 module.exports = function (ngApp, events) {
 
@@ -233,19 +234,41 @@ module.exports = function (ngApp, events) {
 
                 // Set initial model content
                 element = element.find('textarea').first();
-                let content = element.val();
-                scope.mdModel = content;
-                scope.mdChange(md.render(content));
 
-                element.on('change input', (event) => {
-                    content = element.val();
+                // Codemirror Setup
+                let cm = code.markdownEditor(element[0]);
+                cm.on('change', (instance, changeObj) => {
+                    update(instance);
+                });
+
+                cm.on('scroll', instance => {
+                    // Thanks to http://liuhao.im/english/2015/11/10/the-sync-scroll-of-markdown-editor-in-javascript.html
+                    let scroll = instance.getScrollInfo();
+                    let atEnd = scroll.top + scroll.clientHeight === scroll.height;
+                    if (atEnd) {
+                        scope.$emit('markdown-scroll', -1);
+                        return;
+                    }
+                    let lineNum = instance.lineAtHeight(scroll.top, 'local');
+                    let range = instance.getRange({line: 0, ch: null}, {line: lineNum, ch: null});
+                    let parser = new DOMParser();
+                    let doc = parser.parseFromString(md.render(range), 'text/html');
+                    let totalLines = doc.documentElement.querySelectorAll('body > *');
+                    scope.$emit('markdown-scroll', totalLines.length);
+                });
+
+                function update(instance) {
+                    let content = instance.getValue();
+                    element.val(content);
                     $timeout(() => {
                         scope.mdModel = content;
                         scope.mdChange(md.render(content));
                     });
-                });
+                }
+                update(cm);
 
                 scope.$on('markdown-update', (event, value) => {
+                    cm.setValue(value);
                     element.val(value);
                     scope.mdModel = value;
                     scope.mdChange(md.render(value));
@@ -259,7 +282,7 @@ module.exports = function (ngApp, events) {
      * Markdown Editor
      * Handles all functionality of the markdown editor.
      */
-    ngApp.directive('markdownEditor', ['$timeout', function ($timeout) {
+    ngApp.directive('markdownEditor', ['$timeout', '$rootScope', function ($timeout, $rootScope) {
         return {
             restrict: 'A',
             link: function (scope, element, attrs) {
@@ -282,34 +305,15 @@ module.exports = function (ngApp, events) {
                     currentCaretPos = $input[0].selectionStart;
                 });
 
-                // Scroll sync
-                let inputScrollHeight,
-                    inputHeight,
-                    displayScrollHeight,
-                    displayHeight;
-
-                function setScrollHeights() {
-                    inputScrollHeight = $input[0].scrollHeight;
-                    inputHeight = $input.height();
-                    displayScrollHeight = $display[0].scrollHeight;
-                    displayHeight = $display.height();
-                }
-
-                setTimeout(() => {
-                    setScrollHeights();
-                }, 200);
-                window.addEventListener('resize', setScrollHeights);
-                let scrollDebounceTime = 800;
-                let lastScroll = 0;
-                $input.on('scroll', event => {
-                    let now = Date.now();
-                    if (now - lastScroll > scrollDebounceTime) {
-                        setScrollHeights()
+                // Handle scroll sync event from editor scroll
+                $rootScope.$on('markdown-scroll', (event, lineCount) => {
+                    let elems = $display[0].children[0].children;
+                    if (elems.length > lineCount) {
+                        let topElem = (lineCount === -1) ? elems[elems.length-1] : elems[lineCount];
+                        $display.animate({
+                            scrollTop: topElem.offsetTop
+                        }, {queue: false, duration: 200, easing: 'linear'});
                     }
-                    let scrollPercent = ($input.scrollTop() / (inputScrollHeight - inputHeight));
-                    let displayScrollY = (displayScrollHeight - displayHeight) * scrollPercent;
-                    $display.scrollTop(displayScrollY);
-                    lastScroll = now;
                 });
 
                 // Editor key-presses
index 231c7ab3a8fc7fda12a919a32d66aacd8669df78..9f9e38f55c35a1709d172eef767e757bb818ee6e 100644 (file)
@@ -350,7 +350,7 @@ span.CodeMirror-selectedtext { background: none; }
 
 */
 
-.cm-s-base16-light.CodeMirror { background: #f5f5f5; color: #202020; }
+.cm-s-base16-light.CodeMirror { background: #f8f8f8; color: #444444; }
 .cm-s-base16-light div.CodeMirror-selected { background: #e0e0e0; }
 .cm-s-base16-light .CodeMirror-line::selection, .cm-s-base16-light .CodeMirror-line > span::selection, .cm-s-base16-light .CodeMirror-line > span > span::selection { background: #e0e0e0; }
 .cm-s-base16-light .CodeMirror-line::-moz-selection, .cm-s-base16-light .CodeMirror-line > span::-moz-selection, .cm-s-base16-light .CodeMirror-line > span > span::-moz-selection { background: #e0e0e0; }
@@ -388,4 +388,13 @@ span.CodeMirror-selectedtext { background: none; }
   margin-bottom: $-l;
   border: 1px solid #DDD;;
 }
-.cm-s-base16-light .CodeMirror-gutters { background: #f5f5f5; border-right: 1px solid #DDD; }
\ No newline at end of file
+.cm-s-base16-light .CodeMirror-gutters { background: #f5f5f5; border-right: 1px solid #DDD; }
+
+.flex-fill .CodeMirror {
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+}
\ No newline at end of file
index b32dafd384c5377d960fae7dcceb897c8565e8d3..de1ee83fb1caa914cc758ab12623584729286021 100644 (file)
@@ -11,16 +11,17 @@ body.flexbox {
   #content {
     flex: 1;
     display: flex;
-    min-height: 0px;
+    min-height: 0;
   }
 }
 
 .flex-fill {
   display: flex;
   align-items: stretch;
-  min-height: 0px;
+  min-height: 0;
+  position: relative;
   .flex, &.flex {
-    min-height: 0px;
+    min-height: 0;
     flex: 1;
   }
 }
Morty Proxy This is a proxified and sanitized view of the page, visit original site.