From e9dac8c8f30719357b59fa03c54879ec3f95321a Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Mon, 1 Oct 2018 18:16:26 +0200 Subject: [PATCH 01/64] fix scrolling in note list --- browser/main/NoteList/index.js | 3 +-- browser/main/TopBar/index.js | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/browser/main/NoteList/index.js b/browser/main/NoteList/index.js index 30ad93c3..a9818e4f 100644 --- a/browser/main/NoteList/index.js +++ b/browser/main/NoteList/index.js @@ -2,7 +2,6 @@ import PropTypes from 'prop-types' import React from 'react' import CSSModules from 'browser/lib/CSSModules' -import debounceRender from 'react-debounce-render' import styles from './NoteList.styl' import moment from 'moment' import _ from 'lodash' @@ -1078,4 +1077,4 @@ NoteList.propTypes = { }) } -export default debounceRender(CSSModules(NoteList, styles)) +export default CSSModules(NoteList, styles) diff --git a/browser/main/TopBar/index.js b/browser/main/TopBar/index.js index a5687ecb..d1844532 100644 --- a/browser/main/TopBar/index.js +++ b/browser/main/TopBar/index.js @@ -6,6 +6,7 @@ import _ from 'lodash' import ee from 'browser/main/lib/eventEmitter' import NewNoteButton from 'browser/main/NewNoteButton' import i18n from 'browser/lib/i18n' +import debounce from 'lodash/debounce' class TopBar extends React.Component { constructor (props) { @@ -25,6 +26,10 @@ class TopBar extends React.Component { } this.codeInitHandler = this.handleCodeInit.bind(this) + + this.updateKeyword = debounce(this.updateKeyword, 1000 / 60, { + maxWait: 1000 / 8 + }) } componentDidMount () { @@ -94,7 +99,6 @@ class TopBar extends React.Component { } handleKeyUp (e) { - const { router } = this.context // reset states this.setState({ isConfirmTranslation: false @@ -106,21 +110,21 @@ class TopBar extends React.Component { isConfirmTranslation: true }) const keyword = this.refs.searchInput.value - router.push(`/searched/${encodeURIComponent(keyword)}`) - this.setState({ - search: keyword - }) + this.updateKeyword(keyword) } } handleSearchChange (e) { - const { router } = this.context - const keyword = this.refs.searchInput.value if (this.state.isAlphabet || this.state.isConfirmTranslation) { - router.push(`/searched/${encodeURIComponent(keyword)}`) + const keyword = this.refs.searchInput.value + this.updateKeyword(keyword) } else { e.preventDefault() } + } + + updateKeyword(keyword) { + this.context.router.push(`/searched/${encodeURIComponent(keyword)}`) this.setState({ search: keyword }) From fa6c504b34934609edb9a99007a8811addabcdae Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Mon, 1 Oct 2018 18:42:18 +0200 Subject: [PATCH 02/64] fix lint error --- browser/main/TopBar/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/main/TopBar/index.js b/browser/main/TopBar/index.js index d1844532..91256daf 100644 --- a/browser/main/TopBar/index.js +++ b/browser/main/TopBar/index.js @@ -123,7 +123,7 @@ class TopBar extends React.Component { } } - updateKeyword(keyword) { + updateKeyword (keyword) { this.context.router.push(`/searched/${encodeURIComponent(keyword)}`) this.setState({ search: keyword From e52bcf33c579de9ddb80f7fb4bb5751adb39bb02 Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Mon, 1 Oct 2018 18:54:50 +0200 Subject: [PATCH 03/64] add `precommit` command --- package.json | 6 +++ yarn.lock | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 121 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index f4e5f532..4fe3434a 100644 --- a/package.json +++ b/package.json @@ -132,6 +132,7 @@ "grunt": "^0.4.5", "grunt-electron-installer": "2.1.0", "history": "^1.17.0", + "husky": "^1.1.0", "identity-obj-proxy": "^3.0.0", "jest": "^22.4.3", "jest-localstorage-mock": "^2.2.0", @@ -179,5 +180,10 @@ "/tests/jest.js", "jest-localstorage-mock" ] + }, + "husky": { + "hooks": { + "pre-commit": "npm run lint" + } } } diff --git a/yarn.lock b/yarn.lock index 687bb6de..5c9c1cc4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1677,6 +1677,10 @@ ci-info@^1.0.0: version "1.1.3" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.3.tgz#710193264bb05c77b8c90d02f5aaf22216a667b2" +ci-info@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497" + circular-json@^0.3.1: version "0.3.3" resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" @@ -2074,6 +2078,14 @@ core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" +cosmiconfig@^5.0.6: + version "5.0.6" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.0.6.tgz#dca6cf680a0bd03589aff684700858c81abeeb39" + dependencies: + is-directory "^0.3.1" + js-yaml "^3.9.0" + parse-json "^4.0.0" + create-error-class@^3.0.0, create-error-class@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" @@ -3309,6 +3321,18 @@ execa@^0.7.0: signal-exit "^3.0.0" strip-eof "^1.0.0" +execa@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.9.0.tgz#adb7ce62cf985071f60580deb4a88b9e34712d01" + dependencies: + cross-spawn "^5.0.1" + get-stream "^3.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + exit-hook@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" @@ -3615,6 +3639,12 @@ find-up@^2.0.0, find-up@^2.1.0: dependencies: locate-path "^2.0.0" +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + dependencies: + locate-path "^3.0.0" + findup-sync@~0.1.2: version "0.1.3" resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.1.3.tgz#7f3e7a97b82392c653bf06589bd85190e93c3683" @@ -3870,6 +3900,10 @@ get-stdin@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398" +get-stdin@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b" + get-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" @@ -4411,6 +4445,21 @@ humanize-plus@^1.8.1: version "1.8.2" resolved "https://registry.yarnpkg.com/humanize-plus/-/humanize-plus-1.8.2.tgz#a65b34459ad6367adbb3707a82a3c9f916167030" +husky@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/husky/-/husky-1.1.0.tgz#7271e85f5d98b54349788839b720c9a60cd95dba" + dependencies: + cosmiconfig "^5.0.6" + execa "^0.9.0" + find-up "^3.0.0" + get-stdin "^6.0.0" + is-ci "^1.2.1" + pkg-dir "^3.0.0" + please-upgrade-node "^3.1.1" + read-pkg "^4.0.1" + run-node "^1.0.0" + slash "^2.0.0" + i18n-2@^0.7.2: version "0.7.2" resolved "https://registry.yarnpkg.com/i18n-2/-/i18n-2-0.7.2.tgz#7efb1a7adc67869adea0688951577464aa793aaf" @@ -4617,6 +4666,12 @@ is-ci@^1.0.10, is-ci@^1.0.7: dependencies: ci-info "^1.0.0" +is-ci@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c" + dependencies: + ci-info "^1.5.0" + is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -4649,6 +4704,10 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2: is-data-descriptor "^1.0.0" kind-of "^6.0.2" +is-directory@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" + is-dotfile@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" @@ -5286,7 +5345,7 @@ js-yaml@^3.10.0, js-yaml@^3.5.1, js-yaml@^3.7.0: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^3.8.1: +js-yaml@^3.8.1, js-yaml@^3.9.0: version "3.12.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1" dependencies: @@ -5612,6 +5671,13 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + lodash-es@^4.2.1: version "4.17.10" resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.10.tgz#62cd7104cdf5dd87f235a837f0ede0e8e5117e05" @@ -6595,16 +6661,32 @@ p-limit@^1.1.0: dependencies: p-try "^1.0.0" +p-limit@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.0.0.tgz#e624ed54ee8c460a778b3c9f3670496ff8a57aec" + dependencies: + p-try "^2.0.0" + p-locate@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" dependencies: p-limit "^1.1.0" +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + dependencies: + p-limit "^2.0.0" + p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" +p-try@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1" + package-hash@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/package-hash/-/package-hash-1.2.0.tgz#003e56cd57b736a6ed6114cc2b81542672770e44" @@ -6800,12 +6882,24 @@ pkg-dir@^2.0.0: dependencies: find-up "^2.1.0" +pkg-dir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" + dependencies: + find-up "^3.0.0" + pkg-up@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f" dependencies: find-up "^2.1.0" +please-upgrade-node@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.1.1.tgz#ed320051dfcc5024fae696712c8288993595e8ac" + dependencies: + semver-compare "^1.0.0" + plist@^2.0.0, plist@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/plist/-/plist-2.1.0.tgz#57ccdb7a0821df21831217a3cad54e3e146a1025" @@ -7455,6 +7549,14 @@ read-pkg@^2.0.0: normalize-package-data "^2.3.2" path-type "^2.0.0" +read-pkg@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-4.0.1.tgz#963625378f3e1c4d48c85872b5a6ec7d5d093237" + dependencies: + normalize-package-data "^2.3.2" + parse-json "^4.0.0" + pify "^3.0.0" + readable-stream@^1.1.8, readable-stream@~1.1.9: version "1.1.14" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" @@ -7805,6 +7907,10 @@ run-async@^0.1.0: dependencies: once "^1.3.0" +run-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/run-node/-/run-node-1.0.0.tgz#46b50b946a2aa2d4947ae1d886e9856fd9cabe5e" + run-parallel@^1.1.2: version "1.1.9" resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" @@ -7904,6 +8010,10 @@ section-iterator@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/section-iterator/-/section-iterator-2.0.0.tgz#bf444d7afeeb94ad43c39ad2fb26151627ccba2a" +semver-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" + semver-diff@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" @@ -8057,6 +8167,10 @@ slash@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" +slash@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" + slice-ansi@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" From f308836264e8e438c2e111b8607c975fce3afe2c Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Tue, 2 Oct 2018 23:48:07 +0200 Subject: [PATCH 04/64] add new fenced block language: gallery --- browser/components/MarkdownPreview.js | 33 ++++++++++++++++++- browser/components/markdown.styl | 46 ++++++++++++++++++++++++++- browser/lib/markdown.js | 6 ++++ package.json | 2 ++ yarn.lock | 38 ++++++++++++++++++++++ 5 files changed, 123 insertions(+), 2 deletions(-) diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index eacc4e21..48951204 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -21,6 +21,8 @@ import yaml from 'js-yaml' import context from 'browser/lib/context' import i18n from 'browser/lib/i18n' import fs from 'fs' +import { render } from 'react-dom' +import Carousel from 'react-image-carousel' const { remote, shell } = require('electron') const attachmentManagement = require('../main/lib/dataApi/attachmentManagement') @@ -39,7 +41,8 @@ const appPath = fileUrl( ) const CSS_FILES = [ `${appPath}/node_modules/katex/dist/katex.min.css`, - `${appPath}/node_modules/codemirror/lib/codemirror.css` + `${appPath}/node_modules/codemirror/lib/codemirror.css`, + `${appPath}/node_modules/react-image-carousel/lib/css/main.min.css` ] function buildStyle ( @@ -789,6 +792,34 @@ export default class MarkdownPreview extends React.Component { mermaidRender(el, htmlTextHelper.decodeEntities(el.innerHTML), theme) } ) + + _.forEach( + this.refs.root.contentWindow.document.querySelectorAll('.gallery'), + el => { + const images = el.innerHTML.split(/\n/g).filter(i => i.length > 0) + el.innerHTML = '' + + const height = el.attributes.getNamedItem('data-height') + if (height && height.value !== 'undefined') { + el.style.height = height.value + 'vh' + } + + let autoplay = el.attributes.getNamedItem('data-autoplay') + if (autoplay && autoplay.value !== 'undefined') { + autoplay = parseInt(autoplay.value, 10) || 0 + } else { + autoplay = 0 + } + + render( + , + el + ) + } + ) } focus () { diff --git a/browser/components/markdown.styl b/browser/components/markdown.styl index 2e17a75b..6e43273e 100644 --- a/browser/components/markdown.styl +++ b/browser/components/markdown.styl @@ -381,6 +381,26 @@ pre.fence canvas, svg max-width 100% !important + .gallery + width 100% + height 50vh + + .carousel + .carousel-main img + min-width auto + max-width 100% + min-height auto + max-height 100% + + .carousel-footer::-webkit-scrollbar-corner + background-color transparent + + .carousel-main, .carousel-footer + background-color $ui-noteDetail-backgroundColor + .prev, .next + color $ui-text-color + background-color $ui-tag-backgroundColor + themeDarkBackground = darken(#21252B, 10%) themeDarkText = #f9f9f9 themeDarkBorder = lighten(themeDarkBackground, 20%) @@ -432,6 +452,14 @@ body[data-theme="dark"] background-color themeDarkBorder color themeDarkText + pre.fence + .gallery + .carousel-main, .carousel-footer + background-color $ui-dark-noteDetail-backgroundColor + .prev, .next + color $ui-dark-text-color + background-color $ui-dark-tag-backgroundColor + themeSolarizedDarkTableOdd = $ui-solarized-dark-noteDetail-backgroundColor themeSolarizedDarkTableEven = darken($ui-solarized-dark-noteDetail-backgroundColor, 10%) themeSolarizedDarkTableHead = themeSolarizedDarkTableEven @@ -459,6 +487,14 @@ body[data-theme="solarized-dark"] &:last-child border-right solid 1px themeSolarizedDarkTableBorder + pre.fence + .gallery + .carousel-main, .carousel-footer + background-color $ui-solarized-dark-noteDetail-backgroundColor + .prev, .next + color $ui-solarized-dark-text-color + background-color $ui-solarized-dark-tag-backgroundColor + themeMonokaiTableOdd = $ui-monokai-noteDetail-backgroundColor themeMonokaiTableEven = darken($ui-monokai-noteDetail-backgroundColor, 10%) themeMonokaiTableHead = themeMonokaiTableEven @@ -486,4 +522,12 @@ body[data-theme="monokai"] &:last-child border-right solid 1px themeMonokaiTableBorder kbd - background-color themeDarkBackground \ No newline at end of file + background-color themeDarkBackground + + pre.fence + .gallery + .carousel-main, .carousel-footer + background-color $ui-monokai-noteDetail-backgroundColor + .prev, .next + color $ui-monokai-text-color + background-color $ui-monokai-tag-backgroundColor diff --git a/browser/lib/markdown.js b/browser/lib/markdown.js index f8ae7f05..52a13700 100644 --- a/browser/lib/markdown.js +++ b/browser/lib/markdown.js @@ -143,6 +143,12 @@ class Markdown {
${token.content}
` }, + gallery: token => { + return `
+          ${token.fileName}
+          
+        
` + }, mermaid: token => { return `
           ${token.fileName}
diff --git a/package.json b/package.json
index 3e95603b..560a2e7d 100644
--- a/package.json
+++ b/package.json
@@ -95,8 +95,10 @@
     "react-codemirror": "^0.3.0",
     "react-debounce-render": "^4.0.1",
     "react-dom": "^15.0.2",
+    "react-image-carousel": "^2.0.18",
     "react-redux": "^4.4.5",
     "react-sortable-hoc": "^0.6.7",
+    "react-transition-group": "^2.5.0",
     "redux": "^3.5.2",
     "sander": "^0.5.1",
     "sanitize-html": "^1.18.2",
diff --git a/yarn.lock b/yarn.lock
index 5c426c92..55f4102a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2701,6 +2701,10 @@ doctrine@^2.0.0, doctrine@^2.0.2:
   dependencies:
     esutils "^2.0.2"
 
+dom-helpers@^3.3.1:
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.3.1.tgz#fc1a4e15ffdf60ddde03a480a9c0fece821dd4a6"
+
 dom-serializer@0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82"
@@ -5279,6 +5283,10 @@ js-tokens@^3.0.0, js-tokens@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
 
+"js-tokens@^3.0.0 || ^4.0.0":
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
+
 js-yaml@^3.10.0, js-yaml@^3.5.1, js-yaml@^3.7.0:
   version "3.11.0"
   resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.11.0.tgz#597c1a8bd57152f26d622ce4117851a51f5ebaef"
@@ -5729,6 +5737,12 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3
   dependencies:
     js-tokens "^3.0.0"
 
+loose-envify@^1.4.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
+  dependencies:
+    js-tokens "^3.0.0 || ^4.0.0"
+
 loud-rejection@^1.0.0, loud-rejection@^1.2.0:
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
@@ -7155,6 +7169,13 @@ prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.7, prop-types@^15.5.8,
     loose-envify "^1.3.1"
     object-assign "^4.1.1"
 
+prop-types@^15.6.2:
+  version "15.6.2"
+  resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102"
+  dependencies:
+    loose-envify "^1.3.1"
+    object-assign "^4.1.1"
+
 proxy-addr@~2.0.3:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341"
@@ -7330,6 +7351,10 @@ react-dom@^15.0.2:
     object-assign "^4.1.0"
     prop-types "^15.5.10"
 
+react-image-carousel@^2.0.18:
+  version "2.0.18"
+  resolved "https://registry.yarnpkg.com/react-image-carousel/-/react-image-carousel-2.0.18.tgz#5868ea09bd9cca09c4467d3d02695cd4e7792f28"
+
 react-input-autosize@^1.1.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-1.2.0.tgz#87241071159f742123897691da6796ec33b57d05"
@@ -7337,6 +7362,10 @@ react-input-autosize@^1.1.0:
     create-react-class "^15.5.2"
     prop-types "^15.5.8"
 
+react-lifecycles-compat@^3.0.4:
+  version "3.0.4"
+  resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
+
 react-proxy@^1.1.7:
   version "1.1.8"
   resolved "https://registry.yarnpkg.com/react-proxy/-/react-proxy-1.1.8.tgz#9dbfd9d927528c3aa9f444e4558c37830ab8c26a"
@@ -7402,6 +7431,15 @@ react-transform-hmr@^1.0.3:
     global "^4.3.0"
     react-proxy "^1.1.7"
 
+react-transition-group@^2.5.0:
+  version "2.5.0"
+  resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.5.0.tgz#70bca0e3546102c4dc5cf3f5f57f73447cce6874"
+  dependencies:
+    dom-helpers "^3.3.1"
+    loose-envify "^1.4.0"
+    prop-types "^15.6.2"
+    react-lifecycles-compat "^3.0.4"
+
 react@^15.5.4:
   version "15.6.2"
   resolved "https://registry.yarnpkg.com/react/-/react-15.6.2.tgz#dba0434ab439cfe82f108f0f511663908179aa72"

From 39eaed260a834e51283cbcc42c002f1152955a36 Mon Sep 17 00:00:00 2001
From: Baptiste Augrain 
Date: Mon, 8 Oct 2018 15:57:42 +0200
Subject: [PATCH 05/64] display correctly attached image

---
 browser/main/lib/dataApi/attachmentManagement.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/browser/main/lib/dataApi/attachmentManagement.js b/browser/main/lib/dataApi/attachmentManagement.js
index 912450c1..38648941 100644
--- a/browser/main/lib/dataApi/attachmentManagement.js
+++ b/browser/main/lib/dataApi/attachmentManagement.js
@@ -227,7 +227,7 @@ function migrateAttachments (markdownContent, storagePath, noteKey) {
  * @returns {String} postprocessed HTML in which all :storage references are mapped to the actual paths.
  */
 function fixLocalURLS (renderedHTML, storagePath) {
-  return renderedHTML.replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '.*?"', 'g'), function (match) {
+  return renderedHTML.replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '[\w\/]+', 'g'), function (match) {
     var encodedPathSeparators = new RegExp(mdurl.encode(path.win32.sep) + '|' + mdurl.encode(path.posix.sep), 'g')
     return match.replace(encodedPathSeparators, path.sep).replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER, 'g'), 'file:///' + path.join(storagePath, DESTINATION_FOLDER))
   })

From 7a5a821f8a8b8919d9c5ae0b0dca377485ec25f2 Mon Sep 17 00:00:00 2001
From: Baptiste Augrain 
Date: Tue, 9 Oct 2018 00:47:37 +0200
Subject: [PATCH 06/64] fix failing test

---
 browser/main/lib/dataApi/attachmentManagement.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/browser/main/lib/dataApi/attachmentManagement.js b/browser/main/lib/dataApi/attachmentManagement.js
index 38648941..a81d3bd4 100644
--- a/browser/main/lib/dataApi/attachmentManagement.js
+++ b/browser/main/lib/dataApi/attachmentManagement.js
@@ -227,7 +227,7 @@ function migrateAttachments (markdownContent, storagePath, noteKey) {
  * @returns {String} postprocessed HTML in which all :storage references are mapped to the actual paths.
  */
 function fixLocalURLS (renderedHTML, storagePath) {
-  return renderedHTML.replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '[\w\/]+', 'g'), function (match) {
+  return renderedHTML.replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '(?:\\\/|%5C)[\\w.]+', 'g'), function (match) {
     var encodedPathSeparators = new RegExp(mdurl.encode(path.win32.sep) + '|' + mdurl.encode(path.posix.sep), 'g')
     return match.replace(encodedPathSeparators, path.sep).replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER, 'g'), 'file:///' + path.join(storagePath, DESTINATION_FOLDER))
   })

From 5d9b1abe82a68c51b99564f6f117191c246f98b3 Mon Sep 17 00:00:00 2001
From: Baptiste Augrain 
Date: Tue, 9 Oct 2018 01:18:19 +0200
Subject: [PATCH 07/64] allow markdown image syntax

---
 browser/lib/markdown.js | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/browser/lib/markdown.js b/browser/lib/markdown.js
index 52a13700..cc5e86dc 100644
--- a/browser/lib/markdown.js
+++ b/browser/lib/markdown.js
@@ -144,9 +144,18 @@ class Markdown {
         
` }, gallery: token => { + const content = token.content.split('\n').slice(0, -1).map(line => { + const match = /!\[[^\]]*]\(([^\)]*)\)/.exec(line) + if (match) { + return match[1] + } else { + return line + } + }).join('\n') + return `
           ${token.fileName}
-          
+          
         
` }, mermaid: token => { From db97ab51acf97786048061929371d5f6141cd930 Mon Sep 17 00:00:00 2001 From: Guilherme Silva Date: Thu, 8 Nov 2018 11:44:03 +0000 Subject: [PATCH 08/64] Bracket matching option added to config --- browser/components/CodeEditor.js | 373 ++++++++++++------ browser/components/MarkdownEditor.js | 1 + browser/components/MarkdownSplitEditor.js | 1 + browser/main/Detail/SnippetNoteDetail.js | 1 + browser/main/lib/ConfigManager.js | 1 + .../modals/PreferencesModal/SnippetEditor.js | 11 +- .../modals/PreferencesModal/SnippetTab.js | 1 + browser/main/modals/PreferencesModal/UiTab.js | 17 +- 8 files changed, 284 insertions(+), 122 deletions(-) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 41d71622..f4ce77b9 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -5,25 +5,35 @@ import CodeMirror from 'codemirror' import 'codemirror-mode-elixir' import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement' import convertModeName from 'browser/lib/convertModeName' -import { options, TableEditor, Alignment } from '@susisu/mte-kernel' +import { + options, + TableEditor, + Alignment +} from '@susisu/mte-kernel' import TextEditorInterface from 'browser/lib/TextEditorInterface' import eventEmitter from 'browser/main/lib/eventEmitter' import iconv from 'iconv-lite' import crypto from 'crypto' import consts from 'browser/lib/consts' import fs from 'fs' -const { ipcRenderer } = require('electron') +const { + ipcRenderer +} = require('electron') import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily' import TurndownService from 'turndown' -import { gfm } from 'turndown-plugin-gfm' +import { + gfm +} from 'turndown-plugin-gfm' CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js' const buildCMRulers = (rulers, enableRulers) => - (enableRulers ? rulers.map(ruler => ({ column: ruler })) : []) + (enableRulers ? rulers.map(ruler => ({ + column: ruler + })) : []) export default class CodeEditor extends React.Component { - constructor (props) { + constructor(props) { super(props) this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, { @@ -46,7 +56,10 @@ export default class CodeEditor extends React.Component { } this.props.onBlur != null && this.props.onBlur(e) - const { storageKey, noteKey } = this.props + const { + storageKey, + noteKey + } = this.props attachmentManagement.deleteAttachmentsNotPresentInNote( this.editor.getValue(), storageKey, @@ -65,7 +78,7 @@ export default class CodeEditor extends React.Component { this.editorActivityHandler = () => this.handleEditorActivity() } - handleSearch (msg) { + handleSearch(msg) { const cm = this.editor const component = this @@ -76,7 +89,7 @@ export default class CodeEditor extends React.Component { component.searchState = makeOverlay(msg, 'searching') cm.addOverlay(component.searchState) - function makeOverlay (query, style) { + function makeOverlay(query, style) { query = new RegExp( query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'), 'gi' @@ -99,17 +112,19 @@ export default class CodeEditor extends React.Component { }) } - handleFormatTable () { - this.tableEditor.formatAll(options({textWidthOptions: {}})) + handleFormatTable() { + this.tableEditor.formatAll(options({ + textWidthOptions: {} + })) } - handleEditorActivity () { + handleEditorActivity() { if (!this.textEditorInterface.transaction) { this.updateTableEditorState() } } - updateTableEditorState () { + updateTableEditorState() { const active = this.tableEditor.cursorIsInTable(this.tableEditorOptions) if (active) { if (this.extraKeysMode !== 'editor') { @@ -125,19 +140,20 @@ export default class CodeEditor extends React.Component { } } - componentDidMount () { - const { rulers, enableRulers } = this.props + componentDidMount() { + const { + rulers, + enableRulers + } = this.props const expandSnippet = this.expandSnippet.bind(this) eventEmitter.on('line:jump', this.scrollToLineHandeler) - const defaultSnippet = [ - { - id: crypto.randomBytes(16).toString('hex'), - name: 'Dummy text', - prefix: ['lorem', 'ipsum'], - content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.' - } - ] + const defaultSnippet = [{ + id: crypto.randomBytes(16).toString('hex'), + name: 'Dummy text', + prefix: ['lorem', 'ipsum'], + content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.' + }] if (!fs.existsSync(consts.SNIPPET_FILE)) { fs.writeFileSync( consts.SNIPPET_FILE, @@ -215,12 +231,17 @@ export default class CodeEditor extends React.Component { dragDrop: false, foldGutter: true, gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], - autoCloseBrackets: { - pairs: '()[]{}\'\'""$$**``', + autoCloseBrackets: (this.props.enableBracketMatching ? { + pairs: '()[]{}\'\'""$$**``ll', triples: '```"""\'\'\'', explode: '[]{}``$$', override: true - }, + } : { + pairs: '', + triples: '', + explode: '', + override: true + }), extraKeys: this.defaultKeyMap }) @@ -253,43 +274,117 @@ export default class CodeEditor extends React.Component { }) this.editorKeyMap = CodeMirror.normalizeKeyMap({ - 'Tab': () => { this.tableEditor.nextCell(this.tableEditorOptions) }, - 'Shift-Tab': () => { this.tableEditor.previousCell(this.tableEditorOptions) }, - 'Enter': () => { this.tableEditor.nextRow(this.tableEditorOptions) }, - 'Ctrl-Enter': () => { this.tableEditor.escape(this.tableEditorOptions) }, - 'Cmd-Enter': () => { this.tableEditor.escape(this.tableEditorOptions) }, - 'Shift-Ctrl-Left': () => { this.tableEditor.alignColumn(Alignment.LEFT, this.tableEditorOptions) }, - 'Shift-Cmd-Left': () => { this.tableEditor.alignColumn(Alignment.LEFT, this.tableEditorOptions) }, - 'Shift-Ctrl-Right': () => { this.tableEditor.alignColumn(Alignment.RIGHT, this.tableEditorOptions) }, - 'Shift-Cmd-Right': () => { this.tableEditor.alignColumn(Alignment.RIGHT, this.tableEditorOptions) }, - 'Shift-Ctrl-Up': () => { this.tableEditor.alignColumn(Alignment.CENTER, this.tableEditorOptions) }, - 'Shift-Cmd-Up': () => { this.tableEditor.alignColumn(Alignment.CENTER, this.tableEditorOptions) }, - 'Shift-Ctrl-Down': () => { this.tableEditor.alignColumn(Alignment.NONE, this.tableEditorOptions) }, - 'Shift-Cmd-Down': () => { this.tableEditor.alignColumn(Alignment.NONE, this.tableEditorOptions) }, - 'Ctrl-Left': () => { this.tableEditor.moveFocus(0, -1, this.tableEditorOptions) }, - 'Cmd-Left': () => { this.tableEditor.moveFocus(0, -1, this.tableEditorOptions) }, - 'Ctrl-Right': () => { this.tableEditor.moveFocus(0, 1, this.tableEditorOptions) }, - 'Cmd-Right': () => { this.tableEditor.moveFocus(0, 1, this.tableEditorOptions) }, - 'Ctrl-Up': () => { this.tableEditor.moveFocus(-1, 0, this.tableEditorOptions) }, - 'Cmd-Up': () => { this.tableEditor.moveFocus(-1, 0, this.tableEditorOptions) }, - 'Ctrl-Down': () => { this.tableEditor.moveFocus(1, 0, this.tableEditorOptions) }, - 'Cmd-Down': () => { this.tableEditor.moveFocus(1, 0, this.tableEditorOptions) }, - 'Ctrl-K Ctrl-I': () => { this.tableEditor.insertRow(this.tableEditorOptions) }, - 'Cmd-K Cmd-I': () => { this.tableEditor.insertRow(this.tableEditorOptions) }, - 'Ctrl-L Ctrl-I': () => { this.tableEditor.deleteRow(this.tableEditorOptions) }, - 'Cmd-L Cmd-I': () => { this.tableEditor.deleteRow(this.tableEditorOptions) }, - 'Ctrl-K Ctrl-J': () => { this.tableEditor.insertColumn(this.tableEditorOptions) }, - 'Cmd-K Cmd-J': () => { this.tableEditor.insertColumn(this.tableEditorOptions) }, - 'Ctrl-L Ctrl-J': () => { this.tableEditor.deleteColumn(this.tableEditorOptions) }, - 'Cmd-L Cmd-J': () => { this.tableEditor.deleteColumn(this.tableEditorOptions) }, - 'Alt-Shift-Ctrl-Left': () => { this.tableEditor.moveColumn(-1, this.tableEditorOptions) }, - 'Alt-Shift-Cmd-Left': () => { this.tableEditor.moveColumn(-1, this.tableEditorOptions) }, - 'Alt-Shift-Ctrl-Right': () => { this.tableEditor.moveColumn(1, this.tableEditorOptions) }, - 'Alt-Shift-Cmd-Right': () => { this.tableEditor.moveColumn(1, this.tableEditorOptions) }, - 'Alt-Shift-Ctrl-Up': () => { this.tableEditor.moveRow(-1, this.tableEditorOptions) }, - 'Alt-Shift-Cmd-Up': () => { this.tableEditor.moveRow(-1, this.tableEditorOptions) }, - 'Alt-Shift-Ctrl-Down': () => { this.tableEditor.moveRow(1, this.tableEditorOptions) }, - 'Alt-Shift-Cmd-Down': () => { this.tableEditor.moveRow(1, this.tableEditorOptions) } + 'Tab': () => { + this.tableEditor.nextCell(this.tableEditorOptions) + }, + 'Shift-Tab': () => { + this.tableEditor.previousCell(this.tableEditorOptions) + }, + 'Enter': () => { + this.tableEditor.nextRow(this.tableEditorOptions) + }, + 'Ctrl-Enter': () => { + this.tableEditor.escape(this.tableEditorOptions) + }, + 'Cmd-Enter': () => { + this.tableEditor.escape(this.tableEditorOptions) + }, + 'Shift-Ctrl-Left': () => { + this.tableEditor.alignColumn(Alignment.LEFT, this.tableEditorOptions) + }, + 'Shift-Cmd-Left': () => { + this.tableEditor.alignColumn(Alignment.LEFT, this.tableEditorOptions) + }, + 'Shift-Ctrl-Right': () => { + this.tableEditor.alignColumn(Alignment.RIGHT, this.tableEditorOptions) + }, + 'Shift-Cmd-Right': () => { + this.tableEditor.alignColumn(Alignment.RIGHT, this.tableEditorOptions) + }, + 'Shift-Ctrl-Up': () => { + this.tableEditor.alignColumn(Alignment.CENTER, this.tableEditorOptions) + }, + 'Shift-Cmd-Up': () => { + this.tableEditor.alignColumn(Alignment.CENTER, this.tableEditorOptions) + }, + 'Shift-Ctrl-Down': () => { + this.tableEditor.alignColumn(Alignment.NONE, this.tableEditorOptions) + }, + 'Shift-Cmd-Down': () => { + this.tableEditor.alignColumn(Alignment.NONE, this.tableEditorOptions) + }, + 'Ctrl-Left': () => { + this.tableEditor.moveFocus(0, -1, this.tableEditorOptions) + }, + 'Cmd-Left': () => { + this.tableEditor.moveFocus(0, -1, this.tableEditorOptions) + }, + 'Ctrl-Right': () => { + this.tableEditor.moveFocus(0, 1, this.tableEditorOptions) + }, + 'Cmd-Right': () => { + this.tableEditor.moveFocus(0, 1, this.tableEditorOptions) + }, + 'Ctrl-Up': () => { + this.tableEditor.moveFocus(-1, 0, this.tableEditorOptions) + }, + 'Cmd-Up': () => { + this.tableEditor.moveFocus(-1, 0, this.tableEditorOptions) + }, + 'Ctrl-Down': () => { + this.tableEditor.moveFocus(1, 0, this.tableEditorOptions) + }, + 'Cmd-Down': () => { + this.tableEditor.moveFocus(1, 0, this.tableEditorOptions) + }, + 'Ctrl-K Ctrl-I': () => { + this.tableEditor.insertRow(this.tableEditorOptions) + }, + 'Cmd-K Cmd-I': () => { + this.tableEditor.insertRow(this.tableEditorOptions) + }, + 'Ctrl-L Ctrl-I': () => { + this.tableEditor.deleteRow(this.tableEditorOptions) + }, + 'Cmd-L Cmd-I': () => { + this.tableEditor.deleteRow(this.tableEditorOptions) + }, + 'Ctrl-K Ctrl-J': () => { + this.tableEditor.insertColumn(this.tableEditorOptions) + }, + 'Cmd-K Cmd-J': () => { + this.tableEditor.insertColumn(this.tableEditorOptions) + }, + 'Ctrl-L Ctrl-J': () => { + this.tableEditor.deleteColumn(this.tableEditorOptions) + }, + 'Cmd-L Cmd-J': () => { + this.tableEditor.deleteColumn(this.tableEditorOptions) + }, + 'Alt-Shift-Ctrl-Left': () => { + this.tableEditor.moveColumn(-1, this.tableEditorOptions) + }, + 'Alt-Shift-Cmd-Left': () => { + this.tableEditor.moveColumn(-1, this.tableEditorOptions) + }, + 'Alt-Shift-Ctrl-Right': () => { + this.tableEditor.moveColumn(1, this.tableEditorOptions) + }, + 'Alt-Shift-Cmd-Right': () => { + this.tableEditor.moveColumn(1, this.tableEditorOptions) + }, + 'Alt-Shift-Ctrl-Up': () => { + this.tableEditor.moveRow(-1, this.tableEditorOptions) + }, + 'Alt-Shift-Cmd-Up': () => { + this.tableEditor.moveRow(-1, this.tableEditorOptions) + }, + 'Alt-Shift-Ctrl-Down': () => { + this.tableEditor.moveRow(1, this.tableEditorOptions) + }, + 'Alt-Shift-Cmd-Down': () => { + this.tableEditor.moveRow(1, this.tableEditorOptions) + } }) if (this.props.enableTableEditor) { @@ -302,7 +397,7 @@ export default class CodeEditor extends React.Component { }) } - expandSnippet (line, cursor, cm, snippets) { + expandSnippet(line, cursor, cm, snippets) { const wordBeforeCursor = this.getWordBeforeCursor( line, cursor.line, @@ -345,7 +440,7 @@ export default class CodeEditor extends React.Component { return false } - getWordBeforeCursor (line, lineNumber, cursorPosition) { + getWordBeforeCursor(line, lineNumber, cursorPosition) { let wordBeforeCursor = '' const originCursorPosition = cursorPosition const emptyChars = /\t|\s|\r|\n/ @@ -370,17 +465,23 @@ export default class CodeEditor extends React.Component { return { text: wordBeforeCursor, range: { - from: { line: lineNumber, ch: originCursorPosition }, - to: { line: lineNumber, ch: cursorPosition } + from: { + line: lineNumber, + ch: originCursorPosition + }, + to: { + line: lineNumber, + ch: cursorPosition + } } } } - quitEditor () { + quitEditor() { document.querySelector('textarea').blur() } - componentWillUnmount () { + componentWillUnmount() { this.editor.off('focus', this.focusHandler) this.editor.off('blur', this.blurHandler) this.editor.off('change', this.changeHandler) @@ -393,9 +494,12 @@ export default class CodeEditor extends React.Component { eventEmitter.off('code:format-table', this.formatTable) } - componentDidUpdate (prevProps, prevState) { + componentDidUpdate(prevProps, prevState) { let needRefresh = false - const { rulers, enableRulers } = this.props + const { + rulers, + enableRulers + } = this.props if (prevProps.mode !== this.props.mode) { this.setMode(this.props.mode) } @@ -436,6 +540,21 @@ export default class CodeEditor extends React.Component { this.editor.setOption('scrollPastEnd', this.props.scrollPastEnd) } + if (prevProps.enableBracketMatching !== this.props.enableBracketMatching) { + const bracketObject = (this.props.enableBracketMatching ? { + pairs: '()[]{}\'\'""$$**``ll', + triples: '```"""\'\'\'', + explode: '[]{}``$$', + override: true + } : { + pairs: '', + triples: '', + explode: '', + override: true + }); + this.editor.setOption('autoCloseBrackets', bracketObject) + } + if (prevProps.enableTableEditor !== this.props.enableTableEditor) { if (this.props.enableTableEditor) { this.editor.on('cursorActivity', this.editorActivityHandler) @@ -462,7 +581,7 @@ export default class CodeEditor extends React.Component { } } - setMode (mode) { + setMode(mode) { let syntax = CodeMirror.findModeByName(convertModeName(mode)) if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text') @@ -470,16 +589,16 @@ export default class CodeEditor extends React.Component { CodeMirror.autoLoadMode(this.editor, syntax.mode) } - handleChange (e) { + handleChange(e) { this.value = this.editor.getValue() if (this.props.onChange) { this.props.onChange(e) } } - moveCursorTo (row, col) {} + moveCursorTo(row, col) {} - scrollToLine (event, num) { + scrollToLine(event, num) { const cursor = { line: num, ch: 1 @@ -487,15 +606,15 @@ export default class CodeEditor extends React.Component { this.editor.setCursor(cursor) } - focus () { + focus() { this.editor.focus() } - blur () { + blur() { this.editor.blur() } - reload () { + reload() { // Change event shouldn't be fired when switch note this.editor.off('change', this.changeHandler) this.value = this.props.value @@ -505,15 +624,18 @@ export default class CodeEditor extends React.Component { this.editor.refresh() } - setValue (value) { + setValue(value) { const cursor = this.editor.getCursor() this.editor.setValue(value) this.editor.setCursor(cursor) } - handleDropImage (dropEvent) { + handleDropImage(dropEvent) { dropEvent.preventDefault() - const { storageKey, noteKey } = this.props + const { + storageKey, + noteKey + } = this.props attachmentManagement.handleAttachmentDrop( this, storageKey, @@ -522,13 +644,16 @@ export default class CodeEditor extends React.Component { ) } - insertAttachmentMd (imageMd) { + insertAttachmentMd(imageMd) { this.editor.replaceSelection(imageMd) } - handlePaste (editor, e) { + handlePaste(editor, e) { const clipboardData = e.clipboardData - const { storageKey, noteKey } = this.props + const { + storageKey, + noteKey + } = this.props const dataTransferItem = clipboardData.items[0] const pastedTxt = clipboardData.getData('text') const isURL = str => { @@ -537,15 +662,21 @@ export default class CodeEditor extends React.Component { } const isInLinkTag = editor => { const startCursor = editor.getCursor('start') - const prevChar = editor.getRange( - { line: startCursor.line, ch: startCursor.ch - 2 }, - { line: startCursor.line, ch: startCursor.ch } - ) + const prevChar = editor.getRange({ + line: startCursor.line, + ch: startCursor.ch - 2 + }, { + line: startCursor.line, + ch: startCursor.ch + }) const endCursor = editor.getCursor('end') - const nextChar = editor.getRange( - { line: endCursor.line, ch: endCursor.ch }, - { line: endCursor.line, ch: endCursor.ch + 1 } - ) + const nextChar = editor.getRange({ + line: endCursor.line, + ch: endCursor.ch + }, { + line: endCursor.line, + ch: endCursor.ch + 1 + }) return prevChar === '](' && nextChar === ')' } @@ -576,13 +707,13 @@ export default class CodeEditor extends React.Component { } } - handleScroll (e) { + handleScroll(e) { if (this.props.onScroll) { this.props.onScroll(e) } } - handlePasteUrl (e, editor, pastedTxt) { + handlePasteUrl(e, editor, pastedTxt) { e.preventDefault() const taggedUrl = `<${pastedTxt}>` editor.replaceSelection(taggedUrl) @@ -605,8 +736,8 @@ export default class CodeEditor extends React.Component { } fetch(pastedTxt, { - method: 'get' - }) + method: 'get' + }) .then(response => { if (isImageReponse(response)) { return this.mapImageResponse(response, pastedTxt) @@ -622,13 +753,13 @@ export default class CodeEditor extends React.Component { }) } - handlePasteHtml (e, editor, pastedHtml) { + handlePasteHtml(e, editor, pastedHtml) { e.preventDefault() const markdown = this.turndownService.turndown(pastedHtml) editor.replaceSelection(markdown) } - mapNormalResponse (response, pastedTxt) { + mapNormalResponse(response, pastedTxt) { return this.decodeResponse(response).then(body => { return new Promise((resolve, reject) => { try { @@ -648,7 +779,7 @@ export default class CodeEditor extends React.Component { }) } - mapImageResponse (response, pastedTxt) { + mapImageResponse(response, pastedTxt) { return new Promise((resolve, reject) => { try { const url = response.url @@ -661,18 +792,18 @@ export default class CodeEditor extends React.Component { }) } - decodeResponse (response) { + decodeResponse(response) { const headers = response.headers - const _charset = headers.has('content-type') - ? this.extractContentTypeCharset(headers.get('content-type')) - : undefined + const _charset = headers.has('content-type') ? + this.extractContentTypeCharset(headers.get('content-type')) : + undefined return response.arrayBuffer().then(buff => { return new Promise((resolve, reject) => { try { const charset = _charset !== undefined && - iconv.encodingExists(_charset) - ? _charset - : 'utf-8' + iconv.encodingExists(_charset) ? + _charset : + 'utf-8' resolve(iconv.decode(new Buffer(buff), charset).toString()) } catch (e) { reject(e) @@ -681,7 +812,7 @@ export default class CodeEditor extends React.Component { }) } - extractContentTypeCharset (contentType) { + extractContentTypeCharset(contentType) { return contentType .split(';') .filter(str => { @@ -692,21 +823,29 @@ export default class CodeEditor extends React.Component { })[0] } - render () { - const {className, fontSize} = this.props + render() { + const { + className, + fontSize + } = this.props const fontFamily = normalizeEditorFontFamily(this.props.fontFamily) const width = this.props.width - return ( -
this.handleDropImage(e)} + } + } + onDrop = { + e => this.handleDropImage(e) + } /> ) } @@ -731,4 +870,4 @@ CodeEditor.defaultProps = { fontFamily: 'Monaco, Consolas', indentSize: 4, indentType: 'space' -} +} \ No newline at end of file diff --git a/browser/components/MarkdownEditor.js b/browser/components/MarkdownEditor.js index 9c8a06d6..f10ea8bc 100644 --- a/browser/components/MarkdownEditor.js +++ b/browser/components/MarkdownEditor.js @@ -268,6 +268,7 @@ class MarkdownEditor extends React.Component { enableRulers={config.editor.enableRulers} rulers={config.editor.rulers} displayLineNumbers={config.editor.displayLineNumbers} + enableBracketMatching={config.editor.enableBracketMatching} scrollPastEnd={config.editor.scrollPastEnd} storageKey={storageKey} noteKey={noteKey} diff --git a/browser/components/MarkdownSplitEditor.js b/browser/components/MarkdownSplitEditor.js index ca2d3108..004a6eb0 100644 --- a/browser/components/MarkdownSplitEditor.js +++ b/browser/components/MarkdownSplitEditor.js @@ -158,6 +158,7 @@ class MarkdownSplitEditor extends React.Component { fontFamily={config.editor.fontFamily} fontSize={editorFontSize} displayLineNumbers={config.editor.displayLineNumbers} + enableBracketMatching={config.editor.enableBracketMatching} indentType={config.editor.indentType} indentSize={editorIndentSize} enableRulers={config.editor.enableRulers} diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index afd81102..90b8faa9 100644 --- a/browser/main/Detail/SnippetNoteDetail.js +++ b/browser/main/Detail/SnippetNoteDetail.js @@ -699,6 +699,7 @@ class SnippetNoteDetail extends React.Component { indentType={config.editor.indentType} indentSize={editorIndentSize} displayLineNumbers={config.editor.displayLineNumbers} + enableBracketMatching={config.editor.enableBracketMatching} keyMap={config.editor.keyMap} scrollPastEnd={config.editor.scrollPastEnd} fetchUrlTitle={config.editor.fetchUrlTitle} diff --git a/browser/main/lib/ConfigManager.js b/browser/main/lib/ConfigManager.js index 9611f21d..eddf0909 100644 --- a/browser/main/lib/ConfigManager.js +++ b/browser/main/lib/ConfigManager.js @@ -44,6 +44,7 @@ export const DEFAULT_CONFIG = { enableRulers: false, rulers: [80, 120], displayLineNumbers: true, + enableBracketMatching: true, switchPreview: 'BLUR', // 'BLUR', 'DBL_CLICK', 'RIGHTCLICK' delfaultStatus: 'PREVIEW', // 'PREVIEW', 'CODE' scrollPastEnd: false, diff --git a/browser/main/modals/PreferencesModal/SnippetEditor.js b/browser/main/modals/PreferencesModal/SnippetEditor.js index 4ce5dc34..ec3698c7 100644 --- a/browser/main/modals/PreferencesModal/SnippetEditor.js +++ b/browser/main/modals/PreferencesModal/SnippetEditor.js @@ -27,12 +27,17 @@ class SnippetEditor extends React.Component { dragDrop: false, foldGutter: true, gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], - autoCloseBrackets: { - pairs: '()[]{}\'\'""$$**``', + autoCloseBrackets: (this.enableBracketMatching ? { + pairs: '()[]{}\'\'""$$**``ll', triples: '```"""\'\'\'', explode: '[]{}``$$', override: true - }, + }: { + pairs: '', + triples: '', + explode: '', + override: true + }), mode: 'null' }) this.cm.setSize('100%', '100%') diff --git a/browser/main/modals/PreferencesModal/SnippetTab.js b/browser/main/modals/PreferencesModal/SnippetTab.js index b83fa205..40b65d39 100644 --- a/browser/main/modals/PreferencesModal/SnippetTab.js +++ b/browser/main/modals/PreferencesModal/SnippetTab.js @@ -136,6 +136,7 @@ class SnippetTab extends React.Component { enableRulers={config.editor.enableRulers} rulers={config.editor.rulers} displayLineNumbers={config.editor.displayLineNumbers} + enableBracketMatching={config.editor.enableBracketMatching} scrollPastEnd={config.editor.scrollPastEnd} onRef={ref => { this.snippetEditor = ref }} />
diff --git a/browser/main/modals/PreferencesModal/UiTab.js b/browser/main/modals/PreferencesModal/UiTab.js index 6bc3b0a3..5c6110c3 100644 --- a/browser/main/modals/PreferencesModal/UiTab.js +++ b/browser/main/modals/PreferencesModal/UiTab.js @@ -62,7 +62,8 @@ class UiTab extends React.Component { checkHighLight.setAttribute('rel', 'stylesheet') document.head.appendChild(checkHighLight) } - + console.log("This is a console log") + const newConfig = { ui: { theme: this.refs.uiTheme.value, @@ -94,7 +95,8 @@ class UiTab extends React.Component { fetchUrlTitle: this.refs.editorFetchUrlTitle.checked, enableTableEditor: this.refs.enableTableEditor.checked, enableFrontMatterTitle: this.refs.enableFrontMatterTitle.checked, - frontMatterTitleField: this.refs.frontMatterTitleField.value + frontMatterTitleField: this.refs.frontMatterTitleField.value, + enableBracketMatching: this.refs.enableBracketMatching.checked }, preview: { fontSize: this.refs.previewFontSize.value, @@ -539,6 +541,17 @@ class UiTab extends React.Component { +
+ +
+
{i18n.__('Preview')}
From 59d31c9a182bacda1a0b5db2b4211dff6f875bc1 Mon Sep 17 00:00:00 2001 From: Guilherme Silva Date: Thu, 8 Nov 2018 12:01:12 +0000 Subject: [PATCH 09/64] Deleted useless console log --- browser/main/modals/PreferencesModal/UiTab.js | 1 - 1 file changed, 1 deletion(-) diff --git a/browser/main/modals/PreferencesModal/UiTab.js b/browser/main/modals/PreferencesModal/UiTab.js index 5c6110c3..ab72c914 100644 --- a/browser/main/modals/PreferencesModal/UiTab.js +++ b/browser/main/modals/PreferencesModal/UiTab.js @@ -62,7 +62,6 @@ class UiTab extends React.Component { checkHighLight.setAttribute('rel', 'stylesheet') document.head.appendChild(checkHighLight) } - console.log("This is a console log") const newConfig = { ui: { From ab65fb7a5ca41b1bb3561d15b2d9259d64aea330 Mon Sep 17 00:00:00 2001 From: Guilherme Silva Date: Thu, 8 Nov 2018 13:18:13 +0000 Subject: [PATCH 10/64] Choosing which characters to match and explode --- browser/components/CodeEditor.js | 993 ++++++++---------- browser/components/MarkdownEditor.js | 3 + browser/components/MarkdownSplitEditor.js | 3 + browser/main/Detail/SnippetNoteDetail.js | 3 + browser/main/lib/ConfigManager.js | 3 + .../modals/PreferencesModal/SnippetTab.js | 3 + browser/main/modals/PreferencesModal/UiTab.js | 48 +- 7 files changed, 516 insertions(+), 540 deletions(-) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index f4ce77b9..3e416ab2 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -1,121 +1,110 @@ +import 'codemirror-mode-elixir' + +import {Alignment, options, TableEditor} from '@susisu/mte-kernel' +import consts from 'browser/lib/consts' +import convertModeName from 'browser/lib/convertModeName' +import TextEditorInterface from 'browser/lib/TextEditorInterface' +import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement' +import eventEmitter from 'browser/main/lib/eventEmitter' +import CodeMirror from 'codemirror' +import crypto from 'crypto' +import fs from 'fs' +import iconv from 'iconv-lite' +import _ from 'lodash' import PropTypes from 'prop-types' import React from 'react' -import _ from 'lodash' -import CodeMirror from 'codemirror' -import 'codemirror-mode-elixir' -import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement' -import convertModeName from 'browser/lib/convertModeName' -import { - options, - TableEditor, - Alignment -} from '@susisu/mte-kernel' -import TextEditorInterface from 'browser/lib/TextEditorInterface' -import eventEmitter from 'browser/main/lib/eventEmitter' -import iconv from 'iconv-lite' -import crypto from 'crypto' -import consts from 'browser/lib/consts' -import fs from 'fs' -const { - ipcRenderer -} = require('electron') + +const {ipcRenderer} = require('electron') import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily' import TurndownService from 'turndown' -import { - gfm -} from 'turndown-plugin-gfm' +import{gfm} from 'turndown-plugin-gfm' CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js' const buildCMRulers = (rulers, enableRulers) => - (enableRulers ? rulers.map(ruler => ({ - column: ruler - })) : []) + (enableRulers ? rulers.map(ruler => ({column: ruler})) : []) export default class CodeEditor extends React.Component { constructor(props) { super(props) - this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, { - leading: false, - trailing: true - }) - this.changeHandler = e => this.handleChange(e) - this.focusHandler = () => { - ipcRenderer.send('editor:focused', true) - } - this.blurHandler = (editor, e) => { - ipcRenderer.send('editor:focused', false) - if (e == null) return null - let el = e.relatedTarget - while (el != null) { - if (el === this.refs.root) { - return - } - el = el.parentNode - } - this.props.onBlur != null && this.props.onBlur(e) + this.scrollHandler = + _.debounce( + this.handleScroll.bind(this), 100, + {leading: false, trailing: true}) this.changeHandler = e => + this.handleChange(e) this.focusHandler = + () => { + ipcRenderer.send('editor:focused', true) + } this.blurHandler = + (editor, e) => { + ipcRenderer.send('editor:focused', false) + if (e == null) return null + let el = e.relatedTarget + while (el != null) { + if (el === this.refs.root) { + return + } + el = el.parentNode + } + this.props.onBlur != null && this.props.onBlur(e) - const { - storageKey, - noteKey - } = this.props - attachmentManagement.deleteAttachmentsNotPresentInNote( - this.editor.getValue(), - storageKey, - noteKey - ) - } - this.pasteHandler = (editor, e) => this.handlePaste(editor, e) - this.loadStyleHandler = e => { - this.editor.refresh() - } - this.searchHandler = (e, msg) => this.handleSearch(msg) - this.searchState = null - this.scrollToLineHandeler = this.scrollToLine.bind(this) + const {storageKey, noteKey} = this.props + attachmentManagement.deleteAttachmentsNotPresentInNote( + this.editor.getValue(), storageKey, noteKey) + } this.pasteHandler = (editor, e) => + this.handlePaste(editor, e) this.loadStyleHandler = + e => { + this.editor.refresh() + } this.searchHandler = (e, msg) => + this.handleSearch(msg) this.searchState = + null this.scrollToLineHandeler = + this.scrollToLine + .bind(this) - this.formatTable = () => this.handleFormatTable() - this.editorActivityHandler = () => this.handleEditorActivity() + this.formatTable = () => + this.handleFormatTable() this + .editorActivityHandler = () => + this.handleEditorActivity() } handleSearch(msg) { const cm = this.editor const component = this - if (component.searchState) cm.removeOverlay(component.searchState) + if (component.searchState) + cm.removeOverlay(component.searchState) if (msg.length < 3) return - cm.operation(function () { - component.searchState = makeOverlay(msg, 'searching') - cm.addOverlay(component.searchState) + cm.operation(function() { + component.searchState = makeOverlay(msg, 'searching') + cm.addOverlay(component.searchState) - function makeOverlay(query, style) { - query = new RegExp( - query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'), - 'gi' - ) - return { - token: function (stream) { - query.lastIndex = stream.pos - var match = query.exec(stream.string) - if (match && match.index === stream.pos) { - stream.pos += match[0].length || 1 - return style - } else if (match) { - stream.pos = match.index - } else { - stream.skipToEnd() + function makeOverlay(query, style) { + query = new RegExp( + query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'), + 'gi') + return { + token: function(stream) { + query.lastIndex = stream.pos + var match = query.exec(stream.string) + if (match && match.index === stream.pos) { + stream.pos += match[0].length || 1 + return style + } + else if (match) { + stream.pos = match.index + } + else { + stream.skipToEnd() + } + } } } - } - } - }) + }) } handleFormatTable() { - this.tableEditor.formatAll(options({ - textWidthOptions: {} - })) + this.tableEditor.formatAll(options({textWidthOptions: {}})) } handleEditorActivity() { @@ -128,23 +117,23 @@ export default class CodeEditor extends React.Component { const active = this.tableEditor.cursorIsInTable(this.tableEditorOptions) if (active) { if (this.extraKeysMode !== 'editor') { - this.extraKeysMode = 'editor' - this.editor.setOption('extraKeys', this.editorKeyMap) + this.extraKeysMode = + 'editor' this.editor.setOption('extraKeys', this.editorKeyMap) } - } else { + } + else { if (this.extraKeysMode !== 'default') { - this.extraKeysMode = 'default' - this.editor.setOption('extraKeys', this.defaultKeyMap) - this.tableEditor.resetSmartCursor() + this.extraKeysMode = + 'default' this.editor + .setOption( + 'extraKeys', + this.defaultKeyMap) this.tableEditor.resetSmartCursor() } } } componentDidMount() { - const { - rulers, - enableRulers - } = this.props + const {rulers, enableRulers} = this.props const expandSnippet = this.expandSnippet.bind(this) eventEmitter.on('line:jump', this.scrollToLineHandeler) @@ -152,109 +141,99 @@ export default class CodeEditor extends React.Component { id: crypto.randomBytes(16).toString('hex'), name: 'Dummy text', prefix: ['lorem', 'ipsum'], - content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.' - }] - if (!fs.existsSync(consts.SNIPPET_FILE)) { + content: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.' + }] if (!fs.existsSync(consts.SNIPPET_FILE)) { fs.writeFileSync( - consts.SNIPPET_FILE, - JSON.stringify(defaultSnippet, null, 4), - 'utf8' - ) + consts.SNIPPET_FILE, JSON.stringify(defaultSnippet, null, 4), 'utf8') } - this.defaultKeyMap = CodeMirror.normalizeKeyMap({ - Tab: function (cm) { - const cursor = cm.getCursor() - const line = cm.getLine(cursor.line) - const cursorPosition = cursor.ch - const charBeforeCursor = line.substr(cursorPosition - 1, 1) - if (cm.somethingSelected()) cm.indentSelection('add') - else { - const tabs = cm.getOption('indentWithTabs') - if (line.trimLeft().match(/^(-|\*|\+) (\[( |x)] )?$/)) { - cm.execCommand('goLineStart') - if (tabs) { - cm.execCommand('insertTab') - } else { - cm.execCommand('insertSoftTab') - } - cm.execCommand('goLineEnd') - } else if ( - !charBeforeCursor.match(/\t|\s|\r|\n/) && - cursor.ch > 1 - ) { - // text expansion on tab key if the char before is alphabet - const snippets = JSON.parse( - fs.readFileSync(consts.SNIPPET_FILE, 'utf8') - ) - if (expandSnippet(line, cursor, cm, snippets) === false) { - if (tabs) { - cm.execCommand('insertTab') - } else { - cm.execCommand('insertSoftTab') + this.defaultKeyMap = + CodeMirror + .normalizeKeyMap({ + Tab: function(cm) { + const cursor = cm.getCursor() + const line = cm.getLine(cursor.line) + const cursorPosition = cursor.ch + const charBeforeCursor = line.substr(cursorPosition - 1, 1) + if (cm.somethingSelected()) cm.indentSelection('add') else { + const tabs = cm.getOption('indentWithTabs') + if (line.trimLeft().match(/^(-|\*|\+) (\[( |x)] )?$/)) { + cm.execCommand('goLineStart') + if (tabs) { + cm.execCommand('insertTab') + } + else {cm.execCommand('insertSoftTab')} cm.execCommand( + 'goLineEnd') + } + else if ( + !charBeforeCursor.match(/\t|\s|\r|\n/) && cursor.ch > 1) { + // text expansion on tab key if the char before is alphabet + const snippets = + JSON.parse(fs.readFileSync(consts.SNIPPET_FILE, 'utf8')) + if (expandSnippet(line, cursor, cm, snippets) === false) { + if (tabs) { + cm.execCommand('insertTab') + } else { + cm.execCommand('insertSoftTab') + } + } + } + else { + if (tabs) { + cm.execCommand('insertTab') + } else { + cm.execCommand('insertSoftTab') + } + } + } + }, + 'Cmd-T': function(cm) { + // Do nothing + }, + Enter: 'boostNewLineAndIndentContinueMarkdownList', + 'Ctrl-C': cm => { + if (cm.getOption('keyMap').substr(0, 3) === 'vim') { + document.execCommand('copy') + } + return CodeMirror.Pass } - } - } else { - if (tabs) { - cm.execCommand('insertTab') - } else { - cm.execCommand('insertSoftTab') - } - } - } - }, - 'Cmd-T': function (cm) { - // Do nothing - }, - Enter: 'boostNewLineAndIndentContinueMarkdownList', - 'Ctrl-C': cm => { - if (cm.getOption('keyMap').substr(0, 3) === 'vim') { - document.execCommand('copy') - } - return CodeMirror.Pass - } - }) + }) - this.value = this.props.value - this.editor = CodeMirror(this.refs.root, { - rulers: buildCMRulers(rulers, enableRulers), - value: this.props.value, - lineNumbers: this.props.displayLineNumbers, - lineWrapping: true, - theme: this.props.theme, - indentUnit: this.props.indentSize, - tabSize: this.props.indentSize, - indentWithTabs: this.props.indentType !== 'space', - keyMap: this.props.keyMap, - scrollPastEnd: this.props.scrollPastEnd, - inputStyle: 'textarea', - dragDrop: false, - foldGutter: true, - gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], - autoCloseBrackets: (this.props.enableBracketMatching ? { - pairs: '()[]{}\'\'""$$**``ll', - triples: '```"""\'\'\'', - explode: '[]{}``$$', - override: true - } : { - pairs: '', - triples: '', - explode: '', - override: true - }), - extraKeys: this.defaultKeyMap - }) + this.value = this.props.value this.editor = + CodeMirror(this.refs.root, { + rulers: buildCMRulers(rulers, enableRulers), + value: this.props.value, + lineNumbers: this.props.displayLineNumbers, + lineWrapping: true, + theme: this.props.theme, + indentUnit: this.props.indentSize, + tabSize: this.props.indentSize, + indentWithTabs: this.props.indentType !== 'space', + keyMap: this.props.keyMap, + scrollPastEnd: this.props.scrollPastEnd, + inputStyle: 'textarea', + dragDrop: false, + foldGutter: true, + gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], + autoCloseBrackets: { + pairs: this.props.matchingPairs, + triples: this.props.matchingTriples, + explode: this.props.explodingPairs, + override: true + }, + extraKeys: this.defaultKeyMap + }) - this.setMode(this.props.mode) + this.setMode(this.props.mode) - this.editor.on('focus', this.focusHandler) - this.editor.on('blur', this.blurHandler) - this.editor.on('change', this.changeHandler) - this.editor.on('paste', this.pasteHandler) + this.editor.on('focus', this.focusHandler) this.editor + .on('blur', this.blurHandler) this.editor + .on('change', this.changeHandler) this.editor.on( + 'paste', this.pasteHandler) eventEmitter.on('top:search', this.searchHandler) - eventEmitter.emit('code:init') - this.editor.on('scroll', this.scrollHandler) + eventEmitter.emit('code:init') this.editor.on('scroll', this.scrollHandler) const editorTheme = document.getElementById('editorTheme') editorTheme.addEventListener('load', this.loadStyleHandler) @@ -263,146 +242,151 @@ export default class CodeEditor extends React.Component { CodeMirror.Vim.defineEx('q!', 'q!', this.quitEditor) CodeMirror.Vim.defineEx('wq', 'wq', this.quitEditor) CodeMirror.Vim.defineEx('qw', 'qw', this.quitEditor) - CodeMirror.Vim.map('ZZ', ':q', 'normal') + CodeMirror.Vim + .map('ZZ', ':q', 'normal') - this.textEditorInterface = new TextEditorInterface(this.editor) - this.tableEditor = new TableEditor(this.textEditorInterface) - eventEmitter.on('code:format-table', this.formatTable) + this.textEditorInterface = + new TextEditorInterface(this.editor) this.tableEditor = + new TableEditor(this.textEditorInterface) + eventEmitter + .on('code:format-table', this.formatTable) - this.tableEditorOptions = options({ - smartCursor: true - }) + this.tableEditorOptions = options({smartCursor: true}) - this.editorKeyMap = CodeMirror.normalizeKeyMap({ - 'Tab': () => { - this.tableEditor.nextCell(this.tableEditorOptions) - }, - 'Shift-Tab': () => { - this.tableEditor.previousCell(this.tableEditorOptions) - }, - 'Enter': () => { - this.tableEditor.nextRow(this.tableEditorOptions) - }, - 'Ctrl-Enter': () => { - this.tableEditor.escape(this.tableEditorOptions) - }, - 'Cmd-Enter': () => { - this.tableEditor.escape(this.tableEditorOptions) - }, - 'Shift-Ctrl-Left': () => { - this.tableEditor.alignColumn(Alignment.LEFT, this.tableEditorOptions) - }, - 'Shift-Cmd-Left': () => { - this.tableEditor.alignColumn(Alignment.LEFT, this.tableEditorOptions) - }, - 'Shift-Ctrl-Right': () => { - this.tableEditor.alignColumn(Alignment.RIGHT, this.tableEditorOptions) - }, - 'Shift-Cmd-Right': () => { - this.tableEditor.alignColumn(Alignment.RIGHT, this.tableEditorOptions) - }, - 'Shift-Ctrl-Up': () => { - this.tableEditor.alignColumn(Alignment.CENTER, this.tableEditorOptions) - }, - 'Shift-Cmd-Up': () => { - this.tableEditor.alignColumn(Alignment.CENTER, this.tableEditorOptions) - }, - 'Shift-Ctrl-Down': () => { - this.tableEditor.alignColumn(Alignment.NONE, this.tableEditorOptions) - }, - 'Shift-Cmd-Down': () => { - this.tableEditor.alignColumn(Alignment.NONE, this.tableEditorOptions) - }, - 'Ctrl-Left': () => { - this.tableEditor.moveFocus(0, -1, this.tableEditorOptions) - }, - 'Cmd-Left': () => { - this.tableEditor.moveFocus(0, -1, this.tableEditorOptions) - }, - 'Ctrl-Right': () => { - this.tableEditor.moveFocus(0, 1, this.tableEditorOptions) - }, - 'Cmd-Right': () => { - this.tableEditor.moveFocus(0, 1, this.tableEditorOptions) - }, - 'Ctrl-Up': () => { - this.tableEditor.moveFocus(-1, 0, this.tableEditorOptions) - }, - 'Cmd-Up': () => { - this.tableEditor.moveFocus(-1, 0, this.tableEditorOptions) - }, - 'Ctrl-Down': () => { - this.tableEditor.moveFocus(1, 0, this.tableEditorOptions) - }, - 'Cmd-Down': () => { - this.tableEditor.moveFocus(1, 0, this.tableEditorOptions) - }, - 'Ctrl-K Ctrl-I': () => { - this.tableEditor.insertRow(this.tableEditorOptions) - }, - 'Cmd-K Cmd-I': () => { - this.tableEditor.insertRow(this.tableEditorOptions) - }, - 'Ctrl-L Ctrl-I': () => { - this.tableEditor.deleteRow(this.tableEditorOptions) - }, - 'Cmd-L Cmd-I': () => { - this.tableEditor.deleteRow(this.tableEditorOptions) - }, - 'Ctrl-K Ctrl-J': () => { - this.tableEditor.insertColumn(this.tableEditorOptions) - }, - 'Cmd-K Cmd-J': () => { - this.tableEditor.insertColumn(this.tableEditorOptions) - }, - 'Ctrl-L Ctrl-J': () => { - this.tableEditor.deleteColumn(this.tableEditorOptions) - }, - 'Cmd-L Cmd-J': () => { - this.tableEditor.deleteColumn(this.tableEditorOptions) - }, - 'Alt-Shift-Ctrl-Left': () => { - this.tableEditor.moveColumn(-1, this.tableEditorOptions) - }, - 'Alt-Shift-Cmd-Left': () => { - this.tableEditor.moveColumn(-1, this.tableEditorOptions) - }, - 'Alt-Shift-Ctrl-Right': () => { - this.tableEditor.moveColumn(1, this.tableEditorOptions) - }, - 'Alt-Shift-Cmd-Right': () => { - this.tableEditor.moveColumn(1, this.tableEditorOptions) - }, - 'Alt-Shift-Ctrl-Up': () => { - this.tableEditor.moveRow(-1, this.tableEditorOptions) - }, - 'Alt-Shift-Cmd-Up': () => { - this.tableEditor.moveRow(-1, this.tableEditorOptions) - }, - 'Alt-Shift-Ctrl-Down': () => { - this.tableEditor.moveRow(1, this.tableEditorOptions) - }, - 'Alt-Shift-Cmd-Down': () => { - this.tableEditor.moveRow(1, this.tableEditorOptions) - } - }) + this.editorKeyMap = + CodeMirror.normalizeKeyMap({ + 'Tab': () => { + this.tableEditor.nextCell(this.tableEditorOptions) + }, + 'Shift-Tab': () => { + this.tableEditor.previousCell(this.tableEditorOptions) + }, + 'Enter': () => { + this.tableEditor.nextRow(this.tableEditorOptions) + }, + 'Ctrl-Enter': () => { + this.tableEditor.escape(this.tableEditorOptions) + }, + 'Cmd-Enter': () => { + this.tableEditor.escape(this.tableEditorOptions) + }, + 'Shift-Ctrl-Left': () => { + this.tableEditor.alignColumn( + Alignment.LEFT, this.tableEditorOptions) + }, + 'Shift-Cmd-Left': () => { + this.tableEditor.alignColumn( + Alignment.LEFT, this.tableEditorOptions) + }, + 'Shift-Ctrl-Right': () => { + this.tableEditor.alignColumn( + Alignment.RIGHT, this.tableEditorOptions) + }, + 'Shift-Cmd-Right': () => { + this.tableEditor.alignColumn( + Alignment.RIGHT, this.tableEditorOptions) + }, + 'Shift-Ctrl-Up': () => { + this.tableEditor.alignColumn( + Alignment.CENTER, this.tableEditorOptions) + }, + 'Shift-Cmd-Up': () => { + this.tableEditor.alignColumn( + Alignment.CENTER, this.tableEditorOptions) + }, + 'Shift-Ctrl-Down': () => { + this.tableEditor.alignColumn( + Alignment.NONE, this.tableEditorOptions) + }, + 'Shift-Cmd-Down': () => { + this.tableEditor.alignColumn( + Alignment.NONE, this.tableEditorOptions) + }, + 'Ctrl-Left': () => { + this.tableEditor.moveFocus(0, -1, this.tableEditorOptions) + }, + 'Cmd-Left': () => { + this.tableEditor.moveFocus(0, -1, this.tableEditorOptions) + }, + 'Ctrl-Right': () => { + this.tableEditor.moveFocus(0, 1, this.tableEditorOptions) + }, + 'Cmd-Right': () => { + this.tableEditor.moveFocus(0, 1, this.tableEditorOptions) + }, + 'Ctrl-Up': () => { + this.tableEditor.moveFocus(-1, 0, this.tableEditorOptions) + }, + 'Cmd-Up': () => { + this.tableEditor.moveFocus(-1, 0, this.tableEditorOptions) + }, + 'Ctrl-Down': () => { + this.tableEditor.moveFocus(1, 0, this.tableEditorOptions) + }, + 'Cmd-Down': () => { + this.tableEditor.moveFocus(1, 0, this.tableEditorOptions) + }, + 'Ctrl-K Ctrl-I': () => { + this.tableEditor.insertRow(this.tableEditorOptions) + }, + 'Cmd-K Cmd-I': () => { + this.tableEditor.insertRow(this.tableEditorOptions) + }, + 'Ctrl-L Ctrl-I': () => { + this.tableEditor.deleteRow(this.tableEditorOptions) + }, + 'Cmd-L Cmd-I': () => { + this.tableEditor.deleteRow(this.tableEditorOptions) + }, + 'Ctrl-K Ctrl-J': () => { + this.tableEditor.insertColumn(this.tableEditorOptions) + }, + 'Cmd-K Cmd-J': () => { + this.tableEditor.insertColumn(this.tableEditorOptions) + }, + 'Ctrl-L Ctrl-J': () => { + this.tableEditor.deleteColumn(this.tableEditorOptions) + }, + 'Cmd-L Cmd-J': () => { + this.tableEditor.deleteColumn(this.tableEditorOptions) + }, + 'Alt-Shift-Ctrl-Left': () => { + this.tableEditor.moveColumn(-1, this.tableEditorOptions) + }, + 'Alt-Shift-Cmd-Left': () => { + this.tableEditor.moveColumn(-1, this.tableEditorOptions) + }, + 'Alt-Shift-Ctrl-Right': () => { + this.tableEditor.moveColumn(1, this.tableEditorOptions) + }, + 'Alt-Shift-Cmd-Right': () => { + this.tableEditor.moveColumn(1, this.tableEditorOptions) + }, + 'Alt-Shift-Ctrl-Up': () => { + this.tableEditor.moveRow(-1, this.tableEditorOptions) + }, + 'Alt-Shift-Cmd-Up': () => { + this.tableEditor.moveRow(-1, this.tableEditorOptions) + }, + 'Alt-Shift-Ctrl-Down': () => { + this.tableEditor.moveRow(1, this.tableEditorOptions) + }, + 'Alt-Shift-Cmd-Down': () => { + this.tableEditor.moveRow(1, this.tableEditorOptions) + } + }) if (this.props.enableTableEditor) { - this.editor.on('cursorActivity', this.editorActivityHandler) - this.editor.on('changes', this.editorActivityHandler) + this.editor.on('cursorActivity', this.editorActivityHandler) this.editor + .on('changes', this.editorActivityHandler) } - this.setState({ - clientWidth: this.refs.root.clientWidth - }) + this.setState({clientWidth: this.refs.root.clientWidth}) } expandSnippet(line, cursor, cm, snippets) { - const wordBeforeCursor = this.getWordBeforeCursor( - line, - cursor.line, - cursor.ch - ) + const wordBeforeCursor = + this.getWordBeforeCursor(line, cursor.line, cursor.ch) const templateCursorString = ':{}' for (let i = 0; i < snippets.length; i++) { if (snippets[i].prefix.indexOf(wordBeforeCursor.text) !== -1) { @@ -416,10 +400,8 @@ export default class CodeEditor extends React.Component { cursorLineNumber = j cursorLinePosition = cursorIndex cm.replaceRange( - snippets[i].content.replace(templateCursorString, ''), - wordBeforeCursor.range.from, - wordBeforeCursor.range.to - ) + snippets[i].content.replace(templateCursorString, ''), + wordBeforeCursor.range.from, wordBeforeCursor.range.to) cm.setCursor({ line: cursor.line + cursorLineNumber, ch: cursorLinePosition @@ -428,10 +410,8 @@ export default class CodeEditor extends React.Component { } } else { cm.replaceRange( - snippets[i].content, - wordBeforeCursor.range.from, - wordBeforeCursor.range.to - ) + snippets[i].content, wordBeforeCursor.range.from, + wordBeforeCursor.range.to) } return true } @@ -446,7 +426,8 @@ export default class CodeEditor extends React.Component { const emptyChars = /\t|\s|\r|\n/ // to prevent the word to expand is long that will crash the whole app - // the safeStop is there to stop user to expand words that longer than 20 chars + // the safeStop is there to stop user to expand words that longer than 20 + // chars const safeStop = 20 while (cursorPosition > 0) { @@ -454,25 +435,17 @@ export default class CodeEditor extends React.Component { // if char is not an empty char if (!emptyChars.test(currentChar)) { wordBeforeCursor = currentChar + wordBeforeCursor - } else if (wordBeforeCursor.length >= safeStop) { - throw new Error('Your snippet trigger is too long !') - } else { - break } - cursorPosition-- + else if (wordBeforeCursor.length >= safeStop) { + throw new Error('Your snippet trigger is too long !') + } + else {break} cursorPosition-- } return { - text: wordBeforeCursor, - range: { - from: { - line: lineNumber, - ch: originCursorPosition - }, - to: { - line: lineNumber, - ch: cursorPosition - } + text: wordBeforeCursor, range: { + from: {line: lineNumber, ch: originCursorPosition}, + to: {line: lineNumber, ch: cursorPosition} } } } @@ -482,12 +455,12 @@ export default class CodeEditor extends React.Component { } componentWillUnmount() { - this.editor.off('focus', this.focusHandler) - this.editor.off('blur', this.blurHandler) - this.editor.off('change', this.changeHandler) - this.editor.off('paste', this.pasteHandler) - eventEmitter.off('top:search', this.searchHandler) - this.editor.off('scroll', this.scrollHandler) + this.editor.off('focus', this.focusHandler) this.editor + .off('blur', this.blurHandler) this.editor + .off('change', this.changeHandler) this.editor.off( + 'paste', this.pasteHandler) + eventEmitter.off('top:search', this.searchHandler) this.editor.off( + 'scroll', this.scrollHandler) const editorTheme = document.getElementById('editorTheme') editorTheme.removeEventListener('load', this.loadStyleHandler) @@ -496,10 +469,7 @@ export default class CodeEditor extends React.Component { componentDidUpdate(prevProps, prevState) { let needRefresh = false - const { - rulers, - enableRulers - } = this.props + const {rulers, enableRulers} = this.props if (prevProps.mode !== this.props.mode) { this.setMode(this.props.mode) } @@ -517,16 +487,14 @@ export default class CodeEditor extends React.Component { needRefresh = true } - if ( - prevProps.enableRulers !== enableRulers || - prevProps.rulers !== rulers - ) { + if (prevProps.enableRulers !== enableRulers || + prevProps.rulers !== rulers) { this.editor.setOption('rulers', buildCMRulers(rulers, enableRulers)) } if (prevProps.indentSize !== this.props.indentSize) { - this.editor.setOption('indentUnit', this.props.indentSize) - this.editor.setOption('tabSize', this.props.indentSize) + this.editor.setOption('indentUnit', this.props.indentSize) this.editor + .setOption('tabSize', this.props.indentSize) } if (prevProps.indentType !== this.props.indentType) { this.editor.setOption('indentWithTabs', this.props.indentType !== 'space') @@ -540,38 +508,33 @@ export default class CodeEditor extends React.Component { this.editor.setOption('scrollPastEnd', this.props.scrollPastEnd) } - if (prevProps.enableBracketMatching !== this.props.enableBracketMatching) { - const bracketObject = (this.props.enableBracketMatching ? { - pairs: '()[]{}\'\'""$$**``ll', - triples: '```"""\'\'\'', - explode: '[]{}``$$', + if (prevProps.matchingPairs !== this.props.matchingPairs || + prevProps.matchingTriples !== this.props.matchingTriples || + prevProps.explodingPairs !== this.props.explodingPairs) { + const bracketObject = { + pairs: this.props.matchingPairs, + triples: this.props.matchingTriples, + explode: this.props.explodingPairs, override: true - } : { - pairs: '', - triples: '', - explode: '', - override: true - }); + } this.editor.setOption('autoCloseBrackets', bracketObject) } if (prevProps.enableTableEditor !== this.props.enableTableEditor) { if (this.props.enableTableEditor) { - this.editor.on('cursorActivity', this.editorActivityHandler) - this.editor.on('changes', this.editorActivityHandler) + this.editor.on('cursorActivity', this.editorActivityHandler) this.editor + .on('changes', this.editorActivityHandler) } else { - this.editor.off('cursorActivity', this.editorActivityHandler) - this.editor.off('changes', this.editorActivityHandler) + this.editor.off('cursorActivity', this.editorActivityHandler) this + .editor.off('changes', this.editorActivityHandler) } - this.extraKeysMode = 'default' - this.editor.setOption('extraKeys', this.defaultKeyMap) + this.extraKeysMode = + 'default' this.editor.setOption('extraKeys', this.defaultKeyMap) } if (this.state.clientWidth !== this.refs.root.clientWidth) { - this.setState({ - clientWidth: this.refs.root.clientWidth - }) + this.setState({clientWidth: this.refs.root.clientWidth}) needRefresh = true } @@ -583,9 +546,11 @@ export default class CodeEditor extends React.Component { setMode(mode) { let syntax = CodeMirror.findModeByName(convertModeName(mode)) - if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text') + if (syntax == null) syntax = + CodeMirror + .findModeByName('Plain Text') - this.editor.setOption('mode', syntax.mime) + this.editor.setOption('mode', syntax.mime) CodeMirror.autoLoadMode(this.editor, syntax.mode) } @@ -599,11 +564,7 @@ export default class CodeEditor extends React.Component { moveCursorTo(row, col) {} scrollToLine(event, num) { - const cursor = { - line: num, - ch: 1 - } - this.editor.setCursor(cursor) + const cursor = { line: num, ch: 1 } this.editor.setCursor(cursor) } focus() { @@ -616,32 +577,22 @@ export default class CodeEditor extends React.Component { reload() { // Change event shouldn't be fired when switch note - this.editor.off('change', this.changeHandler) - this.value = this.props.value - this.editor.setValue(this.props.value) - this.editor.clearHistory() - this.editor.on('change', this.changeHandler) - this.editor.refresh() + this.editor.off('change', this.changeHandler) this.value = + this.props.value this.editor.setValue(this.props.value) this.editor + .clearHistory() this.editor.on('change', this.changeHandler) this + .editor.refresh() } setValue(value) { - const cursor = this.editor.getCursor() - this.editor.setValue(value) - this.editor.setCursor(cursor) + const cursor = this.editor.getCursor() this.editor.setValue(value) this + .editor.setCursor(cursor) } handleDropImage(dropEvent) { dropEvent.preventDefault() - const { - storageKey, - noteKey - } = this.props + const {storageKey, noteKey} = this.props attachmentManagement.handleAttachmentDrop( - this, - storageKey, - noteKey, - dropEvent - ) + this, storageKey, noteKey, dropEvent) } insertAttachmentMd(imageMd) { @@ -650,59 +601,43 @@ export default class CodeEditor extends React.Component { handlePaste(editor, e) { const clipboardData = e.clipboardData - const { - storageKey, - noteKey - } = this.props - const dataTransferItem = clipboardData.items[0] - const pastedTxt = clipboardData.getData('text') - const isURL = str => { - const matcher = /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/ - return matcher.test(str) - } - const isInLinkTag = editor => { - const startCursor = editor.getCursor('start') - const prevChar = editor.getRange({ - line: startCursor.line, - ch: startCursor.ch - 2 - }, { - line: startCursor.line, - ch: startCursor.ch - }) - const endCursor = editor.getCursor('end') - const nextChar = editor.getRange({ - line: endCursor.line, - ch: endCursor.ch - }, { - line: endCursor.line, - ch: endCursor.ch + 1 - }) - return prevChar === '](' && nextChar === ')' - } + const {storageKey, noteKey} = this.props + const dataTransferItem = clipboardData.items[0] const pastedTxt = + clipboardData.getData('text') + const isURL = + str => { + const matcher = + /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/ + return matcher.test(str) + } const isInLinkTag = + editor => { + const startCursor = editor.getCursor('start') + const prevChar = editor.getRange( + {line: startCursor.line, ch: startCursor.ch - 2}, + {line: startCursor.line, ch: startCursor.ch}) + const endCursor = editor.getCursor('end') + const nextChar = editor.getRange( + {line: endCursor.line, ch: endCursor.ch}, + {line: endCursor.line, ch: endCursor.ch + 1}) + return prevChar === '](' && nextChar === ')' + } const pastedHtml = clipboardData.getData('text/html') if (pastedHtml !== '') { this.handlePasteHtml(e, editor, pastedHtml) - } else if (dataTransferItem.type.match('image')) { + } + else if (dataTransferItem.type.match('image')) { attachmentManagement.handlePastImageEvent( - this, - storageKey, - noteKey, - dataTransferItem - ) - } else if ( - this.props.fetchUrlTitle && - isURL(pastedTxt) && - !isInLinkTag(editor) - ) { + this, storageKey, noteKey, dataTransferItem) + } + else if ( + this.props.fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) { this.handlePasteUrl(e, editor, pastedTxt) } if (attachmentManagement.isAttachmentLink(pastedTxt)) { attachmentManagement - .handleAttachmentLinkPaste(storageKey, noteKey, pastedTxt) - .then(modifiedText => { - this.editor.replaceSelection(modifiedText) - }) + .handleAttachmentLinkPaste(storageKey, noteKey, pastedTxt) + .then(modifiedText => {this.editor.replaceSelection(modifiedText)}) e.preventDefault() } } @@ -718,39 +653,36 @@ export default class CodeEditor extends React.Component { const taggedUrl = `<${pastedTxt}>` editor.replaceSelection(taggedUrl) - const isImageReponse = response => { - return ( - response.headers.has('content-type') && - response.headers.get('content-type').match(/^image\/.+$/) - ) - } - const replaceTaggedUrl = replacement => { - const value = editor.getValue() - const cursor = editor.getCursor() - const newValue = value.replace(taggedUrl, replacement) - const newCursor = Object.assign({}, cursor, { - ch: cursor.ch + newValue.length - value.length - }) - editor.setValue(newValue) - editor.setCursor(newCursor) - } + const isImageReponse = + response => { + return ( + response.headers.has('content-type') && + response.headers.get('content-type').match(/^image\/.+$/)) + } const replaceTaggedUrl = + replacement => { + const value = editor.getValue() + const cursor = editor.getCursor() + const newValue = value.replace(taggedUrl, replacement) + const newCursor = Object.assign( + {}, cursor, {ch: cursor.ch + newValue.length - value.length}) + editor.setValue(newValue) + editor.setCursor(newCursor) + } - fetch(pastedTxt, { - method: 'get' - }) - .then(response => { - if (isImageReponse(response)) { - return this.mapImageResponse(response, pastedTxt) - } else { - return this.mapNormalResponse(response, pastedTxt) - } - }) - .then(replacement => { - replaceTaggedUrl(replacement) - }) - .catch(e => { - replaceTaggedUrl(pastedTxt) - }) + fetch(pastedTxt, {method: 'get'}) + .then(response => { + if (isImageReponse(response)) { + return this.mapImageResponse( + response, pastedTxt) + } else { + return this.mapNormalResponse( + response, pastedTxt) + } + }) + .then( + replacement => { + replaceTaggedUrl(replacement)}) + .catch(e => {replaceTaggedUrl(pastedTxt)}) } handlePasteHtml(e, editor, pastedHtml) { @@ -760,23 +692,21 @@ export default class CodeEditor extends React.Component { } mapNormalResponse(response, pastedTxt) { - return this.decodeResponse(response).then(body => { - return new Promise((resolve, reject) => { - try { - const parsedBody = new window.DOMParser().parseFromString( - body, - 'text/html' - ) - const escapePipe = (str) => { - return str.replace('|', '\\|') + return this.decodeResponse(response).then( + body => {return new Promise((resolve, reject) => { + try { + const parsedBody = + new window.DOMParser().parseFromString(body, 'text/html') + const escapePipe = + (str) => { + return str.replace('|', '\\|') + } const linkWithTitle = + `[${escapePipe(parsedBody.title)}](${pastedTxt})` + resolve(linkWithTitle) + } catch (e) { + reject(e) } - const linkWithTitle = `[${escapePipe(parsedBody.title)}](${pastedTxt})` - resolve(linkWithTitle) - } catch (e) { - reject(e) - } - }) - }) + })}) } mapImageResponse(response, pastedTxt) { @@ -795,39 +725,30 @@ export default class CodeEditor extends React.Component { decodeResponse(response) { const headers = response.headers const _charset = headers.has('content-type') ? - this.extractContentTypeCharset(headers.get('content-type')) : - undefined - return response.arrayBuffer().then(buff => { - return new Promise((resolve, reject) => { - try { - const charset = _charset !== undefined && - iconv.encodingExists(_charset) ? - _charset : - 'utf-8' - resolve(iconv.decode(new Buffer(buff), charset).toString()) - } catch (e) { - reject(e) - } - }) - }) + this.extractContentTypeCharset(headers.get('content-type')) : + undefined + return response.arrayBuffer().then( + buff => {return new Promise((resolve, reject) => { + try { + const charset = + _charset !== undefined && iconv.encodingExists(_charset) ? + _charset : + 'utf-8' + resolve(iconv.decode(new Buffer(buff), charset).toString()) + } catch (e) { + reject(e) + } + })}) } extractContentTypeCharset(contentType) { - return contentType - .split(';') - .filter(str => { - return str.trim().toLowerCase().startsWith('charset') - }) - .map(str => { - return str.replace(/['"]/g, '').split('=')[1] - })[0] + return contentType.split(';') + .filter(str => {return str.trim().toLowerCase().startsWith('charset')}) + .map(str => {return str.replace(/['"]/g, '').split('=')[1]})[0] } render() { - const { - className, - fontSize - } = this.props + const {className, fontSize} = this.props const fontFamily = normalizeEditorFontFamily(this.props.fontFamily) const width = this.props.width return ( < @@ -835,18 +756,12 @@ export default class CodeEditor extends React.Component { className == null ? 'CodeEditor' : `CodeEditor ${className}` } ref = 'root' - tabIndex = '-1' - style = { - { - fontFamily, - fontSize: fontSize, - width: width - } - } - onDrop = { - e => this.handleDropImage(e) - } - /> + tabIndex = '-1' + style = { + { fontFamily, fontSize: fontSize, width: width } + } onDrop = { + e => this.handleDropImage(e) + } /> ) } } diff --git a/browser/components/MarkdownEditor.js b/browser/components/MarkdownEditor.js index f10ea8bc..dcb1b5ea 100644 --- a/browser/components/MarkdownEditor.js +++ b/browser/components/MarkdownEditor.js @@ -269,6 +269,9 @@ class MarkdownEditor extends React.Component { rulers={config.editor.rulers} displayLineNumbers={config.editor.displayLineNumbers} enableBracketMatching={config.editor.enableBracketMatching} + matchingPairs={config.editor.matchingPairs} + matchingTriples={config.editor.matchingTriples} + explodingPairs={config.editor.explodingPairs} scrollPastEnd={config.editor.scrollPastEnd} storageKey={storageKey} noteKey={noteKey} diff --git a/browser/components/MarkdownSplitEditor.js b/browser/components/MarkdownSplitEditor.js index 004a6eb0..689799c8 100644 --- a/browser/components/MarkdownSplitEditor.js +++ b/browser/components/MarkdownSplitEditor.js @@ -159,6 +159,9 @@ class MarkdownSplitEditor extends React.Component { fontSize={editorFontSize} displayLineNumbers={config.editor.displayLineNumbers} enableBracketMatching={config.editor.enableBracketMatching} + matchingPairs={config.editor.matchingPairs} + matchingTriples={config.editor.matchingTriples} + explodingPairs={config.editor.explodingPairs} indentType={config.editor.indentType} indentSize={editorIndentSize} enableRulers={config.editor.enableRulers} diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index 90b8faa9..86ce2c7a 100644 --- a/browser/main/Detail/SnippetNoteDetail.js +++ b/browser/main/Detail/SnippetNoteDetail.js @@ -700,6 +700,9 @@ class SnippetNoteDetail extends React.Component { indentSize={editorIndentSize} displayLineNumbers={config.editor.displayLineNumbers} enableBracketMatching={config.editor.enableBracketMatching} + matchingPairs={config.editor.matchingPairs} + matchingTriples={config.editor.matchingTriples} + explodingPairs={config.editor.explodingPairs} keyMap={config.editor.keyMap} scrollPastEnd={config.editor.scrollPastEnd} fetchUrlTitle={config.editor.fetchUrlTitle} diff --git a/browser/main/lib/ConfigManager.js b/browser/main/lib/ConfigManager.js index eddf0909..c86e9288 100644 --- a/browser/main/lib/ConfigManager.js +++ b/browser/main/lib/ConfigManager.js @@ -45,6 +45,9 @@ export const DEFAULT_CONFIG = { rulers: [80, 120], displayLineNumbers: true, enableBracketMatching: true, + matchingPairs:'()[]{}\'\'""$$**``', + matchingTriples:'```"""\'\'\'', + explodingPairs:'[]{}``$$', switchPreview: 'BLUR', // 'BLUR', 'DBL_CLICK', 'RIGHTCLICK' delfaultStatus: 'PREVIEW', // 'PREVIEW', 'CODE' scrollPastEnd: false, diff --git a/browser/main/modals/PreferencesModal/SnippetTab.js b/browser/main/modals/PreferencesModal/SnippetTab.js index 40b65d39..ebd4012e 100644 --- a/browser/main/modals/PreferencesModal/SnippetTab.js +++ b/browser/main/modals/PreferencesModal/SnippetTab.js @@ -137,6 +137,9 @@ class SnippetTab extends React.Component { rulers={config.editor.rulers} displayLineNumbers={config.editor.displayLineNumbers} enableBracketMatching={config.editor.enableBracketMatching} + matchingPairs={config.editor.matchingPairs} + matchingTriples={config.editor.matchingTriples} + explodingPairs={config.editor.explodingPairs} scrollPastEnd={config.editor.scrollPastEnd} onRef={ref => { this.snippetEditor = ref }} />
diff --git a/browser/main/modals/PreferencesModal/UiTab.js b/browser/main/modals/PreferencesModal/UiTab.js index ab72c914..2bd0c9d0 100644 --- a/browser/main/modals/PreferencesModal/UiTab.js +++ b/browser/main/modals/PreferencesModal/UiTab.js @@ -95,7 +95,10 @@ class UiTab extends React.Component { enableTableEditor: this.refs.enableTableEditor.checked, enableFrontMatterTitle: this.refs.enableFrontMatterTitle.checked, frontMatterTitleField: this.refs.frontMatterTitleField.value, - enableBracketMatching: this.refs.enableBracketMatching.checked + enableBracketMatching: this.refs.enableBracketMatching.checked, + matchingPairs: this.refs.matchingPairs.value, + matchingTriples: this.refs.matchingTriples.value, + explodingPairs: this.refs.explodingPairs.value }, preview: { fontSize: this.refs.previewFontSize.value, @@ -551,6 +554,48 @@ class UiTab extends React.Component {
+
+
+ {i18n.__('Matching character pairs')} +
+
+ this.handleUIChange(e)} + type='text' + /> +
+
+ +
+
+ {i18n.__('Matching character triples')} +
+
+ this.handleUIChange(e)} + type='text' + /> +
+
+ +
+
+ {i18n.__('Exploding character pairs')} +
+
+ this.handleUIChange(e)} + type='text' + /> +
+
+
{i18n.__('Preview')}
@@ -578,6 +623,7 @@ class UiTab extends React.Component { />
+
{i18n.__('Code Block Theme')}
From 441c70b388e7505cdacb7286c6bd40676326a164 Mon Sep 17 00:00:00 2001 From: Guilherme Silva Date: Thu, 8 Nov 2018 13:25:22 +0000 Subject: [PATCH 11/64] Removing checkmark and fixed Code Editor indentation --- browser/components/CodeEditor.js | 973 ++++++++++-------- browser/components/MarkdownEditor.js | 1 - browser/components/MarkdownSplitEditor.js | 1 - browser/main/Detail/SnippetNoteDetail.js | 1 - browser/main/lib/ConfigManager.js | 1 - .../modals/PreferencesModal/SnippetEditor.js | 15 +- .../modals/PreferencesModal/SnippetTab.js | 1 - browser/main/modals/PreferencesModal/UiTab.js | 12 - 8 files changed, 530 insertions(+), 475 deletions(-) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 3e416ab2..83f64a18 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -1,110 +1,121 @@ -import 'codemirror-mode-elixir' - -import {Alignment, options, TableEditor} from '@susisu/mte-kernel' -import consts from 'browser/lib/consts' -import convertModeName from 'browser/lib/convertModeName' -import TextEditorInterface from 'browser/lib/TextEditorInterface' -import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement' -import eventEmitter from 'browser/main/lib/eventEmitter' -import CodeMirror from 'codemirror' -import crypto from 'crypto' -import fs from 'fs' -import iconv from 'iconv-lite' -import _ from 'lodash' import PropTypes from 'prop-types' import React from 'react' - -const {ipcRenderer} = require('electron') +import _ from 'lodash' +import CodeMirror from 'codemirror' +import 'codemirror-mode-elixir' +import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement' +import convertModeName from 'browser/lib/convertModeName' +import { + options, + TableEditor, + Alignment +} from '@susisu/mte-kernel' +import TextEditorInterface from 'browser/lib/TextEditorInterface' +import eventEmitter from 'browser/main/lib/eventEmitter' +import iconv from 'iconv-lite' +import crypto from 'crypto' +import consts from 'browser/lib/consts' +import fs from 'fs' +const { + ipcRenderer +} = require('electron') import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily' import TurndownService from 'turndown' -import{gfm} from 'turndown-plugin-gfm' +import { + gfm +} from 'turndown-plugin-gfm' CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js' const buildCMRulers = (rulers, enableRulers) => - (enableRulers ? rulers.map(ruler => ({column: ruler})) : []) + (enableRulers ? rulers.map(ruler => ({ + column: ruler + })) : []) export default class CodeEditor extends React.Component { constructor(props) { super(props) - this.scrollHandler = - _.debounce( - this.handleScroll.bind(this), 100, - {leading: false, trailing: true}) this.changeHandler = e => - this.handleChange(e) this.focusHandler = - () => { - ipcRenderer.send('editor:focused', true) - } this.blurHandler = - (editor, e) => { - ipcRenderer.send('editor:focused', false) - if (e == null) return null - let el = e.relatedTarget - while (el != null) { - if (el === this.refs.root) { - return - } - el = el.parentNode - } - this.props.onBlur != null && this.props.onBlur(e) + this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, { + leading: false, + trailing: true + }) + this.changeHandler = e => this.handleChange(e) + this.focusHandler = () => { + ipcRenderer.send('editor:focused', true) + } + this.blurHandler = (editor, e) => { + ipcRenderer.send('editor:focused', false) + if (e == null) return null + let el = e.relatedTarget + while (el != null) { + if (el === this.refs.root) { + return + } + el = el.parentNode + } + this.props.onBlur != null && this.props.onBlur(e) - const {storageKey, noteKey} = this.props - attachmentManagement.deleteAttachmentsNotPresentInNote( - this.editor.getValue(), storageKey, noteKey) - } this.pasteHandler = (editor, e) => - this.handlePaste(editor, e) this.loadStyleHandler = - e => { - this.editor.refresh() - } this.searchHandler = (e, msg) => - this.handleSearch(msg) this.searchState = - null this.scrollToLineHandeler = - this.scrollToLine - .bind(this) + const { + storageKey, + noteKey + } = this.props + attachmentManagement.deleteAttachmentsNotPresentInNote( + this.editor.getValue(), + storageKey, + noteKey + ) + } + this.pasteHandler = (editor, e) => this.handlePaste(editor, e) + this.loadStyleHandler = e => { + this.editor.refresh() + } + this.searchHandler = (e, msg) => this.handleSearch(msg) + this.searchState = null + this.scrollToLineHandeler = this.scrollToLine.bind(this) - this.formatTable = () => - this.handleFormatTable() this - .editorActivityHandler = () => - this.handleEditorActivity() + this.formatTable = () => this.handleFormatTable() + this.editorActivityHandler = () => this.handleEditorActivity() } handleSearch(msg) { const cm = this.editor const component = this - if (component.searchState) - cm.removeOverlay(component.searchState) + if (component.searchState) cm.removeOverlay(component.searchState) if (msg.length < 3) return - cm.operation(function() { - component.searchState = makeOverlay(msg, 'searching') - cm.addOverlay(component.searchState) + cm.operation(function () { + component.searchState = makeOverlay(msg, 'searching') + cm.addOverlay(component.searchState) - function makeOverlay(query, style) { - query = new RegExp( - query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'), - 'gi') - return { - token: function(stream) { - query.lastIndex = stream.pos - var match = query.exec(stream.string) - if (match && match.index === stream.pos) { - stream.pos += match[0].length || 1 - return style - } - else if (match) { - stream.pos = match.index - } - else { - stream.skipToEnd() - } - } + function makeOverlay(query, style) { + query = new RegExp( + query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'), + 'gi' + ) + return { + token: function (stream) { + query.lastIndex = stream.pos + var match = query.exec(stream.string) + if (match && match.index === stream.pos) { + stream.pos += match[0].length || 1 + return style + } else if (match) { + stream.pos = match.index + } else { + stream.skipToEnd() } } - }) + } + } + }) } handleFormatTable() { - this.tableEditor.formatAll(options({textWidthOptions: {}})) + this.tableEditor.formatAll(options({ + textWidthOptions: {} + })) } handleEditorActivity() { @@ -117,23 +128,23 @@ export default class CodeEditor extends React.Component { const active = this.tableEditor.cursorIsInTable(this.tableEditorOptions) if (active) { if (this.extraKeysMode !== 'editor') { - this.extraKeysMode = - 'editor' this.editor.setOption('extraKeys', this.editorKeyMap) + this.extraKeysMode = 'editor' + this.editor.setOption('extraKeys', this.editorKeyMap) } - } - else { + } else { if (this.extraKeysMode !== 'default') { - this.extraKeysMode = - 'default' this.editor - .setOption( - 'extraKeys', - this.defaultKeyMap) this.tableEditor.resetSmartCursor() + this.extraKeysMode = 'default' + this.editor.setOption('extraKeys', this.defaultKeyMap) + this.tableEditor.resetSmartCursor() } } } componentDidMount() { - const {rulers, enableRulers} = this.props + const { + rulers, + enableRulers + } = this.props const expandSnippet = this.expandSnippet.bind(this) eventEmitter.on('line:jump', this.scrollToLineHandeler) @@ -141,99 +152,104 @@ export default class CodeEditor extends React.Component { id: crypto.randomBytes(16).toString('hex'), name: 'Dummy text', prefix: ['lorem', 'ipsum'], - content: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.' - }] if (!fs.existsSync(consts.SNIPPET_FILE)) { + content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.' + }] + if (!fs.existsSync(consts.SNIPPET_FILE)) { fs.writeFileSync( - consts.SNIPPET_FILE, JSON.stringify(defaultSnippet, null, 4), 'utf8') + consts.SNIPPET_FILE, + JSON.stringify(defaultSnippet, null, 4), + 'utf8' + ) } - this.defaultKeyMap = - CodeMirror - .normalizeKeyMap({ - Tab: function(cm) { - const cursor = cm.getCursor() - const line = cm.getLine(cursor.line) - const cursorPosition = cursor.ch - const charBeforeCursor = line.substr(cursorPosition - 1, 1) - if (cm.somethingSelected()) cm.indentSelection('add') else { - const tabs = cm.getOption('indentWithTabs') - if (line.trimLeft().match(/^(-|\*|\+) (\[( |x)] )?$/)) { - cm.execCommand('goLineStart') - if (tabs) { - cm.execCommand('insertTab') - } - else {cm.execCommand('insertSoftTab')} cm.execCommand( - 'goLineEnd') - } - else if ( - !charBeforeCursor.match(/\t|\s|\r|\n/) && cursor.ch > 1) { - // text expansion on tab key if the char before is alphabet - const snippets = - JSON.parse(fs.readFileSync(consts.SNIPPET_FILE, 'utf8')) - if (expandSnippet(line, cursor, cm, snippets) === false) { - if (tabs) { - cm.execCommand('insertTab') - } else { - cm.execCommand('insertSoftTab') - } - } - } - else { - if (tabs) { - cm.execCommand('insertTab') - } else { - cm.execCommand('insertSoftTab') - } - } - } - }, - 'Cmd-T': function(cm) { - // Do nothing - }, - Enter: 'boostNewLineAndIndentContinueMarkdownList', - 'Ctrl-C': cm => { - if (cm.getOption('keyMap').substr(0, 3) === 'vim') { - document.execCommand('copy') - } - return CodeMirror.Pass + this.defaultKeyMap = CodeMirror.normalizeKeyMap({ + Tab: function (cm) { + const cursor = cm.getCursor() + const line = cm.getLine(cursor.line) + const cursorPosition = cursor.ch + const charBeforeCursor = line.substr(cursorPosition - 1, 1) + if (cm.somethingSelected()) cm.indentSelection('add') + else { + const tabs = cm.getOption('indentWithTabs') + if (line.trimLeft().match(/^(-|\*|\+) (\[( |x)] )?$/)) { + cm.execCommand('goLineStart') + if (tabs) { + cm.execCommand('insertTab') + } else { + cm.execCommand('insertSoftTab') + } + cm.execCommand('goLineEnd') + } else if ( + !charBeforeCursor.match(/\t|\s|\r|\n/) && + cursor.ch > 1 + ) { + // text expansion on tab key if the char before is alphabet + const snippets = JSON.parse( + fs.readFileSync(consts.SNIPPET_FILE, 'utf8') + ) + if (expandSnippet(line, cursor, cm, snippets) === false) { + if (tabs) { + cm.execCommand('insertTab') + } else { + cm.execCommand('insertSoftTab') } - }) + } + } else { + if (tabs) { + cm.execCommand('insertTab') + } else { + cm.execCommand('insertSoftTab') + } + } + } + }, + 'Cmd-T': function (cm) { + // Do nothing + }, + Enter: 'boostNewLineAndIndentContinueMarkdownList', + 'Ctrl-C': cm => { + if (cm.getOption('keyMap').substr(0, 3) === 'vim') { + document.execCommand('copy') + } + return CodeMirror.Pass + } + }) - this.value = this.props.value this.editor = - CodeMirror(this.refs.root, { - rulers: buildCMRulers(rulers, enableRulers), - value: this.props.value, - lineNumbers: this.props.displayLineNumbers, - lineWrapping: true, - theme: this.props.theme, - indentUnit: this.props.indentSize, - tabSize: this.props.indentSize, - indentWithTabs: this.props.indentType !== 'space', - keyMap: this.props.keyMap, - scrollPastEnd: this.props.scrollPastEnd, - inputStyle: 'textarea', - dragDrop: false, - foldGutter: true, - gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], - autoCloseBrackets: { - pairs: this.props.matchingPairs, - triples: this.props.matchingTriples, - explode: this.props.explodingPairs, - override: true - }, - extraKeys: this.defaultKeyMap - }) + this.value = this.props.value + this.editor = CodeMirror(this.refs.root, { + rulers: buildCMRulers(rulers, enableRulers), + value: this.props.value, + lineNumbers: this.props.displayLineNumbers, + lineWrapping: true, + theme: this.props.theme, + indentUnit: this.props.indentSize, + tabSize: this.props.indentSize, + indentWithTabs: this.props.indentType !== 'space', + keyMap: this.props.keyMap, + scrollPastEnd: this.props.scrollPastEnd, + inputStyle: 'textarea', + dragDrop: false, + foldGutter: true, + gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], + autoCloseBrackets: { + pairs: this.props.matchingPairs, + triples: this.props.matchingTriples, + explode: this.props.explodingPairs, + override: true + }, + extraKeys: this.defaultKeyMap + }) - this.setMode(this.props.mode) + this.setMode(this.props.mode) - this.editor.on('focus', this.focusHandler) this.editor - .on('blur', this.blurHandler) this.editor - .on('change', this.changeHandler) this.editor.on( - 'paste', this.pasteHandler) + this.editor.on('focus', this.focusHandler) + this.editor.on('blur', this.blurHandler) + this.editor.on('change', this.changeHandler) + this.editor.on('paste', this.pasteHandler) eventEmitter.on('top:search', this.searchHandler) - eventEmitter.emit('code:init') this.editor.on('scroll', this.scrollHandler) + eventEmitter.emit('code:init') + this.editor.on('scroll', this.scrollHandler) const editorTheme = document.getElementById('editorTheme') editorTheme.addEventListener('load', this.loadStyleHandler) @@ -242,151 +258,146 @@ export default class CodeEditor extends React.Component { CodeMirror.Vim.defineEx('q!', 'q!', this.quitEditor) CodeMirror.Vim.defineEx('wq', 'wq', this.quitEditor) CodeMirror.Vim.defineEx('qw', 'qw', this.quitEditor) - CodeMirror.Vim - .map('ZZ', ':q', 'normal') + CodeMirror.Vim.map('ZZ', ':q', 'normal') - this.textEditorInterface = - new TextEditorInterface(this.editor) this.tableEditor = - new TableEditor(this.textEditorInterface) - eventEmitter - .on('code:format-table', this.formatTable) + this.textEditorInterface = new TextEditorInterface(this.editor) + this.tableEditor = new TableEditor(this.textEditorInterface) + eventEmitter.on('code:format-table', this.formatTable) - this.tableEditorOptions = options({smartCursor: true}) + this.tableEditorOptions = options({ + smartCursor: true + }) - this.editorKeyMap = - CodeMirror.normalizeKeyMap({ - 'Tab': () => { - this.tableEditor.nextCell(this.tableEditorOptions) - }, - 'Shift-Tab': () => { - this.tableEditor.previousCell(this.tableEditorOptions) - }, - 'Enter': () => { - this.tableEditor.nextRow(this.tableEditorOptions) - }, - 'Ctrl-Enter': () => { - this.tableEditor.escape(this.tableEditorOptions) - }, - 'Cmd-Enter': () => { - this.tableEditor.escape(this.tableEditorOptions) - }, - 'Shift-Ctrl-Left': () => { - this.tableEditor.alignColumn( - Alignment.LEFT, this.tableEditorOptions) - }, - 'Shift-Cmd-Left': () => { - this.tableEditor.alignColumn( - Alignment.LEFT, this.tableEditorOptions) - }, - 'Shift-Ctrl-Right': () => { - this.tableEditor.alignColumn( - Alignment.RIGHT, this.tableEditorOptions) - }, - 'Shift-Cmd-Right': () => { - this.tableEditor.alignColumn( - Alignment.RIGHT, this.tableEditorOptions) - }, - 'Shift-Ctrl-Up': () => { - this.tableEditor.alignColumn( - Alignment.CENTER, this.tableEditorOptions) - }, - 'Shift-Cmd-Up': () => { - this.tableEditor.alignColumn( - Alignment.CENTER, this.tableEditorOptions) - }, - 'Shift-Ctrl-Down': () => { - this.tableEditor.alignColumn( - Alignment.NONE, this.tableEditorOptions) - }, - 'Shift-Cmd-Down': () => { - this.tableEditor.alignColumn( - Alignment.NONE, this.tableEditorOptions) - }, - 'Ctrl-Left': () => { - this.tableEditor.moveFocus(0, -1, this.tableEditorOptions) - }, - 'Cmd-Left': () => { - this.tableEditor.moveFocus(0, -1, this.tableEditorOptions) - }, - 'Ctrl-Right': () => { - this.tableEditor.moveFocus(0, 1, this.tableEditorOptions) - }, - 'Cmd-Right': () => { - this.tableEditor.moveFocus(0, 1, this.tableEditorOptions) - }, - 'Ctrl-Up': () => { - this.tableEditor.moveFocus(-1, 0, this.tableEditorOptions) - }, - 'Cmd-Up': () => { - this.tableEditor.moveFocus(-1, 0, this.tableEditorOptions) - }, - 'Ctrl-Down': () => { - this.tableEditor.moveFocus(1, 0, this.tableEditorOptions) - }, - 'Cmd-Down': () => { - this.tableEditor.moveFocus(1, 0, this.tableEditorOptions) - }, - 'Ctrl-K Ctrl-I': () => { - this.tableEditor.insertRow(this.tableEditorOptions) - }, - 'Cmd-K Cmd-I': () => { - this.tableEditor.insertRow(this.tableEditorOptions) - }, - 'Ctrl-L Ctrl-I': () => { - this.tableEditor.deleteRow(this.tableEditorOptions) - }, - 'Cmd-L Cmd-I': () => { - this.tableEditor.deleteRow(this.tableEditorOptions) - }, - 'Ctrl-K Ctrl-J': () => { - this.tableEditor.insertColumn(this.tableEditorOptions) - }, - 'Cmd-K Cmd-J': () => { - this.tableEditor.insertColumn(this.tableEditorOptions) - }, - 'Ctrl-L Ctrl-J': () => { - this.tableEditor.deleteColumn(this.tableEditorOptions) - }, - 'Cmd-L Cmd-J': () => { - this.tableEditor.deleteColumn(this.tableEditorOptions) - }, - 'Alt-Shift-Ctrl-Left': () => { - this.tableEditor.moveColumn(-1, this.tableEditorOptions) - }, - 'Alt-Shift-Cmd-Left': () => { - this.tableEditor.moveColumn(-1, this.tableEditorOptions) - }, - 'Alt-Shift-Ctrl-Right': () => { - this.tableEditor.moveColumn(1, this.tableEditorOptions) - }, - 'Alt-Shift-Cmd-Right': () => { - this.tableEditor.moveColumn(1, this.tableEditorOptions) - }, - 'Alt-Shift-Ctrl-Up': () => { - this.tableEditor.moveRow(-1, this.tableEditorOptions) - }, - 'Alt-Shift-Cmd-Up': () => { - this.tableEditor.moveRow(-1, this.tableEditorOptions) - }, - 'Alt-Shift-Ctrl-Down': () => { - this.tableEditor.moveRow(1, this.tableEditorOptions) - }, - 'Alt-Shift-Cmd-Down': () => { - this.tableEditor.moveRow(1, this.tableEditorOptions) - } - }) + this.editorKeyMap = CodeMirror.normalizeKeyMap({ + 'Tab': () => { + this.tableEditor.nextCell(this.tableEditorOptions) + }, + 'Shift-Tab': () => { + this.tableEditor.previousCell(this.tableEditorOptions) + }, + 'Enter': () => { + this.tableEditor.nextRow(this.tableEditorOptions) + }, + 'Ctrl-Enter': () => { + this.tableEditor.escape(this.tableEditorOptions) + }, + 'Cmd-Enter': () => { + this.tableEditor.escape(this.tableEditorOptions) + }, + 'Shift-Ctrl-Left': () => { + this.tableEditor.alignColumn(Alignment.LEFT, this.tableEditorOptions) + }, + 'Shift-Cmd-Left': () => { + this.tableEditor.alignColumn(Alignment.LEFT, this.tableEditorOptions) + }, + 'Shift-Ctrl-Right': () => { + this.tableEditor.alignColumn(Alignment.RIGHT, this.tableEditorOptions) + }, + 'Shift-Cmd-Right': () => { + this.tableEditor.alignColumn(Alignment.RIGHT, this.tableEditorOptions) + }, + 'Shift-Ctrl-Up': () => { + this.tableEditor.alignColumn(Alignment.CENTER, this.tableEditorOptions) + }, + 'Shift-Cmd-Up': () => { + this.tableEditor.alignColumn(Alignment.CENTER, this.tableEditorOptions) + }, + 'Shift-Ctrl-Down': () => { + this.tableEditor.alignColumn(Alignment.NONE, this.tableEditorOptions) + }, + 'Shift-Cmd-Down': () => { + this.tableEditor.alignColumn(Alignment.NONE, this.tableEditorOptions) + }, + 'Ctrl-Left': () => { + this.tableEditor.moveFocus(0, -1, this.tableEditorOptions) + }, + 'Cmd-Left': () => { + this.tableEditor.moveFocus(0, -1, this.tableEditorOptions) + }, + 'Ctrl-Right': () => { + this.tableEditor.moveFocus(0, 1, this.tableEditorOptions) + }, + 'Cmd-Right': () => { + this.tableEditor.moveFocus(0, 1, this.tableEditorOptions) + }, + 'Ctrl-Up': () => { + this.tableEditor.moveFocus(-1, 0, this.tableEditorOptions) + }, + 'Cmd-Up': () => { + this.tableEditor.moveFocus(-1, 0, this.tableEditorOptions) + }, + 'Ctrl-Down': () => { + this.tableEditor.moveFocus(1, 0, this.tableEditorOptions) + }, + 'Cmd-Down': () => { + this.tableEditor.moveFocus(1, 0, this.tableEditorOptions) + }, + 'Ctrl-K Ctrl-I': () => { + this.tableEditor.insertRow(this.tableEditorOptions) + }, + 'Cmd-K Cmd-I': () => { + this.tableEditor.insertRow(this.tableEditorOptions) + }, + 'Ctrl-L Ctrl-I': () => { + this.tableEditor.deleteRow(this.tableEditorOptions) + }, + 'Cmd-L Cmd-I': () => { + this.tableEditor.deleteRow(this.tableEditorOptions) + }, + 'Ctrl-K Ctrl-J': () => { + this.tableEditor.insertColumn(this.tableEditorOptions) + }, + 'Cmd-K Cmd-J': () => { + this.tableEditor.insertColumn(this.tableEditorOptions) + }, + 'Ctrl-L Ctrl-J': () => { + this.tableEditor.deleteColumn(this.tableEditorOptions) + }, + 'Cmd-L Cmd-J': () => { + this.tableEditor.deleteColumn(this.tableEditorOptions) + }, + 'Alt-Shift-Ctrl-Left': () => { + this.tableEditor.moveColumn(-1, this.tableEditorOptions) + }, + 'Alt-Shift-Cmd-Left': () => { + this.tableEditor.moveColumn(-1, this.tableEditorOptions) + }, + 'Alt-Shift-Ctrl-Right': () => { + this.tableEditor.moveColumn(1, this.tableEditorOptions) + }, + 'Alt-Shift-Cmd-Right': () => { + this.tableEditor.moveColumn(1, this.tableEditorOptions) + }, + 'Alt-Shift-Ctrl-Up': () => { + this.tableEditor.moveRow(-1, this.tableEditorOptions) + }, + 'Alt-Shift-Cmd-Up': () => { + this.tableEditor.moveRow(-1, this.tableEditorOptions) + }, + 'Alt-Shift-Ctrl-Down': () => { + this.tableEditor.moveRow(1, this.tableEditorOptions) + }, + 'Alt-Shift-Cmd-Down': () => { + this.tableEditor.moveRow(1, this.tableEditorOptions) + } + }) if (this.props.enableTableEditor) { - this.editor.on('cursorActivity', this.editorActivityHandler) this.editor - .on('changes', this.editorActivityHandler) + this.editor.on('cursorActivity', this.editorActivityHandler) + this.editor.on('changes', this.editorActivityHandler) } - this.setState({clientWidth: this.refs.root.clientWidth}) + this.setState({ + clientWidth: this.refs.root.clientWidth + }) } expandSnippet(line, cursor, cm, snippets) { - const wordBeforeCursor = - this.getWordBeforeCursor(line, cursor.line, cursor.ch) + const wordBeforeCursor = this.getWordBeforeCursor( + line, + cursor.line, + cursor.ch + ) const templateCursorString = ':{}' for (let i = 0; i < snippets.length; i++) { if (snippets[i].prefix.indexOf(wordBeforeCursor.text) !== -1) { @@ -400,8 +411,10 @@ export default class CodeEditor extends React.Component { cursorLineNumber = j cursorLinePosition = cursorIndex cm.replaceRange( - snippets[i].content.replace(templateCursorString, ''), - wordBeforeCursor.range.from, wordBeforeCursor.range.to) + snippets[i].content.replace(templateCursorString, ''), + wordBeforeCursor.range.from, + wordBeforeCursor.range.to + ) cm.setCursor({ line: cursor.line + cursorLineNumber, ch: cursorLinePosition @@ -410,8 +423,10 @@ export default class CodeEditor extends React.Component { } } else { cm.replaceRange( - snippets[i].content, wordBeforeCursor.range.from, - wordBeforeCursor.range.to) + snippets[i].content, + wordBeforeCursor.range.from, + wordBeforeCursor.range.to + ) } return true } @@ -426,8 +441,7 @@ export default class CodeEditor extends React.Component { const emptyChars = /\t|\s|\r|\n/ // to prevent the word to expand is long that will crash the whole app - // the safeStop is there to stop user to expand words that longer than 20 - // chars + // the safeStop is there to stop user to expand words that longer than 20 chars const safeStop = 20 while (cursorPosition > 0) { @@ -435,17 +449,25 @@ export default class CodeEditor extends React.Component { // if char is not an empty char if (!emptyChars.test(currentChar)) { wordBeforeCursor = currentChar + wordBeforeCursor - } - else if (wordBeforeCursor.length >= safeStop) { + } else if (wordBeforeCursor.length >= safeStop) { throw new Error('Your snippet trigger is too long !') + } else { + break } - else {break} cursorPosition-- + cursorPosition-- } return { - text: wordBeforeCursor, range: { - from: {line: lineNumber, ch: originCursorPosition}, - to: {line: lineNumber, ch: cursorPosition} + text: wordBeforeCursor, + range: { + from: { + line: lineNumber, + ch: originCursorPosition + }, + to: { + line: lineNumber, + ch: cursorPosition + } } } } @@ -455,12 +477,12 @@ export default class CodeEditor extends React.Component { } componentWillUnmount() { - this.editor.off('focus', this.focusHandler) this.editor - .off('blur', this.blurHandler) this.editor - .off('change', this.changeHandler) this.editor.off( - 'paste', this.pasteHandler) - eventEmitter.off('top:search', this.searchHandler) this.editor.off( - 'scroll', this.scrollHandler) + this.editor.off('focus', this.focusHandler) + this.editor.off('blur', this.blurHandler) + this.editor.off('change', this.changeHandler) + this.editor.off('paste', this.pasteHandler) + eventEmitter.off('top:search', this.searchHandler) + this.editor.off('scroll', this.scrollHandler) const editorTheme = document.getElementById('editorTheme') editorTheme.removeEventListener('load', this.loadStyleHandler) @@ -469,7 +491,10 @@ export default class CodeEditor extends React.Component { componentDidUpdate(prevProps, prevState) { let needRefresh = false - const {rulers, enableRulers} = this.props + const { + rulers, + enableRulers + } = this.props if (prevProps.mode !== this.props.mode) { this.setMode(this.props.mode) } @@ -487,14 +512,16 @@ export default class CodeEditor extends React.Component { needRefresh = true } - if (prevProps.enableRulers !== enableRulers || - prevProps.rulers !== rulers) { + if ( + prevProps.enableRulers !== enableRulers || + prevProps.rulers !== rulers + ) { this.editor.setOption('rulers', buildCMRulers(rulers, enableRulers)) } if (prevProps.indentSize !== this.props.indentSize) { - this.editor.setOption('indentUnit', this.props.indentSize) this.editor - .setOption('tabSize', this.props.indentSize) + this.editor.setOption('indentUnit', this.props.indentSize) + this.editor.setOption('tabSize', this.props.indentSize) } if (prevProps.indentType !== this.props.indentType) { this.editor.setOption('indentWithTabs', this.props.indentType !== 'space') @@ -509,9 +536,9 @@ export default class CodeEditor extends React.Component { } if (prevProps.matchingPairs !== this.props.matchingPairs || - prevProps.matchingTriples !== this.props.matchingTriples || - prevProps.explodingPairs !== this.props.explodingPairs) { - const bracketObject = { + prevProps.matchingTriples !== this.props.matchingTriples || + prevProps.explodingPairs !== this.props.explodingPairs) { + const bracketObject ={ pairs: this.props.matchingPairs, triples: this.props.matchingTriples, explode: this.props.explodingPairs, @@ -522,19 +549,21 @@ export default class CodeEditor extends React.Component { if (prevProps.enableTableEditor !== this.props.enableTableEditor) { if (this.props.enableTableEditor) { - this.editor.on('cursorActivity', this.editorActivityHandler) this.editor - .on('changes', this.editorActivityHandler) + this.editor.on('cursorActivity', this.editorActivityHandler) + this.editor.on('changes', this.editorActivityHandler) } else { - this.editor.off('cursorActivity', this.editorActivityHandler) this - .editor.off('changes', this.editorActivityHandler) + this.editor.off('cursorActivity', this.editorActivityHandler) + this.editor.off('changes', this.editorActivityHandler) } - this.extraKeysMode = - 'default' this.editor.setOption('extraKeys', this.defaultKeyMap) + this.extraKeysMode = 'default' + this.editor.setOption('extraKeys', this.defaultKeyMap) } if (this.state.clientWidth !== this.refs.root.clientWidth) { - this.setState({clientWidth: this.refs.root.clientWidth}) + this.setState({ + clientWidth: this.refs.root.clientWidth + }) needRefresh = true } @@ -546,11 +575,9 @@ export default class CodeEditor extends React.Component { setMode(mode) { let syntax = CodeMirror.findModeByName(convertModeName(mode)) - if (syntax == null) syntax = - CodeMirror - .findModeByName('Plain Text') + if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text') - this.editor.setOption('mode', syntax.mime) + this.editor.setOption('mode', syntax.mime) CodeMirror.autoLoadMode(this.editor, syntax.mode) } @@ -564,7 +591,11 @@ export default class CodeEditor extends React.Component { moveCursorTo(row, col) {} scrollToLine(event, num) { - const cursor = { line: num, ch: 1 } this.editor.setCursor(cursor) + const cursor = { + line: num, + ch: 1 + } + this.editor.setCursor(cursor) } focus() { @@ -577,22 +608,32 @@ export default class CodeEditor extends React.Component { reload() { // Change event shouldn't be fired when switch note - this.editor.off('change', this.changeHandler) this.value = - this.props.value this.editor.setValue(this.props.value) this.editor - .clearHistory() this.editor.on('change', this.changeHandler) this - .editor.refresh() + this.editor.off('change', this.changeHandler) + this.value = this.props.value + this.editor.setValue(this.props.value) + this.editor.clearHistory() + this.editor.on('change', this.changeHandler) + this.editor.refresh() } setValue(value) { - const cursor = this.editor.getCursor() this.editor.setValue(value) this - .editor.setCursor(cursor) + const cursor = this.editor.getCursor() + this.editor.setValue(value) + this.editor.setCursor(cursor) } handleDropImage(dropEvent) { dropEvent.preventDefault() - const {storageKey, noteKey} = this.props + const { + storageKey, + noteKey + } = this.props attachmentManagement.handleAttachmentDrop( - this, storageKey, noteKey, dropEvent) + this, + storageKey, + noteKey, + dropEvent + ) } insertAttachmentMd(imageMd) { @@ -601,43 +642,59 @@ export default class CodeEditor extends React.Component { handlePaste(editor, e) { const clipboardData = e.clipboardData - const {storageKey, noteKey} = this.props - const dataTransferItem = clipboardData.items[0] const pastedTxt = - clipboardData.getData('text') - const isURL = - str => { - const matcher = - /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/ - return matcher.test(str) - } const isInLinkTag = - editor => { - const startCursor = editor.getCursor('start') - const prevChar = editor.getRange( - {line: startCursor.line, ch: startCursor.ch - 2}, - {line: startCursor.line, ch: startCursor.ch}) - const endCursor = editor.getCursor('end') - const nextChar = editor.getRange( - {line: endCursor.line, ch: endCursor.ch}, - {line: endCursor.line, ch: endCursor.ch + 1}) - return prevChar === '](' && nextChar === ')' - } + const { + storageKey, + noteKey + } = this.props + const dataTransferItem = clipboardData.items[0] + const pastedTxt = clipboardData.getData('text') + const isURL = str => { + const matcher = /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/ + return matcher.test(str) + } + const isInLinkTag = editor => { + const startCursor = editor.getCursor('start') + const prevChar = editor.getRange({ + line: startCursor.line, + ch: startCursor.ch - 2 + }, { + line: startCursor.line, + ch: startCursor.ch + }) + const endCursor = editor.getCursor('end') + const nextChar = editor.getRange({ + line: endCursor.line, + ch: endCursor.ch + }, { + line: endCursor.line, + ch: endCursor.ch + 1 + }) + return prevChar === '](' && nextChar === ')' + } const pastedHtml = clipboardData.getData('text/html') if (pastedHtml !== '') { this.handlePasteHtml(e, editor, pastedHtml) - } - else if (dataTransferItem.type.match('image')) { + } else if (dataTransferItem.type.match('image')) { attachmentManagement.handlePastImageEvent( - this, storageKey, noteKey, dataTransferItem) - } - else if ( - this.props.fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) { + this, + storageKey, + noteKey, + dataTransferItem + ) + } else if ( + this.props.fetchUrlTitle && + isURL(pastedTxt) && + !isInLinkTag(editor) + ) { this.handlePasteUrl(e, editor, pastedTxt) } if (attachmentManagement.isAttachmentLink(pastedTxt)) { attachmentManagement - .handleAttachmentLinkPaste(storageKey, noteKey, pastedTxt) - .then(modifiedText => {this.editor.replaceSelection(modifiedText)}) + .handleAttachmentLinkPaste(storageKey, noteKey, pastedTxt) + .then(modifiedText => { + this.editor.replaceSelection(modifiedText) + }) e.preventDefault() } } @@ -653,36 +710,39 @@ export default class CodeEditor extends React.Component { const taggedUrl = `<${pastedTxt}>` editor.replaceSelection(taggedUrl) - const isImageReponse = - response => { - return ( - response.headers.has('content-type') && - response.headers.get('content-type').match(/^image\/.+$/)) - } const replaceTaggedUrl = - replacement => { - const value = editor.getValue() - const cursor = editor.getCursor() - const newValue = value.replace(taggedUrl, replacement) - const newCursor = Object.assign( - {}, cursor, {ch: cursor.ch + newValue.length - value.length}) - editor.setValue(newValue) - editor.setCursor(newCursor) - } + const isImageReponse = response => { + return ( + response.headers.has('content-type') && + response.headers.get('content-type').match(/^image\/.+$/) + ) + } + const replaceTaggedUrl = replacement => { + const value = editor.getValue() + const cursor = editor.getCursor() + const newValue = value.replace(taggedUrl, replacement) + const newCursor = Object.assign({}, cursor, { + ch: cursor.ch + newValue.length - value.length + }) + editor.setValue(newValue) + editor.setCursor(newCursor) + } - fetch(pastedTxt, {method: 'get'}) - .then(response => { - if (isImageReponse(response)) { - return this.mapImageResponse( - response, pastedTxt) - } else { - return this.mapNormalResponse( - response, pastedTxt) - } - }) - .then( - replacement => { - replaceTaggedUrl(replacement)}) - .catch(e => {replaceTaggedUrl(pastedTxt)}) + fetch(pastedTxt, { + method: 'get' + }) + .then(response => { + if (isImageReponse(response)) { + return this.mapImageResponse(response, pastedTxt) + } else { + return this.mapNormalResponse(response, pastedTxt) + } + }) + .then(replacement => { + replaceTaggedUrl(replacement) + }) + .catch(e => { + replaceTaggedUrl(pastedTxt) + }) } handlePasteHtml(e, editor, pastedHtml) { @@ -692,21 +752,23 @@ export default class CodeEditor extends React.Component { } mapNormalResponse(response, pastedTxt) { - return this.decodeResponse(response).then( - body => {return new Promise((resolve, reject) => { - try { - const parsedBody = - new window.DOMParser().parseFromString(body, 'text/html') - const escapePipe = - (str) => { - return str.replace('|', '\\|') - } const linkWithTitle = - `[${escapePipe(parsedBody.title)}](${pastedTxt})` - resolve(linkWithTitle) - } catch (e) { - reject(e) + return this.decodeResponse(response).then(body => { + return new Promise((resolve, reject) => { + try { + const parsedBody = new window.DOMParser().parseFromString( + body, + 'text/html' + ) + const escapePipe = (str) => { + return str.replace('|', '\\|') } - })}) + const linkWithTitle = `[${escapePipe(parsedBody.title)}](${pastedTxt})` + resolve(linkWithTitle) + } catch (e) { + reject(e) + } + }) + }) } mapImageResponse(response, pastedTxt) { @@ -725,30 +787,39 @@ export default class CodeEditor extends React.Component { decodeResponse(response) { const headers = response.headers const _charset = headers.has('content-type') ? - this.extractContentTypeCharset(headers.get('content-type')) : - undefined - return response.arrayBuffer().then( - buff => {return new Promise((resolve, reject) => { - try { - const charset = - _charset !== undefined && iconv.encodingExists(_charset) ? - _charset : - 'utf-8' - resolve(iconv.decode(new Buffer(buff), charset).toString()) - } catch (e) { - reject(e) - } - })}) + this.extractContentTypeCharset(headers.get('content-type')) : + undefined + return response.arrayBuffer().then(buff => { + return new Promise((resolve, reject) => { + try { + const charset = _charset !== undefined && + iconv.encodingExists(_charset) ? + _charset : + 'utf-8' + resolve(iconv.decode(new Buffer(buff), charset).toString()) + } catch (e) { + reject(e) + } + }) + }) } extractContentTypeCharset(contentType) { - return contentType.split(';') - .filter(str => {return str.trim().toLowerCase().startsWith('charset')}) - .map(str => {return str.replace(/['"]/g, '').split('=')[1]})[0] + return contentType + .split(';') + .filter(str => { + return str.trim().toLowerCase().startsWith('charset') + }) + .map(str => { + return str.replace(/['"]/g, '').split('=')[1] + })[0] } render() { - const {className, fontSize} = this.props + const { + className, + fontSize + } = this.props const fontFamily = normalizeEditorFontFamily(this.props.fontFamily) const width = this.props.width return ( < @@ -756,12 +827,18 @@ export default class CodeEditor extends React.Component { className == null ? 'CodeEditor' : `CodeEditor ${className}` } ref = 'root' - tabIndex = '-1' - style = { - { fontFamily, fontSize: fontSize, width: width } - } onDrop = { - e => this.handleDropImage(e) - } /> + tabIndex = '-1' + style = { + { + fontFamily, + fontSize: fontSize, + width: width + } + } + onDrop = { + e => this.handleDropImage(e) + } + /> ) } } diff --git a/browser/components/MarkdownEditor.js b/browser/components/MarkdownEditor.js index dcb1b5ea..fcfc636e 100644 --- a/browser/components/MarkdownEditor.js +++ b/browser/components/MarkdownEditor.js @@ -268,7 +268,6 @@ class MarkdownEditor extends React.Component { enableRulers={config.editor.enableRulers} rulers={config.editor.rulers} displayLineNumbers={config.editor.displayLineNumbers} - enableBracketMatching={config.editor.enableBracketMatching} matchingPairs={config.editor.matchingPairs} matchingTriples={config.editor.matchingTriples} explodingPairs={config.editor.explodingPairs} diff --git a/browser/components/MarkdownSplitEditor.js b/browser/components/MarkdownSplitEditor.js index 689799c8..58867bac 100644 --- a/browser/components/MarkdownSplitEditor.js +++ b/browser/components/MarkdownSplitEditor.js @@ -158,7 +158,6 @@ class MarkdownSplitEditor extends React.Component { fontFamily={config.editor.fontFamily} fontSize={editorFontSize} displayLineNumbers={config.editor.displayLineNumbers} - enableBracketMatching={config.editor.enableBracketMatching} matchingPairs={config.editor.matchingPairs} matchingTriples={config.editor.matchingTriples} explodingPairs={config.editor.explodingPairs} diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index 86ce2c7a..d252d274 100644 --- a/browser/main/Detail/SnippetNoteDetail.js +++ b/browser/main/Detail/SnippetNoteDetail.js @@ -699,7 +699,6 @@ class SnippetNoteDetail extends React.Component { indentType={config.editor.indentType} indentSize={editorIndentSize} displayLineNumbers={config.editor.displayLineNumbers} - enableBracketMatching={config.editor.enableBracketMatching} matchingPairs={config.editor.matchingPairs} matchingTriples={config.editor.matchingTriples} explodingPairs={config.editor.explodingPairs} diff --git a/browser/main/lib/ConfigManager.js b/browser/main/lib/ConfigManager.js index c86e9288..942ce93d 100644 --- a/browser/main/lib/ConfigManager.js +++ b/browser/main/lib/ConfigManager.js @@ -44,7 +44,6 @@ export const DEFAULT_CONFIG = { enableRulers: false, rulers: [80, 120], displayLineNumbers: true, - enableBracketMatching: true, matchingPairs:'()[]{}\'\'""$$**``', matchingTriples:'```"""\'\'\'', explodingPairs:'[]{}``$$', diff --git a/browser/main/modals/PreferencesModal/SnippetEditor.js b/browser/main/modals/PreferencesModal/SnippetEditor.js index ec3698c7..071f265f 100644 --- a/browser/main/modals/PreferencesModal/SnippetEditor.js +++ b/browser/main/modals/PreferencesModal/SnippetEditor.js @@ -27,17 +27,12 @@ class SnippetEditor extends React.Component { dragDrop: false, foldGutter: true, gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], - autoCloseBrackets: (this.enableBracketMatching ? { - pairs: '()[]{}\'\'""$$**``ll', - triples: '```"""\'\'\'', - explode: '[]{}``$$', + autoCloseBrackets: { + pairs: this.props.matchingPairs, + triples: this.props.matchingTriples, + explode: this.props.explodingPairs, override: true - }: { - pairs: '', - triples: '', - explode: '', - override: true - }), + }, mode: 'null' }) this.cm.setSize('100%', '100%') diff --git a/browser/main/modals/PreferencesModal/SnippetTab.js b/browser/main/modals/PreferencesModal/SnippetTab.js index ebd4012e..5f5b0aac 100644 --- a/browser/main/modals/PreferencesModal/SnippetTab.js +++ b/browser/main/modals/PreferencesModal/SnippetTab.js @@ -136,7 +136,6 @@ class SnippetTab extends React.Component { enableRulers={config.editor.enableRulers} rulers={config.editor.rulers} displayLineNumbers={config.editor.displayLineNumbers} - enableBracketMatching={config.editor.enableBracketMatching} matchingPairs={config.editor.matchingPairs} matchingTriples={config.editor.matchingTriples} explodingPairs={config.editor.explodingPairs} diff --git a/browser/main/modals/PreferencesModal/UiTab.js b/browser/main/modals/PreferencesModal/UiTab.js index 2bd0c9d0..ac020238 100644 --- a/browser/main/modals/PreferencesModal/UiTab.js +++ b/browser/main/modals/PreferencesModal/UiTab.js @@ -95,7 +95,6 @@ class UiTab extends React.Component { enableTableEditor: this.refs.enableTableEditor.checked, enableFrontMatterTitle: this.refs.enableFrontMatterTitle.checked, frontMatterTitleField: this.refs.frontMatterTitleField.value, - enableBracketMatching: this.refs.enableBracketMatching.checked, matchingPairs: this.refs.matchingPairs.value, matchingTriples: this.refs.matchingTriples.value, explodingPairs: this.refs.explodingPairs.value @@ -543,17 +542,6 @@ class UiTab extends React.Component {
-
- -
-
{i18n.__('Matching character pairs')} From a46c51945973710be2159969256a05ea2db31ed3 Mon Sep 17 00:00:00 2001 From: Guilherme Silva Date: Thu, 8 Nov 2018 14:03:21 +0000 Subject: [PATCH 12/64] Fixing indentations --- browser/components/CodeEditor.js | 104 +++++++++++++++---------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 83f64a18..9037c2e2 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -33,7 +33,7 @@ const buildCMRulers = (rulers, enableRulers) => })) : []) export default class CodeEditor extends React.Component { - constructor(props) { + constructor (props) { super(props) this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, { @@ -78,7 +78,7 @@ export default class CodeEditor extends React.Component { this.editorActivityHandler = () => this.handleEditorActivity() } - handleSearch(msg) { + handleSearch (msg) { const cm = this.editor const component = this @@ -89,7 +89,7 @@ export default class CodeEditor extends React.Component { component.searchState = makeOverlay(msg, 'searching') cm.addOverlay(component.searchState) - function makeOverlay(query, style) { + function makeOverlay (query, style) { query = new RegExp( query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'), 'gi' @@ -112,19 +112,19 @@ export default class CodeEditor extends React.Component { }) } - handleFormatTable() { + handleFormatTable () { this.tableEditor.formatAll(options({ textWidthOptions: {} })) } - handleEditorActivity() { + handleEditorActivity () { if (!this.textEditorInterface.transaction) { this.updateTableEditorState() } } - updateTableEditorState() { + updateTableEditorState () { const active = this.tableEditor.cursorIsInTable(this.tableEditorOptions) if (active) { if (this.extraKeysMode !== 'editor') { @@ -140,7 +140,7 @@ export default class CodeEditor extends React.Component { } } - componentDidMount() { + componentDidMount () { const { rulers, enableRulers @@ -392,7 +392,7 @@ export default class CodeEditor extends React.Component { }) } - expandSnippet(line, cursor, cm, snippets) { + expandSnippet (line, cursor, cm, snippets) { const wordBeforeCursor = this.getWordBeforeCursor( line, cursor.line, @@ -435,7 +435,7 @@ export default class CodeEditor extends React.Component { return false } - getWordBeforeCursor(line, lineNumber, cursorPosition) { + getWordBeforeCursor (line, lineNumber, cursorPosition) { let wordBeforeCursor = '' const originCursorPosition = cursorPosition const emptyChars = /\t|\s|\r|\n/ @@ -472,11 +472,11 @@ export default class CodeEditor extends React.Component { } } - quitEditor() { + quitEditor () { document.querySelector('textarea').blur() } - componentWillUnmount() { + componentWillUnmount () { this.editor.off('focus', this.focusHandler) this.editor.off('blur', this.blurHandler) this.editor.off('change', this.changeHandler) @@ -489,7 +489,7 @@ export default class CodeEditor extends React.Component { eventEmitter.off('code:format-table', this.formatTable) } - componentDidUpdate(prevProps, prevState) { + componentDidUpdate (prevProps, prevState) { let needRefresh = false const { rulers, @@ -538,7 +538,7 @@ export default class CodeEditor extends React.Component { if (prevProps.matchingPairs !== this.props.matchingPairs || prevProps.matchingTriples !== this.props.matchingTriples || prevProps.explodingPairs !== this.props.explodingPairs) { - const bracketObject ={ + const bracketObject = { pairs: this.props.matchingPairs, triples: this.props.matchingTriples, explode: this.props.explodingPairs, @@ -573,7 +573,7 @@ export default class CodeEditor extends React.Component { } } - setMode(mode) { + setMode (mode) { let syntax = CodeMirror.findModeByName(convertModeName(mode)) if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text') @@ -581,16 +581,16 @@ export default class CodeEditor extends React.Component { CodeMirror.autoLoadMode(this.editor, syntax.mode) } - handleChange(e) { + handleChange (e) { this.value = this.editor.getValue() if (this.props.onChange) { this.props.onChange(e) } } - moveCursorTo(row, col) {} + moveCursorTo (row, col) {} - scrollToLine(event, num) { + scrollToLine (event, num) { const cursor = { line: num, ch: 1 @@ -598,15 +598,15 @@ export default class CodeEditor extends React.Component { this.editor.setCursor(cursor) } - focus() { + focus () { this.editor.focus() } - blur() { + blur () { this.editor.blur() } - reload() { + reload () { // Change event shouldn't be fired when switch note this.editor.off('change', this.changeHandler) this.value = this.props.value @@ -616,13 +616,13 @@ export default class CodeEditor extends React.Component { this.editor.refresh() } - setValue(value) { + setValue (value) { const cursor = this.editor.getCursor() this.editor.setValue(value) this.editor.setCursor(cursor) } - handleDropImage(dropEvent) { + handleDropImage (dropEvent) { dropEvent.preventDefault() const { storageKey, @@ -636,11 +636,11 @@ export default class CodeEditor extends React.Component { ) } - insertAttachmentMd(imageMd) { + insertAttachmentMd (imageMd) { this.editor.replaceSelection(imageMd) } - handlePaste(editor, e) { + handlePaste (editor, e) { const clipboardData = e.clipboardData const { storageKey, @@ -699,13 +699,13 @@ export default class CodeEditor extends React.Component { } } - handleScroll(e) { + handleScroll (e) { if (this.props.onScroll) { this.props.onScroll(e) } } - handlePasteUrl(e, editor, pastedTxt) { + handlePasteUrl (e, editor, pastedTxt) { e.preventDefault() const taggedUrl = `<${pastedTxt}>` editor.replaceSelection(taggedUrl) @@ -728,8 +728,8 @@ export default class CodeEditor extends React.Component { } fetch(pastedTxt, { - method: 'get' - }) + method: 'get' + }) .then(response => { if (isImageReponse(response)) { return this.mapImageResponse(response, pastedTxt) @@ -745,13 +745,13 @@ export default class CodeEditor extends React.Component { }) } - handlePasteHtml(e, editor, pastedHtml) { + handlePasteHtml (e, editor, pastedHtml) { e.preventDefault() const markdown = this.turndownService.turndown(pastedHtml) editor.replaceSelection(markdown) } - mapNormalResponse(response, pastedTxt) { + mapNormalResponse (response, pastedTxt) { return this.decodeResponse(response).then(body => { return new Promise((resolve, reject) => { try { @@ -771,7 +771,7 @@ export default class CodeEditor extends React.Component { }) } - mapImageResponse(response, pastedTxt) { + mapImageResponse (response, pastedTxt) { return new Promise((resolve, reject) => { try { const url = response.url @@ -784,18 +784,18 @@ export default class CodeEditor extends React.Component { }) } - decodeResponse(response) { + decodeResponse (response) { const headers = response.headers - const _charset = headers.has('content-type') ? - this.extractContentTypeCharset(headers.get('content-type')) : - undefined + const _charset = headers.has('content-type') + ? this.extractContentTypeCharset(headers.get('content-type')) + : undefined return response.arrayBuffer().then(buff => { return new Promise((resolve, reject) => { try { const charset = _charset !== undefined && - iconv.encodingExists(_charset) ? - _charset : - 'utf-8' + iconv.encodingExists(_charset) + ? _charset + : 'utf-8' resolve(iconv.decode(new Buffer(buff), charset).toString()) } catch (e) { reject(e) @@ -804,7 +804,7 @@ export default class CodeEditor extends React.Component { }) } - extractContentTypeCharset(contentType) { + extractContentTypeCharset (contentType) { return contentType .split(';') .filter(str => { @@ -815,27 +815,27 @@ export default class CodeEditor extends React.Component { })[0] } - render() { + render () { const { className, fontSize } = this.props const fontFamily = normalizeEditorFontFamily(this.props.fontFamily) const width = this.props.width - return ( < - div className = { + return (< + div className ={ className == null ? 'CodeEditor' : `CodeEditor ${className}` } - ref = 'root' - tabIndex = '-1' - style = { - { - fontFamily, - fontSize: fontSize, - width: width - } + ref= 'root' + tabIndex= '-1' + style= { + { + fontFamily, + fontSize: fontSize, + width: width } - onDrop = { + } + onDrop= { e => this.handleDropImage(e) } /> @@ -862,4 +862,4 @@ CodeEditor.defaultProps = { fontFamily: 'Monaco, Consolas', indentSize: 4, indentType: 'space' -} \ No newline at end of file +} From 8361106660fc78ef4cda5bc55cc04bf5e91c0ae6 Mon Sep 17 00:00:00 2001 From: Guilherme Silva Date: Thu, 8 Nov 2018 14:07:35 +0000 Subject: [PATCH 13/64] Removing trailing spaces and added spaces in config --- browser/main/lib/ConfigManager.js | 6 +++--- browser/main/modals/PreferencesModal/UiTab.js | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/browser/main/lib/ConfigManager.js b/browser/main/lib/ConfigManager.js index 942ce93d..7973d95f 100644 --- a/browser/main/lib/ConfigManager.js +++ b/browser/main/lib/ConfigManager.js @@ -44,9 +44,9 @@ export const DEFAULT_CONFIG = { enableRulers: false, rulers: [80, 120], displayLineNumbers: true, - matchingPairs:'()[]{}\'\'""$$**``', - matchingTriples:'```"""\'\'\'', - explodingPairs:'[]{}``$$', + matchingPairs: '()[]{}\'\'""$$**``', + matchingTriples: '```"""\'\'\'', + explodingPairs: '[]{}``$$', switchPreview: 'BLUR', // 'BLUR', 'DBL_CLICK', 'RIGHTCLICK' delfaultStatus: 'PREVIEW', // 'PREVIEW', 'CODE' scrollPastEnd: false, diff --git a/browser/main/modals/PreferencesModal/UiTab.js b/browser/main/modals/PreferencesModal/UiTab.js index ac020238..5b68d456 100644 --- a/browser/main/modals/PreferencesModal/UiTab.js +++ b/browser/main/modals/PreferencesModal/UiTab.js @@ -62,7 +62,7 @@ class UiTab extends React.Component { checkHighLight.setAttribute('rel', 'stylesheet') document.head.appendChild(checkHighLight) } - + const newConfig = { ui: { theme: this.refs.uiTheme.value, @@ -542,7 +542,7 @@ class UiTab extends React.Component {
-
+
{i18n.__('Matching character pairs')}
@@ -556,7 +556,7 @@ class UiTab extends React.Component {
-
+
{i18n.__('Matching character triples')}
@@ -570,7 +570,7 @@ class UiTab extends React.Component {
-
+
{i18n.__('Exploding character pairs')}
@@ -611,7 +611,7 @@ class UiTab extends React.Component { />
- +
{i18n.__('Code Block Theme')}
From 707dace3d065aaff5466d4a2f03d469a6e3f92a1 Mon Sep 17 00:00:00 2001 From: Guilherme Silva Date: Thu, 8 Nov 2018 14:11:43 +0000 Subject: [PATCH 14/64] removing spaces after = --- browser/components/CodeEditor.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 9037c2e2..98651798 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -823,19 +823,19 @@ export default class CodeEditor extends React.Component { const fontFamily = normalizeEditorFontFamily(this.props.fontFamily) const width = this.props.width return (< - div className ={ + div className={ className == null ? 'CodeEditor' : `CodeEditor ${className}` } - ref= 'root' - tabIndex= '-1' - style= { + ref='root' + tabIndex='-1' + style={ { fontFamily, fontSize: fontSize, width: width } } - onDrop= { + onDrop={ e => this.handleDropImage(e) } /> From b1d2c25ce50a3bf861f28e342f1b9970ea06c1c9 Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Sat, 10 Nov 2018 00:05:45 +0100 Subject: [PATCH 15/64] when dropping an image, switch to editor and add it at the end of the file --- browser/components/MarkdownEditor.js | 24 ++++++++++++++++++++++++ browser/components/MarkdownPreview.js | 12 ++++++++---- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/browser/components/MarkdownEditor.js b/browser/components/MarkdownEditor.js index 9c8a06d6..dbec1dc9 100644 --- a/browser/components/MarkdownEditor.js +++ b/browser/components/MarkdownEditor.js @@ -7,6 +7,7 @@ import MarkdownPreview from 'browser/components/MarkdownPreview' import eventEmitter from 'browser/main/lib/eventEmitter' import { findStorage } from 'browser/lib/findStorage' import ConfigManager from 'browser/main/lib/ConfigManager' +import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement' class MarkdownEditor extends React.Component { constructor (props) { @@ -219,6 +220,28 @@ class MarkdownEditor extends React.Component { this.refs.code.editor.replaceSelection(`${mdElement}${this.refs.code.editor.getSelection()}${mdElement}`) } + handleDropImage (dropEvent) { + dropEvent.preventDefault() + const { storageKey, noteKey } = this.props + + this.setState({ + status: 'CODE' + }, () => { + this.refs.code.focus() + + this.refs.code.editor.execCommand('goDocEnd') + this.refs.code.editor.execCommand('goLineEnd') + this.refs.code.editor.execCommand('newlineAndIndent') + + attachmentManagement.handleAttachmentDrop( + this.refs.code, + storageKey, + noteKey, + dropEvent + ) + }) + } + handleKeyUp (e) { const keyPressed = this.state.keyPressed keyPressed.delete(e.keyCode) @@ -308,6 +331,7 @@ class MarkdownEditor extends React.Component { customCSS={config.preview.customCSS} allowCustomCSS={config.preview.allowCustomCSS} lineThroughCheckbox={config.preview.lineThroughCheckbox} + onDrop={(e) => this.handleDropImage(e)} />
) diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index d9ff7074..704ed9a0 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -206,7 +206,7 @@ export default class MarkdownPreview extends React.Component { this.saveAsHtmlHandler = () => this.handleSaveAsHtml() this.printHandler = () => this.handlePrint() - this.linkClickHandler = this.handlelinkClick.bind(this) + this.linkClickHandler = this.handleLinkClick.bind(this) this.initMarkdown = this.initMarkdown.bind(this) this.initMarkdown() } @@ -438,6 +438,8 @@ export default class MarkdownPreview extends React.Component { } componentDidMount () { + const { onDrop } = this.props + this.refs.root.setAttribute('sandbox', 'allow-scripts') this.refs.root.contentWindow.document.body.addEventListener( 'contextmenu', @@ -475,7 +477,7 @@ export default class MarkdownPreview extends React.Component { ) this.refs.root.contentWindow.document.addEventListener( 'drop', - this.preventImageDroppedHandler + onDrop || this.preventImageDroppedHandler ) this.refs.root.contentWindow.document.addEventListener( 'dragover', @@ -492,6 +494,8 @@ export default class MarkdownPreview extends React.Component { } componentWillUnmount () { + const { onDrop } = this.props + this.refs.root.contentWindow.document.body.removeEventListener( 'contextmenu', this.contextMenuHandler @@ -510,7 +514,7 @@ export default class MarkdownPreview extends React.Component { ) this.refs.root.contentWindow.document.removeEventListener( 'drop', - this.preventImageDroppedHandler + onDrop || this.preventImageDroppedHandler ) this.refs.root.contentWindow.document.removeEventListener( 'dragover', @@ -837,7 +841,7 @@ export default class MarkdownPreview extends React.Component { return new window.Notification(title, options) } - handlelinkClick (e) { + handleLinkClick (e) { e.preventDefault() e.stopPropagation() From 3679fbe3ea36b4331e1f22559728ecb4a84863ad Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Sat, 10 Nov 2018 00:18:06 +0100 Subject: [PATCH 16/64] fix lint errors --- browser/components/MarkdownEditor.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/browser/components/MarkdownEditor.js b/browser/components/MarkdownEditor.js index dbec1dc9..07d2aaf0 100644 --- a/browser/components/MarkdownEditor.js +++ b/browser/components/MarkdownEditor.js @@ -225,21 +225,21 @@ class MarkdownEditor extends React.Component { const { storageKey, noteKey } = this.props this.setState({ - status: 'CODE' - }, () => { - this.refs.code.focus() + status: 'CODE' + }, () => { + this.refs.code.focus() - this.refs.code.editor.execCommand('goDocEnd') - this.refs.code.editor.execCommand('goLineEnd') - this.refs.code.editor.execCommand('newlineAndIndent') + this.refs.code.editor.execCommand('goDocEnd') + this.refs.code.editor.execCommand('goLineEnd') + this.refs.code.editor.execCommand('newlineAndIndent') - attachmentManagement.handleAttachmentDrop( - this.refs.code, - storageKey, - noteKey, - dropEvent - ) - }) + attachmentManagement.handleAttachmentDrop( + this.refs.code, + storageKey, + noteKey, + dropEvent + ) + }) } handleKeyUp (e) { From bf288fdaeb4dc64d4390edf9d35f38c3a38f624a Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Sun, 25 Nov 2018 14:55:29 +0100 Subject: [PATCH 17/64] fix pasting into fenced code block --- browser/components/CodeEditor.js | 84 +++++++++++++++++++++++++------- 1 file changed, 66 insertions(+), 18 deletions(-) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 130cc86e..0193d5cb 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -562,14 +562,17 @@ export default class CodeEditor extends React.Component { } handlePaste (editor, e) { + const { storageKey, noteKey, fetchUrlTitle } = this.props + const clipboardData = e.clipboardData - const { storageKey, noteKey } = this.props const dataTransferItem = clipboardData.items[0] const pastedTxt = clipboardData.getData('text') + const isURL = str => { const matcher = /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/ return matcher.test(str) } + const isInLinkTag = editor => { const startCursor = editor.getCursor('start') const prevChar = editor.getRange( @@ -584,30 +587,69 @@ export default class CodeEditor extends React.Component { return prevChar === '](' && nextChar === ')' } - const pastedHtml = clipboardData.getData('text/html') - if (pastedHtml !== '') { - this.handlePasteHtml(e, editor, pastedHtml) - } else if (dataTransferItem.type.match('image')) { - attachmentManagement.handlePastImageEvent( - this, - storageKey, - noteKey, - dataTransferItem - ) - } else if ( - this.props.fetchUrlTitle && - isURL(pastedTxt) && - !isInLinkTag(editor) - ) { - this.handlePasteUrl(e, editor, pastedTxt) + const isInFencedCodeBlock = editor => { + const cursor = editor.getCursor() + + let token = editor.getTokenAt(cursor) + if (token.state.fencedState) { + return true + } + + let line = line = cursor.line - 1 + while (line >= 0) { + token = editor.getTokenAt({ + ch: 3, + line + }) + + if (token.start === token.end) { + --line + } else if (token.type === 'comment') { + if (line > 0) { + token = editor.getTokenAt({ + ch: 3, + line: line - 1 + }) + + return token.type !== 'comment' + } else { + return true + } + return true + } else { + return false + } + } + + return false } + + if (isInFencedCodeBlock(editor)) { + this.handlePasteText(e, editor, pastedTxt) + } else { + const pastedHtml = clipboardData.getData('text/html') + if (pastedHtml !== '') { + this.handlePasteHtml(e, editor, pastedHtml) + } else if (dataTransferItem.type.match('image')) { + attachmentManagement.handlePastImageEvent( + this, + storageKey, + noteKey, + dataTransferItem + ) + } else if (fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) { + this.handlePasteUrl(e, editor, pastedTxt) + } + } + if (attachmentManagement.isAttachmentLink(pastedTxt)) { + e.preventDefault() + attachmentManagement .handleAttachmentLinkPaste(storageKey, noteKey, pastedTxt) .then(modifiedText => { this.editor.replaceSelection(modifiedText) }) - e.preventDefault() } } @@ -663,6 +705,12 @@ export default class CodeEditor extends React.Component { editor.replaceSelection(markdown) } + handlePasteText (e, editor, pastedTxt) { + e.preventDefault() + + editor.replaceSelection(pastedTxt) + } + mapNormalResponse (response, pastedTxt) { return this.decodeResponse(response).then(body => { return new Promise((resolve, reject) => { From aac075be061b438e269b8048f5189d8b87bde6d8 Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Sun, 25 Nov 2018 14:57:03 +0100 Subject: [PATCH 18/64] fix lint error --- browser/components/CodeEditor.js | 1 - 1 file changed, 1 deletion(-) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 0193d5cb..d2fcb9c5 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -615,7 +615,6 @@ export default class CodeEditor extends React.Component { } else { return true } - return true } else { return false } From 9d81e4be2fe810c18003a99425ac1ed42ef2f665 Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Sun, 25 Nov 2018 16:17:01 +0100 Subject: [PATCH 19/64] undo change --- browser/main/lib/dataApi/attachmentManagement.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/main/lib/dataApi/attachmentManagement.js b/browser/main/lib/dataApi/attachmentManagement.js index c88acfc5..59c91540 100644 --- a/browser/main/lib/dataApi/attachmentManagement.js +++ b/browser/main/lib/dataApi/attachmentManagement.js @@ -227,7 +227,7 @@ function migrateAttachments (markdownContent, storagePath, noteKey) { * @returns {String} postprocessed HTML in which all :storage references are mapped to the actual paths. */ function fixLocalURLS (renderedHTML, storagePath) { - return renderedHTML.replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '(?:\\\/|%5C)[\\w.]+', 'g'), function (match) { + return renderedHTML.replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '.*?', 'g'), function (match) { var encodedPathSeparators = new RegExp(mdurl.encode(path.win32.sep) + '|' + mdurl.encode(path.posix.sep), 'g') return match.replace(encodedPathSeparators, path.sep).replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER, 'g'), 'file:///' + path.join(storagePath, DESTINATION_FOLDER)) }) From 64f7233bfc8124fc6d89e9c212718f3cdd4d02cb Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Sun, 25 Nov 2018 16:58:11 +0100 Subject: [PATCH 20/64] fix regex to match :storage reference, added comment to explain what it does. --- browser/main/lib/dataApi/attachmentManagement.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/browser/main/lib/dataApi/attachmentManagement.js b/browser/main/lib/dataApi/attachmentManagement.js index 59c91540..73bf1303 100644 --- a/browser/main/lib/dataApi/attachmentManagement.js +++ b/browser/main/lib/dataApi/attachmentManagement.js @@ -227,7 +227,15 @@ function migrateAttachments (markdownContent, storagePath, noteKey) { * @returns {String} postprocessed HTML in which all :storage references are mapped to the actual paths. */ function fixLocalURLS (renderedHTML, storagePath) { - return renderedHTML.replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '.*?', 'g'), function (match) { + /* + A :storage reference is like `:storage/3b6f8bd6-4edd-4b15-96e0-eadc4475b564/f939b2c3.jpg`. + + - `STORAGE_FOLDER_PLACEHOLDER` will match `:storage` + - `(?:(?:\\\/|%5C)[\\w.]+)+` will match `/3b6f8bd6-4edd-4b15-96e0-eadc4475b564/f939b2c3.jpg` + - `(?:\\\/|%5C)[\\w.]+` will either match `/3b6f8bd6-4edd-4b15-96e0-eadc4475b564` or `/f939b2c3.jpg` + - `(?:\\\/|%5C)` match the path seperator. `\\\/` for posix systems and `%5C` for windows. + */ + return renderedHTML.replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER + '(?:(?:\\\/|%5C)[\\w.]+)+', 'g'), function (match) { var encodedPathSeparators = new RegExp(mdurl.encode(path.win32.sep) + '|' + mdurl.encode(path.posix.sep), 'g') return match.replace(encodedPathSeparators, path.sep).replace(new RegExp('/?' + STORAGE_FOLDER_PLACEHOLDER, 'g'), 'file:///' + path.join(storagePath, DESTINATION_FOLDER)) }) From 9050035c7405236ea5a93384d58758f26b0d5633 Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Wed, 28 Nov 2018 00:44:15 +0100 Subject: [PATCH 21/64] - add option to enable/disable smart paste - add shortcut to paste smartly - use electron's clipboard --- browser/components/CodeEditor.js | 150 ++++++++++-------- browser/components/MarkdownEditor.js | 2 + browser/components/MarkdownSplitEditor.js | 2 + browser/main/Detail/SnippetNoteDetail.js | 2 + browser/main/lib/ConfigManager.js | 6 +- .../main/lib/dataApi/attachmentManagement.js | 39 +++++ .../main/modals/PreferencesModal/HotkeyTab.js | 14 +- browser/main/modals/PreferencesModal/UiTab.js | 15 +- 8 files changed, 163 insertions(+), 67 deletions(-) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index d2fcb9c5..c7b6e385 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -13,7 +13,7 @@ import crypto from 'crypto' import consts from 'browser/lib/consts' import styles from '../components/CodeEditor.styl' import fs from 'fs' -const { ipcRenderer, remote } = require('electron') +const { ipcRenderer, remote, clipboard } = require('electron') import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily' const spellcheck = require('browser/lib/spellcheck') const buildEditorContextMenu = require('browser/lib/contextMenuBuilder') @@ -25,6 +25,10 @@ CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js' const buildCMRulers = (rulers, enableRulers) => (enableRulers ? rulers.map(ruler => ({ column: ruler })) : []) +function translateHotkey (hotkey) { + return hotkey.replace(/\s*\+\s*/g, '-').replace(/Command/g, 'Cmd').replace(/Control/g, 'Ctrl') +} + export default class CodeEditor extends React.Component { constructor (props) { super(props) @@ -56,7 +60,11 @@ export default class CodeEditor extends React.Component { noteKey ) } - this.pasteHandler = (editor, e) => this.handlePaste(editor, e) + this.pasteHandler = (editor, e) => { + e.preventDefault() + + this.handlePaste(editor, false) + } this.loadStyleHandler = e => { this.editor.refresh() } @@ -120,42 +128,8 @@ export default class CodeEditor extends React.Component { } } - updateTableEditorState () { - const active = this.tableEditor.cursorIsInTable(this.tableEditorOptions) - if (active) { - if (this.extraKeysMode !== 'editor') { - this.extraKeysMode = 'editor' - this.editor.setOption('extraKeys', this.editorKeyMap) - } - } else { - if (this.extraKeysMode !== 'default') { - this.extraKeysMode = 'default' - this.editor.setOption('extraKeys', this.defaultKeyMap) - this.tableEditor.resetSmartCursor() - } - } - } - - componentDidMount () { - const { rulers, enableRulers } = this.props - const expandSnippet = this.expandSnippet.bind(this) - eventEmitter.on('line:jump', this.scrollToLineHandeler) - - const defaultSnippet = [ - { - id: crypto.randomBytes(16).toString('hex'), - name: 'Dummy text', - prefix: ['lorem', 'ipsum'], - content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.' - } - ] - if (!fs.existsSync(consts.SNIPPET_FILE)) { - fs.writeFileSync( - consts.SNIPPET_FILE, - JSON.stringify(defaultSnippet, null, 4), - 'utf8' - ) - } + updateDefaultKeyMap () { + const { hotkey } = this.props this.defaultKeyMap = CodeMirror.normalizeKeyMap({ Tab: function (cm) { @@ -207,8 +181,51 @@ export default class CodeEditor extends React.Component { document.execCommand('copy') } return CodeMirror.Pass + }, + [translateHotkey(hotkey.pasteSmartly)]: cm => { + this.handlePaste(cm, true) } }) + } + + updateTableEditorState () { + const active = this.tableEditor.cursorIsInTable(this.tableEditorOptions) + if (active) { + if (this.extraKeysMode !== 'editor') { + this.extraKeysMode = 'editor' + this.editor.setOption('extraKeys', this.editorKeyMap) + } + } else { + if (this.extraKeysMode !== 'default') { + this.extraKeysMode = 'default' + this.editor.setOption('extraKeys', this.defaultKeyMap) + this.tableEditor.resetSmartCursor() + } + } + } + + componentDidMount () { + const { rulers, enableRulers } = this.props + const expandSnippet = this.expandSnippet.bind(this) + eventEmitter.on('line:jump', this.scrollToLineHandeler) + + const defaultSnippet = [ + { + id: crypto.randomBytes(16).toString('hex'), + name: 'Dummy text', + prefix: ['lorem', 'ipsum'], + content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.' + } + ] + if (!fs.existsSync(consts.SNIPPET_FILE)) { + fs.writeFileSync( + consts.SNIPPET_FILE, + JSON.stringify(defaultSnippet, null, 4), + 'utf8' + ) + } + + this.updateDefaultKeyMap() this.value = this.props.value this.editor = CodeMirror(this.refs.root, { @@ -473,6 +490,14 @@ export default class CodeEditor extends React.Component { this.editor.setOption('extraKeys', this.defaultKeyMap) } + if (prevProps.hotkey !== this.props.hotkey) { + this.updateDefaultKeyMap() + + if (this.extraKeysMode === 'default') { + this.editor.setOption('extraKeys', this.defaultKeyMap) + } + } + if (this.state.clientWidth !== this.refs.root.clientWidth) { this.setState({ clientWidth: this.refs.root.clientWidth @@ -561,12 +586,8 @@ export default class CodeEditor extends React.Component { this.editor.replaceSelection(imageMd) } - handlePaste (editor, e) { - const { storageKey, noteKey, fetchUrlTitle } = this.props - - const clipboardData = e.clipboardData - const dataTransferItem = clipboardData.items[0] - const pastedTxt = clipboardData.getData('text') + handlePaste (editor, forceSmartPaste) { + const { storageKey, noteKey, fetchUrlTitle, enableSmartPaste } = this.props const isURL = str => { const matcher = /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/ @@ -623,27 +644,34 @@ export default class CodeEditor extends React.Component { return false } + const pastedTxt = clipboard.readText() + if (isInFencedCodeBlock(editor)) { - this.handlePasteText(e, editor, pastedTxt) - } else { - const pastedHtml = clipboardData.getData('text/html') - if (pastedHtml !== '') { - this.handlePasteHtml(e, editor, pastedHtml) - } else if (dataTransferItem.type.match('image')) { - attachmentManagement.handlePastImageEvent( + this.handlePasteText(editor, pastedTxt) + } else if (fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) { + this.handlePasteUrl(editor, pastedTxt) + } else if (enableSmartPaste || forceSmartPaste) { + const image = clipboard.readImage() + if (!image.isEmpty()) { + attachmentManagement.handlePastNativeImage( this, storageKey, noteKey, - dataTransferItem + image ) - } else if (fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) { - this.handlePasteUrl(e, editor, pastedTxt) + } else { + const pastedHtml = clipboard.readHTML() + if (pastedHtml.length > 0) { + this.handlePasteHtml(editor, pastedHtml) + } else { + this.handlePasteText(editor, pastedTxt) + } } + } else { + this.handlePasteText(editor, pastedTxt) } if (attachmentManagement.isAttachmentLink(pastedTxt)) { - e.preventDefault() - attachmentManagement .handleAttachmentLinkPaste(storageKey, noteKey, pastedTxt) .then(modifiedText => { @@ -658,8 +686,7 @@ export default class CodeEditor extends React.Component { } } - handlePasteUrl (e, editor, pastedTxt) { - e.preventDefault() + handlePasteUrl (editor, pastedTxt) { const taggedUrl = `<${pastedTxt}>` editor.replaceSelection(taggedUrl) @@ -698,15 +725,12 @@ export default class CodeEditor extends React.Component { }) } - handlePasteHtml (e, editor, pastedHtml) { - e.preventDefault() + handlePasteHtml (editor, pastedHtml) { const markdown = this.turndownService.turndown(pastedHtml) editor.replaceSelection(markdown) } - handlePasteText (e, editor, pastedTxt) { - e.preventDefault() - + handlePasteText (editor, pastedTxt) { editor.replaceSelection(pastedTxt) } diff --git a/browser/components/MarkdownEditor.js b/browser/components/MarkdownEditor.js index 20ce9451..f1aa8d73 100644 --- a/browser/components/MarkdownEditor.js +++ b/browser/components/MarkdownEditor.js @@ -278,6 +278,8 @@ class MarkdownEditor extends React.Component { onChange={(e) => this.handleChange(e)} onBlur={(e) => this.handleBlur(e)} spellCheck={config.editor.spellcheck} + enableSmartPaste={config.editor.enableSmartPaste} + hotkey={config.hotkey} />
this.handleMouseDown(e)} >
diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index 4a38ffe5..c839fb94 100644 --- a/browser/main/Detail/SnippetNoteDetail.js +++ b/browser/main/Detail/SnippetNoteDetail.js @@ -717,6 +717,8 @@ class SnippetNoteDetail extends React.Component { enableTableEditor={config.editor.enableTableEditor} onChange={(e) => this.handleCodeChange(index)(e)} ref={'code-' + index} + enableSmartPaste={config.editor.enableSmartPaste} + hotkey={config.hotkey} /> }
diff --git a/browser/main/lib/ConfigManager.js b/browser/main/lib/ConfigManager.js index d6b04d9b..c2ff9f7a 100644 --- a/browser/main/lib/ConfigManager.js +++ b/browser/main/lib/ConfigManager.js @@ -25,7 +25,8 @@ export const DEFAULT_CONFIG = { hotkey: { toggleMain: OSX ? 'Command + Alt + L' : 'Super + Alt + E', toggleMode: OSX ? 'Command + Alt + M' : 'Ctrl + M', - deleteNote: OSX ? 'Command + Shift + Backspace' : 'Ctrl + Shift + Backspace' + deleteNote: OSX ? 'Command + Shift + Backspace' : 'Ctrl + Shift + Backspace', + pasteSmartly: OSX ? 'Command + Shift + V' : 'Ctrl + Shift + V' }, ui: { language: 'en', @@ -52,7 +53,8 @@ export const DEFAULT_CONFIG = { enableTableEditor: false, enableFrontMatterTitle: true, frontMatterTitleField: 'title', - spellcheck: false + spellcheck: false, + enableSmartPaste: false }, preview: { fontSize: '14', diff --git a/browser/main/lib/dataApi/attachmentManagement.js b/browser/main/lib/dataApi/attachmentManagement.js index c193eaf2..373efddc 100644 --- a/browser/main/lib/dataApi/attachmentManagement.js +++ b/browser/main/lib/dataApi/attachmentManagement.js @@ -316,6 +316,44 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem reader.readAsDataURL(blob) } +/** + * @description Creates a new file in the storage folder belonging to the current note and inserts the correct markdown code + * @param {CodeEditor} codeEditor Markdown editor. Its insertAttachmentMd() method will be called to include the markdown code + * @param {String} storageKey Key of the current storage + * @param {String} noteKey Key of the current note + * @param {NativeImage} image The native image + */ +function handlePastNativeImage (codeEditor, storageKey, noteKey, image) { + if (!codeEditor) { + throw new Error('codeEditor has to be given') + } + if (!storageKey) { + throw new Error('storageKey has to be given') + } + + if (!noteKey) { + throw new Error('noteKey has to be given') + } + if (!image) { + throw new Error('image has to be given') + } + + const targetStorage = findStorage.findStorage(storageKey) + const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey) + + createAttachmentDestinationFolder(targetStorage.path, noteKey) + + const imageName = `${uniqueSlug()}.png` + const imagePath = path.join(destinationDir, imageName) + + const binaryData = image.toPNG() + fs.writeFileSync(imagePath, binaryData, 'binary') + + const imageReferencePath = path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, imageName) + const imageMd = generateAttachmentMarkdown(imageName, imageReferencePath, true) + codeEditor.insertAttachmentMd(imageMd) +} + /** * @description Returns all attachment paths of the given markdown * @param {String} markdownContent content in which the attachment paths should be found @@ -539,6 +577,7 @@ module.exports = { generateAttachmentMarkdown, handleAttachmentDrop, handlePastImageEvent, + handlePastNativeImage, getAttachmentsInMarkdownContent, getAbsolutePathsOfAttachmentsInContent, removeStorageAndNoteReferences, diff --git a/browser/main/modals/PreferencesModal/HotkeyTab.js b/browser/main/modals/PreferencesModal/HotkeyTab.js index 7ad6f606..a0f6a739 100644 --- a/browser/main/modals/PreferencesModal/HotkeyTab.js +++ b/browser/main/modals/PreferencesModal/HotkeyTab.js @@ -79,7 +79,8 @@ class HotkeyTab extends React.Component { config.hotkey = { toggleMain: this.refs.toggleMain.value, toggleMode: this.refs.toggleMode.value, - deleteNote: this.refs.deleteNote.value + deleteNote: this.refs.deleteNote.value, + pasteSmartly: this.refs.pasteSmartly.value } this.setState({ config @@ -149,6 +150,17 @@ class HotkeyTab extends React.Component { />
+
+
{i18n.__('Paste Smartly')}
+
+ this.handleHotkeyChange(e)} + ref='pasteSmartly' + value={config.hotkey.pasteSmartly} + type='text' + /> +
+
+ +
+ +
+