diff --git a/.babelrc b/.babelrc index 92bb81ed..270349d2 100644 --- a/.babelrc +++ b/.babelrc @@ -5,7 +5,7 @@ "presets": ["react-hmre"] }, "test": { - "presets": ["react", "es2015"], + "presets": ["env" ,"react", "es2015"], "plugins": [ [ "babel-plugin-webpack-alias", { "config": "${PWD}/webpack.config.js" } ] ] diff --git a/.boostnoterc.sample b/.boostnoterc.sample index 8419061d..a7981f7f 100644 --- a/.boostnoterc.sample +++ b/.boostnoterc.sample @@ -23,6 +23,7 @@ "lineNumber": true }, "sortBy": "UPDATED_AT", + "sortTagsBy": "ALPHABETICAL", "ui": { "defaultNote": "ALWAYS_ASK", "disableDirectWrite": false, diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index be4b7e4f..f185492a 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -1,10 +1,25 @@ +# Current behavior + +# Expected behavior + +# Steps to reproduce + +1. +2. +3. + +# Environment + +- Version : +- OS Version and name : + +--> \ No newline at end of file diff --git a/__mocks__/electron.js b/__mocks__/electron.js new file mode 100644 index 00000000..2176fbac --- /dev/null +++ b/__mocks__/electron.js @@ -0,0 +1,7 @@ +module.exports = { + require: jest.genMockFunction(), + match: jest.genMockFunction(), + app: jest.genMockFunction(), + remote: jest.genMockFunction(), + dialog: jest.genMockFunction() +} diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js index eeb1a930..872e9ad7 100644 --- a/browser/components/CodeEditor.js +++ b/browser/components/CodeEditor.js @@ -109,6 +109,8 @@ export default class CodeEditor extends React.Component { scrollPastEnd: this.props.scrollPastEnd, inputStyle: 'textarea', dragDrop: false, + foldGutter: true, + gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], autoCloseBrackets: true, extraKeys: { Tab: function (cm) { @@ -275,11 +277,16 @@ export default class CodeEditor extends React.Component { handleDropImage (e) { e.preventDefault() - const imagePath = e.dataTransfer.files[0].path - const filename = path.basename(imagePath) + const ValidImageTypes = ['image/gif', 'image/jpeg', 'image/png'] - copyImage(imagePath, this.props.storageKey).then((imagePath) => { - const imageMd = `![${filename}](${path.join('/:storage', imagePath)})` + const file = e.dataTransfer.files[0] + const filePath = file.path + const filename = path.basename(filePath) + const fileType = file['type'] + + copyImage(filePath, this.props.storageKey).then((imagePath) => { + var showPreview = ValidImageTypes.indexOf(fileType) > 0 + const imageMd = `${showPreview ? '!' : ''}[${filename}](${path.join('/:storage', imagePath)})` this.insertImageMd(imageMd) }) } diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index bd5d3939..6e6bb9ec 100755 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -13,6 +13,7 @@ import htmlTextHelper from 'browser/lib/htmlTextHelper' import copy from 'copy-to-clipboard' import mdurl from 'mdurl' import exportNote from 'browser/main/lib/dataApi/exportNote' +import {escapeHtmlCharacters} from 'browser/lib/utils' const { remote } = require('electron') const { app } = remote @@ -208,7 +209,7 @@ export default class MarkdownPreview extends React.Component { const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme} = this.getStyleParams() const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, lineNumber) - const body = this.markdown.render(noteContent) + const body = this.markdown.render(escapeHtmlCharacters(noteContent)) const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES] files.forEach((file) => { @@ -394,6 +395,9 @@ export default class MarkdownPreview extends React.Component { _.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => { this.fixDecodedURI(el) + el.href = this.markdown.normalizeLinkText(el.href) + if (!/\/:storage/.test(el.href)) return + el.href = `file:///${this.markdown.normalizeLinkText(path.join(storagePath, 'images', path.basename(el.href)))}` el.addEventListener('click', this.anchorClickHandler) }) diff --git a/browser/components/StorageList.js b/browser/components/StorageList.js index 33557eb3..739a5f9a 100644 --- a/browser/components/StorageList.js +++ b/browser/components/StorageList.js @@ -10,8 +10,8 @@ import CSSModules from 'browser/lib/CSSModules' * @param {Array} storgaeList */ -const StorageList = ({storageList}) => ( -
+const StorageList = ({storageList, isFolded}) => ( +
{storageList.length > 0 ? storageList : (
No storage mount.
)} diff --git a/browser/components/StorageList.styl b/browser/components/StorageList.styl index dfb06b45..474f896b 100644 --- a/browser/components/StorageList.styl +++ b/browser/components/StorageList.styl @@ -4,6 +4,10 @@ top 180px overflow-y auto +.storageList-folded + @extend .storageList + width 44px + .storageList-empty padding 0 10px margin-top 15px diff --git a/browser/lib/confirmDeleteNote.js b/browser/lib/confirmDeleteNote.js new file mode 100644 index 00000000..80d1ffc7 --- /dev/null +++ b/browser/lib/confirmDeleteNote.js @@ -0,0 +1,23 @@ +import electron from 'electron' +import i18n from 'browser/lib/i18n' +const { remote } = electron +const { dialog } = remote + +export function confirmDeleteNote (confirmDeletion, permanent) { + if (confirmDeletion || permanent) { + const alertConfig = { + ype: 'warning', + message: i18n.__('Confirm note deletion'), + detail: i18n.__('This will permanently remove this note.'), + buttons: [i18n.__('Confirm'), i18n.__('Cancel')] + } + + const dialogButtonIndex = dialog.showMessageBox( + remote.getCurrentWindow(), alertConfig + ) + + return dialogButtonIndex === 0 + } + + return true +} diff --git a/browser/lib/i18n.js b/browser/lib/i18n.js index fe339072..f7bd96e9 100644 --- a/browser/lib/i18n.js +++ b/browser/lib/i18n.js @@ -1,8 +1,15 @@ +const path = require('path') +const { remote } = require('electron') +const { app } = remote + // load package for localization const i18n = new (require('i18n-2'))({ // setup some locales - other locales default to the first locale - locales: ['en', 'sq', 'zh-CN', 'zh-TW', 'da', 'fr', 'de', 'hu', 'ja', 'ko', 'no', 'pl', 'pt', 'es-ES'], + locales: [ 'da', 'de', 'en', 'es-ES', 'fr', 'hu', 'ja', 'ko', 'pl', 'pt-BR', 'pt-PT', 'ru', 'sq', 'zh-CN', 'zh-TW' ], extension: '.json', + directory: process.env.NODE_ENV === 'production' + ? path.join(app.getAppPath(), './locales') + : path.resolve('./locales'), devMode: false }) diff --git a/browser/lib/search.js b/browser/lib/search.js index dadc9a29..b42fd389 100644 --- a/browser/lib/search.js +++ b/browser/lib/search.js @@ -4,39 +4,28 @@ export default function searchFromNotes (notes, search) { if (search.trim().length === 0) return [] const searchBlocks = search.split(' ').filter(block => { return block !== '' }) - let foundNotes = findByWord(notes, searchBlocks[0]) + let foundNotes = notes searchBlocks.forEach((block) => { - foundNotes = findByWord(foundNotes, block) - if (block.match(/^#.+/)) { - foundNotes = foundNotes.concat(findByTag(notes, block)) - } + foundNotes = findByWordOrTag(foundNotes, block) }) return foundNotes } -function findByTag (notes, block) { - const tag = block.match(/#(.+)/)[1] - const regExp = new RegExp(_.escapeRegExp(tag), 'i') +function findByWordOrTag (notes, block) { + let tag = block + if (tag.match(/^#.+/)) { + tag = tag.match(/#(.+)/)[1] + } + const tagRegExp = new RegExp(_.escapeRegExp(tag), 'i') + const wordRegExp = new RegExp(_.escapeRegExp(block), 'i') return notes.filter((note) => { - if (!_.isArray(note.tags)) return false - return note.tags.some((_tag) => { - return _tag.match(regExp) - }) - }) -} - -function findByWord (notes, block) { - const regExp = new RegExp(_.escapeRegExp(block), 'i') - return notes.filter((note) => { - if (_.isArray(note.tags) && note.tags.some((_tag) => { - return _tag.match(regExp) - })) { + if (_.isArray(note.tags) && note.tags.some((_tag) => _tag.match(tagRegExp))) { return true } if (note.type === 'SNIPPET_NOTE') { - return note.description.match(regExp) + return note.description.match(wordRegExp) } else if (note.type === 'MARKDOWN_NOTE') { - return note.content.match(regExp) + return note.content.match(wordRegExp) } return false }) diff --git a/browser/lib/utils.js b/browser/lib/utils.js index be66f2ec..f67ca377 100644 --- a/browser/lib/utils.js +++ b/browser/lib/utils.js @@ -6,6 +6,55 @@ export function lastFindInArray (array, callback) { } } -export default { - lastFindInArray +export function escapeHtmlCharacters (text) { + const matchHtmlRegExp = /["'&<>]/ + const str = '' + text + const match = matchHtmlRegExp.exec(str) + + if (!match) { + return str + } + + let escape + let html = '' + let index = 0 + let lastIndex = 0 + + for (index = match.index; index < str.length; index++) { + switch (str.charCodeAt(index)) { + case 34: // " + escape = '"' + break + case 38: // & + escape = '&' + break + case 39: // ' + escape = ''' + break + case 60: // < + escape = '<' + break + case 62: // > + escape = '>' + break + default: + continue + } + + if (lastIndex !== index) { + html += str.substring(lastIndex, index) + } + + lastIndex = index + 1 + html += escape + } + + return lastIndex !== index + ? html + str.substring(lastIndex, index) + : html +} + +export default { + lastFindInArray, + escapeHtmlCharacters } diff --git a/browser/main/Detail/MarkdownNoteDetail.js b/browser/main/Detail/MarkdownNoteDetail.js index 6821bf2f..05883c0e 100755 --- a/browser/main/Detail/MarkdownNoteDetail.js +++ b/browser/main/Detail/MarkdownNoteDetail.js @@ -28,6 +28,7 @@ import InfoPanelTrashed from './InfoPanelTrashed' import { formatDate } from 'browser/lib/date-formatter' import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus' import striptags from 'striptags' +import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote' class MarkdownNoteDetail extends React.Component { constructor (props) { @@ -181,10 +182,10 @@ class MarkdownNoteDetail extends React.Component { handleTrashButtonClick (e) { const { note } = this.state const { isTrashed } = note - const { confirmDeletion } = this.props + const { confirmDeletion } = this.props.config.ui if (isTrashed) { - if (confirmDeletion(true)) { + if (confirmDeleteNote(confirmDeletion, true)) { const {note, dispatch} = this.props dataApi .deleteNote(note.storage, note.key) @@ -201,7 +202,7 @@ class MarkdownNoteDetail extends React.Component { .then(() => ee.emit('list:next')) } } else { - if (confirmDeletion()) { + if (confirmDeleteNote(confirmDeletion, false)) { note.isTrashed = true this.setState({ @@ -437,8 +438,7 @@ MarkdownNoteDetail.propTypes = { style: PropTypes.shape({ left: PropTypes.number }), - ignorePreviewPointerEvents: PropTypes.bool, - confirmDeletion: PropTypes.bool.isRequired + ignorePreviewPointerEvents: PropTypes.bool } export default CSSModules(MarkdownNoteDetail, styles) diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js index 620de512..411027d5 100644 --- a/browser/main/Detail/SnippetNoteDetail.js +++ b/browser/main/Detail/SnippetNoteDetail.js @@ -27,6 +27,7 @@ import InfoPanel from './InfoPanel' import InfoPanelTrashed from './InfoPanelTrashed' import { formatDate } from 'browser/lib/date-formatter' import i18n from 'browser/lib/i18n' +import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote' function pass (name) { switch (name) { @@ -197,10 +198,10 @@ class SnippetNoteDetail extends React.Component { handleTrashButtonClick (e) { const { note } = this.state const { isTrashed } = note - const { confirmDeletion } = this.props + const { confirmDeletion } = this.props.config.ui if (isTrashed) { - if (confirmDeletion(true)) { + if (confirmDeleteNote(confirmDeletion, true)) { const {note, dispatch} = this.props dataApi .deleteNote(note.storage, note.key) @@ -217,7 +218,7 @@ class SnippetNoteDetail extends React.Component { .then(() => ee.emit('list:next')) } } else { - if (confirmDeletion()) { + if (confirmDeleteNote(confirmDeletion, false)) { note.isTrashed = true this.setState({ @@ -883,8 +884,7 @@ SnippetNoteDetail.propTypes = { style: PropTypes.shape({ left: PropTypes.number }), - ignorePreviewPointerEvents: PropTypes.bool, - confirmDeletion: PropTypes.bool.isRequired + ignorePreviewPointerEvents: PropTypes.bool } export default CSSModules(SnippetNoteDetail, styles) diff --git a/browser/main/Detail/index.js b/browser/main/Detail/index.js index df38132c..7d2b4cba 100644 --- a/browser/main/Detail/index.js +++ b/browser/main/Detail/index.js @@ -33,26 +33,6 @@ class Detail extends React.Component { ee.off('detail:delete', this.deleteHandler) } - confirmDeletion (permanent) { - if (this.props.config.ui.confirmDeletion || permanent) { - const electron = require('electron') - const { remote } = electron - const { dialog } = remote - - const alertConfig = { - type: 'warning', - message: i18n.__('Confirm note deletion'), - detail: i18n.__('This will permanently remove this note.'), - buttons: [i18n.__('Confirm'), i18n.__('Cancel')] - } - - const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), alertConfig) - return dialogueButtonIndex === 0 - } - - return true - } - render () { const { location, data, config } = this.props let note = null @@ -82,7 +62,6 @@ class Detail extends React.Component { this.confirmDeletion(permanent)} ref='root' {..._.pick(this.props, [ 'dispatch', @@ -99,7 +78,6 @@ class Detail extends React.Component { this.confirmDeletion(permanent)} ref='root' {..._.pick(this.props, [ 'dispatch', diff --git a/browser/main/Main.js b/browser/main/Main.js index 2f431374..14a56225 100644 --- a/browser/main/Main.js +++ b/browser/main/Main.js @@ -140,43 +140,37 @@ class Main extends React.Component { componentDidMount () { const { dispatch, config } = this.props - if (config.ui.theme === 'dark') { - document.body.setAttribute('data-theme', 'dark') - } else if (config.ui.theme === 'white') { - document.body.setAttribute('data-theme', 'white') - } else if (config.ui.theme === 'solarized-dark') { - document.body.setAttribute('data-theme', 'solarized-dark') + const supportedThemes = [ + 'dark', + 'white', + 'solarized-dark' + ] + + if (supportedThemes.indexOf(config.ui.theme) !== -1) { + document.body.setAttribute('data-theme', config.ui.theme) } else { document.body.setAttribute('data-theme', 'default') } - if (config.ui.language === 'sq') { - i18n.setLocale('sq') - } else if (config.ui.language === 'zh-CN') { - i18n.setLocale('zh-CN') - } else if (config.ui.language === 'zh-TW') { - i18n.setLocale('zh-TW') - } else if (config.ui.language === 'da') { - i18n.setLocale('da') - } else if (config.ui.language === 'fr') { - i18n.setLocale('fr') - } else if (config.ui.language === 'de') { - i18n.setLocale('de') - } else if (config.ui.language === 'hu') { - i18n.setLocale('hu') - } else if (config.ui.language === 'ja') { - i18n.setLocale('ja') - } else if (config.ui.language === 'ko') { - i18n.setLocale('ko') - } else if (config.ui.language === 'no') { - i18n.setLocale('no') - } else if (config.ui.language === 'pl') { - i18n.setLocale('pl') - } else if (config.ui.language === 'pt') { - i18n.setLocale('pt') - } else if (config.ui.language === 'ru') { - i18n.setLocale('ru') - } else if (config.ui.language === 'es-ES') { - i18n.setLocale('es-ES') + + const supportedLanguages = [ + 'sq', + 'zh-CN', + 'zh-TW', + 'da', + 'fr', + 'de', + 'hu', + 'ja', + 'ko', + 'no', + 'pl', + 'pt', + 'ru', + 'es-ES' + ] + + if (supportedLanguages.indexOf(config.ui.language) !== -1) { + i18n.setLocale(config.ui.language) } else { i18n.setLocale('en') } diff --git a/browser/main/NoteList/index.js b/browser/main/NoteList/index.js index c4da6358..e413c647 100644 --- a/browser/main/NoteList/index.js +++ b/browser/main/NoteList/index.js @@ -18,6 +18,7 @@ import copy from 'copy-to-clipboard' import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' import Markdown from '../../lib/markdown' import i18n from 'browser/lib/i18n' +import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote' const { remote } = require('electron') const { Menu, MenuItem, dialog } = remote @@ -326,8 +327,10 @@ class NoteList extends React.Component { } if (location.pathname.match(/\/searched/)) { - const searchInputText = document.getElementsByClassName('searchInput')[0].value - if (searchInputText === '') { + const searchInputText = params.searchword + const allNotes = data.noteMap.map((note) => note) + this.contextNotes = allNotes + if (searchInputText === undefined || searchInputText === '') { return this.sortByPin(this.contextNotes) } return searchFromNotes(this.contextNotes, searchInputText) @@ -481,50 +484,53 @@ class NoteList extends React.Component { const openBlogLabel = i18n.__('Open Blog') const menu = new Menu() - if (!location.pathname.match(/\/starred|\/trash/)) { - menu.append(new MenuItem({ - label: pinLabel, - click: this.pinToTop - })) - } if (location.pathname.match(/\/trash/)) { menu.append(new MenuItem({ label: restoreNote, click: this.restoreNote })) - } - - menu.append(new MenuItem({ - label: deleteLabel, - click: this.deleteNote - })) - menu.append(new MenuItem({ - label: cloneNote, - click: this.cloneNote.bind(this) - })) - menu.append(new MenuItem({ - label: copyNoteLink, - click: this.copyNoteLink(note) - })) - if (note.type === 'MARKDOWN_NOTE') { - if (note.blog && note.blog.blogLink && note.blog.blogId) { + menu.append(new MenuItem({ + label: deleteLabel, + click: this.deleteNote + })) + } else { + if (!location.pathname.match(/\/starred/)) { menu.append(new MenuItem({ - label: updateLabel, - click: this.publishMarkdown.bind(this) - })) - menu.append(new MenuItem({ - label: openBlogLabel, - click: () => this.openBlog.bind(this)(note) - })) - } else { - menu.append(new MenuItem({ - label: publishLabel, - click: this.publishMarkdown.bind(this) + label: pinLabel, + click: this.pinToTop })) } + menu.append(new MenuItem({ + label: deleteLabel, + click: this.deleteNote + })) + menu.append(new MenuItem({ + label: cloneNote, + click: this.cloneNote.bind(this) + })) + menu.append(new MenuItem({ + label: copyNoteLink, + click: this.copyNoteLink(note) + })) + if (note.type === 'MARKDOWN_NOTE') { + if (note.blog && note.blog.blogLink && note.blog.blogId) { + menu.append(new MenuItem({ + label: updateLabel, + click: this.publishMarkdown.bind(this) + })) + menu.append(new MenuItem({ + label: openBlogLabel, + click: () => this.openBlog.bind(this)(note) + })) + } else { + menu.append(new MenuItem({ + label: publishLabel, + click: this.publishMarkdown.bind(this) + })) + } + } } - menu.popup() } @@ -580,16 +586,11 @@ class NoteList extends React.Component { const notes = this.notes.map((note) => Object.assign({}, note)) const selectedNotes = findNotesByKeys(notes, selectedNoteKeys) const firstNote = selectedNotes[0] + const { confirmDeletion } = this.props.config.ui if (firstNote.isTrashed) { - const noteExp = selectedNotes.length > 1 ? 'notes' : 'note' - const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), { - type: 'warning', - message: i18n.__('Confirm note deletion'), - detail: `This will permanently remove ${selectedNotes.length} ${noteExp}.`, - buttons: [i18n.__('Confirm'), i18n.__('Cancel')] - }) - if (dialogueButtonIndex === 1) return + if (!confirmDeleteNote(confirmDeletion, true)) return + Promise.all( selectedNotes.map((note) => { return dataApi @@ -610,6 +611,8 @@ class NoteList extends React.Component { }) console.log('Notes were all deleted') } else { + if (!confirmDeleteNote(confirmDeletion, false)) return + Promise.all( selectedNotes.map((note) => { note.isTrashed = true diff --git a/browser/main/SideNav/SideNav.styl b/browser/main/SideNav/SideNav.styl index a0ffb2e7..666ae0cd 100644 --- a/browser/main/SideNav/SideNav.styl +++ b/browser/main/SideNav/SideNav.styl @@ -30,11 +30,33 @@ display flex flex-direction column -.tag-title - padding-left 15px - padding-bottom 13px - p - color $ui-button-default-color +.tag-control + display flex + height 30px + line-height 25px + overflow hidden + .tag-control-title + padding-left 15px + padding-bottom 13px + flex 1 + p + color $ui-button-default-color + .tag-control-sortTagsBy + user-select none + font-size 12px + color $ui-inactive-text-color + margin-left 12px + margin-right 12px + .tag-control-sortTagsBy-select + appearance: none; + margin-left 5px + color $ui-inactive-text-color + padding 0 + border none + background-color transparent + outline none + cursor pointer + font-size 12px .tagList overflow-y auto diff --git a/browser/main/SideNav/index.js b/browser/main/SideNav/index.js index c3ad11ce..f8a65013 100644 --- a/browser/main/SideNav/index.js +++ b/browser/main/SideNav/index.js @@ -82,7 +82,7 @@ class SideNav extends React.Component { } SideNavComponent (isFolded, storageList) { - const { location, data } = this.props + const { location, data, config } = this.props const isHomeActive = !!location.pathname.match(/^\/home$/) const isStarredActive = !!location.pathname.match(/^\/starred$/) @@ -108,15 +108,30 @@ class SideNav extends React.Component { handleFilterButtonContextMenu={this.handleFilterButtonContextMenu.bind(this)} /> - +
) } else { component = (
-
-

{i18n.__('Tags')}

+
+
+

{i18n.__('Tags')}

+
+
+ + +
{this.tagListComponent(data)} @@ -129,17 +144,21 @@ class SideNav extends React.Component { } tagListComponent () { - const { data, location } = this.props - const tagList = _.sortBy(data.tagNoteMap.map((tag, name) => { - return { name, size: tag.size } - }), ['name']) + const { data, location, config } = this.props + let tagList = _.sortBy(data.tagNoteMap.map( + (tag, name) => ({name, size: tag.size})), + ['name'] + ) + if (config.sortTagsBy === 'COUNTER') { + tagList = _.sortBy(tagList, item => (0 - item.size)) + } return ( tagList.map(tag => { return ( @@ -159,6 +178,20 @@ class SideNav extends React.Component { router.push(`/tags/${name}`) } + handleSortTagsByChange (e) { + const { dispatch } = this.props + + const config = { + sortTagsBy: e.target.value + } + + ConfigManager.set(config) + dispatch({ + type: 'SET_CONFIG', + config + }) + } + emptyTrash (entries) { const { dispatch } = this.props const deletionPromises = entries.map((note) => { diff --git a/browser/main/TopBar/index.js b/browser/main/TopBar/index.js index 86cc6b2a..ae4d9664 100644 --- a/browser/main/TopBar/index.js +++ b/browser/main/TopBar/index.js @@ -28,6 +28,14 @@ class TopBar extends React.Component { } componentDidMount () { + const { params } = this.props + const searchWord = params.searchword + if (searchWord !== undefined) { + this.setState({ + search: searchWord, + isSearching: true + }) + } ee.on('top:focus-search', this.focusSearchHandler) ee.on('code:init', this.codeInitHandler) } @@ -97,9 +105,10 @@ class TopBar extends React.Component { this.setState({ isConfirmTranslation: true }) - router.push('/searched') + const keyword = this.refs.searchInput.value + router.push(`/searched/${encodeURIComponent(keyword)}`) this.setState({ - search: this.refs.searchInput.value + search: keyword }) } } @@ -108,7 +117,7 @@ class TopBar extends React.Component { const { router } = this.context const keyword = this.refs.searchInput.value if (this.state.isAlphabet || this.state.isConfirmTranslation) { - router.push('/searched') + router.push(`/searched/${encodeURIComponent(keyword)}`) } else { e.preventDefault() } diff --git a/browser/main/global.styl b/browser/main/global.styl index 1b40587c..613c7611 100644 --- a/browser/main/global.styl +++ b/browser/main/global.styl @@ -108,6 +108,21 @@ body[data-theme="dark"] background #B1D7FE ::selection background #B1D7FE +.CodeMirror-foldmarker + font-family: arial + +.CodeMirror-foldgutter + width: .7em + +.CodeMirror-foldgutter-open, +.CodeMirror-foldgutter-folded + cursor: pointer + +.CodeMirror-foldgutter-open:after + content: "\25BE" + +.CodeMirror-foldgutter-folded:after + content: "\25B8" .sortableItemHelper z-index modalZIndex + 5 diff --git a/browser/main/index.js b/browser/main/index.js index d9c7456e..6e8bdcc5 100644 --- a/browser/main/index.js +++ b/browser/main/index.js @@ -24,6 +24,45 @@ document.addEventListener('dragover', function (e) { e.stopPropagation() }) +// prevent menu from popup when alt pressed +// but still able to toggle menu when only alt is pressed +let isAltPressing = false +let isAltWithMouse = false +let isAltWithOtherKey = false +let isOtherKey = false + +document.addEventListener('keydown', function (e) { + if (e.key === 'Alt') { + isAltPressing = true + if (isOtherKey) { + isAltWithOtherKey = true + } + } else { + if (isAltPressing) { + isAltWithOtherKey = true + } + isOtherKey = true + } +}) + +document.addEventListener('mousedown', function (e) { + if (isAltPressing) { + isAltWithMouse = true + } +}) + +document.addEventListener('keyup', function (e) { + if (e.key === 'Alt') { + if (isAltWithMouse || isAltWithOtherKey) { + e.preventDefault() + } + isAltWithMouse = false + isAltWithOtherKey = false + isAltPressing = false + isOtherKey = false + } +}) + document.addEventListener('click', function (e) { const className = e.target.className if (!className && typeof (className) !== 'string') return @@ -64,7 +103,9 @@ ReactDOM.render(( - + + + diff --git a/browser/main/lib/ConfigManager.js b/browser/main/lib/ConfigManager.js index 157973ea..3e1a2162 100644 --- a/browser/main/lib/ConfigManager.js +++ b/browser/main/lib/ConfigManager.js @@ -16,6 +16,7 @@ export const DEFAULT_CONFIG = { listWidth: 280, navWidth: 200, sortBy: 'UPDATED_AT', // 'CREATED_AT', 'UPDATED_AT', 'APLHABETICAL' + sortTagsBy: 'ALPHABETICAL', // 'ALPHABETICAL', 'COUNTER' listStyle: 'DEFAULT', // 'DEFAULT', 'SMALL' amaEnabled: true, hotkey: { @@ -138,37 +139,7 @@ function set (updates) { document.body.setAttribute('data-theme', 'default') } - if (newConfig.ui.language === 'sq') { - i18n.setLocale('sq') - } else if (newConfig.ui.language === 'zh-CN') { - i18n.setLocale('zh-CN') - } else if (newConfig.ui.language === 'zh-TW') { - i18n.setLocale('zh-TW') - } else if (newConfig.ui.language === 'da') { - i18n.setLocale('da') - } else if (newConfig.ui.language === 'fr') { - i18n.setLocale('fr') - } else if (newConfig.ui.language === 'de') { - i18n.setLocale('de') - } else if (newConfig.ui.language === 'hu') { - i18n.setLocale('hu') - } else if (newConfig.ui.language === 'ja') { - i18n.setLocale('ja') - } else if (newConfig.ui.language === 'ko') { - i18n.setLocale('ko') - } else if (newConfig.ui.language === 'no') { - i18n.setLocale('no') - } else if (newConfig.ui.language === 'pl') { - i18n.setLocale('pl') - } else if (newConfig.ui.language === 'pt') { - i18n.setLocale('pt') - } else if (newConfig.ui.language === 'ru') { - i18n.setLocale('ru') - } else if (newConfig.ui.language === 'es-ES') { - i18n.setLocale('es-ES') - } else { - i18n.setLocale('en') - } + i18n.setLocale(newConfig.ui.language) let editorTheme = document.getElementById('editorTheme') if (editorTheme == null) { diff --git a/browser/main/lib/dataApi/resolveStorageNotes.js b/browser/main/lib/dataApi/resolveStorageNotes.js index 5684f06e..fa3f19ae 100644 --- a/browser/main/lib/dataApi/resolveStorageNotes.js +++ b/browser/main/lib/dataApi/resolveStorageNotes.js @@ -27,9 +27,12 @@ function resolveStorageNotes (storage) { data.storage = storage.key return data } catch (err) { - console.error(notePath) + console.error(`error on note path: ${notePath}, error: ${err}`) } }) + .filter(function filterOnlyNoteObject (noteObj) { + return typeof noteObj === 'object' + }) return Promise.resolve(notes) } diff --git a/browser/main/modals/PreferencesModal/UiTab.js b/browser/main/modals/PreferencesModal/UiTab.js index 748c3914..a092129a 100644 --- a/browser/main/modals/PreferencesModal/UiTab.js +++ b/browser/main/modals/PreferencesModal/UiTab.js @@ -194,9 +194,10 @@ class UiTab extends React.Component { - + + - +
diff --git a/lib/main.html b/lib/main.html index 830d3b48..5e5d13c3 100644 --- a/lib/main.html +++ b/lib/main.html @@ -85,6 +85,7 @@ + @@ -92,6 +93,11 @@ + + + + + diff --git a/locales/en.json b/locales/en.json index 8a4a8eff..b8250ea7 100644 --- a/locales/en.json +++ b/locales/en.json @@ -111,6 +111,7 @@ "Updated": "Updated", "Created": "Created", "Alphabetically": "Alphabetically", + "Counter": "Counter", "Default View": "Default View", "Compressed View": "Compressed View", "Search": "Search", diff --git a/locales/hu.json b/locales/hu.json index 37c19e48..74cd5d04 100644 --- a/locales/hu.json +++ b/locales/hu.json @@ -111,6 +111,7 @@ "Updated": "Módosítás", "Created": "Létrehozás", "Alphabetically": "Ábécé sorrendben", + "Counter": "Számláló", "Default View": "Alapértelmezett Nézet", "Compressed View": "Tömörített Nézet", "Search": "Keresés", diff --git a/locales/zh-CN.json b/locales/zh-CN.json old mode 100644 new mode 100755 index 01a6fe0b..b736520d --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -1,138 +1,138 @@ { - "Notes": "Notes", - "Tags": "Tags", - "Preferences": "Preferences", - "Make a note": "Make a note", + "Notes": "笔记", + "Tags": "标签", + "Preferences": "首选项", + "Make a note": "新建笔记", "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", - "STORAGE": "STORAGE", - "FOLDER": "FOLDER", - "CREATION DATE": "CREATION DATE", - "NOTE LINK": "NOTE LINK", + "to create a new note": "新建笔记", + "Toggle Mode": "切换模式", + "Trash": "废纸篓", + "MODIFICATION DATE": "更改时间", + "Words": "单词", + "Letters": "字数", + "STORAGE": "本地存储", + "FOLDER": "文件夹", + "CREATION DATE": "创建时间", + "NOTE LINK": "笔记链接", ".md": ".md", ".txt": ".txt", ".html": ".html", - "Print": "Print", - "Your preferences for Boostnote": "Your preferences for Boostnote", - "Storages": "Storages", - "Add Storage Location": "Add Storage Location", - "Add Folder": "Add Folder", - "Open Storage folder": "Open Storage folder", - "Unlink": "Unlink", - "Edit": "Edit", - "Delete": "Delete", - "Interface": "Interface", - "Interface Theme": "Interface Theme", - "Default": "Default", + "Print": "打印", + "Your preferences for Boostnote": "个性设置", + "Storages": "本地存储", + "Add Storage Location": "添加一个本地存储位置", + "Add Folder": "新建文件夹", + "Open Storage folder": "打开本地存储位置", + "Unlink": "取消链接", + "Edit": "编辑", + "Delete": "删除", + "Interface": "界面", + "Interface Theme": "主题", + "Default": "默认", "White": "White", "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", + "Show a confirmation dialog when deleting notes": "删除笔记的时候,显示确认框", + "Editor Theme": "编辑器主题", + "Editor Font Size": "编辑器字号", + "Editor Font Family": "编辑器字体", + "Editor Indent Style": "缩进风格", + "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", + "Switch to Preview": "快速切换到预览界面", + "When Editor Blurred": "当编辑器失去焦点的时候,切换到预览界面", + "When Editor Blurred, Edit On Double Click": "当编辑器失去焦点的时候预览,双击切换到编辑界面", + "On Right Click": "右键点击切换两个界面", + "Editor Keymap": "编辑器 Keymap", + "default": "默认", "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", - "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", - "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", - "Community": "Community", - "Subscribe to Newsletter": "Subscribe to Newsletter", + "⚠️ Please restart boostnote after you change the keymap": "⚠️ 设置好快捷键后,记得重启boostnote", + "Show line numbers in the editor": "在编辑器中显示行号", + "Allow editor to scroll past the last line": "允许编辑器滚动到最后一行", + "Bring in web page title when pasting URL on editor": "粘贴网页链接的时候,显示为网页标题", + "Preview": "预览器", + "Preview Font Size": "预览器字号", + "Preview Font Family": "预览器字体", + "Code block Theme": "代码块主题", + "Allow preview to scroll past the last line": "允许预览器滚动到最后一行", + "Show line numbers for preview code blocks": "在预览器中显示行号", + "LaTeX Inline Open Delimiter": "LaTeX 单行开头分隔符", + "LaTeX Inline Close Delimiter": "LaTeX 单行结尾分隔符", + "LaTeX Block Open Delimiter": "LaTeX 多行开头分隔符", + "LaTeX Block Close Delimiter": "LaTeX 多行结尾分隔符", + "Community": "社区", + "Subscribe to Newsletter": "订阅邮件", "GitHub": "GitHub", - "Blog": "Blog", + "Blog": "博客", "Facebook Group": "Facebook Group", "Twitter": "Twitter", - "About": "About", + "About": "关于", "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", - "Development": "Development", - " : Development configurations for Boostnote.": " : Development configurations for Boostnote.", + "An open source note-taking app made for programmers just like you.": "一款专门为程序员朋友量身打造的开源笔记", + "Website": "官网", + "Development": "开发", + " : Development configurations for Boostnote.": " : 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", - "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,", - "Boostnote maintainers": "Boostnote maintainers", - "Support via OpenCollective": "Support via OpenCollective", - "Language": "Language", + "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 收集匿名数据只为了提升软件使用体验,绝对不收集任何个人信息(包括笔记内容)", + "You can see how it works on ": "你可以看看它的源码是如何运作的 ", + "You can choose to enable or disable this option.": "你可以选择开启或不开启这个功能", + "Enable analytics to help improve Boostnote": "允许对数据进行分析,帮助我们改进Boostnote", + "Crowdfunding": "众筹", + "Dear everyone,": "亲爱的用户:", + "Thank you for using Boostnote!": "谢谢你使用Boostnote!", + "Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "大约有200个不同的国家和地区的优秀开发者们都在使用Boostnote!", + "To continue supporting this growth, and to satisfy community expectations,": "为了继续支持这种发展,和满足社区的期待,", + "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!": "如果你喜欢这款软件并且看好它的潜力, 请在OpenCollective上支持我们!", + "Thanks,": "十分感谢!", + "Boostnote maintainers": "Boostnote的维护人员", + "Support via OpenCollective": "在OpenCollective上支持我们", + "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", + "Show \"Saved to Clipboard\" notification when copying": "复制的时候,显示 \"已复制\" 提示", + "All Notes": "所有笔记", + "Starred": "星标收藏", + "Are you sure to ": "你确定要", + " delete": " 删除", + "this folder?": "这个文件夹?", + "Confirm": "确认", + "Cancel": "取消", + "Markdown Note": "Markdown笔记", + "This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "创建文档,清单,代码块甚至是Latex格式文档", + "Snippet Note": "代码笔记", + "This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "创建代码片段,支持多种语法代码片段", + "Tab to switch format": "使用Tab键切换格式", + "Updated": "更新时间", + "Created": "创建时间", + "Alphabetically": "A~Z排序", + "Default View": "默认视图", + "Compressed View": "列表视图", + "Search": "搜索", + "Blog Type": "博客类型", + "Blog Address": "博客地址", + "Save": "保存", "Auth": "Auth", - "Authentication Method": "Authentication Method", + "Authentication Method": "认证方法", "JWT": "JWT", "USER": "USER", "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!", + "Storage": "本地存储", + "Hotkeys": "快捷键", + "Show/Hide Boostnote": "显示/隐藏 Boostnote", + "Restore": "恢复", + "Permanent Delete": "永久删除", + "Confirm note deletion": "确认删除笔记", + "This will permanently remove this note.": "永久地删除这条笔记", + "Successfully applied!": "设置成功", "Albanian": "Albanian", - "Chinese (zh-CN)": "Chinese (zh-CN)", - "Chinese (zh-TW)": "Chinese (zh-TW)", + "Chinese (zh-CN)": "简体中文", + "Chinese (zh-TW)": "繁體中文", "Danish": "Danish", "Japanese": "Japanese", "Korean": "Korean", @@ -140,13 +140,13 @@ "Polish": "Polish", "Portuguese": "Portuguese", "Spanish": "Spanish", - "You have to save!": "You have to save!", + "You have to save!": "你必须保存一下!", "Russian": "Russian", "Editor Rulers": "Editor Rulers", - "Enable": "Enable", - "Disable": "Disable", - "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" + "Enable": "开启", + "Disable": "关闭", + "Sanitization": "代码处理", + "Only allow secure html tags (recommended)": "只允许安全的html标签(推荐)", + "Allow styles": "允许样式", + "Allow dangerous html tags": "允许危险的html标签" } diff --git a/locales/zh-TW.json b/locales/zh-TW.json old mode 100644 new mode 100755 index 01a6fe0b..06f40608 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -1,138 +1,138 @@ { - "Notes": "Notes", - "Tags": "Tags", - "Preferences": "Preferences", - "Make a note": "Make a note", + "Notes": "筆記", + "Tags": "標籤", + "Preferences": "首選項", + "Make a note": "新建筆記", "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", - "STORAGE": "STORAGE", - "FOLDER": "FOLDER", - "CREATION DATE": "CREATION DATE", - "NOTE LINK": "NOTE LINK", + "to create a new note": "新建筆記", + "Toggle Mode": "切換模式", + "Trash": "廢紙簍", + "MODIFICATION DATE": "更改時間", + "Words": "單詞", + "Letters": "字數", + "STORAGE": "本地儲存", + "FOLDER": "資料夾", + "CREATION DATE": "創建時間", + "NOTE LINK": "筆記鏈接", ".md": ".md", ".txt": ".txt", ".html": ".html", - "Print": "Print", - "Your preferences for Boostnote": "Your preferences for Boostnote", - "Storages": "Storages", - "Add Storage Location": "Add Storage Location", - "Add Folder": "Add Folder", - "Open Storage folder": "Open Storage folder", - "Unlink": "Unlink", - "Edit": "Edit", - "Delete": "Delete", - "Interface": "Interface", - "Interface Theme": "Interface Theme", - "Default": "Default", + "Print": "列印", + "Your preferences for Boostnote": "個性設置", + "Storages": "本地儲存", + "Add Storage Location": "添加一個本地儲存位置", + "Add Folder": "新建資料夾", + "Open Storage folder": "打開一個本地儲存位置", + "Unlink": "取消鏈接", + "Edit": "編輯", + "Delete": "刪除", + "Interface": "界面", + "Interface Theme": "主題", + "Default": "默認", "White": "White", "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", + "Show a confirmation dialog when deleting notes": "刪除筆記的時候,顯示確認框", + "Editor Theme": "編輯器主題", + "Editor Font Size": "編輯器字型大小", + "Editor Font Family": "編輯器字體", + "Editor Indent Style": "縮進風格", + "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", + "Switch to Preview": "快速切換到預覽界面", + "When Editor Blurred": "當編輯器失去焦點的時候,切換到預覽界面", + "When Editor Blurred, Edit On Double Click": "當編輯器失去焦點的時候預覽,雙擊切換到編輯界面", + "On Right Click": "右鍵點擊切換兩個界面", + "Editor Keymap": "編輯器 Keymap", + "default": "默認", "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", - "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", - "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", - "Community": "Community", - "Subscribe to Newsletter": "Subscribe to Newsletter", + "⚠️ Please restart boostnote after you change the keymap": "⚠️ 設置好快捷鍵後,記得重啟设置好快捷键后,记得重启boostnote", + "Show line numbers in the editor": "在編輯器中顯示行號", + "Allow editor to scroll past the last line": "允許編輯器滾動到最後一行", + "Bring in web page title when pasting URL on editor": "粘貼網頁鏈接的時候,顯示為網頁標題", + "Preview": "預覽器", + "Preview Font Size": "預覽器字型大小", + "Preview Font Family": "預覽器字體", + "Code block Theme": "代碼塊主題", + "Allow preview to scroll past the last line": "允許預覽器滾動到最後一行", + "Show line numbers for preview code blocks": "在預覽器中顯示行號", + "LaTeX Inline Open Delimiter": "LaTeX 單行開頭分隔符", + "LaTeX Inline Close Delimiter": "LaTeX 單行結尾分隔符", + "LaTeX Block Open Delimiter": "LaTeX 多行開頭分隔符", + "LaTeX Block Close Delimiter": "LaTeX 多行結尾分隔符", + "Community": "社區", + "Subscribe to Newsletter": "訂閱郵件", "GitHub": "GitHub", - "Blog": "Blog", + "Blog": "部落格", "Facebook Group": "Facebook Group", "Twitter": "Twitter", - "About": "About", + "About": "關於", "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", - "Development": "Development", - " : Development configurations for Boostnote.": " : Development configurations for Boostnote.", + "An open source note-taking app made for programmers just like you.": "一款專門為程式員朋友量身打造的開源筆記", + "Website": "官網", + "Development": "開發", + " : Development configurations for Boostnote.": " : 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", - "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,", - "Boostnote maintainers": "Boostnote maintainers", - "Support via OpenCollective": "Support via OpenCollective", - "Language": "Language", + "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 收集匿名數據只為了提升軟體使用體驗,絕對不收集任何個人信息(包括筆記內容)", + "You can see how it works on ": "你可以看看它的源碼是如何運作的 ", + "You can choose to enable or disable this option.": "你可以選擇開啟或不開啟這個功能", + "Enable analytics to help improve Boostnote": "允許對數據進行分析,幫助我們改進Boostnote", + "Crowdfunding": "眾籌", + "Dear everyone,": "親愛的用戶:", + "Thank you for using Boostnote!": "謝謝你使用Boostnote!", + "Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "大約有200個不同的國家和地區的優秀開發者們都在使用Boostnote!", + "To continue supporting this growth, and to satisfy community expectations,": "為了繼續支持這種發展,和滿足社區的期待,", + "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!": "如果你喜歡這款軟體並且看好它的潛力, 請在OpenCollective上支持我們!", + "Thanks,": "十分感謝!", + "Boostnote maintainers": "Boostnote的維護人員", + "Support via OpenCollective": "在OpenCollective上支持我們", + "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", + "Show \"Saved to Clipboard\" notification when copying": "複製的時候,顯示 \"已複製\" 提示", + "All Notes": "所有筆記", + "Starred": "星標收藏", + "Are you sure to ": "你確定要 ", + " delete": " 刪除", + "this folder?": "這個資料夾嗎?", + "Confirm": "確認", + "Cancel": "取消", + "Markdown Note": "Markdown筆記", + "This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "創建文檔,清單,代碼塊甚至是Latex格式文檔", + "Snippet Note": "代碼筆記", + "This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "創建代碼片段,支持多種語法代碼片段", + "Tab to switch format": "使用Tab鍵切換格式", + "Updated": "更新時間", + "Created": "創建時間", + "Alphabetically": "A~Z排序", + "Default View": "默認視圖", + "Compressed View": "列表視圖", + "Search": "搜索", + "Blog Type": "部落格類型", + "Blog Address": "部落格地址", + "Save": "保存", "Auth": "Auth", - "Authentication Method": "Authentication Method", + "Authentication Method": "認證方法", "JWT": "JWT", "USER": "USER", "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!", + "Storage": "本地儲存", + "Hotkeys": "快捷鍵", + "Show/Hide Boostnote": "顯示/隱藏 Boostnote", + "Restore": "恢復", + "Permanent Delete": "永久刪除", + "Confirm note deletion": "確認刪除筆記", + "This will permanently remove this note.": "永久地刪除這條筆記", + "Successfully applied!": "設置成功", "Albanian": "Albanian", - "Chinese (zh-CN)": "Chinese (zh-CN)", - "Chinese (zh-TW)": "Chinese (zh-TW)", + "Chinese (zh-CN)": "简体中文", + "Chinese (zh-TW)": "繁體中文", "Danish": "Danish", "Japanese": "Japanese", "Korean": "Korean", @@ -140,13 +140,13 @@ "Polish": "Polish", "Portuguese": "Portuguese", "Spanish": "Spanish", - "You have to save!": "You have to save!", + "You have to save!": "你必須儲存一下!", "Russian": "Russian", "Editor Rulers": "Editor Rulers", - "Enable": "Enable", - "Disable": "Disable", - "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" + "Enable": "開啟", + "Disable": "關閉", + "Sanitization": "代碼處理", + "Only allow secure html tags (recommended)": "只允許安全的html標籤(推薦)", + "Allow styles": "允許樣式", + "Allow dangerous html tags": "允許危險的html標籤" } diff --git a/package.json b/package.json index ccd0b84c..038d4c00 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "boost", "productName": "Boostnote", - "version": "0.11.3", + "version": "0.11.4", "main": "index.js", "description": "Boostnote", "license": "GPL-3.0", @@ -11,6 +11,7 @@ "webpack": "webpack-dev-server --hot --inline --config webpack.config.js", "compile": "grunt compile", "test": "PWD=$(pwd) NODE_ENV=test ava --serial", + "jest": "jest", "fix": "npm run lint --fix", "lint": "eslint .", "dev-start": "concurrently --kill-others \"npm run webpack\" \"npm run hot\"" @@ -96,11 +97,13 @@ "devDependencies": { "ava": "^0.25.0", "babel-core": "^6.14.0", + "babel-jest": "^22.4.3", "babel-loader": "^6.2.0", "babel-plugin-react-transform": "^2.0.0", "babel-plugin-webpack-alias": "^2.1.1", + "babel-preset-env": "^1.6.1", "babel-preset-es2015": "^6.3.13", - "babel-preset-react": "^6.3.13", + "babel-preset-react": "^6.24.1", "babel-preset-react-hmre": "^1.0.1", "babel-register": "^6.11.6", "browser-env": "^3.2.5", @@ -120,6 +123,9 @@ "grunt": "^0.4.5", "grunt-electron-installer": "2.1.0", "history": "^1.17.0", + "identity-obj-proxy": "^3.0.0", + "jest": "^22.4.3", + "jest-localstorage-mock": "^2.2.0", "jsdom": "^9.4.2", "json-loader": "^0.5.4", "merge-stream": "^1.0.0", @@ -130,6 +136,7 @@ "react-input-autosize": "^1.1.0", "react-router": "^2.4.0", "react-router-redux": "^4.0.4", + "react-test-renderer": "^15.6.2", "standard": "^8.4.0", "style-loader": "^0.12.4", "stylus": "^0.52.4", @@ -152,5 +159,15 @@ "./tests/helpers/setup-electron-mock.js" ], "babel": "inherit" + }, + "jest": { + "moduleNameMapper": { + "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/__mocks__/fileMock.js", + "\\.(css|less|styl)$": "identity-obj-proxy" + }, + "setupFiles": [ + "/tests/jest.js", + "jest-localstorage-mock" + ] } } diff --git a/tests/components/TagListItem.snapshot.test.js b/tests/components/TagListItem.snapshot.test.js new file mode 100644 index 00000000..8bea2ccb --- /dev/null +++ b/tests/components/TagListItem.snapshot.test.js @@ -0,0 +1,9 @@ +import React from 'react' +import renderer from 'react-test-renderer' +import TagListItem from 'browser/components/TagListItem' + +it('TagListItem renders correctly', () => { + const tagListItem = renderer.create() + + expect(tagListItem.toJSON()).toMatchSnapshot() +}) diff --git a/tests/components/__snapshots__/TagListItem.snapshot.test.js.snap b/tests/components/__snapshots__/TagListItem.snapshot.test.js.snap new file mode 100644 index 00000000..0805edcd --- /dev/null +++ b/tests/components/__snapshots__/TagListItem.snapshot.test.js.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`TagListItem renders correctly 1`] = ` + +`; diff --git a/tests/jest.js b/tests/jest.js new file mode 100644 index 00000000..6f830c67 --- /dev/null +++ b/tests/jest.js @@ -0,0 +1,12 @@ +// Here you can mock the libraries connected through direct insertion