From a30b749bd0bfa7fd4ba455c47ac4f484f8f80e46 Mon Sep 17 00:00:00 2001 From: NGPixel Date: Sun, 25 Feb 2018 15:54:35 -0500 Subject: [PATCH] feat: editor preview scroll sync + preview styling fixes --- client/app.js | 3 + client/components/editor-code.vue | 66 ++++++++++++++++++-- client/scss/components/markdown-content.scss | 26 +++++++- package.json | 1 + server/views/master.pug | 2 +- yarn.lock | 4 ++ 6 files changed, 95 insertions(+), 7 deletions(-) diff --git a/client/app.js b/client/app.js index ad2ac32e..086f94fc 100644 --- a/client/app.js +++ b/client/app.js @@ -14,6 +14,7 @@ import { createApolloFetch } from 'apollo-fetch' import { BatchHttpLink } from 'apollo-link-batch-http' import { InMemoryCache } from 'apollo-cache-inmemory' import Vuetify from 'vuetify' +import Velocity from 'velocity-animate' import Hammer from 'hammerjs' import store from './store' @@ -95,6 +96,8 @@ Vue.use(VeeValidate, { }) Vue.use(Vuetify) +Vue.prototype.Velocity = Velocity + // ==================================== // Register Vue Components // ==================================== diff --git a/client/components/editor-code.vue b/client/components/editor-code.vue index cf0633b9..ba91e3c2 100644 --- a/client/components/editor-code.vue +++ b/client/components/editor-code.vue @@ -141,6 +141,21 @@ const md = new MarkdownIt({ .use(mdMark) .use(mdImsize) +// Inject line numbers for preview scroll sync +let linesMap = [] +function injectLineNumbers (tokens, idx, options, env, slf) { + let line + if (tokens[idx].map && tokens[idx].level === 0) { + line = tokens[idx].map[0] + tokens[idx].attrJoin('class', 'line') + tokens[idx].attrSet('data-line', String(line)) + linesMap.push(line) + } + return slf.renderToken(tokens, idx, options, env, slf) +} +md.renderer.rules.paragraph_open = injectLineNumbers +md.renderer.rules.heading_open = injectLineNumbers + export default { components: { codemirror @@ -192,13 +207,34 @@ export default { self.$parent.save() } }) + cm.on('cursorActivity', this.scrollSync) this.onCmInput(this.code) }, onCmInput: _.debounce(function (newContent) { + linesMap = [] this.previewHTML = md.render(newContent) - this.$nextTick(function() { + this.$nextTick(() => { Prism.highlightAllUnder(this.$refs.editorPreview) + this.scrollSync(this.cm) }) + }, 500), + /** + * Update scroll sync + */ + scrollSync: _.debounce(function (cm) { + if (cm.somethingSelected()) { return } + let currentLine = cm.getCursor().line + if (currentLine < 3) { + this.Velocity(this.$refs.editorPreview, 'stop', true) + this.Velocity(this.$refs.editorPreview.firstChild, 'scroll', { offset: '-50', duration: 1000, container: this.$refs.editorPreview }) + } else { + let closestLine = _.findLast(linesMap, n => n <= currentLine) + let destElm = this.$refs.editorPreview.querySelector(`[data-line='${closestLine}']`) + if (destElm) { + this.Velocity(this.$refs.editorPreview, 'stop', true) + this.Velocity(destElm, 'scroll', { offset: '-100', duration: 1000, container: this.$refs.editorPreview }) + } + } }, 500) } } @@ -212,9 +248,10 @@ export default { } &-editor { + background-color: darken(mc('grey', '900'), 4.5%); flex: 1 1 50%; display: block; - min-height: calc(100vh - 100px); + height: calc(100vh - 100px); position: relative; &-title { @@ -229,7 +266,7 @@ export default { position: absolute; top: 0; right: 0; - z-index: 2; + z-index: 7; text-transform: uppercase; font-size: .7rem; @@ -243,14 +280,28 @@ export default { flex: 1 1 50%; background-color: mc('grey', '100'); position: relative; - padding: 30px 1rem 1rem 1rem; + height: calc(100vh - 100px); + overflow: hidden; @include until($tablet) { display: none; } + &-content { + height: calc(100vh - 100px); + overflow-y: scroll; + padding: 30px 1rem 1rem 1rem; + width: calc(100% + 1rem + 17px) + // -ms-overflow-style: none; + + // &::-webkit-scrollbar { + // width: 0px; + // background: transparent; + // } + } + &-title { - background-color: mc('blue', '100'); + background-color: rgba(mc('blue', '100'), .75); border-bottom-right-radius: 5px; display: inline-flex; height: 30px; @@ -318,6 +369,11 @@ export default { } } } + + // Fix FAB revealing under codemirror + .speed-dial--fixed { + z-index: 5; + } } .CodeMirror { diff --git a/client/scss/components/markdown-content.scss b/client/scss/components/markdown-content.scss index c78b32e0..f0f1afcf 100644 --- a/client/scss/components/markdown-content.scss +++ b/client/scss/components/markdown-content.scss @@ -8,7 +8,7 @@ h1, h2, h3, h4, h5, h6 { color: mc('blue-grey', '700'); - font-weight: 600; + font-weight: 500; } > * + h1, > * + h2, > * + h3, > * + h4 { @@ -28,6 +28,7 @@ font-size: 1.15rem; border-bottom: 1px dotted mc('blue-grey', '100'); margin-bottom: .5rem; + color: mc('blue-grey', '500'); } h4 { font-size: 1.1rem; @@ -39,10 +40,32 @@ font-size: 1.025rem; } + // -------------------------------------------- + // Paragraphs + // -------------------------------------------- p + p { margin-top: 1rem; } + // -------------------------------------------- + // Lists + // -------------------------------------------- + ul, ol { + & + p { + margin-top: .5rem; + } + } + + ul { + list-style-type: square; + list-style-position: inside; + } + + ol { + list-style-type: decimal; + list-style-position: inside; + } + // -------------------------------------------- // Code Blocks // -------------------------------------------- @@ -57,6 +80,7 @@ box-shadow: initial; display: block; font-size: .85rem; + font-family: 'Source Code Pro', monospace; &:after, &:before { content: initial; diff --git a/package.json b/package.json index 96aed926..01951816 100644 --- a/package.json +++ b/package.json @@ -195,6 +195,7 @@ "twemoji-awesome": "1.0.6", "uglifyjs-webpack-plugin": "1.2.0", "vee-validate": "2.0.4", + "velocity-animate": "1.5.1", "vue": "2.5.13", "vue-clipboards": "1.2.1", "vue-codemirror": "4.0.3", diff --git a/server/views/master.pug b/server/views/master.pug index bb84e49b..feca4896 100644 --- a/server/views/master.pug +++ b/server/views/master.pug @@ -23,7 +23,7 @@ html //- CSS link(type='text/css', rel='stylesheet', href=config.site.path + 'css/bundle.css') - link(type='text/css', rel='stylesheet', href='https://fonts.googleapis.com/icon?family=Material+Icons') + link(type='text/css', rel='stylesheet', href='https://fonts.googleapis.com/icon?family=Roboto:400,500,700|Source+Code+Pro:400,700|Material+Icons') //- JS script(type='text/javascript', src=config.site.path + 'js/runtime.js') diff --git a/yarn.lock b/yarn.lock index a211a959..0e9738f2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10322,6 +10322,10 @@ vee-validate@2.0.4: dependencies: babel-plugin-transform-object-rest-spread "^6.26.0" +velocity-animate@1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/velocity-animate/-/velocity-animate-1.5.1.tgz#606837047bab8fbfb59a636d1d82ecc3f7bd71a6" + vendors@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.1.tgz#37ad73c8ee417fb3d580e785312307d274847f22"