From 88f93f76ddcccb937ac5d5eea76169118d149bf4 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 28 May 2017 16:02:46 +0100 Subject: [PATCH] Updated the markdown editor to use codemirror as editor Improved scroll sync system to be smarter --- resources/assets/js/code.js | 17 ++++++ resources/assets/js/directives.js | 72 ++++++++++++++------------ resources/assets/sass/_codemirror.scss | 13 ++++- resources/assets/sass/_grid.scss | 7 +-- 4 files changed, 70 insertions(+), 39 deletions(-) diff --git a/resources/assets/js/code.js b/resources/assets/js/code.js index cfe5ab158..872b13426 100644 --- a/resources/assets/js/code.js +++ b/resources/assets/js/code.js @@ -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; + +}; + diff --git a/resources/assets/js/directives.js b/resources/assets/js/directives.js index 3219db8f9..221e18b0e 100644 --- a/resources/assets/js/directives.js +++ b/resources/assets/js/directives.js @@ -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 diff --git a/resources/assets/sass/_codemirror.scss b/resources/assets/sass/_codemirror.scss index 231c7ab3a..9f9e38f55 100644 --- a/resources/assets/sass/_codemirror.scss +++ b/resources/assets/sass/_codemirror.scss @@ -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 diff --git a/resources/assets/sass/_grid.scss b/resources/assets/sass/_grid.scss index b32dafd38..de1ee83fb 100644 --- a/resources/assets/sass/_grid.scss +++ b/resources/assets/sass/_grid.scss @@ -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; } }