From dac23e38d95bb8a1850812524c179c9e9eb12ec9 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 10 Aug 2018 13:27:17 +0300 Subject: [PATCH 001/131] Add support for converting to MD when paste HTML --- browser/components/CodeEditor.js | 17 ++++++ package.json | 4 +- yarn.lock | 93 ++++++++++++++++++++++++++++++-- 3 files changed, 110 insertions(+), 4 deletions(-) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 1b546ed1..eb4299e4 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -14,6 +14,8 @@ 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' CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js' @@ -52,6 +54,9 @@ export default class CodeEditor extends React.Component { this.searchState = null this.formatTable = () => this.handleFormatTable() + + this.turndownService = new TurndownService() + this.turndownService.use(gfm) } handleSearch (msg) { @@ -406,6 +411,12 @@ export default class CodeEditor extends React.Component { ) return prevChar === '](' && nextChar === ')' } + + const pastedHtml = clipboardData.getData('text/html') + if (pastedHtml !== '') { + this.handlePasteHtml(e, editor, pastedHtml) + } + if (dataTransferItem.type.match('image')) { attachmentManagement.handlePastImageEvent(this, storageKey, noteKey, dataTransferItem) } else if (this.props.fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) { @@ -459,6 +470,12 @@ export default class CodeEditor extends React.Component { }) } + handlePasteHtml (e, editor, pastedHtml) { + e.preventDefault() + const markdown = this.turndownService.turndown(pastedHtml) + editor.replaceSelection(markdown) + } + mapNormalResponse (response, pastedTxt) { return this.decodeResponse(response).then((body) => { return new Promise((resolve, reject) => { diff --git a/package.json b/package.json index fbbb025f..d7d49b80 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,9 @@ "sanitize-html": "^1.18.2", "striptags": "^2.2.1", "unique-slug": "2.0.0", - "uuid": "^3.2.1" + "uuid": "^3.2.1", + "turndown":"^4.0.2", + "turndown-plugin-gfm":"^1.0.2" }, "devDependencies": { "ava": "^0.25.0", diff --git a/yarn.lock b/yarn.lock index f77252ca..0f4f6b48 100644 --- a/yarn.lock +++ b/yarn.lock @@ -93,6 +93,10 @@ abab@^1.0.3, abab@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e" +abab@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.0.tgz#aba0ab4c5eee2d4c79d3487d85450fb2376ebb0f" + abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -138,6 +142,10 @@ acorn@^5.0.0, acorn@^5.3.0, acorn@^5.5.0: version "5.5.3" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.5.3.tgz#f473dd47e0277a08e28e9bec5aeeb04751f0b8c9" +acorn@^5.5.3: + version "5.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.1.tgz#f095829297706a7c9776958c0afc8930a9b9d9d8" + ajv-keywords@^1.0.0: version "1.5.1" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" @@ -2162,6 +2170,12 @@ cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": dependencies: cssom "0.3.x" +cssstyle@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-1.0.0.tgz#79b16d51ec5591faec60e688891f15d2a5705129" + dependencies: + cssom "0.3.x" + ctype@0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/ctype/-/ctype-0.5.3.tgz#82c18c2461f74114ef16c135224ad0b9144ca12f" @@ -2673,7 +2687,7 @@ domelementtype@~1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" -domexception@^1.0.0: +domexception@^1.0.0, domexception@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" dependencies: @@ -3010,6 +3024,17 @@ escodegen@^1.6.1, escodegen@^1.9.0: optionalDependencies: source-map "~0.6.1" +escodegen@^1.9.1: + version "1.11.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.11.0.tgz#b27a9389481d5bfd5bec76f7bb1eb3f8f4556589" + dependencies: + esprima "^3.1.3" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + escope@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3" @@ -5257,6 +5282,37 @@ jsdom@^11.5.1: ws "^4.0.0" xml-name-validator "^3.0.0" +jsdom@^11.9.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.12.0.tgz#1a80d40ddd378a1de59656e9e6dc5a3ba8657bc8" + dependencies: + abab "^2.0.0" + acorn "^5.5.3" + acorn-globals "^4.1.0" + array-equal "^1.0.0" + cssom ">= 0.3.2 < 0.4.0" + cssstyle "^1.0.0" + data-urls "^1.0.0" + domexception "^1.0.1" + escodegen "^1.9.1" + html-encoding-sniffer "^1.0.2" + left-pad "^1.3.0" + nwsapi "^2.0.7" + parse5 "4.0.0" + pn "^1.1.0" + request "^2.87.0" + request-promise-native "^1.0.5" + sax "^1.2.4" + symbol-tree "^3.2.2" + tough-cookie "^2.3.4" + w3c-hr-time "^1.0.1" + webidl-conversions "^4.0.2" + whatwg-encoding "^1.0.3" + whatwg-mimetype "^2.1.0" + whatwg-url "^6.4.1" + ws "^5.2.0" + xml-name-validator "^3.0.0" + jsdom@^9.4.2: version "9.12.0" resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-9.12.0.tgz#e8c546fffcb06c00d4833ca84410fed7f8a097d4" @@ -5416,7 +5472,7 @@ lcid@^1.0.0: dependencies: invert-kv "^1.0.0" -left-pad@^1.2.0: +left-pad@^1.2.0, left-pad@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e" @@ -6288,6 +6344,10 @@ nwsapi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.0.0.tgz#7c8faf4ad501e1d17a651ebc5547f966b547c5c7" +nwsapi@^2.0.7: + version "2.0.8" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.0.8.tgz#e3603579b7e162b3dbedae4fb24e46f771d8fa24" + oauth-sign@~0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.6.0.tgz#7dbeae44f6ca454e1f168451d630746735813ce3" @@ -7028,6 +7088,10 @@ pseudomap@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" +psl@^1.1.24: + version "1.1.29" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.29.tgz#60f580d360170bb722a797cc704411e6da850c67" + punycode@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" @@ -7510,7 +7574,7 @@ request@2.55.0: tough-cookie ">=0.12.0" tunnel-agent "~0.4.0" -request@^2.45.0, request@^2.79.0, request@^2.83.0: +request@^2.45.0, request@^2.79.0, request@^2.83.0, request@^2.87.0: version "2.87.0" resolved "https://registry.yarnpkg.com/request/-/request-2.87.0.tgz#32f00235cd08d482b4d0d68db93a829c0ed5756e" dependencies: @@ -8515,6 +8579,13 @@ tough-cookie@>=0.12.0, tough-cookie@>=2.3.3, tough-cookie@^2.3.2, tough-cookie@^ dependencies: punycode "^1.4.1" +tough-cookie@^2.3.4: + version "2.4.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" + dependencies: + psl "^1.1.24" + punycode "^1.4.1" + tr46@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" @@ -8565,6 +8636,16 @@ tunnel-agent@~0.4.0: version "0.4.3" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" +turndown-plugin-gfm@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/turndown-plugin-gfm/-/turndown-plugin-gfm-1.0.2.tgz#6f8678a361f35220b2bdf5619e6049add75bf1c7" + +turndown@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/turndown/-/turndown-4.0.2.tgz#c3ddb8ba32a3665723599be2f4e7860adb6042ae" + dependencies: + jsdom "^11.9.0" + tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" @@ -9098,6 +9179,12 @@ ws@^4.0.0: async-limiter "~1.0.0" safe-buffer "~5.1.0" +ws@^5.2.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f" + dependencies: + async-limiter "~1.0.0" + xdg-basedir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" From 5bf3824f28d41d89180df0520bb4175c5a52eee8 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 10 Aug 2018 15:15:19 +0300 Subject: [PATCH 002/131] Fix --- browser/components/CodeEditor.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index eb4299e4..43b16bc9 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -415,9 +415,7 @@ export default class CodeEditor extends React.Component { const pastedHtml = clipboardData.getData('text/html') if (pastedHtml !== '') { this.handlePasteHtml(e, editor, pastedHtml) - } - - 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.handlePasteUrl(e, editor, pastedTxt) From 7804a229844c372f875ab498fb937744500bdbac Mon Sep 17 00:00:00 2001 From: Maciek Date: Fri, 10 Aug 2018 21:39:59 +0200 Subject: [PATCH 003/131] Automatic table of contents generation for Markdown Adds table of contents for any Markdown note or Markdown snippet. Consequent generations update existing TOC. Generated TOC is case sensitive to handle #2067 Shortcut : CommandOrControl+Alt+T Menu : Edit/Generate/Update Markdown TOC --- browser/lib/markdown-toc-generator.js | 52 +++++++++++++++++++++++ browser/main/Detail/MarkdownNoteDetail.js | 9 ++++ browser/main/Detail/SnippetNoteDetail.js | 15 ++++++- lib/main-menu.js | 10 +++++ package.json | 1 + webpack-skeleton.js | 1 + 6 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 browser/lib/markdown-toc-generator.js diff --git a/browser/lib/markdown-toc-generator.js b/browser/lib/markdown-toc-generator.js new file mode 100644 index 00000000..363a58ce --- /dev/null +++ b/browser/lib/markdown-toc-generator.js @@ -0,0 +1,52 @@ +/** + * @fileoverview Markdown table of contents generator + */ + +import toc from 'markdown-toc' +import diacritics from 'diacritics-map' +import stripColor from 'strip-color' + +/** + * @caseSensitiveSlugify Custom slugify function + * Same implementation that the original used by markdown-toc (node_modules/markdown-toc/lib/utils.js), + * but keeps original case to properly handle https://github.com/BoostIO/Boostnote/issues/2067 + */ +function caseSensitiveSlugify (str) { + function replaceDiacritics (str) { + return str.replace(/[À-ž]/g, function (ch) { + return diacritics[ch] || ch + }) + } + function getTitle (str) { + if (/^\[[^\]]+\]\(/.test(str)) { + var m = /^\[([^\]]+)\]/.exec(str) + if (m) return m[1] + } + return str + } + str = getTitle(str) + str = stripColor(str) + // str = str.toLowerCase() //let's be case sensitive + + // `.split()` is often (but not always) faster than `.replace()` + str = str.split(' ').join('-') + str = str.split(/\t/).join('--') + str = str.split(/<\/?[^>]+>/).join('') + str = str.split(/[|$&`~=\\\/@+*!?({[\]})<>=.,;:'"^]/).join('') + str = str.split(/[。?!,、;:“”【】()〔〕[]﹃﹄“ ”‘’﹁﹂—…-~《》〈〉「」]/).join('') + str = replaceDiacritics(str) + return str +} + +export function generate (currentValue, updateCallback) { + const TOC_MARKER = '' + if (!currentValue.includes(TOC_MARKER)) { + currentValue = TOC_MARKER + currentValue + } + updateCallback(toc.insert(currentValue, {slugify: caseSensitiveSlugify})) +} + +export default { + generate +} + diff --git a/browser/main/Detail/MarkdownNoteDetail.js b/browser/main/Detail/MarkdownNoteDetail.js index 82073162..11197838 100755 --- a/browser/main/Detail/MarkdownNoteDetail.js +++ b/browser/main/Detail/MarkdownNoteDetail.js @@ -29,6 +29,7 @@ import { formatDate } from 'browser/lib/date-formatter' import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus' import striptags from 'striptags' import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote' +import markdownToc from 'browser/lib/markdown-toc-generator' class MarkdownNoteDetail extends React.Component { constructor (props) { @@ -47,6 +48,7 @@ class MarkdownNoteDetail extends React.Component { this.dispatchTimer = null this.toggleLockButton = this.handleToggleLockButton.bind(this) + this.generateToc = () => this.handleGenerateToc() } focus () { @@ -59,6 +61,7 @@ class MarkdownNoteDetail extends React.Component { const reversedType = this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT' this.handleSwitchMode(reversedType) }) + ee.on('code:generate-toc', this.generateToc) } componentWillReceiveProps (nextProps) { @@ -75,6 +78,7 @@ class MarkdownNoteDetail extends React.Component { componentWillUnmount () { ee.off('topbar:togglelockbutton', this.toggleLockButton) + ee.off('code:generate-toc', this.generateToc) if (this.saveQueue != null) this.saveNow() } @@ -262,6 +266,11 @@ class MarkdownNoteDetail extends React.Component { } } + handleGenerateToc () { + markdownToc.generate(this.refs.content.value, + (modifiedValue) => { this.refs.content.refs.code.setValue(modifiedValue) }) + } + handleFocus (e) { this.focus() } diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index 652d1f53..f7a4dd3a 100644 --- a/browser/main/Detail/SnippetNoteDetail.js +++ b/browser/main/Detail/SnippetNoteDetail.js @@ -29,6 +29,7 @@ import InfoPanelTrashed from './InfoPanelTrashed' import { formatDate } from 'browser/lib/date-formatter' import i18n from 'browser/lib/i18n' import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote' +import markdownToc from 'browser/lib/markdown-toc-generator' const electron = require('electron') const { remote } = electron @@ -52,6 +53,7 @@ class SnippetNoteDetail extends React.Component { } this.scrollToNextTabThreshold = 0.7 + this.generateToc = () => this.handleGenerateToc() } componentDidMount () { @@ -65,6 +67,7 @@ class SnippetNoteDetail extends React.Component { enableLeftArrow: allTabs.offsetLeft !== 0 }) } + ee.on('code:generate-toc', this.generateToc) } componentWillReceiveProps (nextProps) { @@ -91,6 +94,16 @@ class SnippetNoteDetail extends React.Component { componentWillUnmount () { if (this.saveQueue != null) this.saveNow() + ee.off('code:generate-toc', this.generateToc) + } + + handleGenerateToc () { + let currentMode = this.state.note.snippets[this.state.snippetIndex].mode + if (currentMode.includes('Markdown')) { + let currentValue = this.refs['code-' + this.state.snippetIndex].value + let currentEditor = this.refs['code-' + this.state.snippetIndex].refs.code.editor + markdownToc.generate(currentValue, (modifiedValue) => { currentEditor.setValue(modifiedValue) }) + } } handleChange (e) { @@ -441,7 +454,7 @@ class SnippetNoteDetail extends React.Component { const isSuper = global.process.platform === 'darwin' ? e.metaKey : e.ctrlKey - if (isSuper && !e.shiftKey) { + if (isSuper && !e.shiftKey && !e.altKey) { e.preventDefault() this.addSnippet() } diff --git a/lib/main-menu.js b/lib/main-menu.js index cda964c5..b86552ac 100644 --- a/lib/main-menu.js +++ b/lib/main-menu.js @@ -228,6 +228,16 @@ const edit = { click () { mainWindow.webContents.send('editor:add-tag') } + }, + { + type: 'separator' + }, + { + label: 'Generate/Update Markdown TOC', + accelerator: 'CommandOrControl+Alt+T', + click () { + mainWindow.webContents.send('code:generate-toc') + } } ] } diff --git a/package.json b/package.json index fbbb025f..062a9c6c 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,7 @@ "markdown-it-named-headers": "^0.0.4", "markdown-it-plantuml": "^1.1.0", "markdown-it-smartarrows": "^1.0.1", + "markdown-toc": "^1.2.0", "mdurl": "^1.0.1", "mermaid": "^8.0.0-rc.8", "moment": "^2.10.3", diff --git a/webpack-skeleton.js b/webpack-skeleton.js index aca0791f..4d221f15 100644 --- a/webpack-skeleton.js +++ b/webpack-skeleton.js @@ -37,6 +37,7 @@ var config = { 'markdown-it-kbd', 'markdown-it-plantuml', 'markdown-it-admonition', + 'markdown-toc', 'devtron', '@rokt33r/season', { From 3c14cc219e0d69d390fffb5bafad9bc776783275 Mon Sep 17 00:00:00 2001 From: Maciek Date: Sat, 11 Aug 2018 10:09:22 +0200 Subject: [PATCH 004/131] ESLint: fix let -> const warnings --- browser/main/Detail/SnippetNoteDetail.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index f7a4dd3a..ce3d03e2 100644 --- a/browser/main/Detail/SnippetNoteDetail.js +++ b/browser/main/Detail/SnippetNoteDetail.js @@ -98,10 +98,10 @@ class SnippetNoteDetail extends React.Component { } handleGenerateToc () { - let currentMode = this.state.note.snippets[this.state.snippetIndex].mode + const currentMode = this.state.note.snippets[this.state.snippetIndex].mode if (currentMode.includes('Markdown')) { - let currentValue = this.refs['code-' + this.state.snippetIndex].value - let currentEditor = this.refs['code-' + this.state.snippetIndex].refs.code.editor + const currentValue = this.refs['code-' + this.state.snippetIndex].value + const currentEditor = this.refs['code-' + this.state.snippetIndex].refs.code.editor markdownToc.generate(currentValue, (modifiedValue) => { currentEditor.setValue(modifiedValue) }) } } From b93d7a204fc1b3a0bb9e9d46c33961a706f549b3 Mon Sep 17 00:00:00 2001 From: zhoufeng1989 Date: Tue, 14 Aug 2018 12:38:31 +1200 Subject: [PATCH 005/131] Fix 2207 and 2273, add export for storage. --- browser/main/SideNav/StorageItem.js | 40 ++++++++++++++ browser/main/lib/dataApi/exportStorage.js | 63 +++++++++++++++++++++++ browser/main/lib/dataApi/index.js | 1 + browser/main/store.js | 15 +----- tests/dataApi/exportStorage-test.js | 51 ++++++++++++++++++ 5 files changed, 157 insertions(+), 13 deletions(-) create mode 100644 browser/main/lib/dataApi/exportStorage.js create mode 100644 tests/dataApi/exportStorage-test.js diff --git a/browser/main/SideNav/StorageItem.js b/browser/main/SideNav/StorageItem.js index d72f0a8f..d17314b3 100644 --- a/browser/main/SideNav/StorageItem.js +++ b/browser/main/SideNav/StorageItem.js @@ -38,6 +38,22 @@ class StorageItem extends React.Component { { type: 'separator' }, + { + label: i18n.__('Export Storage'), + submenu: [ + { + label: i18n.__('Export as txt'), + click: (e) => this.handleExportStorageClick(e, 'txt') + }, + { + label: i18n.__('Export as md'), + click: (e) => this.handleExportStorageClick(e, 'md') + } + ] + }, + { + type: 'separator' + }, { label: i18n.__('Unlink Storage'), click: (e) => this.handleUnlinkStorageClick(e) @@ -68,6 +84,30 @@ class StorageItem extends React.Component { } } + handleExportStorageClick (e, fileType) { + const options = { + properties: ['openDirectory', 'createDirectory'], + buttonLabel: i18n.__('Select directory'), + title: i18n.__('Select a folder to export the files to'), + multiSelections: false + } + dialog.showOpenDialog(remote.getCurrentWindow(), options, + (paths) => { + if (paths && paths.length === 1) { + const { storage, dispatch } = this.props + dataApi + .exportStorage(storage.key, fileType, paths[0]) + .then(data => { + dispatch({ + type: 'EXPORT_STORAGE', + storage: data.storage, + fileType: data.fileType + }) + }) + } + }) + } + handleToggleButtonClick (e) { const { storage, dispatch } = this.props const isOpen = !this.state.isOpen diff --git a/browser/main/lib/dataApi/exportStorage.js b/browser/main/lib/dataApi/exportStorage.js new file mode 100644 index 00000000..ce2c4573 --- /dev/null +++ b/browser/main/lib/dataApi/exportStorage.js @@ -0,0 +1,63 @@ +import { findStorage } from 'browser/lib/findStorage' +import resolveStorageData from './resolveStorageData' +import resolveStorageNotes from './resolveStorageNotes' +import filenamify from 'filenamify' +import * as path from 'path' +import * as fs from 'fs' + +/** + * @param {String} storageKey + * @param {String} fileType + * @param {String} exportDir + * + * @return {Object} + * ``` + * { + * storage: Object, + * fileType: String, + * exportDir: String + * } + * ``` + */ + +function exportStorage (storageKey, fileType, exportDir) { + let targetStorage + try { + targetStorage = findStorage(storageKey) + } catch (e) { + return Promise.reject(e) + } + + return resolveStorageData(targetStorage) + .then(storage => ( + resolveStorageNotes(storage).then(notes => ({storage, notes})) + )) + .then(function exportNotes (data) { + const { storage, notes } = data + const folderNamesMapping = {} + storage.folders.forEach(folder => { + const folderExportedDir = path.join(exportDir, filenamify(folder.name, {replacement: '_'})) + folderNamesMapping[folder.key] = folderExportedDir + // make sure directory exists + try { + fs.mkdirSync(folderExportedDir) + } catch (e) {} + }) + notes + .filter(note => !note.isTrashed && note.type === 'MARKDOWN_NOTE') + .forEach(markdownNote => { + const folderExportedDir = folderNamesMapping[markdownNote.folder] + const snippetName = `${filenamify(markdownNote.title, {replacement: '_'})}.${fileType}` + const notePath = path.join(folderExportedDir, snippetName) + fs.writeFileSync(notePath, markdownNote.content) + }) + + return { + storage, + fileType, + exportDir + } + }) +} + +module.exports = exportStorage diff --git a/browser/main/lib/dataApi/index.js b/browser/main/lib/dataApi/index.js index 4e2f0061..92be6b93 100644 --- a/browser/main/lib/dataApi/index.js +++ b/browser/main/lib/dataApi/index.js @@ -9,6 +9,7 @@ const dataApi = { deleteFolder: require('./deleteFolder'), reorderFolder: require('./reorderFolder'), exportFolder: require('./exportFolder'), + exportStorage: require('./exportStorage'), createNote: require('./createNote'), updateNote: require('./updateNote'), deleteNote: require('./deleteNote'), diff --git a/browser/main/store.js b/browser/main/store.js index a1b6b791..b8f13cc8 100644 --- a/browser/main/store.js +++ b/browser/main/store.js @@ -216,16 +216,10 @@ function data (state = defaultDataMap(), action) { return state } case 'UPDATE_FOLDER': - state = Object.assign({}, state) - state.storageMap = new Map(state.storageMap) - state.storageMap.set(action.storage.key, action.storage) - return state case 'REORDER_FOLDER': - state = Object.assign({}, state) - state.storageMap = new Map(state.storageMap) - state.storageMap.set(action.storage.key, action.storage) - return state case 'EXPORT_FOLDER': + case 'RENAME_STORAGE': + case 'EXPORT_STORAGE': state = Object.assign({}, state) state.storageMap = new Map(state.storageMap) state.storageMap.set(action.storage.key, action.storage) @@ -355,11 +349,6 @@ function data (state = defaultDataMap(), action) { }) } return state - case 'RENAME_STORAGE': - state = Object.assign({}, state) - state.storageMap = new Map(state.storageMap) - state.storageMap.set(action.storage.key, action.storage) - return state case 'EXPAND_STORAGE': state = Object.assign({}, state) state.storageMap = new Map(state.storageMap) diff --git a/tests/dataApi/exportStorage-test.js b/tests/dataApi/exportStorage-test.js new file mode 100644 index 00000000..1ee98328 --- /dev/null +++ b/tests/dataApi/exportStorage-test.js @@ -0,0 +1,51 @@ +const test = require('ava') +const exportStorage = require('browser/main/lib/dataApi/exportStorage') + +global.document = require('jsdom').jsdom('') +global.window = document.defaultView +global.navigator = window.navigator + +const Storage = require('dom-storage') +const localStorage = window.localStorage = global.localStorage = new Storage(null, { strict: true }) +const path = require('path') +const TestDummy = require('../fixtures/TestDummy') +const os = require('os') +const fs = require('fs') +const sander = require('sander') + +test.beforeEach(t => { + t.context.storageDir = path.join(os.tmpdir(), 'test/export-storage') + t.context.storage = TestDummy.dummyStorage(t.context.storageDir) + t.context.exportDir = path.join(os.tmpdir(), 'test/export-storage-output') + try { fs.mkdirSync(t.context.exportDir) } catch (e) {} + localStorage.setItem('storages', JSON.stringify([t.context.storage.cache])) +}) + +test.serial('Export a storage', t => { + const storageKey = t.context.storage.cache.key + const folders = t.context.storage.json.folders + const notes = t.context.storage.notes + const exportDir = t.context.exportDir + const folderKeyToName = folders.reduce( + (acc, folder) => { + acc[folder.key] = folder.name + return acc + }, {}) + return exportStorage(storageKey, 'md', exportDir) + .then(() => { + notes.forEach(note => { + const noteDir = path.join(exportDir, folderKeyToName[note.folder], `${note.title}.md`) + if (note.type === 'MARKDOWN_NOTE') { + t.true(fs.existsSync(noteDir)) + } else if (note.type === 'SNIPPET_NOTE') { + t.false(fs.existsSync(noteDir)) + } + }) + }) +}) + +test.afterEach.always(t => { + localStorage.clear() + sander.rimrafSync(t.context.storageDir) + sander.rimrafSync(t.context.exportDir) +}) From ce3b29085f080ae705b8615c6dcecd0a4c3dc529 Mon Sep 17 00:00:00 2001 From: Maciek Date: Tue, 14 Aug 2018 22:32:22 +0200 Subject: [PATCH 006/131] Change menu position and accelerator for TOC gen. Due to the fact, that submenu "Edit" is visible only in macOS, let's move TOC generator to "File" menu. Also, change accelerator to SHIFT+CTRL+T which is working without conflicts and problems on all platforms. --- lib/main-menu.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/main-menu.js b/lib/main-menu.js index b86552ac..fed5eb15 100644 --- a/lib/main-menu.js +++ b/lib/main-menu.js @@ -145,6 +145,16 @@ const file = { { type: 'separator' }, + { + label: 'Generate/Update Markdown TOC', + accelerator: 'Shift+Ctrl+T', + click () { + mainWindow.webContents.send('code:generate-toc') + } + }, + { + type: 'separator' + }, { label: 'Print', accelerator: 'CommandOrControl+P', @@ -228,16 +238,6 @@ const edit = { click () { mainWindow.webContents.send('editor:add-tag') } - }, - { - type: 'separator' - }, - { - label: 'Generate/Update Markdown TOC', - accelerator: 'CommandOrControl+Alt+T', - click () { - mainWindow.webContents.send('code:generate-toc') - } } ] } From 657806c8cf278720e4806c26840bb05fb97c0626 Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Mon, 20 Aug 2018 20:14:45 +0200 Subject: [PATCH 007/131] add smart table editor --- browser/components/CodeEditor.js | 183 ++++++++++++------ browser/components/MarkdownEditor.js | 3 +- browser/components/MarkdownSplitEditor.js | 1 + browser/lib/TextEditorInterface.js | 170 +++++++++++----- browser/main/Detail/SnippetNoteDetail.js | 1 + browser/main/lib/ConfigManager.js | 3 +- browser/main/modals/PreferencesModal/UiTab.js | 14 +- locales/en.json | 3 +- locales/fr.json | 3 +- 9 files changed, 269 insertions(+), 112 deletions(-) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index f16cc53c..36779e4f 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -141,61 +141,9 @@ export default class CodeEditor extends React.Component { triples: '```"""\'\'\'', explode: '[]{}``$$', override: true - }, - extraKeys: { - 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.setMode(this.props.mode) this.editor.on('focus', this.focusHandler) @@ -216,8 +164,135 @@ export default class CodeEditor extends React.Component { CodeMirror.Vim.defineEx('qw', 'qw', this.quitEditor) CodeMirror.Vim.map('ZZ', ':q', 'normal') this.setState({ isReady: true }) - this.tableEditor = new TableEditor(new TextEditorInterface(this.editor)) + + const editorIntf = new TextEditorInterface(this.editor) + this.tableEditor = new TableEditor(editorIntf) eventEmitter.on('code:format-table', this.formatTable) + + const 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 + } + }) + + if(this.props.enableTableEditor) { + const opts = options({ + smartCursor: true + }) + + const editorKeyMap = CodeMirror.normalizeKeyMap({ + 'Tab' : () => { this.tableEditor.nextCell(opts) }, + 'Shift-Tab' : () => { this.tableEditor.previousCell(opts) }, + 'Enter' : () => { this.tableEditor.nextRow(opts) }, + 'Ctrl-Enter' : () => { this.tableEditor.escape(opts) }, + 'Cmd-Enter' : () => { this.tableEditor.escape(opts) }, + 'Shift-Ctrl-Left' : () => { this.tableEditor.alignColumn(Alignment.LEFT, opts) }, + 'Shift-Cmd-Left' : () => { this.tableEditor.alignColumn(Alignment.LEFT, opts) }, + 'Shift-Ctrl-Right' : () => { this.tableEditor.alignColumn(Alignment.RIGHT, opts) }, + 'Shift-Cmd-Right' : () => { this.tableEditor.alignColumn(Alignment.RIGHT, opts) }, + 'Shift-Ctrl-Up' : () => { this.tableEditor.alignColumn(Alignment.CENTER, opts) }, + 'Shift-Cmd-Up' : () => { this.tableEditor.alignColumn(Alignment.CENTER, opts) }, + 'Shift-Ctrl-Down' : () => { this.tableEditor.alignColumn(Alignment.NONE, opts) }, + 'Shift-Cmd-Down' : () => { this.tableEditor.alignColumn(Alignment.NONE, opts) }, + 'Ctrl-Left' : () => { this.tableEditor.moveFocus(0, -1, opts) }, + 'Cmd-Left' : () => { this.tableEditor.moveFocus(0, -1, opts) }, + 'Ctrl-Right' : () => { this.tableEditor.moveFocus(0, 1, opts) }, + 'Cmd-Right' : () => { this.tableEditor.moveFocus(0, 1, opts) }, + 'Ctrl-Up' : () => { this.tableEditor.moveFocus(-1, 0, opts) }, + 'Cmd-Up' : () => { this.tableEditor.moveFocus(-1, 0, opts) }, + 'Ctrl-Down' : () => { this.tableEditor.moveFocus(1, 0, opts) }, + 'Cmd-Down' : () => { this.tableEditor.moveFocus(1, 0, opts) }, + 'Ctrl-K Ctrl-I' : () => { this.tableEditor.insertRow(opts) }, + 'Cmd-K Cmd-I' : () => { this.tableEditor.insertRow(opts) }, + 'Ctrl-L Ctrl-I' : () => { this.tableEditor.deleteRow(opts) }, + 'Cmd-L Cmd-I' : () => { this.tableEditor.deleteRow(opts) }, + 'Ctrl-K Ctrl-J' : () => { this.tableEditor.insertColumn(opts) }, + 'Cmd-K Cmd-J' : () => { this.tableEditor.insertColumn(opts) }, + 'Ctrl-L Ctrl-J' : () => { this.tableEditor.deleteColumn(opts) }, + 'Cmd-L Cmd-J' : () => { this.tableEditor.deleteColumn(opts) }, + 'Alt-Shift-Ctrl-Left' : () => { this.tableEditor.moveColumn(-1, opts) }, + 'Alt-Shift-Cmd-Left' : () => { this.tableEditor.moveColumn(-1, opts) }, + 'Alt-Shift-Ctrl-Right': () => { this.tableEditor.moveColumn(1, opts) }, + 'Alt-Shift-Cmd-Right' : () => { this.tableEditor.moveColumn(1, opts) }, + 'Alt-Shift-Ctrl-Up' : () => { this.tableEditor.moveRow(-1, opts) }, + 'Alt-Shift-Cmd-Up' : () => { this.tableEditor.moveRow(-1, opts) }, + 'Alt-Shift-Ctrl-Down' : () => { this.tableEditor.moveRow(1, opts) }, + 'Alt-Shift-Cmd-Down' : () => { this.tableEditor.moveRow(1, opts) } + }) + + const updateActiveState = () => { + const active = this.tableEditor.cursorIsInTable(opts) + if (active) { + this.editor.setOption('extraKeys', editorKeyMap) + } else { + this.editor.setOption('extraKeys', defaultKeyMap) + this.tableEditor.resetSmartCursor() + } + } + + this.editor.on('cursorActivity', () => { + if (!editorIntf.transaction) { + updateActiveState() + } + }) + this.editor.on('changes', () => { + if (!editorIntf.transaction) { + updateActiveState() + } + }) + editorIntf.onDidFinishTransaction = () => { + updateActiveState() + } + } else { + this.editor.setOption('extraKeys', defaultKeyMap) + } } expandSnippet (line, cursor, cm, snippets) { diff --git a/browser/components/MarkdownEditor.js b/browser/components/MarkdownEditor.js index 2b388f90..a9a041df 100644 --- a/browser/components/MarkdownEditor.js +++ b/browser/components/MarkdownEditor.js @@ -235,7 +235,7 @@ class MarkdownEditor extends React.Component { if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none' const storage = findStorage(storageKey) - + return (
this.handleChange(e)} onBlur={(e) => this.handleBlur(e)} /> diff --git a/browser/components/MarkdownSplitEditor.js b/browser/components/MarkdownSplitEditor.js index 8fa3cc07..ddc9d7e0 100644 --- a/browser/components/MarkdownSplitEditor.js +++ b/browser/components/MarkdownSplitEditor.js @@ -158,6 +158,7 @@ class MarkdownSplitEditor extends React.Component { rulers={config.editor.rulers} scrollPastEnd={config.editor.scrollPastEnd} fetchUrlTitle={config.editor.fetchUrlTitle} + enableTableEditor={config.editor.enableTableEditor} storageKey={storageKey} noteKey={noteKey} onChange={this.handleOnChange.bind(this)} diff --git a/browser/lib/TextEditorInterface.js b/browser/lib/TextEditorInterface.js index 53ae2337..8e80c313 100644 --- a/browser/lib/TextEditorInterface.js +++ b/browser/lib/TextEditorInterface.js @@ -1,53 +1,117 @@ -import { Point } from '@susisu/mte-kernel' - -export default class TextEditorInterface { - constructor (editor) { - this.editor = editor - } - - getCursorPosition () { - const pos = this.editor.getCursor() - return new Point(pos.line, pos.ch) - } - - setCursorPosition (pos) { - this.editor.setCursor({line: pos.row, ch: pos.column}) - } - - setSelectionRange (range) { - this.editor.setSelection({ - anchor: {line: range.start.row, ch: range.start.column}, - head: {line: range.end.row, ch: range.end.column} - }) - } - - getLastRow () { - return this.editor.lastLine() - } - - acceptsTableEdit (row) { - return true - } - - getLine (row) { - return this.editor.getLine(row) - } - - insertLine (row, line) { - this.editor.replaceRange(line, {line: row, ch: 0}) - } - - deleteLine (row) { - this.editor.replaceRange('', {line: row, ch: 0}, {line: row, ch: this.editor.getLine(row).length}) - } - - replaceLines (startRow, endRow, lines) { - endRow-- // because endRow is a first line after a table. - const endRowCh = this.editor.getLine(endRow).length - this.editor.replaceRange(lines, {line: startRow, ch: 0}, {line: endRow, ch: endRowCh}) - } - - transact (func) { - func() - } -} +import { Point } from '@susisu/mte-kernel' + +export default class TextEditorInterface { + constructor (editor) { + this.editor = editor + this.doc = editor.getDoc() + this.transaction = false + } + + getCursorPosition() { + const { line, ch } = this.doc.getCursor() + return new Point(line, ch) + } + + setCursorPosition(pos) { + this.doc.setCursor({ + line: pos.row, + ch: pos.column + }) + } + + setSelectionRange(range) { + this.doc.setSelection( + { line: range.start.row, ch: range.start.column }, + { line: range.end.row, ch: range.end.column } + ) + } + + getLastRow() { + return this.doc.lineCount() - 1 + } + + acceptsTableEdit() { + return true + } + + getLine(row) { + return this.doc.getLine(row) + } + + insertLine(row, line) { + const lastRow = this.getLastRow() + if (row > lastRow) { + const lastLine = this.getLine(lastRow) + this.doc.replaceRange( + '\n' + line, + { line: lastRow, ch: lastLine.length }, + { line: lastRow, ch: lastLine.length } + ) + } + else { + this.doc.replaceRange( + line + '\n', + { line: row, ch: 0 }, + { line: row, ch: 0 } + ) + } + } + + deleteLine(row) { + const lastRow = this.getLastRow() + if (row >= lastRow) { + if (lastRow > 0) { + const preLastLine = this.getLine(lastRow - 1) + const lastLine = this.getLine(lastRow) + this.doc.replaceRange( + '', + { line: lastRow - 1, ch: preLastLine.length }, + { line: lastRow, ch: lastLine.length } + ) + } + else { + const lastLine = this.getLine(lastRow) + this.doc.replaceRange( + '', + { line: lastRow, ch: 0 }, + { line: lastRow, ch: lastLine.length } + ) + } + } + else { + this.doc.replaceRange( + '', + { line: row, ch: 0 }, + { line: row + 1, ch: 0 } + ) + } + } + + replaceLines(startRow, endRow, lines) { + const lastRow = this.getLastRow() + if (endRow > lastRow) { + const lastLine = this.getLine(lastRow) + this.doc.replaceRange( + lines.join('\n'), + { line: startRow, ch: 0 }, + { line: lastRow, ch: lastLine.length } + ) + } + else { + this.doc.replaceRange( + lines.join('\n') + '\n', + { line: startRow, ch: 0 }, + { line: endRow, ch: 0 } + ) + } + } + + transact(func) { + this.transaction = true + func() + this.transaction = false + if (this.onDidFinishTransaction) { + this.onDidFinishTransaction.call(undefined) + } + } +} diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index 652d1f53..2fe96420 100644 --- a/browser/main/Detail/SnippetNoteDetail.js +++ b/browser/main/Detail/SnippetNoteDetail.js @@ -691,6 +691,7 @@ class SnippetNoteDetail extends React.Component { keyMap={config.editor.keyMap} scrollPastEnd={config.editor.scrollPastEnd} fetchUrlTitle={config.editor.fetchUrlTitle} + enableTableEditor={config.editor.enableTableEditor} onChange={(e) => this.handleCodeChange(index)(e)} ref={'code-' + index} /> diff --git a/browser/main/lib/ConfigManager.js b/browser/main/lib/ConfigManager.js index 434b0d22..2c601b57 100644 --- a/browser/main/lib/ConfigManager.js +++ b/browser/main/lib/ConfigManager.js @@ -46,7 +46,8 @@ export const DEFAULT_CONFIG = { switchPreview: 'BLUR', // Available value: RIGHTCLICK, BLUR scrollPastEnd: false, type: 'SPLIT', - fetchUrlTitle: true + fetchUrlTitle: true, + enableTableEditor: false }, preview: { fontSize: '14', diff --git a/browser/main/modals/PreferencesModal/UiTab.js b/browser/main/modals/PreferencesModal/UiTab.js index aa3568e7..f8a9ab11 100644 --- a/browser/main/modals/PreferencesModal/UiTab.js +++ b/browser/main/modals/PreferencesModal/UiTab.js @@ -86,7 +86,8 @@ class UiTab extends React.Component { switchPreview: this.refs.editorSwitchPreview.value, keyMap: this.refs.editorKeyMap.value, scrollPastEnd: this.refs.scrollPastEnd.checked, - fetchUrlTitle: this.refs.editorFetchUrlTitle.checked + fetchUrlTitle: this.refs.editorFetchUrlTitle.checked, + enableTableEditor: this.refs.enableTableEditor.checked }, preview: { fontSize: this.refs.previewFontSize.value, @@ -419,6 +420,17 @@ class UiTab extends React.Component { {i18n.__('Bring in web page title when pasting URL on editor')}
+ +
+ +
{i18n.__('Preview')}
diff --git a/locales/en.json b/locales/en.json index a9767492..6b12c1e7 100644 --- a/locales/en.json +++ b/locales/en.json @@ -175,5 +175,6 @@ "Allow styles": "Allow styles", "Allow dangerous html tags": "Allow dangerous html tags", "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.", - "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠" + "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠", + "Enable smart table editor": "Enable smart table editor" } diff --git a/locales/fr.json b/locales/fr.json index 8b880aa6..e9b2c168 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -152,5 +152,6 @@ "Allow styles": "Accepter les styles", "Allow dangerous html tags": "Accepter les tags html dangereux", "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convertir des flèches textuelles en jolis signes. ⚠ Cela va interferérer avec les éventuels commentaires HTML dans votre Markdown.", - "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ Vous avez collé un lien qui référence une pièce-jointe qui n'a pas pu être récupéré dans le dossier de stockage de la note. Coller des liens qui font référence à des pièces-jointes ne fonctionne que si la source et la destination et la même. Veuillez plutôt utiliser du Drag & Drop ! ⚠" + "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ Vous avez collé un lien qui référence une pièce-jointe qui n'a pas pu être récupéré dans le dossier de stockage de la note. Coller des liens qui font référence à des pièces-jointes ne fonctionne que si la source et la destination et la même. Veuillez plutôt utiliser du Drag & Drop ! ⚠", + "Enable smart table editor": "Activer l'intelligent éditeur de tableaux" } From 7cf9dda82108b69b34e3ebe7e1494712701f2e04 Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Mon, 20 Aug 2018 20:29:10 +0200 Subject: [PATCH 008/131] fix lint errors --- browser/components/CodeEditor.js | 90 +++++++++---------- browser/components/MarkdownEditor.js | 2 +- browser/lib/TextEditorInterface.js | 32 +++---- browser/main/modals/PreferencesModal/UiTab.js | 2 +- 4 files changed, 61 insertions(+), 65 deletions(-) diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index 36779e4f..b2301c08 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -5,7 +5,7 @@ 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 } 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' @@ -143,7 +143,7 @@ export default class CodeEditor extends React.Component { override: true } }) - + this.setMode(this.props.mode) this.editor.on('focus', this.focusHandler) @@ -164,11 +164,11 @@ export default class CodeEditor extends React.Component { CodeMirror.Vim.defineEx('qw', 'qw', this.quitEditor) CodeMirror.Vim.map('ZZ', ':q', 'normal') this.setState({ isReady: true }) - + const editorIntf = new TextEditorInterface(this.editor) this.tableEditor = new TableEditor(editorIntf) eventEmitter.on('code:format-table', this.formatTable) - + const defaultKeyMap = CodeMirror.normalizeKeyMap({ Tab: function (cm) { const cursor = cm.getCursor() @@ -221,52 +221,52 @@ export default class CodeEditor extends React.Component { return CodeMirror.Pass } }) - - if(this.props.enableTableEditor) { + + if (this.props.enableTableEditor) { const opts = options({ smartCursor: true }) - + const editorKeyMap = CodeMirror.normalizeKeyMap({ - 'Tab' : () => { this.tableEditor.nextCell(opts) }, - 'Shift-Tab' : () => { this.tableEditor.previousCell(opts) }, - 'Enter' : () => { this.tableEditor.nextRow(opts) }, - 'Ctrl-Enter' : () => { this.tableEditor.escape(opts) }, - 'Cmd-Enter' : () => { this.tableEditor.escape(opts) }, - 'Shift-Ctrl-Left' : () => { this.tableEditor.alignColumn(Alignment.LEFT, opts) }, - 'Shift-Cmd-Left' : () => { this.tableEditor.alignColumn(Alignment.LEFT, opts) }, - 'Shift-Ctrl-Right' : () => { this.tableEditor.alignColumn(Alignment.RIGHT, opts) }, - 'Shift-Cmd-Right' : () => { this.tableEditor.alignColumn(Alignment.RIGHT, opts) }, - 'Shift-Ctrl-Up' : () => { this.tableEditor.alignColumn(Alignment.CENTER, opts) }, - 'Shift-Cmd-Up' : () => { this.tableEditor.alignColumn(Alignment.CENTER, opts) }, - 'Shift-Ctrl-Down' : () => { this.tableEditor.alignColumn(Alignment.NONE, opts) }, - 'Shift-Cmd-Down' : () => { this.tableEditor.alignColumn(Alignment.NONE, opts) }, - 'Ctrl-Left' : () => { this.tableEditor.moveFocus(0, -1, opts) }, - 'Cmd-Left' : () => { this.tableEditor.moveFocus(0, -1, opts) }, - 'Ctrl-Right' : () => { this.tableEditor.moveFocus(0, 1, opts) }, - 'Cmd-Right' : () => { this.tableEditor.moveFocus(0, 1, opts) }, - 'Ctrl-Up' : () => { this.tableEditor.moveFocus(-1, 0, opts) }, - 'Cmd-Up' : () => { this.tableEditor.moveFocus(-1, 0, opts) }, - 'Ctrl-Down' : () => { this.tableEditor.moveFocus(1, 0, opts) }, - 'Cmd-Down' : () => { this.tableEditor.moveFocus(1, 0, opts) }, - 'Ctrl-K Ctrl-I' : () => { this.tableEditor.insertRow(opts) }, - 'Cmd-K Cmd-I' : () => { this.tableEditor.insertRow(opts) }, - 'Ctrl-L Ctrl-I' : () => { this.tableEditor.deleteRow(opts) }, - 'Cmd-L Cmd-I' : () => { this.tableEditor.deleteRow(opts) }, - 'Ctrl-K Ctrl-J' : () => { this.tableEditor.insertColumn(opts) }, - 'Cmd-K Cmd-J' : () => { this.tableEditor.insertColumn(opts) }, - 'Ctrl-L Ctrl-J' : () => { this.tableEditor.deleteColumn(opts) }, - 'Cmd-L Cmd-J' : () => { this.tableEditor.deleteColumn(opts) }, - 'Alt-Shift-Ctrl-Left' : () => { this.tableEditor.moveColumn(-1, opts) }, - 'Alt-Shift-Cmd-Left' : () => { this.tableEditor.moveColumn(-1, opts) }, + 'Tab': () => { this.tableEditor.nextCell(opts) }, + 'Shift-Tab': () => { this.tableEditor.previousCell(opts) }, + 'Enter': () => { this.tableEditor.nextRow(opts) }, + 'Ctrl-Enter': () => { this.tableEditor.escape(opts) }, + 'Cmd-Enter': () => { this.tableEditor.escape(opts) }, + 'Shift-Ctrl-Left': () => { this.tableEditor.alignColumn(Alignment.LEFT, opts) }, + 'Shift-Cmd-Left': () => { this.tableEditor.alignColumn(Alignment.LEFT, opts) }, + 'Shift-Ctrl-Right': () => { this.tableEditor.alignColumn(Alignment.RIGHT, opts) }, + 'Shift-Cmd-Right': () => { this.tableEditor.alignColumn(Alignment.RIGHT, opts) }, + 'Shift-Ctrl-Up': () => { this.tableEditor.alignColumn(Alignment.CENTER, opts) }, + 'Shift-Cmd-Up': () => { this.tableEditor.alignColumn(Alignment.CENTER, opts) }, + 'Shift-Ctrl-Down': () => { this.tableEditor.alignColumn(Alignment.NONE, opts) }, + 'Shift-Cmd-Down': () => { this.tableEditor.alignColumn(Alignment.NONE, opts) }, + 'Ctrl-Left': () => { this.tableEditor.moveFocus(0, -1, opts) }, + 'Cmd-Left': () => { this.tableEditor.moveFocus(0, -1, opts) }, + 'Ctrl-Right': () => { this.tableEditor.moveFocus(0, 1, opts) }, + 'Cmd-Right': () => { this.tableEditor.moveFocus(0, 1, opts) }, + 'Ctrl-Up': () => { this.tableEditor.moveFocus(-1, 0, opts) }, + 'Cmd-Up': () => { this.tableEditor.moveFocus(-1, 0, opts) }, + 'Ctrl-Down': () => { this.tableEditor.moveFocus(1, 0, opts) }, + 'Cmd-Down': () => { this.tableEditor.moveFocus(1, 0, opts) }, + 'Ctrl-K Ctrl-I': () => { this.tableEditor.insertRow(opts) }, + 'Cmd-K Cmd-I': () => { this.tableEditor.insertRow(opts) }, + 'Ctrl-L Ctrl-I': () => { this.tableEditor.deleteRow(opts) }, + 'Cmd-L Cmd-I': () => { this.tableEditor.deleteRow(opts) }, + 'Ctrl-K Ctrl-J': () => { this.tableEditor.insertColumn(opts) }, + 'Cmd-K Cmd-J': () => { this.tableEditor.insertColumn(opts) }, + 'Ctrl-L Ctrl-J': () => { this.tableEditor.deleteColumn(opts) }, + 'Cmd-L Cmd-J': () => { this.tableEditor.deleteColumn(opts) }, + 'Alt-Shift-Ctrl-Left': () => { this.tableEditor.moveColumn(-1, opts) }, + 'Alt-Shift-Cmd-Left': () => { this.tableEditor.moveColumn(-1, opts) }, 'Alt-Shift-Ctrl-Right': () => { this.tableEditor.moveColumn(1, opts) }, - 'Alt-Shift-Cmd-Right' : () => { this.tableEditor.moveColumn(1, opts) }, - 'Alt-Shift-Ctrl-Up' : () => { this.tableEditor.moveRow(-1, opts) }, - 'Alt-Shift-Cmd-Up' : () => { this.tableEditor.moveRow(-1, opts) }, - 'Alt-Shift-Ctrl-Down' : () => { this.tableEditor.moveRow(1, opts) }, - 'Alt-Shift-Cmd-Down' : () => { this.tableEditor.moveRow(1, opts) } + 'Alt-Shift-Cmd-Right': () => { this.tableEditor.moveColumn(1, opts) }, + 'Alt-Shift-Ctrl-Up': () => { this.tableEditor.moveRow(-1, opts) }, + 'Alt-Shift-Cmd-Up': () => { this.tableEditor.moveRow(-1, opts) }, + 'Alt-Shift-Ctrl-Down': () => { this.tableEditor.moveRow(1, opts) }, + 'Alt-Shift-Cmd-Down': () => { this.tableEditor.moveRow(1, opts) } }) - + const updateActiveState = () => { const active = this.tableEditor.cursorIsInTable(opts) if (active) { @@ -276,7 +276,7 @@ export default class CodeEditor extends React.Component { this.tableEditor.resetSmartCursor() } } - + this.editor.on('cursorActivity', () => { if (!editorIntf.transaction) { updateActiveState() diff --git a/browser/components/MarkdownEditor.js b/browser/components/MarkdownEditor.js index a9a041df..c9db7eff 100644 --- a/browser/components/MarkdownEditor.js +++ b/browser/components/MarkdownEditor.js @@ -235,7 +235,7 @@ class MarkdownEditor extends React.Component { if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none' const storage = findStorage(storageKey) - + return (
lastRow) { const lastLine = this.getLine(lastRow) @@ -47,8 +47,7 @@ export default class TextEditorInterface { { line: lastRow, ch: lastLine.length }, { line: lastRow, ch: lastLine.length } ) - } - else { + } else { this.doc.replaceRange( line + '\n', { line: row, ch: 0 }, @@ -57,7 +56,7 @@ export default class TextEditorInterface { } } - deleteLine(row) { + deleteLine (row) { const lastRow = this.getLastRow() if (row >= lastRow) { if (lastRow > 0) { @@ -68,8 +67,7 @@ export default class TextEditorInterface { { line: lastRow - 1, ch: preLastLine.length }, { line: lastRow, ch: lastLine.length } ) - } - else { + } else { const lastLine = this.getLine(lastRow) this.doc.replaceRange( '', @@ -77,8 +75,7 @@ export default class TextEditorInterface { { line: lastRow, ch: lastLine.length } ) } - } - else { + } else { this.doc.replaceRange( '', { line: row, ch: 0 }, @@ -87,7 +84,7 @@ export default class TextEditorInterface { } } - replaceLines(startRow, endRow, lines) { + replaceLines (startRow, endRow, lines) { const lastRow = this.getLastRow() if (endRow > lastRow) { const lastLine = this.getLine(lastRow) @@ -96,8 +93,7 @@ export default class TextEditorInterface { { line: startRow, ch: 0 }, { line: lastRow, ch: lastLine.length } ) - } - else { + } else { this.doc.replaceRange( lines.join('\n') + '\n', { line: startRow, ch: 0 }, @@ -106,7 +102,7 @@ export default class TextEditorInterface { } } - transact(func) { + transact (func) { this.transaction = true func() this.transaction = false diff --git a/browser/main/modals/PreferencesModal/UiTab.js b/browser/main/modals/PreferencesModal/UiTab.js index f8a9ab11..6fda7c3a 100644 --- a/browser/main/modals/PreferencesModal/UiTab.js +++ b/browser/main/modals/PreferencesModal/UiTab.js @@ -420,7 +420,7 @@ class UiTab extends React.Component { {i18n.__('Bring in web page title when pasting URL on editor')}
- +
+
+
+ {i18n.__('Default New Note')} +
+
+ +
+
+