From 7804a229844c372f875ab498fb937744500bdbac Mon Sep 17 00:00:00 2001 From: Maciek Date: Fri, 10 Aug 2018 21:39:59 +0200 Subject: [PATCH 01/44] 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 02/44] 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 03/44] 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 04/44] 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 5bb90babbcd06a71c0b9aad58ad9f830ff265b0c Mon Sep 17 00:00:00 2001 From: Maciek Date: Tue, 21 Aug 2018 00:02:25 +0200 Subject: [PATCH 05/44] Add tests for Markdown TOC generator --- tests/lib/markdown-toc-generator-test.js | 444 +++++++++++++++++++++++ 1 file changed, 444 insertions(+) create mode 100644 tests/lib/markdown-toc-generator-test.js diff --git a/tests/lib/markdown-toc-generator-test.js b/tests/lib/markdown-toc-generator-test.js new file mode 100644 index 00000000..9fcc1d8d --- /dev/null +++ b/tests/lib/markdown-toc-generator-test.js @@ -0,0 +1,444 @@ +/** + * @fileoverview Unit test for browser/lib/markdown-toc-generator + */ +const test = require('ava') +const markdownToc = require('browser/lib/markdown-toc-generator') +const EOL = require('os').EOL + +test(t => { + /** + * @testCases Contains array of test cases in format : + * [ + * test title + * input markdown, + * expected output markdown with toc + * ] + * + */ + const testCases = [ + [ + '***************************** empty note', + ` + `, + ` + + + + + + ` + ], + [ + '***************************** single level', + ` +# one + `, + ` + + +- [one](#one) + + + +# one + ` + ], + [ + '***************************** two levels', + ` +# one +# two + `, + ` + + +- [one](#one) +- [two](#two) + + + +# one +# two + ` + ], + [ + '***************************** 3 levels with children', + ` +# one +## one one +# two +## two two +# three +## three three + `, + ` + + +- [one](#one) + * [one one](#one-one) +- [two](#two) + * [two two](#two-two) +- [three](#three) + * [three three](#three-three) + + + +# one +## one one +# two +## two two +# three +## three three + ` + ], + [ + '***************************** 3 levels, 3rd with 6 sub-levels', + ` +# one +## one one +# two +## two two +# three +## three three +### three three three +#### three three three three +##### three three three three three +###### three three three three three three + `, + ` + + +- [one](#one) + * [one one](#one-one) +- [two](#two) + * [two two](#two-two) +- [three](#three) + * [three three](#three-three) + + [three three three](#three-three-three) + - [three three three three](#three-three-three-three) + * [three three three three three](#three-three-three-three-three) + + [three three three three three three](#three-three-three-three-three-three) + + + +# one +## one one +# two +## two two +# three +## three three +### three three three +#### three three three three +##### three three three three three +###### three three three three three three + ` + ], + [ + '***************************** multilevel with texts in between', + ` +# one +this is a level one text +this is a level one text +## one one +# two + this is a level two text + this is a level two text +## two two + this is a level two two text + this is a level two two text +# three + this is a level three three text + this is a level three three text +## three three + this is a text + this is a text +### three three three + this is a text + this is a text +### three three three 2 + this is a text + this is a text +#### three three three three + this is a text + this is a text +#### three three three three 2 + this is a text + this is a text +##### three three three three three + this is a text + this is a text +##### three three three three three 2 + this is a text + this is a text +###### three three three three three three + this is a text + this is a text + this is a text + `, + ` + + +- [one](#one) + * [one one](#one-one) +- [two](#two) + * [two two](#two-two) +- [three](#three) + * [three three](#three-three) + + [three three three](#three-three-three) + + [three three three 2](#three-three-three-2) + - [three three three three](#three-three-three-three) + - [three three three three 2](#three-three-three-three-2) + * [three three three three three](#three-three-three-three-three) + * [three three three three three 2](#three-three-three-three-three-2) + + [three three three three three three](#three-three-three-three-three-three) + + + +# one +this is a level one text +this is a level one text +## one one +# two + this is a level two text + this is a level two text +## two two + this is a level two two text + this is a level two two text +# three + this is a level three three text + this is a level three three text +## three three + this is a text + this is a text +### three three three + this is a text + this is a text +### three three three 2 + this is a text + this is a text +#### three three three three + this is a text + this is a text +#### three three three three 2 + this is a text + this is a text +##### three three three three three + this is a text + this is a text +##### three three three three three 2 + this is a text + this is a text +###### three three three three three three + this is a text + this is a text + this is a text + ` + ], + [ + '***************************** already generated toc', + ` + + +- [one](#one) + * [one one](#one-one) +- [two](#two) + * [two two](#two-two) +- [three](#three) + * [three three](#three-three) + + [three three three](#three-three-three) + - [three three three three](#three-three-three-three) + * [three three three three three](#three-three-three-three-three) + + [three three three three three three](#three-three-three-three-three-three) + + + +# one +## one one +# two +## two two +# three +## three three +### three three three +#### three three three three +##### three three three three three +###### three three three three three three + `, + ` + + +- [one](#one) + * [one one](#one-one) +- [two](#two) + * [two two](#two-two) +- [three](#three) + * [three three](#three-three) + + [three three three](#three-three-three) + - [three three three three](#three-three-three-three) + * [three three three three three](#three-three-three-three-three) + + [three three three three three three](#three-three-three-three-three-three) + + + +# one +## one one +# two +## two two +# three +## three three +### three three three +#### three three three three +##### three three three three three +###### three three three three three three + ` + ], + [ + '***************************** note with just an opening TOC marker', + ` + + + +# one +## one one + + `, + ` + + +- [one](#one) + * [one one](#one-one) + + + +# one +## one one + ` + ], + [ + '***************************** note with just a closing TOC marker', + ` + + +# one +## one one + `, + ` + + +- [one](#one) + * [one one](#one-one) + + + +# one +## one one + + ` + ], + + [ + '***************************** outdated TOC', + ` + + +- [one](#one) + * [one one](#one-one) + + + +# one modified +## one one + + `, + ` + + +- [one modified](#one-modified) + * [one one](#one-one) + + + +# one modified +## one one + ` + ], + [ + '***************************** properly generated case sensitive TOC', + ` +# onE +## oNe one + `, + ` + + +- [onE](#onE) + * [oNe one](#oNe-one) + + + +# onE +## oNe one + ` + ], + [ + '***************************** position of TOC is stable (do not use elements above toc marker)', + ` +# title + +this is a text + + + +- [onE](#onE) + * [oNe one](#oNe-one) + + + +# onE +## oNe one + `, + ` +# title + +this is a text + + + +- [onE](#onE) + * [oNe one](#oNe-one) + + + +# onE +## oNe one + ` + ], + [ + '***************************** properly handle generation of not completed TOC', + ` +# hoge + +## + `, + ` + + +- [hoge](#hoge) + + + +# hoge + +## + ` + ] + ] + + testCases.forEach(testCase => { + const title = testCase[0] + const inputMd = testCase[1].trim() + const expectedOutput = testCase[2].trim() + let generatedOutput + markdownToc.generate(inputMd, (o) => { generatedOutput = o.trim() }) + t.is(generatedOutput, expectedOutput, `Test ${title} , generated : ${EOL}${generatedOutput}, expected : ${EOL}${expectedOutput}`) + }) +}) From 00b4874d098e0151dd1db553d653b3754886be2b Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Tue, 21 Aug 2018 10:57:25 +0200 Subject: [PATCH 06/44] add tag autocomplete --- browser/main/Detail/MarkdownNoteDetail.js | 1 + browser/main/Detail/NoteDetailInfo.styl | 1 + browser/main/Detail/TagSelect.js | 19 +++- browser/main/Detail/TagSelect.styl | 3 + browser/main/global.styl | 2 + browser/styles/awesomplete.styl | 114 ++++++++++++++++++++++ lib/main.html | 2 + package.json | 1 + yarn.lock | 4 + 9 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 browser/styles/awesomplete.styl diff --git a/browser/main/Detail/MarkdownNoteDetail.js b/browser/main/Detail/MarkdownNoteDetail.js index 82073162..9aca81be 100755 --- a/browser/main/Detail/MarkdownNoteDetail.js +++ b/browser/main/Detail/MarkdownNoteDetail.js @@ -363,6 +363,7 @@ class MarkdownNoteDetail extends React.Component { diff --git a/browser/main/Detail/NoteDetailInfo.styl b/browser/main/Detail/NoteDetailInfo.styl index 8d454203..7166a497 100644 --- a/browser/main/Detail/NoteDetailInfo.styl +++ b/browser/main/Detail/NoteDetailInfo.styl @@ -13,6 +13,7 @@ $info-margin-under-border = 30px display flex align-items center padding 0 20px + z-index 99 .info-left padding 0 10px diff --git a/browser/main/Detail/TagSelect.js b/browser/main/Detail/TagSelect.js index e251dd42..2ba26336 100644 --- a/browser/main/Detail/TagSelect.js +++ b/browser/main/Detail/TagSelect.js @@ -20,6 +20,12 @@ class TagSelect extends React.Component { componentDidMount () { this.value = this.props.value ee.on('editor:add-tag', this.addtagHandler) + + new Awesomplete(this.refs.newTag, { + minChars: 1, + autoFirst: true, + list: '#datalist' + }) } componentDidUpdate () { @@ -119,7 +125,7 @@ class TagSelect extends React.Component { } render () { - const { value, className } = this.props + const { value, className, data } = this.props const tagList = _.isArray(value) ? value.map((tag) => { @@ -137,6 +143,14 @@ class TagSelect extends React.Component { ) }) : [] + + const datalist = _.sortBy(data.tagNoteMap.map( + (tag, name) => ({ name, size: tag.size }) + ).filter( + tag => tag.size > 0 + ), ['name']).map( + tag =>
  • {tag.name}
  • + ) return (
    this.handleNewTagInputKeyDown(e)} onBlur={(e) => this.handleNewTagBlur(e)} /> +
      + {datalist} +
    ) } diff --git a/browser/main/Detail/TagSelect.styl b/browser/main/Detail/TagSelect.styl index 0ff4c6a3..052c86b6 100644 --- a/browser/main/Detail/TagSelect.styl +++ b/browser/main/Detail/TagSelect.styl @@ -50,6 +50,9 @@ padding 0 4px font-size 13px +.datalist + display none + body[data-theme="dark"] .tag background-color alpha($ui-dark-tag-backgroundColor, 60%) diff --git a/browser/main/global.styl b/browser/main/global.styl index e4505a4e..6046861a 100644 --- a/browser/main/global.styl +++ b/browser/main/global.styl @@ -156,3 +156,5 @@ body[data-theme="monokai"] body[data-theme="default"] .SideNav ::-webkit-scrollbar-thumb background-color rgba(255, 255, 255, 0.3) + +@import '../styles/awesomplete.styl' \ No newline at end of file diff --git a/browser/styles/awesomplete.styl b/browser/styles/awesomplete.styl new file mode 100644 index 00000000..738fb379 --- /dev/null +++ b/browser/styles/awesomplete.styl @@ -0,0 +1,114 @@ +.awesomplete + display inline-block + position relative + + .visually-hidden + position absolute + clip rect(0, 0, 0, 0) + + ul + position fixed + z-index 1 + box-sizing border-box + list-style none + padding 0 + margin 0 + + border-radius 4px + margin .2em 0 0 + background-color $ui-noteList-backgroundColor + border 1px solid rgba(0,0,0,.3) + box-shadow .05em .2em .6em rgba(0,0,0,.2) + text-shadow none + + &:empty, + &[hidden] + display none + + &:before + content "" + position absolute + top -.43em + left 1em + width 0 height 0 + padding .4em + background-color $ui-noteList-backgroundColor + border inherit + border-right 0 + border-bottom 0 + -webkit-transform rotate(45deg) + transform rotate(45deg) + + li + position relative + padding 6px 18px 6px 10px + cursor pointer + + li:hover + background-color alpha($ui-button--active-backgroundColor, 20%) + color $ui-text-color + + li[aria-selected="true"] + background-color alpha($ui-button--active-backgroundColor, 40%) + color $ui-text-color + +body[data-theme="dark"] + .awesomplete ul + border-color $ui-dark-borderColor + background-color $ui-dark-noteList-backgroundColor + color $ui-dark-text-color + + &:before + background-color $ui-dark-noteList-backgroundColor + + li:hover + background-color alpha($ui-dark-button--active-backgroundColor, 20%) + color $ui-dark-text-color + + li[aria-selected="true"] + background-color $ui-dark-button--active-backgroundColor + color $ui-dark-text-color + +body[data-theme="white"] + .awesomplete ul + background-color $ui-white-noteList-backgroundColor + + li:hover + background-color alpha($ui-button--active-backgroundColor, 60%) + + li[aria-selected="true"] + background-color $ui-button--active-backgroundColor + +body[data-theme="solarized-dark"] + .awesomplete ul + border-color $ui-solarized-dark-borderColor + background-color $ui-solarized-dark-noteList-backgroundColor + color $ui-solarized-dark-text-color + + &:before + background-color $ui-solarized-dark-noteList-backgroundColor + + li:hover + background-color alpha($ui-dark-button--active-backgroundColor, 20%) + color $ui-solarized-dark-text-color + + li[aria-selected="true"] + background-color $ui-dark-button--active-backgroundColor + color $ui-solarized-dark-text-color + +body[data-theme="monokai"] + .awesomplete ul + border-color $ui-monokai-borderColor + background-color $ui-monokai-noteList-backgroundColor + color $ui-monokai-text-color + + &:before + background-color $ui-dark-noteList-backgroundColor + + li:hover + background-color alpha($ui-dark-button--active-backgroundColor, 20%) + color $ui-monokai-text-color + + li[aria-selected="true"] + background-color $ui-monokai-button-backgroundColor + color $ui-monokai-text-color \ No newline at end of file diff --git a/lib/main.html b/lib/main.html index 7366fa04..3c2d33eb 100644 --- a/lib/main.html +++ b/lib/main.html @@ -118,6 +118,8 @@ + + diff --git a/package.json b/package.json index e9949adf..4ca816e1 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "@rokt33r/markdown-it-math": "^4.0.1", "@rokt33r/season": "^5.3.0", "@susisu/mte-kernel": "^2.0.0", + "awesomplete": "^1.1.2", "aws-sdk": "^2.48.0", "aws-sdk-mobile-analytics": "^0.9.2", "chart.js": "^2.7.2", diff --git a/yarn.lock b/yarn.lock index 4ecfa51b..02207b14 100644 --- a/yarn.lock +++ b/yarn.lock @@ -568,6 +568,10 @@ ava@^0.25.0: unique-temp-dir "^1.0.0" update-notifier "^2.3.0" +awesomplete@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/awesomplete/-/awesomplete-1.1.2.tgz#b6e253f73474e46278bba5ae7f81d4262160fb75" + aws-sdk-mobile-analytics@^0.9.2: version "0.9.2" resolved "https://registry.yarnpkg.com/aws-sdk-mobile-analytics/-/aws-sdk-mobile-analytics-0.9.2.tgz#b56a6e5206fc8c3975a19170b41536c53f6d5d91" From 13d44ae56a53a92eaf6b1260584807ff1f265e72 Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Tue, 21 Aug 2018 11:19:46 +0200 Subject: [PATCH 07/44] fix lint errors --- browser/main/Detail/TagSelect.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/browser/main/Detail/TagSelect.js b/browser/main/Detail/TagSelect.js index 2ba26336..325b51e6 100644 --- a/browser/main/Detail/TagSelect.js +++ b/browser/main/Detail/TagSelect.js @@ -20,8 +20,8 @@ class TagSelect extends React.Component { componentDidMount () { this.value = this.props.value ee.on('editor:add-tag', this.addtagHandler) - - new Awesomplete(this.refs.newTag, { + + const awesomplete = new Awesomplete(this.refs.newTag, { minChars: 1, autoFirst: true, list: '#datalist' @@ -143,13 +143,13 @@ class TagSelect extends React.Component { ) }) : [] - + const datalist = _.sortBy(data.tagNoteMap.map( (tag, name) => ({ name, size: tag.size }) ).filter( tag => tag.size > 0 ), ['name']).map( - tag =>
  • {tag.name}
  • + tag =>
  • {tag.name}
  • ) return ( From f195e87568cf028ba99aae6588373a21f6d964e2 Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Tue, 21 Aug 2018 11:52:46 +0200 Subject: [PATCH 08/44] update background color of markers --- browser/styles/awesomplete.styl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/browser/styles/awesomplete.styl b/browser/styles/awesomplete.styl index 738fb379..f46037a3 100644 --- a/browser/styles/awesomplete.styl +++ b/browser/styles/awesomplete.styl @@ -51,6 +51,9 @@ li[aria-selected="true"] background-color alpha($ui-button--active-backgroundColor, 40%) color $ui-text-color + + mark + background-color rgba(255, 255, 0, 0.8) body[data-theme="dark"] .awesomplete ul From 07e810a23181ea1b82271389a44793b919b5bbcf Mon Sep 17 00:00:00 2001 From: Mika Andrianarijaona Date: Wed, 22 Aug 2018 10:58:53 +0200 Subject: [PATCH 09/44] init touchbar menu --- lib/main-app.js | 2 ++ lib/touchbar-menu.js | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 lib/touchbar-menu.js diff --git a/lib/main-app.js b/lib/main-app.js index 1f3f1320..1ab9f4ca 100644 --- a/lib/main-app.js +++ b/lib/main-app.js @@ -78,9 +78,11 @@ app.on('ready', function () { var template = require('./main-menu') var menu = Menu.buildFromTemplate(template) + var touchBarMenu = require('./touchbar-menu') switch (process.platform) { case 'darwin': Menu.setApplicationMenu(menu) + mainWindow.setTouchBar(touchBarMenu) break case 'win32': mainWindow.setMenu(menu) diff --git a/lib/touchbar-menu.js b/lib/touchbar-menu.js new file mode 100644 index 00000000..bb7ae79a --- /dev/null +++ b/lib/touchbar-menu.js @@ -0,0 +1,26 @@ +const {TouchBar} = require('electron') +const {TouchBarButton, TouchBarSpacer} = TouchBar + +const allNotes = new TouchBarButton({ + label: '📒', + click: () => {} +}) + +const starredNotes = new TouchBarButton({ + label: '⭐️', + click: () => {} +}) + +const trash = new TouchBarButton({ + label: '🗑', + click: () => {} +}) + +module.exports = new TouchBar([ + allNotes, + new TouchBarSpacer({size: 'small'}), + starredNotes, + new TouchBarSpacer({size: 'small'}), + trash +]) + From 64d4cd84afdc644ecd25200600b23c037ccd8a89 Mon Sep 17 00:00:00 2001 From: Mika Andrianarijaona Date: Wed, 22 Aug 2018 18:09:55 +0200 Subject: [PATCH 10/44] send events when user click touchbar buttons --- browser/main/NoteList/index.js | 12 ++++++++++++ lib/touchbar-menu.js | 24 ++++++++++++++++++++---- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/browser/main/NoteList/index.js b/browser/main/NoteList/index.js index f7dd0764..880f8479 100644 --- a/browser/main/NoteList/index.js +++ b/browser/main/NoteList/index.js @@ -80,6 +80,7 @@ class NoteList extends React.Component { this.getViewType = this.getViewType.bind(this) this.restoreNote = this.restoreNote.bind(this) this.copyNoteLink = this.copyNoteLink.bind(this) + this.navigate = this.navigate.bind(this) // TODO: not Selected noteKeys but SelectedNote(for reusing) this.state = { @@ -98,6 +99,7 @@ class NoteList extends React.Component { ee.on('list:isMarkdownNote', this.alertIfSnippetHandler) ee.on('import:file', this.importFromFileHandler) ee.on('list:jump', this.jumpNoteByHash) + ee.on('list:navigate', this.navigate) } componentWillReceiveProps (nextProps) { @@ -687,6 +689,16 @@ class NoteList extends React.Component { return copy(noteLink) } + navigate (sender, pathname) { + const { router } = this.context + router.push({ + pathname, + query: { + // key: noteKey + } + }) + } + save (note) { const { dispatch } = this.props dataApi diff --git a/lib/touchbar-menu.js b/lib/touchbar-menu.js index bb7ae79a..fa2fae9e 100644 --- a/lib/touchbar-menu.js +++ b/lib/touchbar-menu.js @@ -1,19 +1,33 @@ const {TouchBar} = require('electron') const {TouchBarButton, TouchBarSpacer} = TouchBar +const mainWindow = require('./main-window') const allNotes = new TouchBarButton({ label: '📒', - click: () => {} + click: () => { + mainWindow.webContents.send('list:navigate', '/home') + } }) const starredNotes = new TouchBarButton({ label: '⭐️', - click: () => {} + click: () => { + mainWindow.webContents.send('list:navigate', '/starred') + } }) const trash = new TouchBarButton({ label: '🗑', - click: () => {} + click: () => { + mainWindow.webContents.send('list:navigate', '/trashed') + } +}) + +const newNote = new TouchBarButton({ + label: '✎', + click: () => { + mainWindow.webContents.send('top:new-note') + } }) module.exports = new TouchBar([ @@ -21,6 +35,8 @@ module.exports = new TouchBar([ new TouchBarSpacer({size: 'small'}), starredNotes, new TouchBarSpacer({size: 'small'}), - trash + trash, + new TouchBarSpacer({size: 'large'}), + newNote ]) From a19c13eb3c4bac65386073a097877e13b8f4b8f5 Mon Sep 17 00:00:00 2001 From: Mika Andrianarijaona Date: Wed, 22 Aug 2018 18:14:31 +0200 Subject: [PATCH 11/44] remove unused spacing --- lib/touchbar-menu.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/touchbar-menu.js b/lib/touchbar-menu.js index fa2fae9e..90a64410 100644 --- a/lib/touchbar-menu.js +++ b/lib/touchbar-menu.js @@ -26,17 +26,16 @@ const trash = new TouchBarButton({ const newNote = new TouchBarButton({ label: '✎', click: () => { + mainWindow.webContents.send('list:navigate', '/home') mainWindow.webContents.send('top:new-note') } }) module.exports = new TouchBar([ allNotes, - new TouchBarSpacer({size: 'small'}), starredNotes, - new TouchBarSpacer({size: 'small'}), trash, - new TouchBarSpacer({size: 'large'}), + new TouchBarSpacer({size: 'small'}), newNote ]) From 2f7b62f7101b0dd95c89a737ec59193b521fefcb Mon Sep 17 00:00:00 2001 From: owazae <39860768+owazae@users.noreply.github.com> Date: Thu, 23 Aug 2018 14:48:33 +0200 Subject: [PATCH 12/44] Update contributing.md Adding french translation --- contributing.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/contributing.md b/contributing.md index 867d4161..8260837b 100644 --- a/contributing.md +++ b/contributing.md @@ -86,4 +86,23 @@ Pull requestをすることはその変化分のコードの著作権をBoostIO 如果您提供了一个Pull Request,这表示您将您所修改的代码的著作权移交给BoostIO。 这并不表示Boostnote会成为一个需要付费的软件。如果我们想获得收益,我们会尝试一些其他的方法,比如说云存储、绑定手机软件等。 -因为GPLv3过于严格,不能和其他的一些协议兼容,所以我们有可能在将来会把BoostNote的协议改为一些较为宽松的协议,比如说BSD、MIT。 +因为GPLv3过于严格,不能和其他的一些协议兼容,所以我们有可能在将来会把BoostNote的协议改为一些较为宽松的协议,比如说BSD、MIT。 + +--- + +# Contributing to Boostnote (Français) + +### Lorsque vous signalez un problème ou un bug +Il n'y a pas de modèle pour un signaler problème. Mais nous vous demandons : + +**Merci de founir une capture d'écran de Boostnote avec l'outil de développement ouvert** +(vous pouvez l'ouvrir avec `Ctrl+Shift+I`) + +Merci en avance pour votre aide. + +### À propos des droits d'auteurs et des requêtes (`Pull Request`) + +Si vous faites une requête, vous acceptez de transmettre les modifications du code à BoostIO. + +Cela ne veut pas dire que Boostnote deviendra une application payante. Si nous voulons gagner de l'argent, nous trouverons un autre moyen, comme un service de sauvegarde sur le Cloud, une application mobile ou des options payantes. +Puisque GPL v3 est trop strict pour être compatible avec n'importe quelle autre licence, nous pensons avoir un jour besoin de la remplacer avec une licence bien plus libre (comme BSD, MIT). From 5c8254a9c4390a61b12564d1229fb689f6852dbf Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Fri, 24 Aug 2018 23:48:26 +0200 Subject: [PATCH 13/44] fixing bug #2321 due to the unescaped characters `#` or `?` in the route --- browser/main/SideNav/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/browser/main/SideNav/index.js b/browser/main/SideNav/index.js index c4fa417b..977a8fb5 100644 --- a/browser/main/SideNav/index.js +++ b/browser/main/SideNav/index.js @@ -198,12 +198,12 @@ class SideNav extends React.Component { const tags = pathSegments[pathSegments.length - 1] return (tags === 'alltags') ? [] - : tags.split(' ') + : tags.split(' ').map(tag => decodeURIComponent(tag)) } handleClickTagListItem (name) { const { router } = this.context - router.push(`/tags/${name}`) + router.push(`/tags/${encodeURIComponent(name)}`) } handleSortTagsByChange (e) { @@ -230,7 +230,7 @@ class SideNav extends React.Component { } else { listOfTags.push(tag) } - router.push(`/tags/${listOfTags.join(' ')}`) + router.push(`/tags/${listOfTags.map(tag => encodeURIComponent(tag)).join(' ')}`) } emptyTrash (entries) { From ede733888d7bd05e41c0fc6e6e92ad32488b01d7 Mon Sep 17 00:00:00 2001 From: Maciek Date: Sat, 25 Aug 2018 17:59:04 +0200 Subject: [PATCH 14/44] Code style: remove redundant brackets from lambda expression --- browser/main/Detail/MarkdownNoteDetail.js | 2 +- browser/main/Detail/SnippetNoteDetail.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/browser/main/Detail/MarkdownNoteDetail.js b/browser/main/Detail/MarkdownNoteDetail.js index 11197838..b0cdbb65 100755 --- a/browser/main/Detail/MarkdownNoteDetail.js +++ b/browser/main/Detail/MarkdownNoteDetail.js @@ -268,7 +268,7 @@ class MarkdownNoteDetail extends React.Component { handleGenerateToc () { markdownToc.generate(this.refs.content.value, - (modifiedValue) => { this.refs.content.refs.code.setValue(modifiedValue) }) + (modifiedValue) => this.refs.content.refs.code.setValue(modifiedValue)) } handleFocus (e) { diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index ce3d03e2..f4459f84 100644 --- a/browser/main/Detail/SnippetNoteDetail.js +++ b/browser/main/Detail/SnippetNoteDetail.js @@ -102,7 +102,7 @@ class SnippetNoteDetail extends React.Component { if (currentMode.includes('Markdown')) { 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) }) + markdownToc.generate(currentValue, (modifiedValue) => currentEditor.setValue(modifiedValue)) } } From 53923c9c873cb8bf70b3dd98b4bf8297ab8542eb Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Sat, 25 Aug 2018 18:22:40 +0200 Subject: [PATCH 15/44] filtering out the note's tags --- browser/main/Detail/TagSelect.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/browser/main/Detail/TagSelect.js b/browser/main/Detail/TagSelect.js index 325b51e6..17494b93 100644 --- a/browser/main/Detail/TagSelect.js +++ b/browser/main/Detail/TagSelect.js @@ -21,10 +21,11 @@ class TagSelect extends React.Component { this.value = this.props.value ee.on('editor:add-tag', this.addtagHandler) - const awesomplete = new Awesomplete(this.refs.newTag, { + this.awesomplete = new Awesomplete(this.refs.newTag, { minChars: 1, autoFirst: true, - list: '#datalist' + list: '#datalist', + filter: (text, input) => !_.includes(this.value, text.value) && Awesomplete.FILTER_CONTAINS(text, input) }) } @@ -34,6 +35,8 @@ class TagSelect extends React.Component { componentWillUnmount () { ee.off('editor:add-tag', this.addtagHandler) + + this.awesomplete.destroy() } handleAddTag () { From aa0566b8ca2aae8bc64f30e6a844cc8683477f5a Mon Sep 17 00:00:00 2001 From: zhoufeng1989 Date: Sun, 26 Aug 2018 21:15:20 +1200 Subject: [PATCH 16/44] Update test cases for export storage, check content of exported notes. --- tests/dataApi/exportStorage-test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/dataApi/exportStorage-test.js b/tests/dataApi/exportStorage-test.js index 1ee98328..e5594329 100644 --- a/tests/dataApi/exportStorage-test.js +++ b/tests/dataApi/exportStorage-test.js @@ -37,6 +37,7 @@ test.serial('Export a storage', t => { const noteDir = path.join(exportDir, folderKeyToName[note.folder], `${note.title}.md`) if (note.type === 'MARKDOWN_NOTE') { t.true(fs.existsSync(noteDir)) + t.is(fs.readFileSync(noteDir, 'utf8'), note.content) } else if (note.type === 'SNIPPET_NOTE') { t.false(fs.existsSync(noteDir)) } From fa9d8b8881eea06263268a93e15d91a74faaa0df Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Sun, 26 Aug 2018 15:48:41 +0200 Subject: [PATCH 17/44] replace `awesomplete` with React component `react-autosuggest` --- browser/main/Detail/TagSelect.js | 220 ++++++++++++++++----------- browser/main/Detail/TagSelect.styl | 28 +--- browser/main/global.styl | 2 +- browser/styles/Detail/TagSelect.styl | 109 +++++++++++++ browser/styles/awesomplete.styl | 117 -------------- lib/main.html | 2 - package.json | 2 +- yarn.lock | 38 ++++- 8 files changed, 280 insertions(+), 238 deletions(-) create mode 100644 browser/styles/Detail/TagSelect.styl delete mode 100644 browser/styles/awesomplete.styl diff --git a/browser/main/Detail/TagSelect.js b/browser/main/Detail/TagSelect.js index 17494b93..e01d503b 100644 --- a/browser/main/Detail/TagSelect.js +++ b/browser/main/Detail/TagSelect.js @@ -6,80 +6,33 @@ import _ from 'lodash' import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' import i18n from 'browser/lib/i18n' import ee from 'browser/main/lib/eventEmitter' +import Autosuggest from 'react-autosuggest' class TagSelect extends React.Component { constructor (props) { super(props) this.state = { - newTag: '' + newTag: '', + suggestions: [] } - this.addtagHandler = this.handleAddTag.bind(this) + + this.handleAddTag = this.handleAddTag.bind(this) + this.onInputBlur = this.onInputBlur.bind(this) + this.onInputChange = this.onInputChange.bind(this) + this.onInputKeyDown = this.onInputKeyDown.bind(this) + this.onSuggestionsClearRequested = this.onSuggestionsClearRequested.bind(this) + this.onSuggestionsFetchRequested = this.onSuggestionsFetchRequested.bind(this) + this.onSuggestionSelected = this.onSuggestionSelected.bind(this) } - componentDidMount () { - this.value = this.props.value - ee.on('editor:add-tag', this.addtagHandler) - - this.awesomplete = new Awesomplete(this.refs.newTag, { - minChars: 1, - autoFirst: true, - list: '#datalist', - filter: (text, input) => !_.includes(this.value, text.value) && Awesomplete.FILTER_CONTAINS(text, input) - }) - } - - componentDidUpdate () { - this.value = this.props.value - } - - componentWillUnmount () { - ee.off('editor:add-tag', this.addtagHandler) - - this.awesomplete.destroy() - } - - handleAddTag () { - this.refs.newTag.focus() - } - - handleNewTagInputKeyDown (e) { - switch (e.keyCode) { - case 9: - e.preventDefault() - this.submitTag() - break - case 13: - this.submitTag() - break - case 8: - if (this.refs.newTag.value.length === 0) { - this.removeLastTag() - } - } - } - - handleNewTagBlur (e) { - this.submitTag() - } - - removeLastTag () { - this.removeTagByCallback((value) => { - value.pop() - }) - } - - reset () { - this.setState({ - newTag: '' - }) - } - - submitTag () { + addNewTag (newTag) { AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_TAG') - let { value } = this.props - let newTag = this.refs.newTag.value.trim().replace(/ +/g, '_') - newTag = newTag.charAt(0) === '#' ? newTag.substring(1) : newTag + + newTag = newTag.trim().replace(/ +/g, '_') + if (newTag.charAt(0) === '#') { + newTag.substring(1) + } if (newTag.length <= 0) { this.setState({ @@ -88,6 +41,7 @@ class TagSelect extends React.Component { return } + let { value } = this.props value = _.isArray(value) ? value.slice() : [] @@ -102,10 +56,36 @@ class TagSelect extends React.Component { }) } - handleNewTagInputChange (e) { - this.setState({ - newTag: this.refs.newTag.value - }) + buildSuggestions () { + this.suggestions = _.sortBy(this.props.data.tagNoteMap.map( + (tag, name) => ({ + name, + nameLC: name.toLowerCase(), + size: tag.size + }) + ).filter( + tag => tag.size > 0 + ), ['name']) + } + + componentDidMount () { + this.value = this.props.value + + this.buildSuggestions() + + ee.on('editor:add-tag', this.handleAddTag) + } + + componentDidUpdate () { + this.value = this.props.value + } + + componentWillUnmount () { + ee.off('editor:add-tag', this.handleAddTag) + } + + handleAddTag () { + this.refs.newTag.input.focus() } handleTagRemoveButtonClick (tag) { @@ -114,6 +94,60 @@ class TagSelect extends React.Component { }, tag) } + onInputBlur (e) { + this.submitNewTag() + } + + onInputChange (e, { newValue, method }) { + this.setState({ + newTag: newValue + }) + } + + onInputKeyDown (e) { + switch (e.keyCode) { + case 9: + e.preventDefault() + this.submitNewTag() + break + case 13: + this.submitNewTag() + break + case 8: + if (this.state.newTag.length === 0) { + this.removeLastTag() + } + } + } + + onSuggestionsClearRequested () { + this.setState({ + suggestions: [] + }) + } + + onSuggestionsFetchRequested ({ value }) { + const valueLC = value.toLowerCase() + const suggestions = _.filter( + this.suggestions, + tag => !_.includes(this.value, tag.name) && tag.nameLC.indexOf(valueLC) !== -1 + ) + + this.setState({ + suggestions + }) + } + + onSuggestionSelected (event, { suggestion, suggestionValue }) { + this.addNewTag(suggestionValue) + } + + removeLastTag () { + this.removeTagByCallback((value) => { + value.pop() + }) + } + removeTagByCallback (callback, tag = null) { let { value } = this.props @@ -127,8 +161,20 @@ class TagSelect extends React.Component { this.props.onChange() } + reset () { + this.buildSuggestions() + + this.setState({ + newTag: '' + }) + } + + submitNewTag () { + this.addNewTag(this.refs.newTag.input.value) + } + render () { - const { value, className, data } = this.props + const { value, className } = this.props const tagList = _.isArray(value) ? value.map((tag) => { @@ -147,13 +193,7 @@ class TagSelect extends React.Component { }) : [] - const datalist = _.sortBy(data.tagNoteMap.map( - (tag, name) => ({ name, size: tag.size }) - ).filter( - tag => tag.size > 0 - ), ['name']).map( - tag =>
  • {tag.name}
  • - ) + const { newTag, suggestions } = this.state return (
    {tagList} - this.handleNewTagInputChange(e)} - onKeyDown={(e) => this.handleNewTagInputKeyDown(e)} - onBlur={(e) => this.handleNewTagBlur(e)} + suggestions={suggestions} + onSuggestionsFetchRequested={this.onSuggestionsFetchRequested} + onSuggestionsClearRequested={this.onSuggestionsClearRequested} + onSuggestionSelected={this.onSuggestionSelected} + getSuggestionValue={suggestion => suggestion.name} + renderSuggestion={suggestion => ( +
    + {suggestion.name} +
    + )} + inputProps={{ + placeholder: i18n.__('Add tag...'), + value: newTag, + onChange: this.onInputChange, + onKeyDown: this.onInputKeyDown, + onBlur: this.onInputBlur + }} /> -
      - {datalist} -
    ) } @@ -183,7 +232,6 @@ TagSelect.propTypes = { className: PropTypes.string, value: PropTypes.arrayOf(PropTypes.string), onChange: PropTypes.func - } export default CSSModules(TagSelect, styles) diff --git a/browser/main/Detail/TagSelect.styl b/browser/main/Detail/TagSelect.styl index 052c86b6..7dc9dfe4 100644 --- a/browser/main/Detail/TagSelect.styl +++ b/browser/main/Detail/TagSelect.styl @@ -42,17 +42,6 @@ color: $ui-text-color padding 4px 16px 4px 8px -.newTag - box-sizing border-box - border none - background-color transparent - outline none - padding 0 4px - font-size 13px - -.datalist - display none - body[data-theme="dark"] .tag background-color alpha($ui-dark-tag-backgroundColor, 60%) @@ -65,11 +54,6 @@ body[data-theme="dark"] .tag-label color $ui-dark-text-color - .newTag - border-color none - background-color transparent - color $ui-dark-text-color - body[data-theme="solarized-dark"] .tag background-color $ui-solarized-dark-tag-backgroundColor @@ -81,11 +65,6 @@ body[data-theme="solarized-dark"] .tag-label color $ui-solarized-dark-text-color - .newTag - border-color none - background-color transparent - color $ui-solarized-dark-text-color - body[data-theme="monokai"] .tag background-color $ui-monokai-button-backgroundColor @@ -95,9 +74,4 @@ body[data-theme="monokai"] background-color transparent .tag-label - color $ui-monokai-text-color - - .newTag - border-color none - background-color transparent - color $ui-monokai-text-color + color $ui-monokai-text-color \ No newline at end of file diff --git a/browser/main/global.styl b/browser/main/global.styl index 6046861a..75ace0b3 100644 --- a/browser/main/global.styl +++ b/browser/main/global.styl @@ -157,4 +157,4 @@ body[data-theme="default"] .SideNav ::-webkit-scrollbar-thumb background-color rgba(255, 255, 255, 0.3) -@import '../styles/awesomplete.styl' \ No newline at end of file +@import '../styles/Detail/TagSelect.styl' \ No newline at end of file diff --git a/browser/styles/Detail/TagSelect.styl b/browser/styles/Detail/TagSelect.styl new file mode 100644 index 00000000..84fd74c2 --- /dev/null +++ b/browser/styles/Detail/TagSelect.styl @@ -0,0 +1,109 @@ +.TagSelect + .react-autosuggest__input + box-sizing border-box + border none + background-color transparent + outline none + padding 0 4px + font-size 13px + + ul + position fixed + z-index 999 + box-sizing border-box + list-style none + padding 0 + margin 0 + + border-radius 4px + margin .2em 0 0 + background-color $ui-noteList-backgroundColor + border 1px solid rgba(0,0,0,.3) + box-shadow .05em .2em .6em rgba(0,0,0,.2) + text-shadow none + + &:empty, + &[hidden] + display none + + &:before + content "" + position absolute + top -7px + left 14px + width 0 height 0 + padding 6px + background-color $ui-noteList-backgroundColor + border inherit + border-right 0 + border-bottom 0 + -webkit-transform rotate(45deg) + transform rotate(45deg) + + li + position relative + padding 6px 18px 6px 10px + cursor pointer + + li[aria-selected="true"] + background-color alpha($ui-button--active-backgroundColor, 40%) + color $ui-text-color + +body[data-theme="dark"] + .TagSelect + .react-autosuggest__input + color $ui-dark-text-color + + ul + border-color $ui-dark-borderColor + background-color $ui-dark-noteList-backgroundColor + color $ui-dark-text-color + + &:before + background-color $ui-dark-noteList-backgroundColor + + li[aria-selected="true"] + background-color $ui-dark-button--active-backgroundColor + color $ui-dark-text-color + +body[data-theme="monokai"] + .TagSelect + .react-autosuggest__input + color $ui-monokai-text-color + + ul + border-color $ui-monokai-borderColor + background-color $ui-monokai-noteList-backgroundColor + color $ui-monokai-text-color + + &:before + background-color $ui-dark-noteList-backgroundColor + + li[aria-selected="true"] + background-color $ui-monokai-button-backgroundColor + color $ui-monokai-text-color + +body[data-theme="solarized-dark"] + .TagSelect + .react-autosuggest__input + color $ui-solarized-dark-text-color + + ul + border-color $ui-solarized-dark-borderColor + background-color $ui-solarized-dark-noteList-backgroundColor + color $ui-solarized-dark-text-color + + &:before + background-color $ui-solarized-dark-noteList-backgroundColor + + li[aria-selected="true"] + background-color $ui-dark-button--active-backgroundColor + color $ui-solarized-dark-text-color + +body[data-theme="white"] + .TagSelect + ul + background-color $ui-white-noteList-backgroundColor + + li[aria-selected="true"] + background-color $ui-button--active-backgroundColor \ No newline at end of file diff --git a/browser/styles/awesomplete.styl b/browser/styles/awesomplete.styl deleted file mode 100644 index f46037a3..00000000 --- a/browser/styles/awesomplete.styl +++ /dev/null @@ -1,117 +0,0 @@ -.awesomplete - display inline-block - position relative - - .visually-hidden - position absolute - clip rect(0, 0, 0, 0) - - ul - position fixed - z-index 1 - box-sizing border-box - list-style none - padding 0 - margin 0 - - border-radius 4px - margin .2em 0 0 - background-color $ui-noteList-backgroundColor - border 1px solid rgba(0,0,0,.3) - box-shadow .05em .2em .6em rgba(0,0,0,.2) - text-shadow none - - &:empty, - &[hidden] - display none - - &:before - content "" - position absolute - top -.43em - left 1em - width 0 height 0 - padding .4em - background-color $ui-noteList-backgroundColor - border inherit - border-right 0 - border-bottom 0 - -webkit-transform rotate(45deg) - transform rotate(45deg) - - li - position relative - padding 6px 18px 6px 10px - cursor pointer - - li:hover - background-color alpha($ui-button--active-backgroundColor, 20%) - color $ui-text-color - - li[aria-selected="true"] - background-color alpha($ui-button--active-backgroundColor, 40%) - color $ui-text-color - - mark - background-color rgba(255, 255, 0, 0.8) - -body[data-theme="dark"] - .awesomplete ul - border-color $ui-dark-borderColor - background-color $ui-dark-noteList-backgroundColor - color $ui-dark-text-color - - &:before - background-color $ui-dark-noteList-backgroundColor - - li:hover - background-color alpha($ui-dark-button--active-backgroundColor, 20%) - color $ui-dark-text-color - - li[aria-selected="true"] - background-color $ui-dark-button--active-backgroundColor - color $ui-dark-text-color - -body[data-theme="white"] - .awesomplete ul - background-color $ui-white-noteList-backgroundColor - - li:hover - background-color alpha($ui-button--active-backgroundColor, 60%) - - li[aria-selected="true"] - background-color $ui-button--active-backgroundColor - -body[data-theme="solarized-dark"] - .awesomplete ul - border-color $ui-solarized-dark-borderColor - background-color $ui-solarized-dark-noteList-backgroundColor - color $ui-solarized-dark-text-color - - &:before - background-color $ui-solarized-dark-noteList-backgroundColor - - li:hover - background-color alpha($ui-dark-button--active-backgroundColor, 20%) - color $ui-solarized-dark-text-color - - li[aria-selected="true"] - background-color $ui-dark-button--active-backgroundColor - color $ui-solarized-dark-text-color - -body[data-theme="monokai"] - .awesomplete ul - border-color $ui-monokai-borderColor - background-color $ui-monokai-noteList-backgroundColor - color $ui-monokai-text-color - - &:before - background-color $ui-dark-noteList-backgroundColor - - li:hover - background-color alpha($ui-dark-button--active-backgroundColor, 20%) - color $ui-monokai-text-color - - li[aria-selected="true"] - background-color $ui-monokai-button-backgroundColor - color $ui-monokai-text-color \ No newline at end of file diff --git a/lib/main.html b/lib/main.html index 3c2d33eb..e03cac37 100644 --- a/lib/main.html +++ b/lib/main.html @@ -119,8 +119,6 @@ window._ = require('lodash') - - diff --git a/package.json b/package.json index 4ca816e1..408f6d90 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,6 @@ "@rokt33r/markdown-it-math": "^4.0.1", "@rokt33r/season": "^5.3.0", "@susisu/mte-kernel": "^2.0.0", - "awesomplete": "^1.1.2", "aws-sdk": "^2.48.0", "aws-sdk-mobile-analytics": "^0.9.2", "chart.js": "^2.7.2", @@ -89,6 +88,7 @@ "node-ipc": "^8.1.0", "raphael": "^2.2.7", "react": "^15.5.4", + "react-autosuggest": "^9.4.0", "react-codemirror": "^0.3.0", "react-debounce-render": "^4.0.1", "react-dom": "^15.0.2", diff --git a/yarn.lock b/yarn.lock index 02207b14..50374f74 100644 --- a/yarn.lock +++ b/yarn.lock @@ -568,10 +568,6 @@ ava@^0.25.0: unique-temp-dir "^1.0.0" update-notifier "^2.3.0" -awesomplete@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/awesomplete/-/awesomplete-1.1.2.tgz#b6e253f73474e46278bba5ae7f81d4262160fb75" - aws-sdk-mobile-analytics@^0.9.2: version "0.9.2" resolved "https://registry.yarnpkg.com/aws-sdk-mobile-analytics/-/aws-sdk-mobile-analytics-0.9.2.tgz#b56a6e5206fc8c3975a19170b41536c53f6d5d91" @@ -6356,6 +6352,10 @@ oauth-sign@~0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" +object-assign@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2" + object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -7194,6 +7194,22 @@ rcedit@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/rcedit/-/rcedit-1.1.0.tgz#ae21c28d4efdd78e95fcab7309a5dd084920b16a" +react-autosuggest@^9.4.0: + version "9.4.0" + resolved "https://registry.yarnpkg.com/react-autosuggest/-/react-autosuggest-9.4.0.tgz#3146bc9afa4f171bed067c542421edec5ca94294" + dependencies: + prop-types "^15.5.10" + react-autowhatever "^10.1.2" + shallow-equal "^1.0.0" + +react-autowhatever@^10.1.2: + version "10.1.2" + resolved "https://registry.yarnpkg.com/react-autowhatever/-/react-autowhatever-10.1.2.tgz#200ffc41373b2189e3f6140ac7bdb82363a79fd3" + dependencies: + prop-types "^15.5.8" + react-themeable "^1.1.0" + section-iterator "^2.0.0" + react-codemirror@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/react-codemirror/-/react-codemirror-0.3.0.tgz#cd6bd6ef458ec1e035cfd8b3fe7b30c8c7883c6c" @@ -7294,6 +7310,12 @@ react-test-renderer@^15.6.2: fbjs "^0.8.9" object-assign "^4.1.0" +react-themeable@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/react-themeable/-/react-themeable-1.1.0.tgz#7d4466dd9b2b5fa75058727825e9f152ba379a0e" + dependencies: + object-assign "^3.0.0" + react-transform-catch-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/react-transform-catch-errors/-/react-transform-catch-errors-1.0.2.tgz#1b4d4a76e97271896fc16fe3086c793ec88a9eeb" @@ -7796,6 +7818,10 @@ scope-css@^1.0.5: version "1.1.0" resolved "http://registry.npm.taobao.org/scope-css/download/scope-css-1.1.0.tgz#74eff45461bc9d3f3b29ed575b798cd722fa1256" +section-iterator@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/section-iterator/-/section-iterator-2.0.0.tgz#bf444d7afeeb94ad43c39ad2fb26151627ccba2a" + semver-diff@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" @@ -7891,6 +7917,10 @@ sha.js@2.2.6: version "2.2.6" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.2.6.tgz#17ddeddc5f722fb66501658895461977867315ba" +shallow-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shallow-equal/-/shallow-equal-1.0.0.tgz#508d1838b3de590ab8757b011b25e430900945f7" + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" From 20f573c477ff16fb22780fff71a136f43e73498f Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Mon, 27 Aug 2018 10:06:20 +0200 Subject: [PATCH 18/44] - hide front-matter in preview - skip front-matter when looking for note's title --- browser/lib/findNoteTitle.js | 10 ++++++++++ browser/lib/markdown.js | 1 + package.json | 1 + yarn.lock | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/browser/lib/findNoteTitle.js b/browser/lib/findNoteTitle.js index 81c9400f..25417d53 100644 --- a/browser/lib/findNoteTitle.js +++ b/browser/lib/findNoteTitle.js @@ -1,8 +1,18 @@ +const frontMatterRegex = /^\-{3,}/ + export function findNoteTitle (value) { const splitted = value.split('\n') let title = null let isInsideCodeBlock = false + if (frontMatterRegex.exec(splitted[0])) { + let index = 0 + while (++index < splitted.length && !frontMatterRegex.exec(splitted[index])) { + } + + splitted.splice(0, index + 1) + } + splitted.some((line, index) => { const trimmedLine = line.trim() const trimmedNextLine = splitted[index + 1] === undefined ? '' : splitted[index + 1].trim() diff --git a/browser/lib/markdown.js b/browser/lib/markdown.js index 49fd2f86..aeebc7ac 100644 --- a/browser/lib/markdown.js +++ b/browser/lib/markdown.js @@ -149,6 +149,7 @@ class Markdown { }) this.md.use(require('markdown-it-kbd')) this.md.use(require('markdown-it-admonition')) + this.md.use(require('markdown-it-front-matter'), fm => {}) const deflate = require('markdown-it-plantuml/lib/deflate') this.md.use(require('markdown-it-plantuml'), '', { diff --git a/package.json b/package.json index e9949adf..04e3f7c6 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "markdown-it-admonition": "^1.0.4", "markdown-it-emoji": "^1.1.1", "markdown-it-footnote": "^3.0.0", + "markdown-it-front-matter": "^0.1.2", "markdown-it-imsize": "^2.0.1", "markdown-it-kbd": "^1.1.1", "markdown-it-multimd-table": "^2.0.1", diff --git a/yarn.lock b/yarn.lock index 4ecfa51b..a4f682be 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5735,6 +5735,10 @@ markdown-it-footnote@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/markdown-it-footnote/-/markdown-it-footnote-3.0.1.tgz#7f3730747cacc86e2fe0bf8a17a710f34791517a" +markdown-it-front-matter@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/markdown-it-front-matter/-/markdown-it-front-matter-0.1.2.tgz#e50bf56e77e6a4f5ac4ffa894d4d45ccd9896b20" + markdown-it-imsize@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/markdown-it-imsize/-/markdown-it-imsize-2.0.1.tgz#cca0427905d05338a247cb9ca9d968c5cddd5170" From 1bb841d5c50ba9662ee15926038ecc34f31048b2 Mon Sep 17 00:00:00 2001 From: Maciek Date: Sun, 26 Aug 2018 00:34:35 +0200 Subject: [PATCH 19/44] Create Markdown TOC at current cursor position If there is no TOC in the current document, it's created at current cursor position. Subsequent generation calls update TOC at existing position. Add additional tests with CodeMirror editor mock. --- browser/lib/markdown-toc-generator.js | 62 ++- browser/main/Detail/MarkdownNoteDetail.js | 4 +- browser/main/Detail/SnippetNoteDetail.js | 3 +- tests/helpers/setup-browser-env.js | 20 +- tests/lib/markdown-toc-generator-test.js | 614 +++++++++++++++------- 5 files changed, 496 insertions(+), 207 deletions(-) diff --git a/browser/lib/markdown-toc-generator.js b/browser/lib/markdown-toc-generator.js index 363a58ce..716be83a 100644 --- a/browser/lib/markdown-toc-generator.js +++ b/browser/lib/markdown-toc-generator.js @@ -6,6 +6,8 @@ import toc from 'markdown-toc' import diacritics from 'diacritics-map' import stripColor from 'strip-color' +const EOL = require('os').EOL + /** * @caseSensitiveSlugify Custom slugify function * Same implementation that the original used by markdown-toc (node_modules/markdown-toc/lib/utils.js), @@ -17,6 +19,7 @@ function caseSensitiveSlugify (str) { return diacritics[ch] || ch }) } + function getTitle (str) { if (/^\[[^\]]+\]\(/.test(str)) { var m = /^\[([^\]]+)\]/.exec(str) @@ -24,6 +27,7 @@ function caseSensitiveSlugify (str) { } return str } + str = getTitle(str) str = stripColor(str) // str = str.toLowerCase() //let's be case sensitive @@ -38,15 +42,59 @@ function caseSensitiveSlugify (str) { return str } -export function generate (currentValue, updateCallback) { - const TOC_MARKER = '' - if (!currentValue.includes(TOC_MARKER)) { - currentValue = TOC_MARKER + currentValue +const TOC_MARKER_START = '' +const TOC_MARKER_END = '' + +/** + * Takes care of proper updating given editor with TOC. + * If TOC doesn't exit in the editor, it's inserted at current caret position. + * Otherwise,TOC is updated in place. + * @param editor CodeMirror editor to be updated with TOC + */ +export function generateInEditor (editor) { + const tocRegex = new RegExp(`${TOC_MARKER_START}[\\s\\S]*?${TOC_MARKER_END}`) + + function tocExistsInEditor () { + return tocRegex.test(editor.getValue()) } - updateCallback(toc.insert(currentValue, {slugify: caseSensitiveSlugify})) + + function updateExistingToc () { + const toc = generate(editor.getValue()) + const search = editor.getSearchCursor(tocRegex) + while (search.findNext()) { + search.replace(toc) + } + } + + function addTocAtCursorPosition () { + const toc = generate(editor.getRange(editor.getCursor(), {line: Infinity})) + editor.replaceRange(wrapTocWithEol(toc, editor), editor.getCursor()) + } + + if (tocExistsInEditor()) { + updateExistingToc() + } else { + addTocAtCursorPosition() + } +} + +/** + * Generates MD TOC based on MD document passed as string. + * @param markdownText MD document + * @returns generatedTOC String containing generated TOC + */ +export function generate (markdownText) { + const generatedToc = toc(markdownText, {slugify: caseSensitiveSlugify}) + return TOC_MARKER_START + EOL + EOL + generatedToc.content + EOL + EOL + TOC_MARKER_END +} + +function wrapTocWithEol (toc, editor) { + const leftWrap = editor.getCursor().ch === 0 ? '' : EOL + const rightWrap = editor.getLine(editor.getCursor().line).length === editor.getCursor().ch ? '' : EOL + return leftWrap + toc + rightWrap } export default { - generate + generate, + generateInEditor } - diff --git a/browser/main/Detail/MarkdownNoteDetail.js b/browser/main/Detail/MarkdownNoteDetail.js index b0cdbb65..74542266 100755 --- a/browser/main/Detail/MarkdownNoteDetail.js +++ b/browser/main/Detail/MarkdownNoteDetail.js @@ -267,8 +267,8 @@ class MarkdownNoteDetail extends React.Component { } handleGenerateToc () { - markdownToc.generate(this.refs.content.value, - (modifiedValue) => this.refs.content.refs.code.setValue(modifiedValue)) + const editor = this.refs.content.refs.code.editor + markdownToc.generateInEditor(editor) } handleFocus (e) { diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index f4459f84..cf4df18c 100644 --- a/browser/main/Detail/SnippetNoteDetail.js +++ b/browser/main/Detail/SnippetNoteDetail.js @@ -100,9 +100,8 @@ class SnippetNoteDetail extends React.Component { handleGenerateToc () { const currentMode = this.state.note.snippets[this.state.snippetIndex].mode if (currentMode.includes('Markdown')) { - 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)) + markdownToc.generateInEditor(currentEditor) } } diff --git a/tests/helpers/setup-browser-env.js b/tests/helpers/setup-browser-env.js index d7615c01..3e3232b7 100644 --- a/tests/helpers/setup-browser-env.js +++ b/tests/helpers/setup-browser-env.js @@ -1,5 +1,23 @@ import browserEnv from 'browser-env' -browserEnv(['window', 'document']) +browserEnv(['window', 'document', 'navigator']) + +// for CodeMirror mockup +document.body.createTextRange = function () { + return { + setEnd: function () {}, + setStart: function () {}, + getBoundingClientRect: function () { + return {right: 0} + }, + getClientRects: function () { + return { + length: 0, + left: 0, + right: 0 + } + } + } +} window.localStorage = { // polyfill diff --git a/tests/lib/markdown-toc-generator-test.js b/tests/lib/markdown-toc-generator-test.js index 9fcc1d8d..60568741 100644 --- a/tests/lib/markdown-toc-generator-test.js +++ b/tests/lib/markdown-toc-generator-test.js @@ -1,19 +1,22 @@ /** * @fileoverview Unit test for browser/lib/markdown-toc-generator */ + +import CodeMirror from 'codemirror' +require('codemirror/addon/search/searchcursor.js') const test = require('ava') const markdownToc = require('browser/lib/markdown-toc-generator') const EOL = require('os').EOL test(t => { /** - * @testCases Contains array of test cases in format : - * [ - * test title - * input markdown, - * expected output markdown with toc - * ] - * + * Contains array of test cases in format : + * [ + * test title + * input markdown, + * expected toc + * ] + * @type {*[]} */ const testCases = [ [ @@ -39,8 +42,6 @@ test(t => { - [one](#one) - -# one ` ], [ @@ -55,10 +56,7 @@ test(t => { - [one](#one) - [two](#two) - - -# one -# two + ` ], [ @@ -82,13 +80,6 @@ test(t => { * [three three](#three-three) - -# one -## one one -# two -## two two -# three -## three three ` ], [ @@ -120,17 +111,6 @@ test(t => { + [three three three three three three](#three-three-three-three-three-three) - -# one -## one one -# two -## two two -# three -## three three -### three three three -#### three three three three -##### three three three three three -###### three three three three three three ` ], [ @@ -193,148 +173,8 @@ this is a level one text + [three three three three three three](#three-three-three-three-three-three) - -# one -this is a level one text -this is a level one text -## one one -# two - this is a level two text - this is a level two text -## two two - this is a level two two text - this is a level two two text -# three - this is a level three three text - this is a level three three text -## three three - this is a text - this is a text -### three three three - this is a text - this is a text -### three three three 2 - this is a text - this is a text -#### three three three three - this is a text - this is a text -#### three three three three 2 - this is a text - this is a text -##### three three three three three - this is a text - this is a text -##### three three three three three 2 - this is a text - this is a text -###### three three three three three three - this is a text - this is a text - this is a text ` ], - [ - '***************************** already generated toc', - ` - - -- [one](#one) - * [one one](#one-one) -- [two](#two) - * [two two](#two-two) -- [three](#three) - * [three three](#three-three) - + [three three three](#three-three-three) - - [three three three three](#three-three-three-three) - * [three three three three three](#three-three-three-three-three) - + [three three three three three three](#three-three-three-three-three-three) - - - -# one -## one one -# two -## two two -# three -## three three -### three three three -#### three three three three -##### three three three three three -###### three three three three three three - `, - ` - - -- [one](#one) - * [one one](#one-one) -- [two](#two) - * [two two](#two-two) -- [three](#three) - * [three three](#three-three) - + [three three three](#three-three-three) - - [three three three three](#three-three-three-three) - * [three three three three three](#three-three-three-three-three) - + [three three three three three three](#three-three-three-three-three-three) - - - -# one -## one one -# two -## two two -# three -## three three -### three three three -#### three three three three -##### three three three three three -###### three three three three three three - ` - ], - [ - '***************************** note with just an opening TOC marker', - ` - - - -# one -## one one - - `, - ` - - -- [one](#one) - * [one one](#one-one) - - - -# one -## one one - ` - ], - [ - '***************************** note with just a closing TOC marker', - ` - - -# one -## one one - `, - ` - - -- [one](#one) - * [one one](#one-one) - - - -# one -## one one - - ` - ], - [ '***************************** outdated TOC', ` @@ -346,8 +186,7 @@ this is a level one text # one modified -## one one - +## one one `, ` @@ -356,9 +195,6 @@ this is a level one text * [one one](#one-one) - -# one modified -## one one ` ], [ @@ -374,9 +210,6 @@ this is a level one text * [oNe one](#oNe-one) - -# onE -## oNe one ` ], [ @@ -397,19 +230,12 @@ this is a text ## oNe one `, ` -# title - -this is a text - - [onE](#onE) * [oNe one](#oNe-one) - -# onE -## oNe one ` ], [ @@ -424,11 +250,7 @@ this is a text - [hoge](#hoge) - - -# hoge - -## + ` ] ] @@ -436,9 +258,411 @@ this is a text testCases.forEach(testCase => { const title = testCase[0] const inputMd = testCase[1].trim() - const expectedOutput = testCase[2].trim() - let generatedOutput - markdownToc.generate(inputMd, (o) => { generatedOutput = o.trim() }) - t.is(generatedOutput, expectedOutput, `Test ${title} , generated : ${EOL}${generatedOutput}, expected : ${EOL}${expectedOutput}`) + const expectedToc = testCase[2].trim() + const generatedToc = markdownToc.generate(inputMd) + + t.is(generatedToc, expectedToc, `generate test : ${title} , generated : ${EOL}${generatedToc}, expected : ${EOL}${expectedToc}`) + }) +}) + +test(t => { + /** + * Contains array of test cases in format : + * [ + * title + * cursor + * inputMd + * expectedMd + * ] + * @type {*[]} + */ + const testCases = [ + [ + `***************************** Empty note, cursor at the top`, + {line: 0, ch: 0}, + ``, + ` + + + + + + ` + ], + [ + `***************************** Two level note,TOC at the beginning `, + {line: 0, ch: 0}, + ` +# one +this is a level one text +this is a level one text + +## one one +# two + this is a level two text + this is a level two text +## two two + this is a level two two text + this is a level two two text + `, + ` + + +- [one](#one) + * [one one](#one-one) +- [two](#two) + * [two two](#two-two) + + +# one +this is a level one text +this is a level one text + +## one one +# two + this is a level two text + this is a level two text +## two two + this is a level two two text + this is a level two two text + ` + ], + [ + `***************************** Two level note, cursor just after 'header text' `, + {line: 1, ch: 12}, + ` +# header + header text + +# one +this is a level one text +this is a level one text + +## one one +# two + this is a level two text + this is a level two text +## two two + this is a level two two text + this is a level two two text + `, + ` +# header + header text + + +- [one](#one) + * [one one](#one-one) +- [two](#two) + * [two two](#two-two) + + + +# one +this is a level one text +this is a level one text + +## one one +# two + this is a level two text + this is a level two text +## two two + this is a level two two text + this is a level two two text + ` + ], + [ + `***************************** Two level note, cursor at empty line under 'header text' `, + {line: 2, ch: 0}, + ` +# header + header text + +# one +this is a level one text +this is a level one text + +## one one +# two + this is a level two text + this is a level two text +## two two + this is a level two two text + this is a level two two text + `, + ` +# header + header text + + +- [one](#one) + * [one one](#one-one) +- [two](#two) + * [two two](#two-two) + + +# one +this is a level one text +this is a level one text + +## one one +# two + this is a level two text + this is a level two text +## two two + this is a level two two text + this is a level two two text + ` + ], + [ + `***************************** Two level note, cursor just before 'text' word`, + {line: 1, ch: 8}, + ` +# header + header text + +# one +this is a level one text +this is a level one text + +## one one +# two + this is a level two text + this is a level two text +## two two + this is a level two two text + this is a level two two text + `, + ` +# header + header + + +- [one](#one) + * [one one](#one-one) +- [two](#two) + * [two two](#two-two) + + +text + +# one +this is a level one text +this is a level one text + +## one one +# two + this is a level two text + this is a level two text +## two two + this is a level two two text + this is a level two two text + ` + ], + [ + `***************************** Already generated TOC without header file, regenerate TOC in place, no changes`, + {line: 13, ch: 0}, + ` +# header + header text + + +- [one](#one) + * [one one](#one-one) +- [two](#two) + * [two two](#two-two) + + +# one +this is a level one text +this is a level one text + +## one one +# two + this is a level two text + this is a level two text +## two two + this is a level two two text + this is a level two two text + `, + ` +# header + header text + + +- [one](#one) + * [one one](#one-one) +- [two](#two) + * [two two](#two-two) + + +# one +this is a level one text +this is a level one text + +## one one +# two + this is a level two text + this is a level two text +## two two + this is a level two two text + this is a level two two text + ` + ], + [ + `***************************** Already generated TOC, needs updating in place`, + {line: 0, ch: 0}, + ` +# header + header text + + +- [one](#one) + * [one one](#one-one) +- [two](#two) + * [two two](#two-two) + + +# This is the one +this is a level one text +this is a level one text + +## one one +# two + this is a level two text + this is a level two text +## two two + this is a level two two text + this is a level two two text + `, + ` +# header + header text + + +- [This is the one](#This-is-the-one) + * [one one](#one-one) +- [two](#two) + * [two two](#two-two) + + +# This is the one +this is a level one text +this is a level one text + +## one one +# two + this is a level two text + this is a level two text +## two two + this is a level two two text + this is a level two two text + ` + ], + [ + `***************************** Document with cursor at the last line, expecting empty TOC `, + {line: 13, ch: 30}, + ` +# header + header text + +# This is the one +this is a level one text +this is a level one text + +## one one +# two + this is a level two text + this is a level two text +## two two + this is a level two two text + this is a level two two text + `, + ` +# header + header text + +# This is the one +this is a level one text +this is a level one text + +## one one +# two + this is a level two text + this is a level two text +## two two + this is a level two two text + this is a level two two text + + + + + + ` + ], + [ + `***************************** Empty, not actual TOC , should be supplemented with two new points beneath`, + {line: 0, ch: 0}, + ` +# header + header text + +# This is the one +this is a level one text +this is a level one text + +## one one +# two + this is a level two text + this is a level two text +## two two + this is a level two two text + this is a level two two text + + + + + +# new point included in toc +## new subpoint + `, + ` +# header + header text + +# This is the one +this is a level one text +this is a level one text + +## one one +# two + this is a level two text + this is a level two text +## two two + this is a level two two text + this is a level two two text + + +- [new point included in toc](#new-point-included-in-toc) + * [new subpoint](#new-subpoint) + + +# new point included in toc +## new subpoint + ` + ] + ] + testCases.forEach(testCase => { + const title = testCase[0] + const cursor = testCase[1] + const inputMd = testCase[2].trim() + const expectedMd = testCase[3].trim() + + const editor = CodeMirror() + editor.setValue(inputMd) + editor.setCursor(cursor) + markdownToc.generateInEditor(editor) + + t.is(expectedMd, editor.getValue(), `generateInEditor test : ${title} , generated : ${EOL}${editor.getValue()}, expected : ${EOL}${expectedMd}`) }) }) From 7c0c81207bf0b93d16bd47bf4eb0f326f093a522 Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Fri, 31 Aug 2018 10:27:02 +0200 Subject: [PATCH 20/44] add test --- tests/lib/find-title-test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/lib/find-title-test.js b/tests/lib/find-title-test.js index 2c91a3dc..f587804c 100644 --- a/tests/lib/find-title-test.js +++ b/tests/lib/find-title-test.js @@ -14,7 +14,8 @@ test('findNoteTitle#find should return a correct title (string)', t => { ['hoge\n====\nfuga', 'hoge'], ['====', '===='], ['```\n# hoge\n```', '```'], - ['hoge', 'hoge'] + ['hoge', 'hoge'], + ['---\nlayout: test\n---\n # hoge', '# hoge'] ] testCases.forEach(testCase => { From e9070fadab349fb74b62d74fa4a9fb623833cf6c Mon Sep 17 00:00:00 2001 From: Maciek Date: Sun, 2 Sep 2018 19:38:15 +0200 Subject: [PATCH 21/44] Refactoring : use object destructuring to retain file code style --- browser/main/Detail/SnippetNoteDetail.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index cf4df18c..9a394683 100644 --- a/browser/main/Detail/SnippetNoteDetail.js +++ b/browser/main/Detail/SnippetNoteDetail.js @@ -98,9 +98,10 @@ class SnippetNoteDetail extends React.Component { } handleGenerateToc () { - const currentMode = this.state.note.snippets[this.state.snippetIndex].mode + const { note, snippetIndex } = this.state + const currentMode = note.snippets[snippetIndex].mode if (currentMode.includes('Markdown')) { - const currentEditor = this.refs['code-' + this.state.snippetIndex].refs.code.editor + const currentEditor = this.refs[`code-${snippetIndex}`].refs.code.editor markdownToc.generateInEditor(currentEditor) } } From da81f10e04c3c861dfa7b00317bdde0b7e1b97cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Har=C4=99=C5=BClak?= Date: Tue, 4 Sep 2018 21:39:14 +0200 Subject: [PATCH 22/44] Add Polish translations --- locales/pl.json | 230 ++++++++++++++++++++++++------------------------ 1 file changed, 115 insertions(+), 115 deletions(-) diff --git a/locales/pl.json b/locales/pl.json index 33b1b763..626129ff 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -1,155 +1,155 @@ { - "Notes": "Notes", - "Tags": "Tags", - "Preferences": "Preferences", - "Make a note": "Make a note", + "Notes": "Notatki", + "Tags": "Tagi", + "Preferences": "Ustawienia", + "Make a note": "Stwórz notatkę", "Ctrl": "Ctrl", "Ctrl(^)": "Ctrl", - "to create a new note": "to create a new note", - "Toggle Mode": "Toggle Mode", - "Trash": "Trash", - "MODIFICATION DATE": "MODIFICATION DATE", - "Words": "Words", - "Letters": "Letters", + "to create a new note": "Aby stworzyć nową notatkę", + "Toggle Mode": "Przełącz tryb", + "Trash": "Kosz", + "MODIFICATION DATE": "DATA MODYFIKACJI", + "Words": "Słowa", + "Letters": "Litery", "STORAGE": "STORAGE", "FOLDER": "FOLDER", - "CREATION DATE": "CREATION DATE", - "NOTE LINK": "NOTE LINK", + "CREATION DATE": "DATA UTWORZENIA", + "NOTE LINK": "LINK NOTATKI", ".md": ".md", ".txt": ".txt", ".html": ".html", "Print": "Print", - "Your preferences for Boostnote": "Your preferences for Boostnote", + "Your preferences for Boostnote": "Twoje ustawienia dla Boostnote", "Storages": "Storages", "Add Storage Location": "Add Storage Location", - "Add Folder": "Add Folder", + "Add Folder": "Dodaj Folder", "Open Storage folder": "Open Storage folder", - "Unlink": "Unlink", - "Edit": "Edit", - "Delete": "Delete", - "Interface": "Interface", - "Interface Theme": "Interface Theme", - "Default": "Default", - "White": "White", + "Unlink": "Odlinkuj", + "Edit": "Edytuj", + "Delete": "Usuń", + "Interface": "Interfejs", + "Interface Theme": "Wygląd interfejsu", + "Default": "Domyślny", + "White": "Biały", "Solarized Dark": "Solarized Dark", - "Dark": "Dark", - "Show a confirmation dialog when deleting notes": "Show a confirmation dialog when deleting notes", - "Editor Theme": "Editor Theme", - "Editor Font Size": "Editor Font Size", - "Editor Font Family": "Editor Font Family", - "Editor Indent Style": "Editor Indent Style", - "Spaces": "Spaces", - "Tabs": "Tabs", - "Switch to Preview": "Switch to Preview", - "When Editor Blurred": "When Editor Blurred", - "When Editor Blurred, Edit On Double Click": "When Editor Blurred, Edit On Double Click", - "On Right Click": "On Right Click", - "Editor Keymap": "Editor Keymap", - "default": "default", + "Dark": "Ciemny", + "Show a confirmation dialog when deleting notes": "Wymagaj potwierdzenia usunięcia notatek", + "Editor Theme": "Wygląd edytota", + "Editor Font Size": "Rozmiar czcionki edytora", + "Editor Font Family": "Czcionka edytora", + "Editor Indent Style": "Rodzaj wcięć edytora", + "Spaces": "Spacje", + "Tabs": "Tabulatory", + "Switch to Preview": "Przełącz na podgląd", + "When Editor Blurred": "Gdy edytor w pracuje w tle", + "When Editor Blurred, Edit On Double Click": "Gdy edytor w pracuje w tle, edytuj za pomocą podwójnego kliknięcia", + "On Right Click": "Kliknięcie prawego przycisku myszy", + "Editor Keymap": "Układ klawiszy edytora", + "default": "domyślny", "vim": "vim", "emacs": "emacs", - "⚠️ Please restart boostnote after you change the keymap": "⚠️ Please restart boostnote after you change the keymap", - "Show line numbers in the editor": "Show line numbers in the editor", + "⚠️ Please restart boostnote after you change the keymap": "⚠️ Wymagane zresetowania Boostnote po zmianie ustawień klawiszy", + "Show line numbers in the editor": "Pokazuj numery lini w edytorze", "Allow editor to scroll past the last line": "Allow editor to scroll past the last line", "Bring in web page title when pasting URL on editor": "Bring in web page title when pasting URL on editor", - "Preview": "Preview", - "Preview Font Size": "Preview Font Size", - "Preview Font Family": "Preview Font Family", - "Code block Theme": "Code block Theme", + "Preview": "Podgląd", + "Preview Font Size": "Podgląd rozmiaru czcionki", + "Preview Font Family": "Podgląd czcionki", + "Code block Theme": "Wygląd bloku kodu", "Allow preview to scroll past the last line": "Allow preview to scroll past the last line", "Show line numbers for preview code blocks": "Show line numbers for preview code blocks", "LaTeX Inline Open Delimiter": "LaTeX Inline Open Delimiter", "LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter", "LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter", "LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter", - "PlantUML Server": "PlantUML Server", - "Community": "Community", - "Subscribe to Newsletter": "Subscribe to Newsletter", + "PlantUML Server": "Serwer PlantUML", + "Community": "Społecznośc", + "Subscribe to Newsletter": "Zapisz do Newsletter", "GitHub": "GitHub", "Blog": "Blog", - "Facebook Group": "Facebook Group", + "Facebook Group": "Grupa Facebook", "Twitter": "Twitter", - "About": "About", + "About": "O nas", "Boostnote": "Boostnote", - "An open source note-taking app made for programmers just like you.": "An open source note-taking app made for programmers just like you.", - "Website": "Website", + "An open source note-taking app made for programmers just like you.": "Społecznościowa aplikacja do przechowywania notatek, stworzona dla programistów takich jak Ty.", + "Website": "Strona WWW", "Development": "Development", " : Development configurations for Boostnote.": " : Development configurations for Boostnote.", "Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", - "License: GPL v3": "License: GPL v3", - "Analytics": "Analytics", - "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.", - "You can see how it works on ": "You can see how it works on ", - "You can choose to enable or disable this option.": "You can choose to enable or disable this option.", - "Enable analytics to help improve Boostnote": "Enable analytics to help improve Boostnote", + "License: GPL v3": "Licencja: GPL v3", + "Analytics": "Statystyki", + "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote zbiera anonimowe informacje na temat aplikacji by pomóc w jej ulepszaniu, nie pobiera informacji z Twoich notatek.", + "You can see how it works on ": "Możesz zobaczyć jak to działa ", + "You can choose to enable or disable this option.": "Możesz włączyć lub wyłączyć tę opcje.", + "Enable analytics to help improve Boostnote": "Włącz zbierania statystyk by pomóc polepszać Boostnote", "Crowdfunding": "Crowdfunding", - "Dear everyone,": "Dear everyone,", - "Thank you for using Boostnote!": "Thank you for using Boostnote!", - "Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "Boostnote is used in about 200 different countries and regions by an awesome community of developers.", - "To continue supporting this growth, and to satisfy community expectations,": "To continue supporting this growth, and to satisfy community expectations,", - "we would like to invest more time and resources in this project.": "we would like to invest more time and resources in this project.", - "If you like this project and see its potential, you can help by supporting us on OpenCollective!": "If you like this project and see its potential, you can help by supporting us on OpenCollective!", - "Thanks,": "Thanks,", + "Dear everyone,": "Droga społeczności,", + "Thank you for using Boostnote!": "Dziękujemy za używanie Boostnote!", + "Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "Boostnote jest używany w około 200 krajach i regionach przez wspaniałą społeczność programistów.", + "To continue supporting this growth, and to satisfy community expectations,": "Do kontynuowania wzrostu popularności i satysfakcji naszej społeczności,", + "we would like to invest more time and resources in this project.": "chcielibyśmy poświęcić więcej czasu na rozwój naszego projektu.", + "If you like this project and see its potential, you can help by supporting us on OpenCollective!": "Jeśli podoba Ci się naszy projekt i lubisz go używać, możesz wspomóc nasz przez OpenCollective!", + "Thanks,": "Dzięki,", "Boostnote maintainers": "Boostnote maintainers", - "Support via OpenCollective": "Support via OpenCollective", - "Language": "Language", - "English": "English", - "German": "German", - "French": "French", - "Show \"Saved to Clipboard\" notification when copying": "Show \"Saved to Clipboard\" notification when copying", - "All Notes": "All Notes", - "Starred": "Starred", - "Are you sure to ": "Are you sure to ", - " delete": " delete", - "this folder?": "this folder?", - "Confirm": "Confirm", - "Cancel": "Cancel", - "Markdown Note": "Markdown Note", - "This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "This format is for creating text documents. Checklists, code blocks and Latex blocks are available.", - "Snippet Note": "Snippet Note", - "This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "This format is for creating code snippets. Multiple snippets can be grouped into a single note.", - "Tab to switch format": "Tab to switch format", - "Updated": "Updated", - "Created": "Created", - "Alphabetically": "Alphabetically", - "Default View": "Default View", - "Compressed View": "Compressed View", - "Search": "Search", - "Blog Type": "Blog Type", - "Blog Address": "Blog Address", - "Save": "Save", - "Auth": "Auth", - "Authentication Method": "Authentication Method", + "Support via OpenCollective": "Wspomóż przez OpenCollective", + "Language": "Język", + "English": "Angielski", + "German": "Niemiecki", + "French": "Francuski", + "Show \"Saved to Clipboard\" notification when copying": "Pokazuj \"Skopiowano do schowka\" podczas kopiowania", + "All Notes": "Notatki", + "Starred": "Oznaczone", + "Are you sure to ": "Czy na pewno chcesz ", + " delete": " usunąc", + "this folder?": "ten folder?", + "Confirm": "Potwierdź", + "Cancel": "Anuluj", + "Markdown Note": "Notatka Markdown", + "This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "Ten format pozwala na tworzenie dokumentów tekstowych. List zadań, bloków kodu czy bloków Latex.", + "Snippet Note": "Snippet Kodu", + "This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "Ten format zezwala na tworzenia snippetów kodu. Kilka snippetów można zgrupować w jedną notatkę.", + "Tab to switch format": "Tabulator by zmienić format", + "Updated": "Zaaktualizowano", + "Created": "Stworzono", + "Alphabetically": "Alfabetycznie", + "Default View": "Widok domyślny", + "Compressed View": "Widok skompresowany", + "Search": "Szukaj", + "Blog Type": "Typ bloga", + "Blog Address": "Adres bloga", + "Save": "Zapisz", + "Auth": "Autoryzacja", + "Authentication Method": "Metoda Autoryzacji", "JWT": "JWT", - "USER": "USER", + "USER": "Użutkownik", "Token": "Token", "Storage": "Storage", - "Hotkeys": "Hotkeys", - "Show/Hide Boostnote": "Show/Hide Boostnote", - "Restore": "Restore", - "Permanent Delete": "Permanent Delete", - "Confirm note deletion": "Confirm note deletion", - "This will permanently remove this note.": "This will permanently remove this note.", - "Successfully applied!": "Successfully applied!", - "Albanian": "Albanian", - "Chinese (zh-CN)": "Chinese (zh-CN)", - "Chinese (zh-TW)": "Chinese (zh-TW)", - "Danish": "Danish", - "Japanese": "Japanese", - "Korean": "Korean", - "Norwegian": "Norwegian", - "Polish": "Polish", - "Portuguese": "Portuguese", - "Spanish": "Spanish", - "You have to save!": "You have to save!", - "Russian": "Russian", + "Hotkeys": "Skróty klawiszowe", + "Show/Hide Boostnote": "Pokaż/Ukryj Boostnote", + "Restore": "Przywróć", + "Permanent Delete": "Usuń trwale", + "Confirm note deletion": "Potwierdź usunięcie notatki", + "This will permanently remove this note.": "Spodowoduje to trwałe usunięcie notatki", + "Successfully applied!": "Sukces!", + "Albanian": "Albański", + "Chinese (zh-CN)": "Chiński (zh-CN)", + "Chinese (zh-TW)": "Chiński (zh-TW)", + "Danish": "Duński", + "Japanese": "Japoński", + "Korean": "Koreański", + "Norwegian": "Norweski", + "Polish": "Polski", + "Portuguese": "Portugalski", + "Spanish": "Hiszpański", + "You have to save!": "Musisz zapisać!", + "Russian": "Rosyjski", "Editor Rulers": "Editor Rulers", - "Enable": "Enable", - "Disable": "Disable", + "Enable": "Włącz", + "Disable": "Wyłącz", "Sanitization": "Sanitization", - "Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)", - "Allow styles": "Allow styles", - "Allow dangerous html tags": "Allow dangerous html tags", + "Only allow secure html tags (recommended)": "Zezwól tylko na bezpieczne tagi HTML (zalecane)", + "Allow styles": "Zezwól na style", + "Allow dangerous html tags": "Zezwól na niebezpieczne tagi HTML", "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! ⚠" } From bb32c3a8d32376debfa7af153a65932a6a39dd98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20J=2E=20P=C3=A9rez=20Nieto?= Date: Wed, 5 Sep 2018 08:41:06 +0200 Subject: [PATCH 23/44] Improved es-ES translation. --- locales/es-ES.json | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/locales/es-ES.json b/locales/es-ES.json index 2ee7da71..a301dc9e 100644 --- a/locales/es-ES.json +++ b/locales/es-ES.json @@ -1,8 +1,8 @@ -{ + { "Notes": "Notas", "Tags": "Etiquetas", "Preferences": "Preferencias", - "Make a note": "Tomar una nota", + "Make a note": "Crear nota", "Ctrl": "Ctrl", "Ctrl(^)": "Ctrl", "to create a new note": "para crear una nueva nota", @@ -14,7 +14,7 @@ "STORAGE": "ALMACENAMIENTO", "FOLDER": "CARPETA", "CREATION DATE": "FECHA DE CREACIÓN", - "NOTE LINK": "ENLACE A LA NOTA", + "NOTE LINK": "ENLACE DE LA NOTA", ".md": ".md", ".txt": ".txt", ".html": ".html", @@ -23,7 +23,7 @@ "Storages": "Almacenamientos", "Add Storage Location": "Añadir ubicación de almacenamiento", "Add Folder": "Añadir carpeta", - "Open Storage folder": "Añadir carpeta de almacenamiento", + "Open Storage folder": "Abrir carpeta de almacenamiento", "Unlink": "Desvincular", "Edit": "Editar", "Delete": "Eliminar", @@ -39,8 +39,8 @@ "Editor Font Family": "Fuente del editor", "Editor Indent Style": "Estilo de indentado del editor", "Spaces": "Espacios", - "Tabs": "Tabulación", - "Switch to Preview": "Cambiar a Previsualización", + "Tabs": "Tabulaciónes", + "Switch to Preview": "Cambiar a previsualización", "When Editor Blurred": "Cuando el editor pierde el foco", "When Editor Blurred, Edit On Double Click": "Cuando el editor pierde el foco, editar con doble clic", "On Right Click": "Al hacer clic derecho", @@ -48,19 +48,19 @@ "default": "por defecto", "vim": "vim", "emacs": "emacs", - "⚠️ Please restart boostnote after you change the keymap": "⚠️ Reinicie boostnote después de cambiar el mapeo de teclas", + "⚠️ Please restart boostnote after you change the keymap": "⚠️ Por favor reinicie boostnote después de cambiar el mapeo de teclas", "Show line numbers in the editor": "Mostrar números de línea en el editor", "Allow editor to scroll past the last line": "Permitir al editor desplazarse más allá de la última línea", - "Bring in web page title when pasting URL on editor": "Al pegar una URL en el editor, insertar el título de la web automáticamente", - "Preview": "Previsualización", + "Bring in web page title when pasting URL on editor": "Al pegar una URL en el editor, insertar el título de la web", + "Preview": "Previsualizar", "Preview Font Size": "Previsualizar tamaño de la fuente", "Preview Font Family": "Previsualizar fuente", "Code block Theme": "Tema de los bloques de código", "Allow preview to scroll past the last line": "Permitir a la previsualización desplazarse más allá de la última línea", - "Show line numbers for preview code blocks": "Mostar números de línea al previsualizar bloques de código", + "Show line numbers for preview code blocks": "Mostrar números de línea al previsualizar bloques de código", "LaTeX Inline Open Delimiter": "Delimitador de apertura LaTeX en línea", "LaTeX Inline Close Delimiter": "Delimitador de cierre LaTeX en línea", - "LaTeX Block Open Delimiter": "Delimitado de apertura bloque LaTeX", + "LaTeX Block Open Delimiter": "Delimitador de apertura bloque LaTeX", "LaTeX Block Close Delimiter": "Delimitador de cierre bloque LaTeX", "PlantUML Server": "PlantUML Server", "Community": "Comunidad", @@ -71,20 +71,20 @@ "Twitter": "Twitter", "About": "Sobre", "Boostnote": "Boostnote", - "An open source note-taking app made for programmers just like you.": "Una aplicación para tomar notas de código abieto para programadores como tú.", + "An open source note-taking app made for programmers just like you.": "Una aplicación de código abierto para tomar notas hecho para programadores como tú.", "Website": "Página web", "Development": "Desarrollo", " : Development configurations for Boostnote.": " : Configuraciones de desarrollo para Boostnote.", "Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "License: GPL v3": "Licencia: GPL v3", "Analytics": "Analítica", - "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote recopila datos anónimos con el único propósito de mejorar la aplicación. No recopila ninguna información personal, como puede ser el contenido de sus notas.", + "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote recopila datos anónimos con el único propósito de mejorar la aplicación, y de ninguna manera recopila información personal como el contenido de sus notas.", "You can see how it works on ": "Puedes ver cómo funciona en ", "You can choose to enable or disable this option.": "Puedes elegir activar o desactivar esta opción.", - "Enable analytics to help improve Boostnote": "Activa analítica para ayudar a mejorar Boostnote", + "Enable analytics to help improve Boostnote": "Activar analítica para ayudar a mejorar Boostnote", "Crowdfunding": "Crowdfunding", "Dear everyone,": "Hola a todos,", - "Thank you for using Boostnote!": "Gracias por usar Boostnote!", + "Thank you for using Boostnote!": "¡Gracias por usar Boostnote!", "Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "Boostnote es utilizado en alrededor de 200 países y regiones diferentes por una increíble comunidad de desarrolladores.", "To continue supporting this growth, and to satisfy community expectations,": "Para continuar apoyando este crecimiento y satisfacer las expectativas de la comunidad,", "we would like to invest more time and resources in this project.": "nos gustaría invertir más tiempo y recursos en este proyecto.", @@ -99,7 +99,7 @@ "Show \"Saved to Clipboard\" notification when copying": "Mostrar la notificaión \"Guardado en Portapapeles\" al copiar", "All Notes": "Todas las notas", "Starred": "Destacado", - "Are you sure to ": "Estás seguro de ", + "Are you sure to ": "¿Estás seguro de ", " delete": " eliminar", "this folder?": "esta carpeta?", "Confirm": "Confirmar", @@ -129,8 +129,8 @@ "Restore": "Restaurar", "Permanent Delete": "Eliminar permanentemente", "Confirm note deletion": "Confirmar eliminación de nota", - "This will permanently remove this note.": "La nota se eliminará permanentemente.", - "Successfully applied!": "Aplicado con éxito!", + "This will permanently remove this note.": "Esto eliminará la nota permanentemente.", + "Successfully applied!": "¡Aplicado con éxito!", "Albanian": "Albanés", "Chinese (zh-CN)": "Chino - China", "Chinese (zh-TW)": "Chino - Taiwan", @@ -139,11 +139,11 @@ "Korean": "Coreano", "Norwegian": "Noruego", "Polish": "Polaco", - "Portuguese": "Portugues", + "Portuguese": "Portugués", "Spanish": "Español", - "You have to save!": "Tienes que guardar!", + "You have to save!": "¡Tienes que guardar!", "Russian": "Ruso", - "Command(⌘)": "Command(⌘)", + "Command(⌘)": "Comando(⌘)", "Editor Rulers": "Reglas del editor", "Enable": "Activar", "Disable": "Desactivar", @@ -151,6 +151,6 @@ "Only allow secure html tags (recommended)": "Solo permitir etiquetas html seguras (recomendado)", "Allow styles": "Permitir estilos", "Allow dangerous html tags": "Permitir etiquetas html peligrosas", - "⚠ 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! ⚠", - "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 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! ⚠": "⚠ Ha pegado un enlace a un archivo adjunto que no se puede encontrar en el almacenamiento de esta nota. Pegar enlaces a archivos adjuntos solo está soportado si el origen y el destino son el mismo almacenamiento. ¡Por favor arrastre el archivo en su lugar! ⚠", + "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convertir flechas textuales a símbolos bonitos. ⚠ Esto interferirá cuando use comentarios HTML en Markdown." } From d9783490ecdb2aff3ce7904415d8d44e672940b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20J=2E=20P=C3=A9rez=20Nieto?= Date: Wed, 5 Sep 2018 08:48:43 +0200 Subject: [PATCH 24/44] New phrase from last commit Added the new phrase from last commit. --- locales/es-ES.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/locales/es-ES.json b/locales/es-ES.json index a301dc9e..d78f8153 100644 --- a/locales/es-ES.json +++ b/locales/es-ES.json @@ -152,5 +152,6 @@ "Allow styles": "Permitir estilos", "Allow dangerous html tags": "Permitir etiquetas html peligrosas", "⚠ You have pasted a link referring an attachment that could not be found in the 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! ⚠": "⚠ Ha pegado un enlace a un archivo adjunto que no se puede encontrar en el almacenamiento de esta nota. Pegar enlaces a archivos adjuntos solo está soportado si el origen y el destino son el mismo almacenamiento. ¡Por favor arrastre el archivo en su lugar! ⚠", - "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convertir flechas textuales a símbolos bonitos. ⚠ Esto interferirá cuando use comentarios HTML en Markdown." + "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convertir flechas textuales a símbolos bonitos. ⚠ Esto interferirá cuando use comentarios HTML en Markdown.", + "Snippet Default Language": "Lenguaje por defecto de los fragmentos de código" } From 1c8e379fdddab1f431bfbb55764d674d01e7fcfe Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Wed, 5 Sep 2018 17:08:39 +0200 Subject: [PATCH 25/44] add option for default new note --- browser/lib/newNote.js | 62 +++++++++++++++++ browser/main/NewNoteButton/index.js | 23 +++++-- browser/main/modals/NewNoteModal.js | 66 ++----------------- browser/main/modals/PreferencesModal/UiTab.js | 17 +++++ 4 files changed, 102 insertions(+), 66 deletions(-) create mode 100644 browser/lib/newNote.js diff --git a/browser/lib/newNote.js b/browser/lib/newNote.js new file mode 100644 index 00000000..d9834dec --- /dev/null +++ b/browser/lib/newNote.js @@ -0,0 +1,62 @@ +import { hashHistory } from 'react-router' +import dataApi from 'browser/main/lib/dataApi' +import ee from 'browser/main/lib/eventEmitter' +import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' + +export function createMarkdownNote (storage, folder, dispatch, location) { + AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_MARKDOWN') + AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE') + return dataApi + .createNote(storage, { + type: 'MARKDOWN_NOTE', + folder: folder, + title: '', + content: '' + }) + .then(note => { + const noteHash = note.key + dispatch({ + type: 'UPDATE_NOTE', + note: note + }) + + hashHistory.push({ + pathname: location.pathname, + query: { key: noteHash } + }) + ee.emit('list:jump', noteHash) + ee.emit('detail:focus') + }) +} + +export function createSnippetNote (storage, folder, dispatch, location, config) { + AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_SNIPPET') + AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE') + return dataApi + .createNote(storage, { + type: 'SNIPPET_NOTE', + folder: folder, + title: '', + description: '', + snippets: [ + { + name: '', + mode: config.editor.snippetDefaultLanguage || 'text', + content: '' + } + ] + }) + .then(note => { + const noteHash = note.key + dispatch({ + type: 'UPDATE_NOTE', + note: note + }) + hashHistory.push({ + pathname: location.pathname, + query: { key: noteHash } + }) + ee.emit('list:jump', noteHash) + ee.emit('detail:focus') + }) +} \ No newline at end of file diff --git a/browser/main/NewNoteButton/index.js b/browser/main/NewNoteButton/index.js index 85dc7f40..85fbbfbf 100644 --- a/browser/main/NewNoteButton/index.js +++ b/browser/main/NewNoteButton/index.js @@ -7,6 +7,7 @@ import modal from 'browser/main/lib/modal' import NewNoteModal from 'browser/main/modals/NewNoteModal' import eventEmitter from 'browser/main/lib/eventEmitter' import i18n from 'browser/lib/i18n' +import { createMarkdownNote, createSnippetNote } from 'browser/lib/newNote' const { remote } = require('electron') const { dialog } = remote @@ -37,13 +38,21 @@ class NewNoteButton extends React.Component { const { location, dispatch, config } = this.props const { storage, folder } = this.resolveTargetFolder() - modal.open(NewNoteModal, { - storage: storage.key, - folder: folder.key, - dispatch, - location, - config - }) + console.log(config) + + if (config.ui.defaultNote === 'MARKDOWN_NOTE') { + createMarkdownNote(storage.key, folder.key, dispatch, location) + } else if (config.ui.defaultNote === 'SNIPPET_NOTE') { + createSnippetNote(storage.key, folder.key, dispatch, location, config) + } else { + modal.open(NewNoteModal, { + storage: storage.key, + folder: folder.key, + dispatch, + location, + config + }) + } } resolveTargetFolder () { diff --git a/browser/main/modals/NewNoteModal.js b/browser/main/modals/NewNoteModal.js index f6aa2c67..8b16f2a2 100644 --- a/browser/main/modals/NewNoteModal.js +++ b/browser/main/modals/NewNoteModal.js @@ -1,12 +1,9 @@ import React from 'react' import CSSModules from 'browser/lib/CSSModules' import styles from './NewNoteModal.styl' -import dataApi from 'browser/main/lib/dataApi' -import { hashHistory } from 'react-router' -import ee from 'browser/main/lib/eventEmitter' import ModalEscButton from 'browser/components/ModalEscButton' -import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' import i18n from 'browser/lib/i18n' +import { createMarkdownNote, createSnippetNote } from 'browser/lib/newNote' class NewNoteModal extends React.Component { constructor (props) { @@ -24,31 +21,10 @@ class NewNoteModal extends React.Component { } handleMarkdownNoteButtonClick (e) { - AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_MARKDOWN') - AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE') const { storage, folder, dispatch, location } = this.props - dataApi - .createNote(storage, { - type: 'MARKDOWN_NOTE', - folder: folder, - title: '', - content: '' - }) - .then(note => { - const noteHash = note.key - dispatch({ - type: 'UPDATE_NOTE', - note: note - }) - - hashHistory.push({ - pathname: location.pathname, - query: { key: noteHash } - }) - ee.emit('list:jump', noteHash) - ee.emit('detail:focus') - setTimeout(this.props.close, 200) - }) + createMarkdownNote(storage, folder, dispatch, location).then(() => { + setTimeout(this.props.close, 200) + }) } handleMarkdownNoteButtonKeyDown (e) { @@ -59,38 +35,10 @@ class NewNoteModal extends React.Component { } handleSnippetNoteButtonClick (e) { - AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_SNIPPET') - AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE') const { storage, folder, dispatch, location, config } = this.props - - dataApi - .createNote(storage, { - type: 'SNIPPET_NOTE', - folder: folder, - title: '', - description: '', - snippets: [ - { - name: '', - mode: config.editor.snippetDefaultLanguage || 'text', - content: '' - } - ] - }) - .then(note => { - const noteHash = note.key - dispatch({ - type: 'UPDATE_NOTE', - note: note - }) - hashHistory.push({ - pathname: location.pathname, - query: { key: noteHash } - }) - ee.emit('list:jump', noteHash) - ee.emit('detail:focus') - setTimeout(this.props.close, 200) - }) + createSnippetNote(storage, folder, dispatch, location, config).then(() => { + setTimeout(this.props.close, 200) + }) } handleSnippetNoteButtonKeyDown (e) { diff --git a/browser/main/modals/PreferencesModal/UiTab.js b/browser/main/modals/PreferencesModal/UiTab.js index 74047d44..6661d95d 100644 --- a/browser/main/modals/PreferencesModal/UiTab.js +++ b/browser/main/modals/PreferencesModal/UiTab.js @@ -67,6 +67,7 @@ class UiTab extends React.Component { ui: { theme: this.refs.uiTheme.value, language: this.refs.uiLanguage.value, + defaultNote: this.refs.defaultNote.value, showCopyNotification: this.refs.showCopyNotification.checked, confirmDeletion: this.refs.confirmDeletion.checked, showOnlyRelatedTags: this.refs.showOnlyRelatedTags.checked, @@ -202,6 +203,22 @@ class UiTab extends React.Component { +
    +
    + {i18n.__('Default New Note')} +
    +
    + +
    +
    +