1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-13 09:46:22 +00:00

merge from master

This commit is contained in:
Callum booth
2019-06-10 19:13:58 +01:00
96 changed files with 2248 additions and 1026 deletions

View File

@@ -7,7 +7,7 @@
"test": { "test": {
"presets": ["env" ,"react", "es2015"], "presets": ["env" ,"react", "es2015"],
"plugins": [ "plugins": [
[ "babel-plugin-webpack-alias", { "config": "${PWD}/webpack.config.js" } ] [ "babel-plugin-webpack-alias", { "config": "<rootDir>/webpack.config.js" } ]
] ]
} }
} }

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
issuehunt: BoostIo/Boostnote

View File

@@ -1,10 +1,10 @@
language: node_js language: node_js
node_js: node_js:
- 7 - 8
script: script:
- npm run lint && npm run test - npm run lint && npm run test
- yarn jest - yarn jest
- 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" ]]; then npm install -g grunt npm@5.2 && grunt pre-build; fi' - 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" ]]; then npm install -g grunt npm@6.4 && grunt pre-build; fi'
after_success: after_success:
- openssl aes-256-cbc -K $encrypted_440d7f9a3c38_key -iv $encrypted_440d7f9a3c38_iv - openssl aes-256-cbc -K $encrypted_440d7f9a3c38_key -iv $encrypted_440d7f9a3c38_iv
-in .snapcraft/travis_snapcraft.cfg -out .snapcraft/snapcraft.cfg -d -in .snapcraft/travis_snapcraft.cfg -out .snapcraft/snapcraft.cfg -d

View File

@@ -14,18 +14,20 @@ import {
import TextEditorInterface from 'browser/lib/TextEditorInterface' import TextEditorInterface from 'browser/lib/TextEditorInterface'
import eventEmitter from 'browser/main/lib/eventEmitter' import eventEmitter from 'browser/main/lib/eventEmitter'
import iconv from 'iconv-lite' import iconv from 'iconv-lite'
import crypto from 'crypto'
import consts from 'browser/lib/consts' import { isMarkdownTitleURL } from 'browser/lib/utils'
import styles from '../components/CodeEditor.styl' import styles from '../components/CodeEditor.styl'
import fs from 'fs'
const { ipcRenderer, remote, clipboard } = require('electron') const { ipcRenderer, remote, clipboard } = require('electron')
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily' import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
const spellcheck = require('browser/lib/spellcheck') const spellcheck = require('browser/lib/spellcheck')
const buildEditorContextMenu = require('browser/lib/contextMenuBuilder') const buildEditorContextMenu = require('browser/lib/contextMenuBuilder')
import TurndownService from 'turndown' import TurndownService from 'turndown'
import { import {languageMaps} from '../lib/CMLanguageList'
gfm import snippetManager from '../lib/SnippetManager'
} from 'turndown-plugin-gfm' import {generateInEditor, tocExistsInEditor} from 'browser/lib/markdown-toc-generator'
import markdownlint from 'markdownlint'
import Jsonlint from 'jsonlint-mod'
import { DEFAULT_CONFIG } from '../main/lib/ConfigManager'
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js' CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
@@ -38,85 +40,6 @@ function translateHotkey (hotkey) {
return hotkey.replace(/\s*\+\s*/g, '-').replace(/Command/g, 'Cmd').replace(/Control/g, 'Ctrl') return hotkey.replace(/\s*\+\s*/g, '-').replace(/Command/g, 'Cmd').replace(/Control/g, 'Ctrl')
} }
const languageMaps = {
brainfuck: 'Brainfuck',
cpp: 'C++',
cs: 'C#',
clojure: 'Clojure',
'clojure-repl': 'ClojureScript',
cmake: 'CMake',
coffeescript: 'CoffeeScript',
crystal: 'Crystal',
css: 'CSS',
d: 'D',
dart: 'Dart',
delphi: 'Pascal',
diff: 'Diff',
django: 'Django',
dockerfile: 'Dockerfile',
ebnf: 'EBNF',
elm: 'Elm',
erlang: 'Erlang',
'erlang-repl': 'Erlang',
fortran: 'Fortran',
fsharp: 'F#',
gherkin: 'Gherkin',
go: 'Go',
groovy: 'Groovy',
haml: 'HAML',
haskell: 'Haskell',
haxe: 'Haxe',
http: 'HTTP',
ini: 'toml',
java: 'Java',
javascript: 'JavaScript',
json: 'JSON',
julia: 'Julia',
kotlin: 'Kotlin',
less: 'LESS',
livescript: 'LiveScript',
lua: 'Lua',
markdown: 'Markdown',
mathematica: 'Mathematica',
nginx: 'Nginx',
nsis: 'NSIS',
objectivec: 'Objective-C',
ocaml: 'Ocaml',
perl: 'Perl',
php: 'PHP',
powershell: 'PowerShell',
properties: 'Properties files',
protobuf: 'ProtoBuf',
python: 'Python',
puppet: 'Puppet',
q: 'Q',
r: 'R',
ruby: 'Ruby',
rust: 'Rust',
sas: 'SAS',
scala: 'Scala',
scheme: 'Scheme',
scss: 'SCSS',
shell: 'Shell',
smalltalk: 'Smalltalk',
sml: 'SML',
sql: 'SQL',
stylus: 'Stylus',
swift: 'Swift',
tcl: 'Tcl',
tex: 'LaTex',
typescript: 'TypeScript',
twig: 'Twig',
vbnet: 'VB.NET',
vbscript: 'VBScript',
verilog: 'Verilog',
vhdl: 'VHDL',
xml: 'HTML',
xquery: 'XQuery',
yaml: 'YAML',
elixir: 'Elixir'
}
export default class CodeEditor extends React.Component { export default class CodeEditor extends React.Component {
constructor (props) { constructor (props) {
super(props) super(props)
@@ -163,6 +86,8 @@ export default class CodeEditor extends React.Component {
this.searchHandler = (e, msg) => this.handleSearch(msg) this.searchHandler = (e, msg) => this.handleSearch(msg)
this.searchState = null this.searchState = null
this.scrollToLineHandeler = this.scrollToLine.bind(this) this.scrollToLineHandeler = this.scrollToLine.bind(this)
this.getCodeEditorLintConfig = this.getCodeEditorLintConfig.bind(this)
this.validatorOfMarkdown = this.validatorOfMarkdown.bind(this)
this.formatTable = () => this.handleFormatTable() this.formatTable = () => this.handleFormatTable()
@@ -228,7 +153,8 @@ export default class CodeEditor extends React.Component {
updateDefaultKeyMap () { updateDefaultKeyMap () {
const { hotkey } = this.props const { hotkey } = this.props
const expandSnippet = this.expandSnippet.bind(this) const self = this
const expandSnippet = snippetManager.expandSnippet
this.defaultKeyMap = CodeMirror.normalizeKeyMap({ this.defaultKeyMap = CodeMirror.normalizeKeyMap({
Tab: function (cm) { Tab: function (cm) {
@@ -248,14 +174,16 @@ export default class CodeEditor extends React.Component {
} }
cm.execCommand('goLineEnd') cm.execCommand('goLineEnd')
} else if ( } else if (
!charBeforeCursor.match(/\t|\s|\r|\n/) && !charBeforeCursor.match(/\t|\s|\r|\n|\$/) &&
cursor.ch > 1 cursor.ch > 1
) { ) {
// text expansion on tab key if the char before is alphabet // text expansion on tab key if the char before is alphabet
const snippets = JSON.parse( const wordBeforeCursor = self.getWordBeforeCursor(
fs.readFileSync(consts.SNIPPET_FILE, 'utf8') line,
cursor.line,
cursor.ch
) )
if (expandSnippet(line, cursor, cm, snippets) === false) { if (expandSnippet(wordBeforeCursor, cursor, cm) === false) {
if (tabs) { if (tabs) {
cm.execCommand('insertTab') cm.execCommand('insertTab')
} else { } else {
@@ -277,6 +205,26 @@ export default class CodeEditor extends React.Component {
'Cmd-T': function (cm) { 'Cmd-T': function (cm) {
// Do nothing // Do nothing
}, },
'Ctrl-/': function (cm) {
if (global.process.platform === 'darwin') { return }
const dateNow = new Date()
cm.replaceSelection(dateNow.toLocaleDateString())
},
'Cmd-/': function (cm) {
if (global.process.platform !== 'darwin') { return }
const dateNow = new Date()
cm.replaceSelection(dateNow.toLocaleDateString())
},
'Shift-Ctrl-/': function (cm) {
if (global.process.platform === 'darwin') { return }
const dateNow = new Date()
cm.replaceSelection(dateNow.toLocaleString())
},
'Shift-Cmd-/': function (cm) {
if (global.process.platform !== 'darwin') { return }
const dateNow = new Date()
cm.replaceSelection(dateNow.toLocaleString())
},
Enter: 'boostNewLineAndIndentContinueMarkdownList', Enter: 'boostNewLineAndIndentContinueMarkdownList',
'Ctrl-C': cm => { 'Ctrl-C': cm => {
if (cm.getOption('keyMap').substr(0, 3) === 'vim') { if (cm.getOption('keyMap').substr(0, 3) === 'vim') {
@@ -307,25 +255,10 @@ export default class CodeEditor extends React.Component {
} }
componentDidMount () { componentDidMount () {
const { rulers, enableRulers } = this.props const { rulers, enableRulers, enableMarkdownLint } = this.props
eventEmitter.on('line:jump', this.scrollToLineHandeler) eventEmitter.on('line:jump', this.scrollToLineHandeler)
const defaultSnippet = [ snippetManager.init()
{
id: crypto.randomBytes(16).toString('hex'),
name: 'Dummy text',
prefix: ['lorem', 'ipsum'],
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
}
]
if (!fs.existsSync(consts.SNIPPET_FILE)) {
fs.writeFileSync(
consts.SNIPPET_FILE,
JSON.stringify(defaultSnippet, null, 4),
'utf8'
)
}
this.updateDefaultKeyMap() this.updateDefaultKeyMap()
this.value = this.props.value this.value = this.props.value
@@ -344,7 +277,8 @@ export default class CodeEditor extends React.Component {
inputStyle: 'textarea', inputStyle: 'textarea',
dragDrop: false, dragDrop: false,
foldGutter: true, foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], lint: enableMarkdownLint ? this.getCodeEditorLintConfig() : false,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'],
autoCloseBrackets: { autoCloseBrackets: {
pairs: this.props.matchingPairs, pairs: this.props.matchingPairs,
triples: this.props.matchingTriples, triples: this.props.matchingTriples,
@@ -354,6 +288,8 @@ export default class CodeEditor extends React.Component {
extraKeys: this.defaultKeyMap extraKeys: this.defaultKeyMap
}) })
document.querySelector('.CodeMirror-lint-markers').style.display = enableMarkdownLint ? 'inline-block' : 'none'
if (!this.props.mode && this.props.value && this.props.autoDetect) { if (!this.props.mode && this.props.value && this.props.autoDetect) {
this.autoDetectLanguage(this.props.value) this.autoDetectLanguage(this.props.value)
} else { } else {
@@ -520,61 +456,12 @@ export default class CodeEditor extends React.Component {
this.initialHighlighting() this.initialHighlighting()
} }
expandSnippet (line, cursor, cm, snippets) {
const wordBeforeCursor = this.getWordBeforeCursor(
line,
cursor.line,
cursor.ch
)
const templateCursorString = ':{}'
for (let i = 0; i < snippets.length; i++) {
if (snippets[i].prefix.indexOf(wordBeforeCursor.text) !== -1) {
if (snippets[i].content.indexOf(templateCursorString) !== -1) {
const snippetLines = snippets[i].content.split('\n')
let cursorLineNumber = 0
let cursorLinePosition = 0
let cursorIndex
for (let j = 0; j < snippetLines.length; j++) {
cursorIndex = snippetLines[j].indexOf(templateCursorString)
if (cursorIndex !== -1) {
cursorLineNumber = j
cursorLinePosition = cursorIndex
break
}
}
cm.replaceRange(
snippets[i].content.replace(templateCursorString, ''),
wordBeforeCursor.range.from,
wordBeforeCursor.range.to
)
cm.setCursor({
line: cursor.line + cursorLineNumber,
ch: cursorLinePosition + cursor.ch - wordBeforeCursor.text.length
})
} else {
cm.replaceRange(
snippets[i].content,
wordBeforeCursor.range.from,
wordBeforeCursor.range.to
)
}
return true
}
}
return false
}
getWordBeforeCursor (line, lineNumber, cursorPosition) { getWordBeforeCursor (line, lineNumber, cursorPosition) {
let wordBeforeCursor = '' let wordBeforeCursor = ''
const originCursorPosition = cursorPosition const originCursorPosition = cursorPosition
const emptyChars = /\t|\s|\r|\n/ const emptyChars = /\t|\s|\r|\n|\$/
// to prevent the word to expand is long that will crash the whole app // to prevent the word is long that will crash the whole app
// the safeStop is there to stop user to expand words that longer than 20 chars // the safeStop is there to stop user to expand words that longer than 20 chars
const safeStop = 20 const safeStop = 20
@@ -584,7 +471,7 @@ export default class CodeEditor extends React.Component {
if (!emptyChars.test(currentChar)) { if (!emptyChars.test(currentChar)) {
wordBeforeCursor = currentChar + wordBeforeCursor wordBeforeCursor = currentChar + wordBeforeCursor
} else if (wordBeforeCursor.length >= safeStop) { } else if (wordBeforeCursor.length >= safeStop) {
throw new Error('Your snippet trigger is too long !') throw new Error('Stopped after 20 loops for safety reason !')
} else { } else {
break break
} }
@@ -629,7 +516,9 @@ export default class CodeEditor extends React.Component {
let needRefresh = false let needRefresh = false
const { const {
rulers, rulers,
enableRulers enableRulers,
enableMarkdownLint,
customMarkdownLintConfig
} = this.props } = this.props
if (prevProps.mode !== this.props.mode) { if (prevProps.mode !== this.props.mode) {
this.setMode(this.props.mode) this.setMode(this.props.mode)
@@ -647,6 +536,16 @@ export default class CodeEditor extends React.Component {
if (prevProps.keyMap !== this.props.keyMap) { if (prevProps.keyMap !== this.props.keyMap) {
needRefresh = true needRefresh = true
} }
if (prevProps.enableMarkdownLint !== enableMarkdownLint || prevProps.customMarkdownLintConfig !== customMarkdownLintConfig) {
if (!enableMarkdownLint) {
this.editor.setOption('lint', {default: false})
document.querySelector('.CodeMirror-lint-markers').style.display = 'none'
} else {
this.editor.setOption('lint', this.getCodeEditorLintConfig())
document.querySelector('.CodeMirror-lint-markers').style.display = 'inline-block'
}
needRefresh = true
}
if ( if (
prevProps.enableRulers !== enableRulers || prevProps.enableRulers !== enableRulers ||
@@ -727,6 +626,56 @@ export default class CodeEditor extends React.Component {
} }
} }
getCodeEditorLintConfig () {
const { mode } = this.props
const checkMarkdownNoteIsOpen = mode === 'Boost Flavored Markdown'
return checkMarkdownNoteIsOpen ? {
'getAnnotations': this.validatorOfMarkdown,
'async': true
} : false
}
validatorOfMarkdown (text, updateLinting) {
const { customMarkdownLintConfig } = this.props
let lintConfigJson
try {
Jsonlint.parse(customMarkdownLintConfig)
lintConfigJson = JSON.parse(customMarkdownLintConfig)
} catch (err) {
eventEmitter.emit('APP_SETTING_ERROR')
return
}
const lintOptions = {
'strings': {
'content': text
},
'config': lintConfigJson
}
return markdownlint(lintOptions, (err, result) => {
if (!err) {
const foundIssues = []
const splitText = text.split('\n')
result.content.map(item => {
let ruleNames = ''
item.ruleNames.map((ruleName, index) => {
ruleNames += ruleName
ruleNames += (index === item.ruleNames.length - 1) ? ': ' : '/'
})
const lineNumber = item.lineNumber - 1
foundIssues.push({
from: CodeMirror.Pos(lineNumber, 0),
to: CodeMirror.Pos(lineNumber, splitText[lineNumber].length),
message: ruleNames + item.ruleDescription,
severity: 'warning'
})
})
updateLinting(foundIssues)
}
})
}
setMode (mode) { setMode (mode) {
let syntax = CodeMirror.findModeByName(convertModeName(mode || 'text')) let syntax = CodeMirror.findModeByName(convertModeName(mode || 'text'))
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text') if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
@@ -738,6 +687,34 @@ export default class CodeEditor extends React.Component {
handleChange (editor, changeObject) { handleChange (editor, changeObject) {
spellcheck.handleChange(editor, changeObject) spellcheck.handleChange(editor, changeObject)
// The current note contains an toc. We'll check for changes on headlines.
// origin is undefined when markdownTocGenerator replace the old tod
if (tocExistsInEditor(editor) && changeObject.origin !== undefined) {
let requireTocUpdate
// Check if one of the changed lines contains a headline
for (let line = 0; line < changeObject.text.length; line++) {
if (this.linePossibleContainsHeadline(editor.getLine(changeObject.from.line + line))) {
requireTocUpdate = true
break
}
}
if (!requireTocUpdate) {
// Check if one of the removed lines contains a headline
for (let line = 0; line < changeObject.removed.length; line++) {
if (this.linePossibleContainsHeadline(changeObject.removed[line])) {
requireTocUpdate = true
break
}
}
}
if (requireTocUpdate) {
generateInEditor(editor)
}
}
this.updateHighlight(editor, changeObject) this.updateHighlight(editor, changeObject)
this.value = editor.getValue() this.value = editor.getValue()
@@ -746,6 +723,12 @@ export default class CodeEditor extends React.Component {
} }
} }
linePossibleContainsHeadline (currentLine) {
// We can't check if the line start with # because when some write text before
// the # we also need to update the toc
return currentLine.includes('# ')
}
incrementLines (start, linesAdded, linesRemoved, editor) { incrementLines (start, linesAdded, linesRemoved, editor) {
const highlightedLines = editor.options.linesHighlighted const highlightedLines = editor.options.linesHighlighted
@@ -835,6 +818,9 @@ export default class CodeEditor extends React.Component {
ch: 1 ch: 1
} }
this.editor.setCursor(cursor) this.editor.setCursor(cursor)
const top = this.editor.charCoords({line: num, ch: 0}, 'local').top
const middleHeight = this.editor.getScrollerElement().offsetHeight / 2
this.editor.scrollTo(null, top - middleHeight - 5)
} }
focus () { focus () {
@@ -950,6 +936,8 @@ export default class CodeEditor extends React.Component {
if (isInFencedCodeBlock(editor)) { if (isInFencedCodeBlock(editor)) {
this.handlePasteText(editor, pastedTxt) this.handlePasteText(editor, pastedTxt)
} else if (fetchUrlTitle && isMarkdownTitleURL(pastedTxt) && !isInLinkTag(editor)) {
this.handlePasteUrl(editor, pastedTxt)
} else if (fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) { } else if (fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) {
this.handlePasteUrl(editor, pastedTxt) this.handlePasteUrl(editor, pastedTxt)
} else if (attachmentManagement.isAttachmentLink(pastedTxt)) { } else if (attachmentManagement.isAttachmentLink(pastedTxt)) {
@@ -991,7 +979,17 @@ export default class CodeEditor extends React.Component {
} }
handlePasteUrl (editor, pastedTxt) { handlePasteUrl (editor, pastedTxt) {
const taggedUrl = `<${pastedTxt}>` let taggedUrl = `<${pastedTxt}>`
let urlToFetch = pastedTxt
let titleMark = ''
if (isMarkdownTitleURL(pastedTxt)) {
const pastedTxtSplitted = pastedTxt.split(' ')
titleMark = `${pastedTxtSplitted[0]} `
urlToFetch = pastedTxtSplitted[1]
taggedUrl = `<${urlToFetch}>`
}
editor.replaceSelection(taggedUrl) editor.replaceSelection(taggedUrl)
const isImageReponse = response => { const isImageReponse = response => {
@@ -1003,22 +1001,23 @@ export default class CodeEditor extends React.Component {
const replaceTaggedUrl = replacement => { const replaceTaggedUrl = replacement => {
const value = editor.getValue() const value = editor.getValue()
const cursor = editor.getCursor() const cursor = editor.getCursor()
const newValue = value.replace(taggedUrl, replacement) const newValue = value.replace(taggedUrl, titleMark + replacement)
const newCursor = Object.assign({}, cursor, { const newCursor = Object.assign({}, cursor, {
ch: cursor.ch + newValue.length - value.length ch: cursor.ch + newValue.length - (value.length - titleMark.length)
}) })
editor.setValue(newValue) editor.setValue(newValue)
editor.setCursor(newCursor) editor.setCursor(newCursor)
} }
fetch(pastedTxt, { fetch(urlToFetch, {
method: 'get' method: 'get'
}) })
.then(response => { .then(response => {
if (isImageReponse(response)) { if (isImageReponse(response)) {
return this.mapImageResponse(response, pastedTxt) return this.mapImageResponse(response, urlToFetch)
} else { } else {
return this.mapNormalResponse(response, pastedTxt) return this.mapNormalResponse(response, urlToFetch)
} }
}) })
.then(replacement => { .then(replacement => {
@@ -1140,8 +1139,7 @@ export default class CodeEditor extends React.Component {
} }
ref='root' ref='root'
tabIndex='-1' tabIndex='-1'
style={ style={{
{
fontFamily, fontFamily,
fontSize: fontSize, fontSize: fontSize,
width: width, width: width,
@@ -1185,7 +1183,9 @@ CodeEditor.propTypes = {
onChange: PropTypes.func, onChange: PropTypes.func,
readOnly: PropTypes.bool, readOnly: PropTypes.bool,
autoDetect: PropTypes.bool, autoDetect: PropTypes.bool,
spellCheck: PropTypes.bool spellCheck: PropTypes.bool,
enableMarkdownLint: PropTypes.bool,
customMarkdownLintConfig: PropTypes.string
} }
CodeEditor.defaultProps = { CodeEditor.defaultProps = {
@@ -1197,5 +1197,7 @@ CodeEditor.defaultProps = {
indentSize: 4, indentSize: 4,
indentType: 'space', indentType: 'space',
autoDetect: false, autoDetect: false,
spellCheck: false spellCheck: false,
enableMarkdownLint: DEFAULT_CONFIG.editor.enableMarkdownLint,
customMarkdownLintConfig: DEFAULT_CONFIG.editor.customMarkdownLintConfig
} }

View File

@@ -3,4 +3,3 @@
.spellcheck-select .spellcheck-select
border: none border: none
text-decoration underline wavy red

View File

@@ -32,6 +32,7 @@ class MarkdownEditor extends React.Component {
componentDidMount () { componentDidMount () {
this.value = this.refs.code.value this.value = this.refs.code.value
eventEmitter.on('editor:lock', this.lockEditorCode) eventEmitter.on('editor:lock', this.lockEditorCode)
eventEmitter.on('editor:focus', this.focusEditor.bind(this))
} }
componentDidUpdate () { componentDidUpdate () {
@@ -47,6 +48,15 @@ class MarkdownEditor extends React.Component {
componentWillUnmount () { componentWillUnmount () {
this.cancelQueue() this.cancelQueue()
eventEmitter.off('editor:lock', this.lockEditorCode) eventEmitter.off('editor:lock', this.lockEditorCode)
eventEmitter.off('editor:focus', this.focusEditor.bind(this))
}
focusEditor () {
this.setState({
status: 'CODE'
}, () => {
this.refs.code.focus()
})
} }
queueRendering (value) { queueRendering (value) {
@@ -149,10 +159,10 @@ class MarkdownEditor extends React.Component {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
const idMatch = /checkbox-([0-9]+)/ const idMatch = /checkbox-([0-9]+)/
const checkedMatch = /^\s*[\+\-\*] \[x\]/i const checkedMatch = /^(\s*>?)*\s*[+\-*] \[x]/i
const uncheckedMatch = /^\s*[\+\-\*] \[ \]/ const uncheckedMatch = /^(\s*>?)*\s*[+\-*] \[ ]/
const checkReplace = /\[x\]/i const checkReplace = /\[x]/i
const uncheckReplace = /\[ \]/ const uncheckReplace = /\[ ]/
if (idMatch.test(e.target.getAttribute('id'))) { if (idMatch.test(e.target.getAttribute('id'))) {
const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1 const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
const lines = this.refs.code.value const lines = this.refs.code.value
@@ -309,6 +319,8 @@ class MarkdownEditor extends React.Component {
enableSmartPaste={config.editor.enableSmartPaste} enableSmartPaste={config.editor.enableSmartPaste}
hotkey={config.hotkey} hotkey={config.hotkey}
switchPreview={config.editor.switchPreview} switchPreview={config.editor.switchPreview}
enableMarkdownLint={config.editor.enableMarkdownLint}
customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
/> />
<MarkdownPreview styleName={this.state.status === 'PREVIEW' <MarkdownPreview styleName={this.state.status === 'PREVIEW'
? 'preview' ? 'preview'

View File

@@ -8,7 +8,7 @@ import consts from 'browser/lib/consts'
import Raphael from 'raphael' import Raphael from 'raphael'
import flowchart from 'flowchart' import flowchart from 'flowchart'
import mermaidRender from './render/MermaidRender' import mermaidRender from './render/MermaidRender'
import SequenceDiagram from 'js-sequence-diagrams' import SequenceDiagram from '@rokt33r/js-sequence-diagrams'
import Chart from 'chart.js' import Chart from 'chart.js'
import eventEmitter from 'browser/main/lib/eventEmitter' import eventEmitter from 'browser/main/lib/eventEmitter'
import htmlTextHelper from 'browser/lib/htmlTextHelper' import htmlTextHelper from 'browser/lib/htmlTextHelper'
@@ -192,6 +192,19 @@ const defaultCodeBlockFontFamily = [
'source-code-pro', 'source-code-pro',
'monospace' 'monospace'
] ]
// return the line number of the line that used to generate the specified element
// return -1 if the line is not found
function getSourceLineNumberByElement (element) {
let isHasLineNumber = element.dataset.line !== undefined
let parent = element
while (!isHasLineNumber && parent.parentElement !== null) {
parent = parent.parentElement
isHasLineNumber = parent.dataset.line !== undefined
}
return parent.dataset.line !== undefined ? parseInt(parent.dataset.line) : -1
}
export default class MarkdownPreview extends React.Component { export default class MarkdownPreview extends React.Component {
constructor (props) { constructor (props) {
super(props) super(props)
@@ -208,6 +221,7 @@ export default class MarkdownPreview extends React.Component {
this.saveAsTextHandler = () => this.handleSaveAsText() this.saveAsTextHandler = () => this.handleSaveAsText()
this.saveAsMdHandler = () => this.handleSaveAsMd() this.saveAsMdHandler = () => this.handleSaveAsMd()
this.saveAsHtmlHandler = () => this.handleSaveAsHtml() this.saveAsHtmlHandler = () => this.handleSaveAsHtml()
this.saveAsPdfHandler = () => this.handleSaveAsPdf()
this.printHandler = () => this.handlePrint() this.printHandler = () => this.handlePrint()
this.linkClickHandler = this.handleLinkClick.bind(this) this.linkClickHandler = this.handleLinkClick.bind(this)
@@ -241,7 +255,7 @@ export default class MarkdownPreview extends React.Component {
return return
} }
// No contextMenu was passed to us -> execute our own link-opener // No contextMenu was passed to us -> execute our own link-opener
if (event.target.tagName.toLowerCase() === 'a') { if (event.target.tagName.toLowerCase() === 'a' && event.target.getAttribute('href')) {
const href = event.target.href const href = event.target.href
const isLocalFile = href.startsWith('file:') const isLocalFile = href.startsWith('file:')
if (isLocalFile) { if (isLocalFile) {
@@ -268,17 +282,27 @@ export default class MarkdownPreview extends React.Component {
handleMouseDown (e) { handleMouseDown (e) {
const config = ConfigManager.get() const config = ConfigManager.get()
const clickElement = e.target
const targetTag = clickElement.tagName // The direct parent HTML of where was clicked ie "BODY" or "DIV"
const lineNumber = getSourceLineNumberByElement(clickElement) // Line location of element clicked.
if (config.editor.switchPreview === 'RIGHTCLICK' && e.buttons === 2 && config.editor.type === 'SPLIT') { if (config.editor.switchPreview === 'RIGHTCLICK' && e.buttons === 2 && config.editor.type === 'SPLIT') {
eventEmitter.emit('topbar:togglemodebutton', 'CODE') eventEmitter.emit('topbar:togglemodebutton', 'CODE')
} }
if (e.target != null) { if (e.ctrlKey) {
switch (e.target.tagName) { if (config.editor.type === 'SPLIT') {
case 'A': if (lineNumber !== -1) {
case 'INPUT': eventEmitter.emit('line:jump', lineNumber)
return null }
} else {
if (lineNumber !== -1) {
eventEmitter.emit('editor:focus')
eventEmitter.emit('line:jump', lineNumber)
}
} }
} }
if (this.props.onMouseDown != null) this.props.onMouseDown(e)
if (this.props.onMouseDown != null && targetTag === 'BODY') this.props.onMouseDown(e)
} }
handleMouseUp (e) { handleMouseUp (e) {
@@ -297,58 +321,77 @@ export default class MarkdownPreview extends React.Component {
this.exportAsDocument('md') this.exportAsDocument('md')
} }
handleSaveAsHtml () { htmlContentFormatter (noteContent, exportTasks, targetDir) {
this.exportAsDocument('html', (noteContent, exportTasks) => { const {
const { fontFamily,
fontFamily, fontSize,
fontSize, codeBlockFontFamily,
codeBlockFontFamily, lineNumber,
lineNumber, codeBlockTheme,
codeBlockTheme, scrollPastEnd,
scrollPastEnd, theme,
theme, allowCustomCSS,
allowCustomCSS, customCSS
customCSS } = this.getStyleParams()
} = this.getStyleParams()
const inlineStyles = buildStyle( const inlineStyles = buildStyle(
fontFamily, fontFamily,
fontSize, fontSize,
codeBlockFontFamily, codeBlockFontFamily,
lineNumber, lineNumber,
scrollPastEnd, scrollPastEnd,
theme, theme,
allowCustomCSS, allowCustomCSS,
customCSS customCSS
) )
const body = this.markdown.render(noteContent) const body = this.markdown.render(noteContent)
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES] const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
files.forEach(file => { files.forEach(file => {
if (global.process.platform === 'win32') { if (global.process.platform === 'win32') {
file = file.replace('file:///', '') file = file.replace('file:///', '')
} else { } else {
file = file.replace('file://', '') file = file.replace('file://', '')
} }
exportTasks.push({ exportTasks.push({
src: file, src: file,
dst: 'css' dst: 'css'
})
})
let styles = ''
files.forEach(file => {
styles += `<link rel="stylesheet" href="css/${path.basename(file)}">`
})
return `<html>
<head>
<base href="file://${targetDir}/">
<meta charset="UTF-8">
<meta name = "viewport" content = "width = device-width, initial-scale = 1, maximum-scale = 1">
<style id="style">${inlineStyles}</style>
${styles}
</head>
<body>${body}</body>
</html>`
}
handleSaveAsHtml () {
this.exportAsDocument('html', (noteContent, exportTasks, targetDir) => Promise.resolve(this.htmlContentFormatter(noteContent, exportTasks, targetDir)))
}
handleSaveAsPdf () {
this.exportAsDocument('pdf', (noteContent, exportTasks, targetDir) => {
const printout = new remote.BrowserWindow({show: false, webPreferences: {webSecurity: false}})
printout.loadURL('data:text/html;charset=UTF-8,' + this.htmlContentFormatter(noteContent, exportTasks, targetDir))
return new Promise((resolve, reject) => {
printout.webContents.on('did-finish-load', () => {
printout.webContents.printToPDF({}, (err, data) => {
if (err) reject(err)
else resolve(data)
printout.destroy()
})
}) })
}) })
let styles = ''
files.forEach(file => {
styles += `<link rel="stylesheet" href="css/${path.basename(file)}">`
})
return `<html>
<head>
<meta charset="UTF-8">
<meta name = "viewport" content = "width = device-width, initial-scale = 1, maximum-scale = 1">
<style id="style">${inlineStyles}</style>
${styles}
</head>
<body>${body}</body>
</html>`
}) })
} }
@@ -490,6 +533,7 @@ export default class MarkdownPreview extends React.Component {
eventEmitter.on('export:save-text', this.saveAsTextHandler) eventEmitter.on('export:save-text', this.saveAsTextHandler)
eventEmitter.on('export:save-md', this.saveAsMdHandler) eventEmitter.on('export:save-md', this.saveAsMdHandler)
eventEmitter.on('export:save-html', this.saveAsHtmlHandler) eventEmitter.on('export:save-html', this.saveAsHtmlHandler)
eventEmitter.on('export:save-pdf', this.saveAsPdfHandler)
eventEmitter.on('print', this.printHandler) eventEmitter.on('print', this.printHandler)
} }
@@ -527,6 +571,7 @@ export default class MarkdownPreview extends React.Component {
eventEmitter.off('export:save-text', this.saveAsTextHandler) eventEmitter.off('export:save-text', this.saveAsTextHandler)
eventEmitter.off('export:save-md', this.saveAsMdHandler) eventEmitter.off('export:save-md', this.saveAsMdHandler)
eventEmitter.off('export:save-html', this.saveAsHtmlHandler) eventEmitter.off('export:save-html', this.saveAsHtmlHandler)
eventEmitter.off('export:save-pdf', this.saveAsPdfHandler)
eventEmitter.off('print', this.printHandler) eventEmitter.off('print', this.printHandler)
} }
@@ -625,14 +670,14 @@ export default class MarkdownPreview extends React.Component {
) )
} }
GetCodeThemeLink (theme) { GetCodeThemeLink (name) {
theme = consts.THEMES.some(_theme => _theme === theme) && const theme = consts.THEMES.find(theme => theme.name === name)
theme !== 'default'
? theme if (theme) {
: 'elegant' return `${appPath}/${theme.path}`
return theme.startsWith('solarized') } else {
? `${appPath}/node_modules/codemirror/theme/solarized.css` return `${appPath}/node_modules/codemirror/theme/elegant.css`
: `${appPath}/node_modules/codemirror/theme/${theme}.css` }
} }
rewriteIframe () { rewriteIframe () {
@@ -690,9 +735,9 @@ export default class MarkdownPreview extends React.Component {
} }
) )
codeBlockTheme = consts.THEMES.some(_theme => _theme === codeBlockTheme) codeBlockTheme = consts.THEMES.find(theme => theme.name === codeBlockTheme)
? codeBlockTheme
: 'default' const codeBlockThemeClassName = codeBlockTheme ? codeBlockTheme.className : 'cm-s-default'
_.forEach( _.forEach(
this.refs.root.contentWindow.document.querySelectorAll('.code code'), this.refs.root.contentWindow.document.querySelectorAll('.code code'),
@@ -705,6 +750,8 @@ export default class MarkdownPreview extends React.Component {
copyIcon.innerHTML = copyIcon.innerHTML =
'<button class="clipboardButton"><svg width="13" height="13" viewBox="0 0 1792 1792" ><path d="M768 1664h896v-640h-416q-40 0-68-28t-28-68v-416h-384v1152zm256-1440v-64q0-13-9.5-22.5t-22.5-9.5h-704q-13 0-22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h704q13 0 22.5-9.5t9.5-22.5zm256 672h299l-299-299v299zm512 128v672q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-160h-544q-40 0-68-28t-28-68v-1344q0-40 28-68t68-28h1088q40 0 68 28t28 68v328q21 13 36 28l408 408q28 28 48 76t20 88z"/></svg></button>' '<button class="clipboardButton"><svg width="13" height="13" viewBox="0 0 1792 1792" ><path d="M768 1664h896v-640h-416q-40 0-68-28t-28-68v-416h-384v1152zm256-1440v-64q0-13-9.5-22.5t-22.5-9.5h-704q-13 0-22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h704q13 0 22.5-9.5t9.5-22.5zm256 672h299l-299-299v299zm512 128v672q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-160h-544q-40 0-68-28t-28-68v-1344q0-40 28-68t68-28h1088q40 0 68 28t28 68v328q21 13 36 28l408 408q28 28 48 76t20 88z"/></svg></button>'
copyIcon.onclick = e => { copyIcon.onclick = e => {
e.preventDefault()
e.stopPropagation()
copy(content) copy(content)
if (showCopyNotification) { if (showCopyNotification) {
this.notify('Saved to Clipboard!', { this.notify('Saved to Clipboard!', {
@@ -713,14 +760,11 @@ export default class MarkdownPreview extends React.Component {
}) })
} }
} }
el.parentNode.appendChild(copyIcon) el.parentNode.appendChild(copyIcon)
el.innerHTML = '' el.innerHTML = ''
if (codeBlockTheme.indexOf('solarized') === 0) { el.parentNode.className += ` ${codeBlockThemeClassName}`
const [refThema, color] = codeBlockTheme.split(' ')
el.parentNode.className += ` cm-s-${refThema} cm-s-${color}`
} else {
el.parentNode.className += ` cm-s-${codeBlockTheme}`
}
CodeMirror.runMode(content, syntax.mime, el, { CodeMirror.runMode(content, syntax.mime, el, {
tabSize: indentSize tabSize: indentSize
}) })
@@ -835,78 +879,96 @@ export default class MarkdownPreview extends React.Component {
const markdownPreviewIframe = document.querySelector('.MarkdownPreview') const markdownPreviewIframe = document.querySelector('.MarkdownPreview')
const rect = markdownPreviewIframe.getBoundingClientRect() const rect = markdownPreviewIframe.getBoundingClientRect()
const config = { attributes: true, subtree: true }
const imgObserver = new MutationObserver((mutationList) => {
for (const mu of mutationList) {
if (mu.target.className === 'carouselContent-enter-done') {
this.setImgOnClickEventHelper(mu.target, rect)
break
}
}
})
const imgList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll('img') const imgList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll('img')
for (const img of imgList) { for (const img of imgList) {
img.onclick = () => { const parentEl = img.parentElement
const widthMagnification = document.body.clientWidth / img.width this.setImgOnClickEventHelper(img, rect)
const heightMagnification = document.body.clientHeight / img.height imgObserver.observe(parentEl, config)
const baseOnWidth = widthMagnification < heightMagnification }
const magnification = baseOnWidth ? widthMagnification : heightMagnification }
const zoomImgWidth = img.width * magnification setImgOnClickEventHelper (img, rect) {
const zoomImgHeight = img.height * magnification img.onclick = () => {
const zoomImgTop = (document.body.clientHeight - zoomImgHeight) / 2 const widthMagnification = document.body.clientWidth / img.width
const zoomImgLeft = (document.body.clientWidth - zoomImgWidth) / 2 const heightMagnification = document.body.clientHeight / img.height
const originalImgTop = img.y + rect.top const baseOnWidth = widthMagnification < heightMagnification
const originalImgLeft = img.x + rect.left const magnification = baseOnWidth ? widthMagnification : heightMagnification
const originalImgRect = {
top: `${originalImgTop}px`,
left: `${originalImgLeft}px`,
width: `${img.width}px`,
height: `${img.height}px`
}
const zoomInImgRect = {
top: `${baseOnWidth ? zoomImgTop : 0}px`,
left: `${baseOnWidth ? 0 : zoomImgLeft}px`,
width: `${zoomImgWidth}px`,
height: `${zoomImgHeight}px`
}
const animationSpeed = 300
const zoomImg = document.createElement('img') const zoomImgWidth = img.width * magnification
zoomImg.src = img.src const zoomImgHeight = img.height * magnification
const zoomImgTop = (document.body.clientHeight - zoomImgHeight) / 2
const zoomImgLeft = (document.body.clientWidth - zoomImgWidth) / 2
const originalImgTop = img.y + rect.top
const originalImgLeft = img.x + rect.left
const originalImgRect = {
top: `${originalImgTop}px`,
left: `${originalImgLeft}px`,
width: `${img.width}px`,
height: `${img.height}px`
}
const zoomInImgRect = {
top: `${baseOnWidth ? zoomImgTop : 0}px`,
left: `${baseOnWidth ? 0 : zoomImgLeft}px`,
width: `${zoomImgWidth}px`,
height: `${zoomImgHeight}px`
}
const animationSpeed = 300
const zoomImg = document.createElement('img')
zoomImg.src = img.src
zoomImg.style = `
position: absolute;
top: ${baseOnWidth ? zoomImgTop : 0}px;
left: ${baseOnWidth ? 0 : zoomImgLeft}px;
width: ${zoomImgWidth};
height: ${zoomImgHeight}px;
`
zoomImg.animate([
originalImgRect,
zoomInImgRect
], animationSpeed)
const overlay = document.createElement('div')
overlay.style = `
background-color: rgba(0,0,0,0.5);
cursor: zoom-out;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: ${document.body.clientHeight}px;
z-index: 100;
`
overlay.onclick = () => {
zoomImg.style = ` zoomImg.style = `
position: absolute; position: absolute;
top: ${baseOnWidth ? zoomImgTop : 0}px; top: ${originalImgTop}px;
left: ${baseOnWidth ? 0 : zoomImgLeft}px; left: ${originalImgLeft}px;
width: ${zoomImgWidth}; width: ${img.width}px;
height: ${zoomImgHeight}px; height: ${img.height}px;
` `
zoomImg.animate([ const zoomOutImgAnimation = zoomImg.animate([
originalImgRect, zoomInImgRect,
zoomInImgRect originalImgRect
], animationSpeed) ], animationSpeed)
zoomOutImgAnimation.onfinish = () => overlay.remove()
const overlay = document.createElement('div')
overlay.style = `
background-color: rgba(0,0,0,0.5);
cursor: zoom-out;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: ${document.body.clientHeight}px;
z-index: 100;
`
overlay.onclick = () => {
zoomImg.style = `
position: absolute;
top: ${originalImgTop}px;
left: ${originalImgLeft}px;
width: ${img.width}px;
height: ${img.height}px;
`
const zoomOutImgAnimation = zoomImg.animate([
zoomInImgRect,
originalImgRect
], animationSpeed)
zoomOutImgAnimation.onfinish = () => overlay.remove()
}
overlay.appendChild(zoomImg)
document.body.appendChild(overlay)
} }
overlay.appendChild(zoomImg)
document.body.appendChild(overlay)
} }
this.getWindow().scrollTo(0, 0)
} }
focus () { focus () {
@@ -953,13 +1015,19 @@ export default class MarkdownPreview extends React.Component {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
const href = e.target.href const rawHref = e.target.getAttribute('href')
const linkHash = href.split('/').pop() const parser = document.createElement('a')
parser.href = e.target.getAttribute('href')
const { href, hash } = parser
const linkHash = hash === '' ? rawHref : hash // needed because we're having special link formats that are removed by parser e.g. :line:10
const regexNoteInternalLink = /main.html#(.+)/ if (!rawHref) return // not checked href because parser will create file://... string for [empty link]()
if (regexNoteInternalLink.test(linkHash)) {
const targetId = mdurl.encode(linkHash.match(regexNoteInternalLink)[1]) const regexNoteInternalLink = /.*[main.\w]*.html#/
const targetElement = this.refs.root.contentWindow.document.getElementById(
if (regexNoteInternalLink.test(href)) {
const targetId = mdurl.encode(linkHash)
const targetElement = this.refs.root.contentWindow.document.querySelector(
targetId targetId
) )

View File

@@ -79,10 +79,10 @@ class MarkdownSplitEditor extends React.Component {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
const idMatch = /checkbox-([0-9]+)/ const idMatch = /checkbox-([0-9]+)/
const checkedMatch = /^\s*[\+\-\*] \[x\]/i const checkedMatch = /^(\s*>?)*\s*[+\-*] \[x]/i
const uncheckedMatch = /^\s*[\+\-\*] \[ \]/ const uncheckedMatch = /^(\s*>?)*\s*[+\-*] \[ ]/
const checkReplace = /\[x\]/i const checkReplace = /\[x]/i
const uncheckReplace = /\[ \]/ const uncheckReplace = /\[ ]/
if (idMatch.test(e.target.getAttribute('id'))) { if (idMatch.test(e.target.getAttribute('id'))) {
const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1 const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
const lines = this.refs.code.value const lines = this.refs.code.value
@@ -224,6 +224,8 @@ class MarkdownSplitEditor extends React.Component {
enableSmartPaste={config.editor.enableSmartPaste} enableSmartPaste={config.editor.enableSmartPaste}
hotkey={config.hotkey} hotkey={config.hotkey}
switchPreview={config.editor.switchPreview} switchPreview={config.editor.switchPreview}
enableMarkdownLint={config.editor.enableMarkdownLint}
customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
/> />
<div styleName={isStacking ? 'slider-hoz' : 'slider'} style={{left: sliderStyle.left, top: sliderStyle.top}} onMouseDown={e => this.handleMouseDown(e)} > <div styleName={isStacking ? 'slider-hoz' : 'slider'} style={{left: sliderStyle.left, top: sliderStyle.top}} onMouseDown={e => this.handleMouseDown(e)} >
<div styleName='slider-hitbox' /> <div styleName='slider-hitbox' />

View File

@@ -16,8 +16,8 @@ const NavToggleButton = ({isFolded, handleToggleButtonClick}) => (
onClick={(e) => handleToggleButtonClick(e)} onClick={(e) => handleToggleButtonClick(e)}
> >
{isFolded {isFolded
? <i className='fa fa-angle-double-right' /> ? <i className='fa fa-angle-double-right fa-2x' />
: <i className='fa fa-angle-double-left' /> : <i className='fa fa-angle-double-left fa-2x' />
} }
</button> </button>
) )

View File

@@ -7,7 +7,7 @@
border-radius 16.5px border-radius 16.5px
height 34px height 34px
width 34px width 34px
line-height 32px line-height 100%
padding 0 padding 0
&:hover &:hover
border: 1px solid #1EC38B; border: 1px solid #1EC38B;

View File

@@ -0,0 +1,78 @@
export const languageMaps = {
brainfuck: 'Brainfuck',
cpp: 'C++',
cs: 'C#',
clojure: 'Clojure',
'clojure-repl': 'ClojureScript',
cmake: 'CMake',
coffeescript: 'CoffeeScript',
crystal: 'Crystal',
css: 'CSS',
d: 'D',
dart: 'Dart',
delphi: 'Pascal',
diff: 'Diff',
django: 'Django',
dockerfile: 'Dockerfile',
ebnf: 'EBNF',
elm: 'Elm',
erlang: 'Erlang',
'erlang-repl': 'Erlang',
fortran: 'Fortran',
fsharp: 'F#',
gherkin: 'Gherkin',
go: 'Go',
groovy: 'Groovy',
haml: 'HAML',
haskell: 'Haskell',
haxe: 'Haxe',
http: 'HTTP',
ini: 'toml',
java: 'Java',
javascript: 'JavaScript',
json: 'JSON',
julia: 'Julia',
kotlin: 'Kotlin',
less: 'LESS',
livescript: 'LiveScript',
lua: 'Lua',
markdown: 'Markdown',
mathematica: 'Mathematica',
nginx: 'Nginx',
nsis: 'NSIS',
objectivec: 'Objective-C',
ocaml: 'Ocaml',
perl: 'Perl',
php: 'PHP',
powershell: 'PowerShell',
properties: 'Properties files',
protobuf: 'ProtoBuf',
python: 'Python',
puppet: 'Puppet',
q: 'Q',
r: 'R',
ruby: 'Ruby',
rust: 'Rust',
sas: 'SAS',
scala: 'Scala',
scheme: 'Scheme',
scss: 'SCSS',
shell: 'Shell',
smalltalk: 'Smalltalk',
sml: 'SML',
sql: 'SQL',
stylus: 'Stylus',
swift: 'Swift',
tcl: 'Tcl',
tex: 'LaTex',
typescript: 'TypeScript',
twig: 'Twig',
vbnet: 'VB.NET',
vbscript: 'VBScript',
verilog: 'Verilog',
vhdl: 'VHDL',
xml: 'HTML',
xquery: 'XQuery',
yaml: 'YAML',
elixir: 'Elixir'
}

View File

@@ -1,5 +1,5 @@
import CSSModules from 'react-css-modules' import CSSModules from 'react-css-modules'
export default function (component, styles) { export default function (component, styles) {
return CSSModules(component, styles, {errorWhenNotFound: false}) return CSSModules(component, styles, {handleNotFoundStyleName: 'log'})
} }

View File

@@ -62,10 +62,12 @@ const languages = [
{ {
name: 'Spanish', name: 'Spanish',
locale: 'es-ES' locale: 'es-ES'
}, { },
{
name: 'Turkish', name: 'Turkish',
locale: 'tr' locale: 'tr'
}, { },
{
name: 'Thai', name: 'Thai',
locale: 'th' locale: 'th'
} }
@@ -82,4 +84,3 @@ module.exports = {
return languages return languages
} }
} }

View File

@@ -0,0 +1,91 @@
import crypto from 'crypto'
import fs from 'fs'
import consts from './consts'
class SnippetManager {
constructor () {
this.defaultSnippet = [
{
id: crypto.randomBytes(16).toString('hex'),
name: 'Dummy text',
prefix: ['lorem', 'ipsum'],
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
}
]
this.snippets = []
this.expandSnippet = this.expandSnippet.bind(this)
this.init = this.init.bind(this)
this.assignSnippets = this.assignSnippets.bind(this)
}
init () {
if (fs.existsSync(consts.SNIPPET_FILE)) {
try {
this.snippets = JSON.parse(
fs.readFileSync(consts.SNIPPET_FILE, { encoding: 'UTF-8' })
)
} catch (error) {
console.log('Error while parsing snippet file')
}
return
}
fs.writeFileSync(
consts.SNIPPET_FILE,
JSON.stringify(this.defaultSnippet, null, 4),
'utf8'
)
this.snippets = this.defaultSnippet
}
assignSnippets (snippets) {
this.snippets = snippets
}
expandSnippet (wordBeforeCursor, cursor, cm) {
const templateCursorString = ':{}'
for (let i = 0; i < this.snippets.length; i++) {
if (this.snippets[i].prefix.indexOf(wordBeforeCursor.text) === -1) {
continue
}
if (this.snippets[i].content.indexOf(templateCursorString) !== -1) {
const snippetLines = this.snippets[i].content.split('\n')
let cursorLineNumber = 0
let cursorLinePosition = 0
let cursorIndex
for (let j = 0; j < snippetLines.length; j++) {
cursorIndex = snippetLines[j].indexOf(templateCursorString)
if (cursorIndex !== -1) {
cursorLineNumber = j
cursorLinePosition = cursorIndex
break
}
}
cm.replaceRange(
this.snippets[i].content.replace(templateCursorString, ''),
wordBeforeCursor.range.from,
wordBeforeCursor.range.to
)
cm.setCursor({
line: cursor.line + cursorLineNumber,
ch: cursorLinePosition + cursor.ch - wordBeforeCursor.text.length
})
} else {
cm.replaceRange(
this.snippets[i].content,
wordBeforeCursor.range.from,
wordBeforeCursor.range.to
)
}
return true
}
return false
}
}
const manager = new SnippetManager()
export default manager

View File

@@ -3,14 +3,43 @@ const fs = require('sander')
const { remote } = require('electron') const { remote } = require('electron')
const { app } = remote const { app } = remote
const themePath = process.env.NODE_ENV === 'production' const CODEMIRROR_THEME_PATH = 'node_modules/codemirror/theme'
? path.join(app.getAppPath(), './node_modules/codemirror/theme') const CODEMIRROR_EXTRA_THEME_PATH = 'extra_scripts/codemirror/theme'
: require('path').resolve('./node_modules/codemirror/theme')
const themes = fs.readdirSync(themePath) const isProduction = process.env.NODE_ENV === 'production'
.map((themePath) => { const paths = [
return themePath.substring(0, themePath.lastIndexOf('.')) isProduction ? path.join(app.getAppPath(), CODEMIRROR_THEME_PATH) : path.resolve(CODEMIRROR_THEME_PATH),
}) isProduction ? path.join(app.getAppPath(), CODEMIRROR_EXTRA_THEME_PATH) : path.resolve(CODEMIRROR_EXTRA_THEME_PATH)
themes.splice(themes.indexOf('solarized'), 1, 'solarized dark', 'solarized light') ]
const themes = paths
.map(directory => fs.readdirSync(directory).map(file => {
const name = file.substring(0, file.lastIndexOf('.'))
return {
name,
path: path.join(directory.split(/\//g).slice(-3).join('/'), file),
className: `cm-s-${name}`
}
}))
.reduce((accumulator, value) => accumulator.concat(value), [])
.sort((a, b) => a.name.localeCompare(b.name))
themes.splice(themes.findIndex(({ name }) => name === 'solarized'), 1, {
name: 'solarized dark',
path: `${CODEMIRROR_THEME_PATH}/solarized.css`,
className: `cm-s-solarized cm-s-dark`
}, {
name: 'solarized light',
path: `${CODEMIRROR_THEME_PATH}/solarized.css`,
className: `cm-s-solarized cm-s-light`
})
themes.splice(0, 0, {
name: 'default',
path: `${CODEMIRROR_THEME_PATH}/elegant.css`,
className: `cm-s-default`
})
const snippetFile = process.env.NODE_ENV !== 'test' const snippetFile = process.env.NODE_ENV !== 'test'
? path.join(app.getPath('userData'), 'snippets.json') ? path.join(app.getPath('userData'), 'snippets.json')
@@ -35,7 +64,7 @@ const consts = {
'Dodger Blue', 'Dodger Blue',
'Violet Eggplant' 'Violet Eggplant'
], ],
THEMES: ['default'].concat(themes), THEMES: themes,
SNIPPET_FILE: snippetFile, SNIPPET_FILE: snippetFile,
DEFAULT_EDITOR_FONT_FAMILY: [ DEFAULT_EDITOR_FONT_FAMILY: [
'Monaco', 'Monaco',

View File

@@ -4,11 +4,11 @@ export function getTodoStatus (content) {
let numberOfCompletedTodo = 0 let numberOfCompletedTodo = 0
splitted.forEach((line) => { splitted.forEach((line) => {
const trimmedLine = line.trim() const trimmedLine = line.trim().replace(/^(>\s*)*/, '')
if (trimmedLine.match(/^[\+\-\*] \[(\s|x)\] ./i)) { if (trimmedLine.match(/^[+\-*] \[(\s|x)] ./i)) {
numberOfTodo++ numberOfTodo++
} }
if (trimmedLine.match(/^[\+\-\*] \[x\] ./i)) { if (trimmedLine.match(/^[+\-*] \[x] ./i)) {
numberOfCompletedTodo++ numberOfCompletedTodo++
} }
}) })

View File

@@ -28,6 +28,8 @@ function linkify (token) {
const TOC_MARKER_START = '<!-- toc -->' const TOC_MARKER_START = '<!-- toc -->'
const TOC_MARKER_END = '<!-- tocstop -->' const TOC_MARKER_END = '<!-- tocstop -->'
const tocRegex = new RegExp(`${TOC_MARKER_START}[\\s\\S]*?${TOC_MARKER_END}`)
/** /**
* Takes care of proper updating given editor with TOC. * Takes care of proper updating given editor with TOC.
* If TOC doesn't exit in the editor, it's inserted at current caret position. * If TOC doesn't exit in the editor, it's inserted at current caret position.
@@ -35,12 +37,6 @@ const TOC_MARKER_END = '<!-- tocstop -->'
* @param editor CodeMirror editor to be updated with TOC * @param editor CodeMirror editor to be updated with TOC
*/ */
export function generateInEditor (editor) { export function generateInEditor (editor) {
const tocRegex = new RegExp(`${TOC_MARKER_START}[\\s\\S]*?${TOC_MARKER_END}`)
function tocExistsInEditor () {
return tocRegex.test(editor.getValue())
}
function updateExistingToc () { function updateExistingToc () {
const toc = generate(editor.getValue()) const toc = generate(editor.getValue())
const search = editor.getSearchCursor(tocRegex) const search = editor.getSearchCursor(tocRegex)
@@ -54,13 +50,17 @@ export function generateInEditor (editor) {
editor.replaceRange(wrapTocWithEol(toc, editor), editor.getCursor()) editor.replaceRange(wrapTocWithEol(toc, editor), editor.getCursor())
} }
if (tocExistsInEditor()) { if (tocExistsInEditor(editor)) {
updateExistingToc() updateExistingToc()
} else { } else {
addTocAtCursorPosition() addTocAtCursorPosition()
} }
} }
export function tocExistsInEditor (editor) {
return tocRegex.test(editor.getValue())
}
/** /**
* Generates MD TOC based on MD document passed as string. * Generates MD TOC based on MD document passed as string.
* @param markdownText MD document * @param markdownText MD document
@@ -94,5 +94,6 @@ function wrapTocWithEol (toc, editor) {
export default { export default {
generate, generate,
generateInEditor generateInEditor,
tocExistsInEditor
} }

View File

@@ -32,6 +32,7 @@ class Markdown {
const updatedOptions = Object.assign(defaultOptions, options) const updatedOptions = Object.assign(defaultOptions, options)
this.md = markdownit(updatedOptions) this.md = markdownit(updatedOptions)
this.md.linkify.set({ fuzzyLink: false })
if (updatedOptions.sanitize !== 'NONE') { if (updatedOptions.sanitize !== 'NONE') {
const allowedTags = ['iframe', 'input', 'b', const allowedTags = ['iframe', 'input', 'b',
@@ -181,7 +182,7 @@ class Markdown {
}) })
const deflate = require('markdown-it-plantuml/lib/deflate') const deflate = require('markdown-it-plantuml/lib/deflate')
this.md.use(require('markdown-it-plantuml'), '', { this.md.use(require('markdown-it-plantuml'), {
generateSource: function (umlCode) { generateSource: function (umlCode) {
const stripTrailingSlash = (url) => url.endsWith('/') ? url.slice(0, -1) : url const stripTrailingSlash = (url) => url.endsWith('/') ? url.slice(0, -1) : url
const serverAddress = stripTrailingSlash(config.preview.plantUMLServerAddress) + '/svg' const serverAddress = stripTrailingSlash(config.preview.plantUMLServerAddress) + '/svg'

View File

@@ -1,7 +1,8 @@
import { hashHistory } from 'react-router'
import dataApi from 'browser/main/lib/dataApi' import dataApi from 'browser/main/lib/dataApi'
import ee from 'browser/main/lib/eventEmitter' import ee from 'browser/main/lib/eventEmitter'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import queryString from 'query-string'
import { push } from 'connected-react-router'
export function createMarkdownNote (storage, folder, dispatch, location, params, config) { export function createMarkdownNote (storage, folder, dispatch, location, params, config) {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_MARKDOWN') AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_MARKDOWN')
@@ -28,10 +29,10 @@ export function createMarkdownNote (storage, folder, dispatch, location, params,
note: note note: note
}) })
hashHistory.push({ dispatch(push({
pathname: location.pathname, pathname: location.pathname,
query: { key: noteHash } search: queryString.stringify({ key: noteHash })
}) }))
ee.emit('list:jump', noteHash) ee.emit('list:jump', noteHash)
ee.emit('detail:focus') ee.emit('detail:focus')
}) })
@@ -70,10 +71,10 @@ export function createSnippetNote (storage, folder, dispatch, location, params,
type: 'UPDATE_NOTE', type: 'UPDATE_NOTE',
note: note note: note
}) })
hashHistory.push({ dispatch(push({
pathname: location.pathname, pathname: location.pathname,
query: { key: noteHash } search: queryString.stringify({ key: noteHash })
}) }))
ee.emit('list:jump', noteHash) ee.emit('list:jump', noteHash)
ee.emit('detail:focus') ee.emit('detail:focus')
}) })

View File

@@ -14,7 +14,7 @@ let self
function getAvailableDictionaries () { function getAvailableDictionaries () {
return [ return [
{label: i18n.__('Disabled'), value: SPELLCHECK_DISABLED}, {label: i18n.__('Spellcheck disabled'), value: SPELLCHECK_DISABLED},
{label: i18n.__('English'), value: 'en_GB'}, {label: i18n.__('English'), value: 'en_GB'},
{label: i18n.__('German'), value: 'de_DE'}, {label: i18n.__('German'), value: 'de_DE'},
{label: i18n.__('French'), value: 'fr_FR'} {label: i18n.__('French'), value: 'fr_FR'}

View File

@@ -132,8 +132,13 @@ export function isObjectEqual (a, b) {
return true return true
} }
export function isMarkdownTitleURL (str) {
return /(^#{1,6}\s)(?:\w+:|^)\/\/(?:[^\s\.]+\.\S{2}|localhost[\:?\d]*)/.test(str)
}
export default { export default {
lastFindInArray, lastFindInArray,
escapeHtmlCharacters, escapeHtmlCharacters,
isObjectEqual isObjectEqual,
isMarkdownTitleURL
} }

View File

@@ -14,7 +14,7 @@ class InfoPanel extends React.Component {
render () { render () {
const { const {
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, wordCount, letterCount, type, print storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, exportAsPdf, wordCount, letterCount, type, print
} = this.props } = this.props
return ( return (
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}> <div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
@@ -85,6 +85,11 @@ class InfoPanel extends React.Component {
<p>{i18n.__('.html')}</p> <p>{i18n.__('.html')}</p>
</button> </button>
<button styleName='export--enable' onClick={(e) => exportAsPdf(e, 'export-pdf')}>
<i className='fa fa-file-pdf-o' />
<p>{i18n.__('.pdf')}</p>
</button>
<button styleName='export--enable' onClick={(e) => print(e, 'print')}> <button styleName='export--enable' onClick={(e) => print(e, 'print')}>
<i className='fa fa-print' /> <i className='fa fa-print' />
<p>{i18n.__('Print')}</p> <p>{i18n.__('Print')}</p>
@@ -104,6 +109,7 @@ InfoPanel.propTypes = {
exportAsMd: PropTypes.func.isRequired, exportAsMd: PropTypes.func.isRequired,
exportAsTxt: PropTypes.func.isRequired, exportAsTxt: PropTypes.func.isRequired,
exportAsHtml: PropTypes.func.isRequired, exportAsHtml: PropTypes.func.isRequired,
exportAsPdf: PropTypes.func.isRequired,
wordCount: PropTypes.number, wordCount: PropTypes.number,
letterCount: PropTypes.number, letterCount: PropTypes.number,
type: PropTypes.string.isRequired, type: PropTypes.string.isRequired,

View File

@@ -15,7 +15,7 @@
right 25px right 25px
position absolute position absolute
padding 20px 25px 0 25px padding 20px 25px 0 25px
width 300px // width 300px
overflow auto overflow auto
background-color $ui-noteList-backgroundColor background-color $ui-noteList-backgroundColor
box-shadow 2px 12px 15px 2px rgba(0, 0, 0, 0.1), 2px 1px 50px 2px rgba(0, 0, 0, 0.1) box-shadow 2px 12px 15px 2px rgba(0, 0, 0, 0.1), 2px 1px 50px 2px rgba(0, 0, 0, 0.1)

View File

@@ -5,7 +5,7 @@ import styles from './InfoPanel.styl'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
const InfoPanelTrashed = ({ const InfoPanelTrashed = ({
storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, exportAsPdf
}) => ( }) => (
<div className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}> <div className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}>
<div> <div>
@@ -46,7 +46,7 @@ const InfoPanelTrashed = ({
<p>.html</p> <p>.html</p>
</button> </button>
<button styleName='export--unable'> <button styleName='export--enable' onClick={(e) => exportAsPdf(e, 'export-pdf')}>
<i className='fa fa-file-pdf-o' /> <i className='fa fa-file-pdf-o' />
<p>.pdf</p> <p>.pdf</p>
</button> </button>
@@ -61,7 +61,8 @@ InfoPanelTrashed.propTypes = {
createdAt: PropTypes.string.isRequired, createdAt: PropTypes.string.isRequired,
exportAsMd: PropTypes.func.isRequired, exportAsMd: PropTypes.func.isRequired,
exportAsTxt: PropTypes.func.isRequired, exportAsTxt: PropTypes.func.isRequired,
exportAsHtml: PropTypes.func.isRequired exportAsHtml: PropTypes.func.isRequired,
exportAsPdf: PropTypes.func.isRequired
} }
export default CSSModules(InfoPanelTrashed, styles) export default CSSModules(InfoPanelTrashed, styles)

View File

@@ -9,7 +9,6 @@ import StarButton from './StarButton'
import TagSelect from './TagSelect' import TagSelect from './TagSelect'
import FolderSelect from './FolderSelect' import FolderSelect from './FolderSelect'
import dataApi from 'browser/main/lib/dataApi' import dataApi from 'browser/main/lib/dataApi'
import { hashHistory } from 'react-router'
import ee from 'browser/main/lib/eventEmitter' import ee from 'browser/main/lib/eventEmitter'
import markdown from 'browser/lib/markdownTextHelper' import markdown from 'browser/lib/markdownTextHelper'
import StatusBar from '../StatusBar' import StatusBar from '../StatusBar'
@@ -31,6 +30,8 @@ import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus'
import striptags from 'striptags' import striptags from 'striptags'
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote' import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
import markdownToc from 'browser/lib/markdown-toc-generator' import markdownToc from 'browser/lib/markdown-toc-generator'
import queryString from 'query-string'
import { replace } from 'connected-react-router'
class MarkdownNoteDetail extends React.Component { class MarkdownNoteDetail extends React.Component {
constructor (props) { constructor (props) {
@@ -142,6 +143,7 @@ class MarkdownNoteDetail extends React.Component {
} }
handleFolderChange (e) { handleFolderChange (e) {
const { dispatch } = this.props
const { note } = this.state const { note } = this.state
const value = this.refs.folder.value const value = this.refs.folder.value
const splitted = value.split('-') const splitted = value.split('-')
@@ -161,12 +163,12 @@ class MarkdownNoteDetail extends React.Component {
originNote: note, originNote: note,
note: newNote note: newNote
}) })
hashHistory.replace({ dispatch(replace({
pathname: location.pathname, pathname: location.pathname,
query: { search: queryString.stringify({
key: newNote.key key: newNote.key
} })
}) }))
this.setState({ this.setState({
isMovingNote: false isMovingNote: false
}) })
@@ -203,6 +205,10 @@ class MarkdownNoteDetail extends React.Component {
ee.emit('export:save-html') ee.emit('export:save-html')
} }
exportAsPdf () {
ee.emit('export:save-pdf')
}
handleKeyDown (e) { handleKeyDown (e) {
switch (e.keyCode) { switch (e.keyCode) {
// tab key // tab key
@@ -438,6 +444,7 @@ class MarkdownNoteDetail extends React.Component {
exportAsHtml={this.exportAsHtml} exportAsHtml={this.exportAsHtml}
exportAsMd={this.exportAsMd} exportAsMd={this.exportAsMd}
exportAsTxt={this.exportAsTxt} exportAsTxt={this.exportAsTxt}
exportAsPdf={this.exportAsPdf}
/> />
</div> </div>
</div> </div>
@@ -503,12 +510,13 @@ class MarkdownNoteDetail extends React.Component {
<InfoPanel <InfoPanel
storageName={currentOption.storage.name} storageName={currentOption.storage.name}
folderName={currentOption.folder.name} folderName={currentOption.folder.name}
noteLink={`[${note.title}](:note:${location.query.key})`} noteLink={`[${note.title}](:note:${queryString.parse(location.search).key})`}
updatedAt={formatDate(note.updatedAt)} updatedAt={formatDate(note.updatedAt)}
createdAt={formatDate(note.createdAt)} createdAt={formatDate(note.createdAt)}
exportAsMd={this.exportAsMd} exportAsMd={this.exportAsMd}
exportAsTxt={this.exportAsTxt} exportAsTxt={this.exportAsTxt}
exportAsHtml={this.exportAsHtml} exportAsHtml={this.exportAsHtml}
exportAsPdf={this.exportAsPdf}
wordCount={note.content.split(' ').length} wordCount={note.content.split(' ').length}
letterCount={note.content.replace(/\r?\n/g, '').length} letterCount={note.content.replace(/\r?\n/g, '').length}
type={note.type} type={note.type}

View File

@@ -81,3 +81,11 @@ body[data-theme="dracula"]
.root .root
border-left 1px solid $ui-dracula-borderColor border-left 1px solid $ui-dracula-borderColor
background-color $ui-dracula-noteDetail-backgroundColor background-color $ui-dracula-noteDetail-backgroundColor
div
> button, div
-webkit-user-drag none
user-select none
> img, span
-webkit-user-drag none
user-select none

View File

@@ -8,7 +8,6 @@ import StarButton from './StarButton'
import TagSelect from './TagSelect' import TagSelect from './TagSelect'
import FolderSelect from './FolderSelect' import FolderSelect from './FolderSelect'
import dataApi from 'browser/main/lib/dataApi' import dataApi from 'browser/main/lib/dataApi'
import {hashHistory} from 'react-router'
import ee from 'browser/main/lib/eventEmitter' import ee from 'browser/main/lib/eventEmitter'
import CodeMirror from 'codemirror' import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir' import 'codemirror-mode-elixir'
@@ -18,7 +17,6 @@ import context from 'browser/lib/context'
import ConfigManager from 'browser/main/lib/ConfigManager' import ConfigManager from 'browser/main/lib/ConfigManager'
import _ from 'lodash' import _ from 'lodash'
import {findNoteTitle} from 'browser/lib/findNoteTitle' import {findNoteTitle} from 'browser/lib/findNoteTitle'
import convertModeName from 'browser/lib/convertModeName'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import FullscreenButton from './FullscreenButton' import FullscreenButton from './FullscreenButton'
import TrashButton from './TrashButton' import TrashButton from './TrashButton'
@@ -31,6 +29,8 @@ import { formatDate } from 'browser/lib/date-formatter'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote' import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
import markdownToc from 'browser/lib/markdown-toc-generator' import markdownToc from 'browser/lib/markdown-toc-generator'
import queryString from 'query-string'
import { replace } from 'connected-react-router'
const electron = require('electron') const electron = require('electron')
const { remote } = electron const { remote } = electron
@@ -166,12 +166,12 @@ class SnippetNoteDetail extends React.Component {
originNote: note, originNote: note,
note: newNote note: newNote
}) })
hashHistory.replace({ dispatch(replace({
pathname: location.pathname, pathname: location.pathname,
query: { search: queryString.stringify({
key: newNote.key key: newNote.key
} })
}) }))
this.setState({ this.setState({
isMovingNote: false isMovingNote: false
}) })
@@ -657,6 +657,7 @@ class SnippetNoteDetail extends React.Component {
'export-txt': 'Text export', 'export-txt': 'Text export',
'export-md': 'Markdown export', 'export-md': 'Markdown export',
'export-html': 'HTML export', 'export-html': 'HTML export',
'export-pdf': 'PDF export',
'print': 'Print' 'print': 'Print'
})[msg] })[msg]
@@ -770,6 +771,7 @@ class SnippetNoteDetail extends React.Component {
exportAsMd={this.showWarning} exportAsMd={this.showWarning}
exportAsTxt={this.showWarning} exportAsTxt={this.showWarning}
exportAsHtml={this.showWarning} exportAsHtml={this.showWarning}
exportAsPdf={this.showWarning}
/> />
</div> </div>
</div> </div>
@@ -812,12 +814,13 @@ class SnippetNoteDetail extends React.Component {
<InfoPanel <InfoPanel
storageName={currentOption.storage.name} storageName={currentOption.storage.name}
folderName={currentOption.folder.name} folderName={currentOption.folder.name}
noteLink={`[${note.title}](:note:${location.query.key})`} noteLink={`[${note.title}](:note:${queryString.parse(location.search).key})`}
updatedAt={formatDate(note.updatedAt)} updatedAt={formatDate(note.updatedAt)}
createdAt={formatDate(note.createdAt)} createdAt={formatDate(note.createdAt)}
exportAsMd={this.showWarning} exportAsMd={this.showWarning}
exportAsTxt={this.showWarning} exportAsTxt={this.showWarning}
exportAsHtml={this.showWarning} exportAsHtml={this.showWarning}
exportAsPdf={this.showWarning}
type={note.type} type={note.type}
print={this.showWarning} print={this.showWarning}
/> />

View File

@@ -10,6 +10,7 @@ import StatusBar from '../StatusBar'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import debounceRender from 'react-debounce-render' import debounceRender from 'react-debounce-render'
import searchFromNotes from 'browser/lib/search' import searchFromNotes from 'browser/lib/search'
import queryString from 'query-string'
const OSX = global.process.platform === 'darwin' const OSX = global.process.platform === 'darwin'
@@ -36,11 +37,11 @@ class Detail extends React.Component {
} }
render () { render () {
const { location, data, params, config } = this.props const { location, data, match: { params }, config } = this.props
const noteKey = location.search !== '' && queryString.parse(location.search).key
let note = null let note = null
if (location.query.key != null) { if (location.search !== '') {
const noteKey = location.query.key
const allNotes = data.noteMap.map(note => note) const allNotes = data.noteMap.map(note => note)
const trashedNotes = data.trashedSet.toJS().map(uniqueKey => data.noteMap.get(uniqueKey)) const trashedNotes = data.trashedSet.toJS().map(uniqueKey => data.noteMap.get(uniqueKey))
let displayedNotes = allNotes let displayedNotes = allNotes

View File

@@ -0,0 +1,16 @@
import React from 'react'
import { createDevTools } from 'redux-devtools'
import LogMonitor from 'redux-devtools-log-monitor'
import DockMonitor from 'redux-devtools-dock-monitor'
const DevTools = createDevTools(
<DockMonitor
toggleVisibilityKey='ctrl-h'
changePositionKey='ctrl-q'
defaultIsVisible={false}
>
<LogMonitor theme='tomorrow' />
</DockMonitor>
)
export default DevTools

View File

@@ -0,0 +1,8 @@
/* eslint-disable no-undef */
if (process.env.NODE_ENV === 'production') {
// eslint-disable-next-line global-require
module.exports = require('./index.prod').default
} else {
// eslint-disable-next-line global-require
module.exports = require('./index.dev').default
}

View File

@@ -0,0 +1,6 @@
import React from 'react'
const DevTools = () => <div />
DevTools.instrument = () => {}
export default DevTools

View File

@@ -12,11 +12,11 @@ import _ from 'lodash'
import ConfigManager from 'browser/main/lib/ConfigManager' import ConfigManager from 'browser/main/lib/ConfigManager'
import mobileAnalytics from 'browser/main/lib/AwsMobileAnalyticsConfig' import mobileAnalytics from 'browser/main/lib/AwsMobileAnalyticsConfig'
import eventEmitter from 'browser/main/lib/eventEmitter' import eventEmitter from 'browser/main/lib/eventEmitter'
import { hashHistory } from 'react-router' import { store } from 'browser/main/store'
import store from 'browser/main/store'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import { getLocales } from 'browser/lib/Languages' import { getLocales } from 'browser/lib/Languages'
import applyShortcuts from 'browser/main/lib/shortcutManager' import applyShortcuts from 'browser/main/lib/shortcutManager'
import { push } from 'connected-react-router'
const path = require('path') const path = require('path')
const electron = require('electron') const electron = require('electron')
const { remote } = electron const { remote } = electron
@@ -132,7 +132,7 @@ class Main extends React.Component {
.then(() => data.storage) .then(() => data.storage)
}) })
.then(storage => { .then(storage => {
hashHistory.push('/storages/' + storage.key) store.dispatch(push('/storages/' + storage.key))
}) })
.catch(err => { .catch(err => {
throw err throw err
@@ -311,7 +311,7 @@ class Main extends React.Component {
onMouseUp={e => this.handleMouseUp(e)} onMouseUp={e => this.handleMouseUp(e)}
> >
<SideNav <SideNav
{..._.pick(this.props, ['dispatch', 'data', 'config', 'params', 'location'])} {..._.pick(this.props, ['dispatch', 'data', 'config', 'match', 'location'])}
width={this.state.navWidth} width={this.state.navWidth}
/> />
{!config.isSideNavFolded && {!config.isSideNavFolded &&
@@ -341,7 +341,7 @@ class Main extends React.Component {
'dispatch', 'dispatch',
'config', 'config',
'data', 'data',
'params', 'match',
'location' 'location'
])} ])}
/> />
@@ -351,7 +351,7 @@ class Main extends React.Component {
'dispatch', 'dispatch',
'data', 'data',
'config', 'config',
'params', 'match',
'location' 'location'
])} ])}
/> />
@@ -373,7 +373,7 @@ class Main extends React.Component {
'dispatch', 'dispatch',
'data', 'data',
'config', 'config',
'params', 'match',
'location' 'location'
])} ])}
ignorePreviewPointerEvents={this.state.isRightSliderFocused} ignorePreviewPointerEvents={this.state.isRightSliderFocused}

View File

@@ -21,23 +21,20 @@ class NewNoteButton extends React.Component {
this.state = { this.state = {
} }
this.newNoteHandler = () => { this.handleNewNoteButtonClick = this.handleNewNoteButtonClick.bind(this)
this.handleNewNoteButtonClick()
}
} }
componentDidMount () { componentDidMount () {
eventEmitter.on('top:new-note', this.newNoteHandler) eventEmitter.on('top:new-note', this.handleNewNoteButtonClick)
} }
componentWillUnmount () { componentWillUnmount () {
eventEmitter.off('top:new-note', this.newNoteHandler) eventEmitter.off('top:new-note', this.handleNewNoteButtonClick)
} }
handleNewNoteButtonClick (e) { handleNewNoteButtonClick (e) {
const { location, params, dispatch, config } = this.props const { location, dispatch, match: { params }, config } = this.props
const { storage, folder } = this.resolveTargetFolder() const { storage, folder } = this.resolveTargetFolder()
if (config.ui.defaultNote === 'MARKDOWN_NOTE') { if (config.ui.defaultNote === 'MARKDOWN_NOTE') {
createMarkdownNote(storage.key, folder.key, dispatch, location, params, config) createMarkdownNote(storage.key, folder.key, dispatch, location, params, config)
} else if (config.ui.defaultNote === 'SNIPPET_NOTE') { } else if (config.ui.defaultNote === 'SNIPPET_NOTE') {
@@ -55,9 +52,8 @@ class NewNoteButton extends React.Component {
} }
resolveTargetFolder () { resolveTargetFolder () {
const { data, params } = this.props const { data, match: { params } } = this.props
let storage = data.storageMap.get(params.storageKey) let storage = data.storageMap.get(params.storageKey)
// Find first storage // Find first storage
if (storage == null) { if (storage == null) {
for (const kv of data.storageMap) { for (const kv of data.storageMap) {
@@ -93,7 +89,7 @@ class NewNoteButton extends React.Component {
> >
<div styleName='control'> <div styleName='control'>
<button styleName='control-newNoteButton' <button styleName='control-newNoteButton'
onClick={(e) => this.handleNewNoteButtonClick(e)}> onClick={this.handleNewNoteButtonClick}>
<img styleName='iconTag' src='../resources/icon/icon-newnote.svg' /> <img styleName='iconTag' src='../resources/icon/icon-newnote.svg' />
<span styleName='control-newNoteButton-tooltip'> <span styleName='control-newNoteButton-tooltip'>
{i18n.__('Make a note')} {OSX ? '⌘' : i18n.__('Ctrl')} + N {i18n.__('Make a note')} {OSX ? '⌘' : i18n.__('Ctrl')} + N

View File

@@ -14,23 +14,42 @@ import NoteItemSimple from 'browser/components/NoteItemSimple'
import searchFromNotes from 'browser/lib/search' import searchFromNotes from 'browser/lib/search'
import fs from 'fs' import fs from 'fs'
import path from 'path' import path from 'path'
import { hashHistory } from 'react-router' import { push, replace } from 'connected-react-router'
import copy from 'copy-to-clipboard' import copy from 'copy-to-clipboard'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import Markdown from '../../lib/markdown' import Markdown from '../../lib/markdown'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote' import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
import context from 'browser/lib/context' import context from 'browser/lib/context'
import queryString from 'query-string'
const { remote } = require('electron') const { remote } = require('electron')
const { dialog } = remote const { dialog } = remote
const WP_POST_PATH = '/wp/v2/posts' const WP_POST_PATH = '/wp/v2/posts'
const regexMatchStartingTitleNumber = new RegExp('^([0-9]*\.?[0-9]+).*$')
function sortByCreatedAt (a, b) { function sortByCreatedAt (a, b) {
return new Date(b.createdAt) - new Date(a.createdAt) return new Date(b.createdAt) - new Date(a.createdAt)
} }
function sortByAlphabetical (a, b) { function sortByAlphabetical (a, b) {
const matchA = regexMatchStartingTitleNumber.exec(a.title)
const matchB = regexMatchStartingTitleNumber.exec(b.title)
if (matchA && matchA.length === 2 && matchB && matchB.length === 2) {
// Both note titles are starting with a float. We will compare it now.
const floatA = parseFloat(matchA[1])
const floatB = parseFloat(matchB[1])
const diff = floatA - floatB
if (diff !== 0) {
return diff
}
// The float values are equal. We will compare the full title.
}
return a.title.localeCompare(b.title) return a.title.localeCompare(b.title)
} }
@@ -127,15 +146,15 @@ class NoteList extends React.Component {
} }
componentDidUpdate (prevProps) { componentDidUpdate (prevProps) {
const { location } = this.props const { dispatch, location } = this.props
const { selectedNoteKeys } = this.state const { selectedNoteKeys } = this.state
const visibleNoteKeys = this.notes.map(note => note.key) const visibleNoteKeys = this.notes && this.notes.map(note => note.key)
const note = this.notes[0] const note = this.notes && this.notes[0]
const prevKey = prevProps.location.query.key const key = location.search && queryString.parse(location.search).key
const prevKey = prevProps.location.search && queryString.parse(prevProps.location.search).key
const noteKey = visibleNoteKeys.includes(prevKey) ? prevKey : note && note.key const noteKey = visibleNoteKeys.includes(prevKey) ? prevKey : note && note.key
if (note && location.query.key == null) { if (note && location.search === '') {
const { router } = this.context
if (!location.pathname.match(/\/searched/)) this.contextNotes = this.getContextNotes() if (!location.pathname.match(/\/searched/)) this.contextNotes = this.getContextNotes()
// A visible note is an active note // A visible note is an active note
@@ -145,17 +164,17 @@ class NoteList extends React.Component {
ee.emit('list:moved') ee.emit('list:moved')
} }
router.replace({ dispatch(replace({ // was passed with context - we can use connected router here
pathname: location.pathname, pathname: location.pathname,
query: { search: queryString.stringify({
key: noteKey key: noteKey
} })
}) }))
return return
} }
// Auto scroll // Auto scroll
if (_.isString(location.query.key) && prevProps.location.query.key === location.query.key) { if (_.isString(key) && prevKey === key) {
const targetIndex = this.getTargetIndex() const targetIndex = this.getTargetIndex()
if (targetIndex > -1) { if (targetIndex > -1) {
const list = this.refs.list const list = this.refs.list
@@ -176,18 +195,18 @@ class NoteList extends React.Component {
} }
focusNote (selectedNoteKeys, noteKey, pathname) { focusNote (selectedNoteKeys, noteKey, pathname) {
const { router } = this.context const { dispatch } = this.props
this.setState({ this.setState({
selectedNoteKeys selectedNoteKeys
}) })
router.push({ dispatch(push({
pathname, pathname,
query: { search: queryString.stringify({
key: noteKey key: noteKey
} })
}) }))
} }
getNoteKeyFromTargetIndex (targetIndex) { getNoteKeyFromTargetIndex (targetIndex) {
@@ -259,13 +278,22 @@ class NoteList extends React.Component {
} }
jumpNoteByHashHandler (event, noteHash) { jumpNoteByHashHandler (event, noteHash) {
const { data } = this.props
// first argument event isn't used. // first argument event isn't used.
if (this.notes === null || this.notes.length === 0) { if (this.notes === null || this.notes.length === 0) {
return return
} }
const selectedNoteKeys = [noteHash] const selectedNoteKeys = [noteHash]
this.focusNote(selectedNoteKeys, noteHash, '/home')
let locationToSelect = '/home'
const noteByHash = data.noteMap.map((note) => note).find(note => note.key === noteHash)
if (noteByHash !== undefined) {
locationToSelect = '/storages/' + noteByHash.storage + '/folders/' + noteByHash.folder
}
this.focusNote(selectedNoteKeys, noteHash, locationToSelect)
ee.emit('list:moved') ee.emit('list:moved')
} }
@@ -321,8 +349,7 @@ class NoteList extends React.Component {
} }
getNotes () { getNotes () {
const { data, params, location } = this.props const { data, match: { params }, location } = this.props
if (location.pathname.match(/\/home/) || location.pathname.match(/alltags/)) { if (location.pathname.match(/\/home/) || location.pathname.match(/alltags/)) {
const allNotes = data.noteMap.map((note) => note) const allNotes = data.noteMap.map((note) => note)
this.contextNotes = allNotes this.contextNotes = allNotes
@@ -363,7 +390,7 @@ class NoteList extends React.Component {
// get notes in the current folder // get notes in the current folder
getContextNotes () { getContextNotes () {
const { data, params } = this.props const { data, match: { params } } = this.props
const storageKey = params.storageKey const storageKey = params.storageKey
const folderKey = params.folderKey const folderKey = params.folderKey
const storage = data.storageMap.get(storageKey) const storage = data.storageMap.get(storageKey)
@@ -403,8 +430,7 @@ class NoteList extends React.Component {
} }
handleNoteClick (e, uniqueKey) { handleNoteClick (e, uniqueKey) {
const { router } = this.context const { dispatch, location } = this.props
const { location } = this.props
let { selectedNoteKeys, prevShiftNoteIndex } = this.state let { selectedNoteKeys, prevShiftNoteIndex } = this.state
const { ctrlKeyDown, shiftKeyDown } = this.state const { ctrlKeyDown, shiftKeyDown } = this.state
const hasSelectedNoteKey = selectedNoteKeys.length > 0 const hasSelectedNoteKey = selectedNoteKeys.length > 0
@@ -455,16 +481,16 @@ class NoteList extends React.Component {
prevShiftNoteIndex prevShiftNoteIndex
}) })
router.push({ dispatch(push({
pathname: location.pathname, pathname: location.pathname,
query: { search: queryString.stringify({
key: uniqueKey key: uniqueKey
} })
}) }))
} }
handleSortByChange (e) { handleSortByChange (e) {
const { dispatch, params: { folderKey } } = this.props const { dispatch, match: { params: { folderKey } } } = this.props
const config = { const config = {
[folderKey]: { sortBy: e.target.value } [folderKey]: { sortBy: e.target.value }
@@ -496,6 +522,7 @@ class NoteList extends React.Component {
'export-txt': 'Text export', 'export-txt': 'Text export',
'export-md': 'Markdown export', 'export-md': 'Markdown export',
'export-html': 'HTML export', 'export-html': 'HTML export',
'export-pdf': 'PDF export',
'print': 'Print' 'print': 'Print'
})[msg] })[msg]
@@ -716,7 +743,11 @@ class NoteList extends React.Component {
folder: folder.key, folder: folder.key,
title: firstNote.title + ' ' + i18n.__('copy'), title: firstNote.title + ' ' + i18n.__('copy'),
content: firstNote.content, content: firstNote.content,
linesHighlighted: firstNote.linesHighlighted linesHighlighted: firstNote.linesHighlighted,
description: firstNote.description,
snippets: firstNote.snippets,
tags: firstNote.tags,
isStarred: firstNote.isStarred
}) })
.then((note) => { .then((note) => {
attachmentManagement.cloneAttachments(firstNote, note) attachmentManagement.cloneAttachments(firstNote, note)
@@ -732,10 +763,10 @@ class NoteList extends React.Component {
selectedNoteKeys: [note.key] selectedNoteKeys: [note.key]
}) })
hashHistory.push({ dispatch(push({
pathname: location.pathname, pathname: location.pathname,
query: {key: note.key} search: queryString.stringify({key: note.key})
}) }))
}) })
} }
@@ -745,13 +776,13 @@ class NoteList extends React.Component {
} }
navigate (sender, pathname) { navigate (sender, pathname) {
const { router } = this.context const { dispatch } = this.props
router.push({ dispatch(push({
pathname, pathname,
query: { search: queryString.stringify({
// key: noteKey // key: noteKey
} })
}) }))
} }
save (note) { save (note) {
@@ -881,7 +912,7 @@ class NoteList extends React.Component {
if (!location.pathname.match(/\/trashed/)) this.addNotesFromFiles(filepaths) if (!location.pathname.match(/\/trashed/)) this.addNotesFromFiles(filepaths)
} }
// Add notes to the current folder // Add notes to the current folder
addNotesFromFiles (filepaths) { addNotesFromFiles (filepaths) {
const { dispatch, location } = this.props const { dispatch, location } = this.props
const { storage, folder } = this.resolveTargetFolder() const { storage, folder } = this.resolveTargetFolder()
@@ -905,13 +936,20 @@ class NoteList extends React.Component {
} }
dataApi.createNote(storage.key, newNote) dataApi.createNote(storage.key, newNote)
.then((note) => { .then((note) => {
dispatch({ attachmentManagement.importAttachments(note.content, filepath, storage.key, note.key)
type: 'UPDATE_NOTE', .then((newcontent) => {
note: note note.content = newcontent
})
hashHistory.push({ dataApi.updateNote(storage.key, note.key, note)
pathname: location.pathname,
query: {key: getNoteKey(note)} dispatch({
type: 'UPDATE_NOTE',
note: note
})
dispatch(push({
pathname: location.pathname,
search: queryString.stringify({key: getNoteKey(note)})
}))
}) })
}) })
}) })
@@ -921,14 +959,15 @@ class NoteList extends React.Component {
getTargetIndex () { getTargetIndex () {
const { location } = this.props const { location } = this.props
const key = queryString.parse(location.search).key
const targetIndex = _.findIndex(this.notes, (note) => { const targetIndex = _.findIndex(this.notes, (note) => {
return getNoteKey(note) === location.query.key return getNoteKey(note) === key
}) })
return targetIndex return targetIndex
} }
resolveTargetFolder () { resolveTargetFolder () {
const { data, params } = this.props const { data, match: { params } } = this.props
let storage = data.storageMap.get(params.storageKey) let storage = data.storageMap.get(params.storageKey)
// Find first storage // Find first storage
@@ -976,7 +1015,7 @@ class NoteList extends React.Component {
} }
render () { render () {
const { location, config, params: { folderKey } } = this.props const { location, config, match: { params: { folderKey } } } = this.props
let { notes } = this.props let { notes } = this.props
const { selectedNoteKeys } = this.state const { selectedNoteKeys } = this.state
const sortBy = _.get(config, [folderKey, 'sortBy'], config.sortBy.default) const sortBy = _.get(config, [folderKey, 'sortBy'], config.sortBy.default)

View File

@@ -2,7 +2,6 @@ import PropTypes from 'prop-types'
import React from 'react' import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './StorageItem.styl' import styles from './StorageItem.styl'
import { hashHistory } from 'react-router'
import modal from 'browser/main/lib/modal' import modal from 'browser/main/lib/modal'
import CreateFolderModal from 'browser/main/modals/CreateFolderModal' import CreateFolderModal from 'browser/main/modals/CreateFolderModal'
import RenameFolderModal from 'browser/main/modals/RenameFolderModal' import RenameFolderModal from 'browser/main/modals/RenameFolderModal'
@@ -12,6 +11,7 @@ import _ from 'lodash'
import { SortableElement } from 'react-sortable-hoc' import { SortableElement } from 'react-sortable-hoc'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import context from 'browser/lib/context' import context from 'browser/lib/context'
import { push } from 'connected-react-router'
const { remote } = require('electron') const { remote } = require('electron')
const { dialog } = remote const { dialog } = remote
@@ -134,14 +134,14 @@ class StorageItem extends React.Component {
} }
handleHeaderInfoClick (e) { handleHeaderInfoClick (e) {
const { storage } = this.props const { storage, dispatch } = this.props
hashHistory.push('/storages/' + storage.key) dispatch(push('/storages/' + storage.key))
} }
handleFolderButtonClick (folderKey) { handleFolderButtonClick (folderKey) {
return (e) => { return (e) => {
const { storage } = this.props const { storage, dispatch } = this.props
hashHistory.push('/storages/' + storage.key + '/folders/' + folderKey) dispatch(push('/storages/' + storage.key + '/folders/' + folderKey))
} }
} }

View File

@@ -1,5 +1,6 @@
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import React from 'react' import React from 'react'
import { push } from 'connected-react-router'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import dataApi from 'browser/main/lib/dataApi' import dataApi from 'browser/main/lib/dataApi'
import styles from './SideNav.styl' import styles from './SideNav.styl'
@@ -62,7 +63,7 @@ class SideNav extends React.Component {
}) })
if (selectedButton === 0) { if (selectedButton === 0) {
const { data, dispatch, location, params } = this.props const { data, dispatch, location, match: { params } } = this.props
const notes = data.noteMap const notes = data.noteMap
.map(note => note) .map(note => note)
@@ -92,7 +93,7 @@ class SideNav extends React.Component {
if (index !== -1) { if (index !== -1) {
tags.splice(index, 1) tags.splice(index, 1)
this.context.router.push(`/tags/${tags.map(tag => encodeURIComponent(tag)).join(' ')}`) dispatch(push(`/tags/${tags.map(tag => encodeURIComponent(tag)).join(' ')}`))
} }
} }
}) })
@@ -104,13 +105,13 @@ class SideNav extends React.Component {
} }
handleHomeButtonClick (e) { handleHomeButtonClick (e) {
const { router } = this.context const { dispatch } = this.props
router.push('/home') dispatch(push('/home'))
} }
handleStarredButtonClick (e) { handleStarredButtonClick (e) {
const { router } = this.context const { dispatch } = this.props
router.push('/starred') dispatch(push('/starred'))
} }
handleTagContextMenu (e, tag) { handleTagContextMenu (e, tag) {
@@ -190,18 +191,18 @@ class SideNav extends React.Component {
} }
handleTrashedButtonClick (e) { handleTrashedButtonClick (e) {
const { router } = this.context const { dispatch } = this.props
router.push('/trashed') dispatch(push('/trashed'))
} }
handleSwitchFoldersButtonClick () { handleSwitchFoldersButtonClick () {
const { router } = this.context const { dispatch } = this.props
router.push('/home') dispatch(push('/home'))
} }
handleSwitchTagsButtonClick () { handleSwitchTagsButtonClick () {
const { router } = this.context const { dispatch } = this.props
router.push('/alltags') dispatch(push('/alltags'))
} }
onSortEnd (storage) { onSortEnd (storage) {
@@ -348,8 +349,8 @@ class SideNav extends React.Component {
} }
handleClickTagListItem (name) { handleClickTagListItem (name) {
const { router } = this.context const { dispatch } = this.props
router.push(`/tags/${encodeURIComponent(name)}`) dispatch(push(`/tags/${encodeURIComponent(name)}`))
} }
handleSortTagsByChange (e) { handleSortTagsByChange (e) {
@@ -367,8 +368,7 @@ class SideNav extends React.Component {
} }
handleClickNarrowToTag (tag) { handleClickNarrowToTag (tag) {
const { router } = this.context const { dispatch, location } = this.props
const { location } = this.props
const listOfTags = this.getActiveTags(location.pathname) const listOfTags = this.getActiveTags(location.pathname)
const indexOfTag = listOfTags.indexOf(tag) const indexOfTag = listOfTags.indexOf(tag)
if (indexOfTag > -1) { if (indexOfTag > -1) {
@@ -376,7 +376,7 @@ class SideNav extends React.Component {
} else { } else {
listOfTags.push(tag) listOfTags.push(tag)
} }
router.push(`/tags/${encodeURIComponent(listOfTags.join(' '))}`) dispatch(push(`/tags/${encodeURIComponent(listOfTags.join(' '))}`))
} }
emptyTrash (entries) { emptyTrash (entries) {

View File

@@ -7,6 +7,8 @@ import ee from 'browser/main/lib/eventEmitter'
import NewNoteButton from 'browser/main/NewNoteButton' import NewNoteButton from 'browser/main/NewNoteButton'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import debounce from 'lodash/debounce' import debounce from 'lodash/debounce'
import { push } from 'connected-react-router'
import queryString from 'query-string'
class TopBar extends React.Component { class TopBar extends React.Component {
constructor (props) { constructor (props) {
@@ -26,6 +28,8 @@ class TopBar extends React.Component {
} }
this.codeInitHandler = this.handleCodeInit.bind(this) this.codeInitHandler = this.handleCodeInit.bind(this)
this.updateKeyword = this.updateKeyword.bind(this)
this.handleSearchClearButton = this.handleSearchClearButton.bind(this)
this.updateKeyword = debounce(this.updateKeyword, 1000 / 60, { this.updateKeyword = debounce(this.updateKeyword, 1000 / 60, {
maxWait: 1000 / 8 maxWait: 1000 / 8
@@ -33,8 +37,8 @@ class TopBar extends React.Component {
} }
componentDidMount () { componentDidMount () {
const { params } = this.props const { match: { params } } = this.props
const searchWord = params.searchword const searchWord = params && params.searchword
if (searchWord !== undefined) { if (searchWord !== undefined) {
this.setState({ this.setState({
search: searchWord, search: searchWord,
@@ -51,13 +55,13 @@ class TopBar extends React.Component {
} }
handleSearchClearButton (e) { handleSearchClearButton (e) {
const { router } = this.context const { dispatch } = this.props
this.setState({ this.setState({
search: '', search: '',
isSearching: false isSearching: false
}) })
this.refs.search.childNodes[0].blur this.refs.search.childNodes[0].blur
router.push('/searched') dispatch(push('/searched'))
e.preventDefault() e.preventDefault()
} }
@@ -124,7 +128,8 @@ class TopBar extends React.Component {
} }
updateKeyword (keyword) { updateKeyword (keyword) {
this.context.router.push(`/searched/${encodeURIComponent(keyword)}`) const { dispatch } = this.props
dispatch(push(`/searched/${encodeURIComponent(keyword)}`))
this.setState({ this.setState({
search: keyword search: keyword
}) })
@@ -210,8 +215,8 @@ class TopBar extends React.Component {
'dispatch', 'dispatch',
'data', 'data',
'config', 'config',
'params', 'location',
'location' 'match'
])} ])}
/>} />}
</div> </div>

View File

@@ -1,11 +1,13 @@
import { Provider } from 'react-redux' import { Provider } from 'react-redux'
import Main from './Main' import Main from './Main'
import store from './store' import { store, history } from './store'
import React from 'react' import React, { Fragment } from 'react'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
require('!!style!css!stylus?sourceMap!./global.styl') require('!!style!css!stylus?sourceMap!./global.styl')
import { Router, Route, IndexRoute, IndexRedirect, hashHistory } from 'react-router' import { Route, Switch, Redirect } from 'react-router-dom'
import { syncHistoryWithStore } from 'react-router-redux' import { ConnectedRouter } from 'connected-react-router'
import DevTools from './DevTools'
require('./lib/ipcClient') require('./lib/ipcClient')
require('../lib/customMeta') require('../lib/customMeta')
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
@@ -77,7 +79,6 @@ document.addEventListener('click', function (e) {
}) })
const el = document.getElementById('content') const el = document.getElementById('content')
const history = syncHistoryWithStore(hashHistory, store)
function notify (...args) { function notify (...args) {
return new window.Notification(...args) return new window.Notification(...args)
@@ -98,29 +99,24 @@ function updateApp () {
ReactDOM.render(( ReactDOM.render((
<Provider store={store}> <Provider store={store}>
<Router history={history}> <ConnectedRouter history={history}>
<Route path='/' component={Main}> <Fragment>
<IndexRedirect to='/home' /> <Switch>
<Route path='home' /> <Redirect path='/' to='/home' exact />
<Route path='starred' /> <Route path='/(home|alltags|starred|trashed)' component={Main} />
<Route path='searched'> <Route path='/searched' component={Main} exact />
<Route path=':searchword' /> <Route path='/searched/:searchword' component={Main} />
</Route> <Redirect path='/tags' to='/alltags' exact />
<Route path='trashed' /> <Route path='/tags/:tagname' component={Main} />
<Route path='alltags' />
<Route path='tags'> {/* storages */}
<IndexRedirect to='/alltags' /> <Redirect path='/storages' to='/home' exact />
<Route path=':tagname' /> <Route path='/storages/:storageKey' component={Main} exact />
</Route> <Route path='/storages/:storageKey/folders/:folderKey' component={Main} />
<Route path='storages'> </Switch>
<IndexRedirect to='/home' /> <DevTools />
<Route path=':storageKey'> </Fragment>
<IndexRoute /> </ConnectedRouter>
<Route path='folders/:folderKey' />
</Route>
</Route>
</Route>
</Router>
</Provider> </Provider>
), el, function () { ), el, function () {
const loadingCover = document.getElementById('loadingCover') const loadingCover = document.getElementById('loadingCover')

View File

@@ -11,6 +11,10 @@ const consts = require('browser/lib/consts')
let isInitialized = false let isInitialized = false
const DEFAULT_MARKDOWN_LINT_CONFIG = `{
"default": true
}`
export const DEFAULT_CONFIG = { export const DEFAULT_CONFIG = {
zoom: 1, zoom: 1,
isSideNavFolded: false, isSideNavFolded: false,
@@ -47,7 +51,7 @@ export const DEFAULT_CONFIG = {
enableRulers: false, enableRulers: false,
rulers: [80, 120], rulers: [80, 120],
displayLineNumbers: true, displayLineNumbers: true,
matchingPairs: '()[]{}\'\'""$$**``', matchingPairs: '()[]{}\'\'""$$**``~~__',
matchingTriples: '```"""\'\'\'', matchingTriples: '```"""\'\'\'',
explodingPairs: '[]{}``$$', explodingPairs: '[]{}``$$',
switchPreview: 'BLUR', // 'BLUR', 'DBL_CLICK', 'RIGHTCLICK' switchPreview: 'BLUR', // 'BLUR', 'DBL_CLICK', 'RIGHTCLICK'
@@ -60,7 +64,9 @@ export const DEFAULT_CONFIG = {
enableFrontMatterTitle: true, enableFrontMatterTitle: true,
frontMatterTitleField: 'title', frontMatterTitleField: 'title',
spellcheck: false, spellcheck: false,
enableSmartPaste: false enableSmartPaste: false,
enableMarkdownLint: false,
customMarkdownLintConfig: DEFAULT_MARKDOWN_LINT_CONFIG
}, },
preview: { preview: {
fontSize: '14', fontSize: '14',
@@ -133,16 +139,12 @@ function get () {
document.head.appendChild(editorTheme) document.head.appendChild(editorTheme)
} }
config.editor.theme = consts.THEMES.some((theme) => theme === config.editor.theme) const theme = consts.THEMES.find(theme => theme.name === config.editor.theme)
? config.editor.theme
: 'default'
if (config.editor.theme !== 'default') { if (theme) {
if (config.editor.theme.startsWith('solarized')) { editorTheme.setAttribute('href', `../${theme.path}`)
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/solarized.css') } else {
} else { config.editor.theme = 'default'
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/' + config.editor.theme + '.css')
}
} }
} }
@@ -178,16 +180,11 @@ function set (updates) {
editorTheme.setAttribute('rel', 'stylesheet') editorTheme.setAttribute('rel', 'stylesheet')
document.head.appendChild(editorTheme) document.head.appendChild(editorTheme)
} }
const newTheme = consts.THEMES.some((theme) => theme === newConfig.editor.theme)
? newConfig.editor.theme
: 'default'
if (newTheme !== 'default') { const newTheme = consts.THEMES.find(theme => theme.name === newConfig.editor.theme)
if (newTheme.startsWith('solarized')) {
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/solarized.css') if (newTheme) {
} else { editorTheme.setAttribute('href', `../${newTheme.path}`)
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/' + newTheme + '.css')
}
} }
ipcRenderer.send('config-renew', { ipcRenderer.send('config-renew', {

View File

@@ -85,7 +85,7 @@ function getOrientation (file) {
return view.getUint16(offset + (i * 12) + 8, little) return view.getUint16(offset + (i * 12) + 8, little)
} }
} }
} else if ((marker & 0xFF00) !== 0xFF00) { // If not start with 0xFF, not a Marker } else if ((marker & 0xFF00) !== 0xFF00) { // If not start with 0xFF, not a Marker.
break break
} else { } else {
offset += view.getUint16(offset, false) offset += view.getUint16(offset, false)
@@ -278,27 +278,40 @@ function handleAttachmentDrop (codeEditor, storageKey, noteKey, dropEvent) {
let promise let promise
if (dropEvent.dataTransfer.files.length > 0) { if (dropEvent.dataTransfer.files.length > 0) {
promise = Promise.all(Array.from(dropEvent.dataTransfer.files).map(file => { promise = Promise.all(Array.from(dropEvent.dataTransfer.files).map(file => {
if (file.type.startsWith('image')) { const filePath = file.path
if (file.type === 'image/gif' || file.type === 'image/svg+xml') { const fileType = file.type // EX) 'image/gif' or 'text/html'
return copyAttachment(file.path, storageKey, noteKey).then(fileName => ({ if (fileType.startsWith('image')) {
if (fileType === 'image/gif' || fileType === 'image/svg+xml') {
return copyAttachment(filePath, storageKey, noteKey).then(fileName => ({
fileName, fileName,
title: path.basename(file.path), title: path.basename(filePath),
isImage: true isImage: true
})) }))
} else { } else {
return fixRotate(file) return getOrientation(file)
.then(data => copyAttachment({type: 'base64', data: data, sourceFilePath: file.path}, storageKey, noteKey) .then((orientation) => {
.then(fileName => ({ if (orientation === -1) { // The image rotation is correct and does not need adjustment
return copyAttachment(filePath, storageKey, noteKey)
} else {
return fixRotate(file).then(data => copyAttachment({
type: 'base64',
data: data,
sourceFilePath: filePath
}, storageKey, noteKey))
}
})
.then(fileName =>
({
fileName, fileName,
title: path.basename(file.path), title: path.basename(filePath),
isImage: true isImage: true
})) })
) )
} }
} else { } else {
return copyAttachment(file.path, storageKey, noteKey).then(fileName => ({ return copyAttachment(filePath, storageKey, noteKey).then(fileName => ({
fileName, fileName,
title: path.basename(file.path), title: path.basename(filePath),
isImage: false isImage: false
})) }))
} }
@@ -325,13 +338,18 @@ function handleAttachmentDrop (codeEditor, storageKey, noteKey, dropEvent) {
canvas.height = image.height canvas.height = image.height
context.drawImage(image, 0, 0) context.drawImage(image, 0, 0)
return copyAttachment({type: 'base64', data: canvas.toDataURL(), sourceFilePath: imageURL}, storageKey, noteKey) return copyAttachment({
type: 'base64',
data: canvas.toDataURL(),
sourceFilePath: imageURL
}, storageKey, noteKey)
}) })
.then(fileName => ({ .then(fileName => ({
fileName, fileName,
title: imageURL, title: imageURL,
isImage: true isImage: true
}))]) }))
])
} }
promise.then(files => { promise.then(files => {
@@ -449,6 +467,54 @@ function getAbsolutePathsOfAttachmentsInContent (markdownContent, storagePath) {
return result return result
} }
/**
* @description Copies the attachments to the storage folder and returns the mardown content it should be replaced with
* @param {String} markDownContent content in which the attachment paths should be found
* @param {String} filepath The path of the file with attachments to import
* @param {String} storageKey Storage key of the destination storage
* @param {String} noteKey Key of the current note. Will be used as subfolder in :storage
*/
function importAttachments (markDownContent, filepath, storageKey, noteKey) {
return new Promise((resolve, reject) => {
const nameRegex = /(!\[.*?]\()(.+?\..+?)(\))/g
let attachPath = nameRegex.exec(markDownContent)
const promiseArray = []
const attachmentPaths = []
const groupIndex = 2
while (attachPath) {
let attachmentPath = attachPath[groupIndex]
attachmentPaths.push(attachmentPath)
attachmentPath = path.isAbsolute(attachmentPath) ? attachmentPath : path.join(path.dirname(filepath), attachmentPath)
promiseArray.push(this.copyAttachment(attachmentPath, storageKey, noteKey))
attachPath = nameRegex.exec(markDownContent)
}
let numResolvedPromises = 0
if (promiseArray.length === 0) {
resolve(markDownContent)
}
for (let j = 0; j < promiseArray.length; j++) {
promiseArray[j]
.then((fileName) => {
const newPath = path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName)
markDownContent = markDownContent.replace(attachmentPaths[j], newPath)
})
.catch((e) => {
console.error('File does not exist in path: ' + attachmentPaths[j])
})
.finally(() => {
numResolvedPromises++
if (numResolvedPromises === promiseArray.length) {
resolve(markDownContent)
}
})
}
})
}
/** /**
* @description Moves the attachments of the current note to the new location. * @description Moves the attachments of the current note to the new location.
* Returns a modified version of the given content so that the links to the attachments point to the new note key. * Returns a modified version of the given content so that the links to the attachments point to the new note key.
@@ -656,6 +722,7 @@ module.exports = {
handlePasteNativeImage, handlePasteNativeImage,
getAttachmentsInMarkdownContent, getAttachmentsInMarkdownContent,
getAbsolutePathsOfAttachmentsInContent, getAbsolutePathsOfAttachmentsInContent,
importAttachments,
removeStorageAndNoteReferences, removeStorageAndNoteReferences,
deleteAttachmentFolder, deleteAttachmentFolder,
deleteAttachmentsNotPresentInNote, deleteAttachmentsNotPresentInNote,

View File

@@ -43,19 +43,18 @@ function exportFolder (storageKey, folderKey, fileType, exportDir) {
.then(function exportNotes (data) { .then(function exportNotes (data) {
const { storage, notes } = data const { storage, notes } = data
notes return Promise.all(notes
.filter(note => note.folder === folderKey && note.isTrashed === false && note.type === 'MARKDOWN_NOTE') .filter(note => note.folder === folderKey && note.isTrashed === false && note.type === 'MARKDOWN_NOTE')
.forEach(note => { .map(note => {
const notePath = path.join(exportDir, `${filenamify(note.title, {replacement: '_'})}.${fileType}`) const notePath = path.join(exportDir, `${filenamify(note.title, {replacement: '_'})}.${fileType}`)
exportNote(note.key, storage.path, note.content, notePath, null) return exportNote(note.key, storage.path, note.content, notePath, null)
}) })
).then(() => ({
return {
storage, storage,
folderKey, folderKey,
fileType, fileType,
exportDir exportDir
} }))
}) })
} }

View File

@@ -43,14 +43,17 @@ function exportNote (nodeKey, storageKey, noteContent, targetPath, outputFormatt
) )
if (outputFormatter) { if (outputFormatter) {
exportedData = outputFormatter(exportedData, exportTasks) exportedData = outputFormatter(exportedData, exportTasks, path.dirname(targetPath))
} else {
exportedData = Promise.resolve(exportedData)
} }
const tasks = prepareTasks(exportTasks, storagePath, path.dirname(targetPath)) const tasks = prepareTasks(exportTasks, storagePath, path.dirname(targetPath))
return Promise.all(tasks.map((task) => copyFile(task.src, task.dst))) return Promise.all(tasks.map((task) => copyFile(task.src, task.dst)))
.then(() => { .then(() => exportedData)
return saveToFile(exportedData, targetPath) .then(data => {
return saveToFile(data, targetPath)
}).catch((err) => { }).catch((err) => {
rollbackExport(tasks) rollbackExport(tasks)
throw err throw err

View File

@@ -1,7 +1,7 @@
import React from 'react' import React from 'react'
import { Provider } from 'react-redux' import { Provider } from 'react-redux'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import store from '../store' import { store } from '../store'
class ModalBase extends React.Component { class ModalBase extends React.Component {
constructor (props) { constructor (props) {

View File

@@ -3,7 +3,7 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './CreateFolderModal.styl' import styles from './CreateFolderModal.styl'
import dataApi from 'browser/main/lib/dataApi' import dataApi from 'browser/main/lib/dataApi'
import store from 'browser/main/store' import { store } from 'browser/main/store'
import consts from 'browser/lib/consts' import consts from 'browser/lib/consts'
import ModalEscButton from 'browser/components/ModalEscButton' import ModalEscButton from 'browser/components/ModalEscButton'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'

View File

@@ -4,11 +4,12 @@ import styles from './NewNoteModal.styl'
import ModalEscButton from 'browser/components/ModalEscButton' import ModalEscButton from 'browser/components/ModalEscButton'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import { createMarkdownNote, createSnippetNote } from 'browser/lib/newNote' import { createMarkdownNote, createSnippetNote } from 'browser/lib/newNote'
import queryString from 'query-string'
class NewNoteModal extends React.Component { class NewNoteModal extends React.Component {
constructor (props) { constructor (props) {
super(props) super(props)
this.lock = false
this.state = {} this.state = {}
} }
@@ -21,10 +22,14 @@ class NewNoteModal extends React.Component {
} }
handleMarkdownNoteButtonClick (e) { handleMarkdownNoteButtonClick (e) {
const { storage, folder, dispatch, location, params, config } = this.props const { storage, folder, dispatch, location, config } = this.props
createMarkdownNote(storage, folder, dispatch, location, params, config).then(() => { const params = location.search !== '' && queryString.parse(location.search)
setTimeout(this.props.close, 200) if (!this.lock) {
}) this.lock = true
createMarkdownNote(storage, folder, dispatch, location, params, config).then(() => {
setTimeout(this.props.close, 200)
})
}
} }
handleMarkdownNoteButtonKeyDown (e) { handleMarkdownNoteButtonKeyDown (e) {
@@ -35,10 +40,14 @@ class NewNoteModal extends React.Component {
} }
handleSnippetNoteButtonClick (e) { handleSnippetNoteButtonClick (e) {
const { storage, folder, dispatch, location, params, config } = this.props const { storage, folder, dispatch, location, config } = this.props
createSnippetNote(storage, folder, dispatch, location, params, config).then(() => { const params = location.search !== '' && queryString.parse(location.search)
setTimeout(this.props.close, 200) if (!this.lock) {
}) this.lock = true
createSnippetNote(storage, folder, dispatch, location, params, config).then(() => {
setTimeout(this.props.close, 200)
})
}
} }
handleSnippetNoteButtonKeyDown (e) { handleSnippetNoteButtonKeyDown (e) {

View File

@@ -2,7 +2,7 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './ConfigTab.styl' import styles from './ConfigTab.styl'
import ConfigManager from 'browser/main/lib/ConfigManager' import ConfigManager from 'browser/main/lib/ConfigManager'
import store from 'browser/main/store' import { store } from 'browser/main/store'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import _ from 'lodash' import _ from 'lodash'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'

View File

@@ -18,6 +18,14 @@
margin-bottom 15px margin-bottom 15px
margin-top 30px margin-top 30px
.group-header--sub
@extend .group-header
margin-bottom 10px
.group-header2--sub
@extend .group-header2
margin-bottom 10px
.group-section .group-section
margin-bottom 20px margin-bottom 20px
display flex display flex
@@ -148,10 +156,12 @@ body[data-theme="dark"]
color $ui-dark-text-color color $ui-dark-text-color
.group-header .group-header
.group-header--sub
color $ui-dark-text-color color $ui-dark-text-color
border-color $ui-dark-borderColor border-color $ui-dark-borderColor
.group-header2 .group-header2
.group-header2--sub
color $ui-dark-text-color color $ui-dark-text-color
.group-section-control-input .group-section-control-input
@@ -176,10 +186,12 @@ body[data-theme="solarized-dark"]
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
.group-header .group-header
.group-header--sub
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
border-color $ui-solarized-dark-borderColor border-color $ui-solarized-dark-borderColor
.group-header2 .group-header2
.group-header2--sub
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
.group-section-control-input .group-section-control-input
@@ -203,10 +215,12 @@ body[data-theme="monokai"]
color $ui-monokai-text-color color $ui-monokai-text-color
.group-header .group-header
.group-header--sub
color $ui-monokai-text-color color $ui-monokai-text-color
border-color $ui-monokai-borderColor border-color $ui-monokai-borderColor
.group-header2 .group-header2
.group-header2--sub
color $ui-monokai-text-color color $ui-monokai-text-color
.group-section-control-input .group-section-control-input
@@ -230,10 +244,12 @@ body[data-theme="dracula"]
color $ui-dracula-text-color color $ui-dracula-text-color
.group-header .group-header
.group-header--sub
color $ui-dracula-text-color color $ui-dracula-text-color
border-color $ui-dracula-borderColor border-color $ui-dracula-borderColor
.group-header2 .group-header2
.group-header2--sub
color $ui-dracula-text-color color $ui-dracula-text-color
.group-section-control-input .group-section-control-input

View File

@@ -22,18 +22,16 @@ class Crowdfunding extends React.Component {
render () { render () {
return ( return (
<div styleName='root'> <div styleName='root'>
<div styleName='header'>{i18n.__('Crowdfunding')}</div> <div styleName='group-header'>{i18n.__('Crowdfunding')}</div>
<p>{i18n.__('Thank you for using Boostnote!')}</p> <p>{i18n.__('Thank you for using Boostnote!')}</p>
<br /> <br />
<p>{i18n.__('We launched IssueHunt which is an issue-based crowdfunding / sourcing platform for open source projects.')}</p> <p>{i18n.__('We launched IssueHunt which is an issue-based crowdfunding / sourcing platform for open source projects.')}</p>
<p>{i18n.__('Anyone can put a bounty on not only a bug but also on OSS feature requests listed on IssueHunt. Collected funds will be distributed to project owners and contributors.')}</p> <p>{i18n.__('Anyone can put a bounty on not only a bug but also on OSS feature requests listed on IssueHunt. Collected funds will be distributed to project owners and contributors.')}</p>
<br /> <div styleName='group-header2--sub'>{i18n.__('Sustainable Open Source Ecosystem')}</div>
<p>{i18n.__('### Sustainable Open Source Ecosystem')}</p>
<p>{i18n.__('We discussed about open-source ecosystem and IssueHunt concept with the Boostnote team repeatedly. We actually also discussed with Matz who father of Ruby.')}</p> <p>{i18n.__('We discussed about open-source ecosystem and IssueHunt concept with the Boostnote team repeatedly. We actually also discussed with Matz who father of Ruby.')}</p>
<p>{i18n.__('The original reason why we made IssueHunt was to reward our contributors of Boostnote project. Weve got tons of Github stars and hundred of contributors in two years.')}</p> <p>{i18n.__('The original reason why we made IssueHunt was to reward our contributors of Boostnote project. Weve got tons of Github stars and hundred of contributors in two years.')}</p>
<p>{i18n.__('We thought that it will be nice if we can pay reward for our contributors.')}</p> <p>{i18n.__('We thought that it will be nice if we can pay reward for our contributors.')}</p>
<br /> <div styleName='group-header2--sub'>{i18n.__('We believe Meritocracy')}</div>
<p>{i18n.__('### We believe Meritocracy')}</p>
<p>{i18n.__('We think developers who have skills and do great things must be rewarded properly.')}</p> <p>{i18n.__('We think developers who have skills and do great things must be rewarded properly.')}</p>
<p>{i18n.__('OSS projects are used in everywhere on the internet, but no matter how they great, most of owners of those projects need to have another job to sustain their living.')}</p> <p>{i18n.__('OSS projects are used in everywhere on the internet, but no matter how they great, most of owners of those projects need to have another job to sustain their living.')}</p>
<p>{i18n.__('It sometimes looks like exploitation.')}</p> <p>{i18n.__('It sometimes looks like exploitation.')}</p>

View File

@@ -1,14 +1,8 @@
@import('./Tab') @import('./ConfigTab')
.root
padding 15px
white-space pre
line-height 1.4
color alpha($ui-text-color, 90%)
width 100%
font-size 14px
p p
font-size 16px font-size 16px
line-height 1.4
.cf-link .cf-link
height 35px height 35px

View File

@@ -4,7 +4,7 @@ import CSSModules from 'browser/lib/CSSModules'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import styles from './FolderItem.styl' import styles from './FolderItem.styl'
import dataApi from 'browser/main/lib/dataApi' import dataApi from 'browser/main/lib/dataApi'
import store from 'browser/main/store' import { store } from 'browser/main/store'
import { SketchPicker } from 'react-color' import { SketchPicker } from 'react-color'
import { SortableElement, SortableHandle } from 'react-sortable-hoc' import { SortableElement, SortableHandle } from 'react-sortable-hoc'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'

View File

@@ -3,7 +3,7 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import dataApi from 'browser/main/lib/dataApi' import dataApi from 'browser/main/lib/dataApi'
import styles from './FolderList.styl' import styles from './FolderList.styl'
import store from 'browser/main/store' import { store } from 'browser/main/store'
import FolderItem from './FolderItem' import FolderItem from './FolderItem'
import { SortableContainer } from 'react-sortable-hoc' import { SortableContainer } from 'react-sortable-hoc'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'

View File

@@ -3,7 +3,7 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './ConfigTab.styl' import styles from './ConfigTab.styl'
import ConfigManager from 'browser/main/lib/ConfigManager' import ConfigManager from 'browser/main/lib/ConfigManager'
import store from 'browser/main/store' import { store } from 'browser/main/store'
import _ from 'lodash' import _ from 'lodash'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'

View File

@@ -2,7 +2,7 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './InfoTab.styl' import styles from './InfoTab.styl'
import ConfigManager from 'browser/main/lib/ConfigManager' import ConfigManager from 'browser/main/lib/ConfigManager'
import store from 'browser/main/store' import { store } from 'browser/main/store'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import _ from 'lodash' import _ from 'lodash'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
@@ -69,8 +69,7 @@ class InfoTab extends React.Component {
render () { render () {
return ( return (
<div styleName='root'> <div styleName='root'>
<div styleName='group-header'>{i18n.__('Community')}</div>
<div styleName='header--sub'>{i18n.__('Community')}</div>
<div styleName='top'> <div styleName='top'>
<ul styleName='list'> <ul styleName='list'>
<li> <li>
@@ -108,7 +107,7 @@ class InfoTab extends React.Component {
<hr /> <hr />
<div styleName='header--sub'>{i18n.__('About')}</div> <div styleName='group-header--sub'>{i18n.__('About')}</div>
<div styleName='top'> <div styleName='top'>
<div styleName='icon-space'> <div styleName='icon-space'>
@@ -143,7 +142,7 @@ class InfoTab extends React.Component {
<hr styleName='separate-line' /> <hr styleName='separate-line' />
<div styleName='policy'>{i18n.__('Analytics')}</div> <div styleName='group-header2--sub'>{i18n.__('Analytics')}</div>
<div>{i18n.__('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.')}</div> <div>{i18n.__('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.')}</div>
<div>{i18n.__('You can see how it works on ')}<a href='https://github.com/BoostIO/Boostnote' onClick={(e) => this.handleLinkClick(e)}>GitHub</a>.</div> <div>{i18n.__('You can see how it works on ')}<a href='https://github.com/BoostIO/Boostnote' onClick={(e) => this.handleLinkClick(e)}>GitHub</a>.</div>
<br /> <br />

View File

@@ -1,16 +1,4 @@
@import('./Tab') @import('./ConfigTab.styl')
.root
padding 15px
white-space pre
line-height 1.4
color alpha($ui-text-color, 90%)
width 100%
font-size 14px
.top
text-align left
margin-bottom 20px
.icon-space .icon-space
margin 20px 0 margin 20px 0
@@ -45,13 +33,21 @@
.separate-line .separate-line
margin 40px 0 margin 40px 0
.policy
width 100%
font-size 20px
margin-bottom 10px
.policy-submit .policy-submit
margin-top 10px margin-top 10px
height 35px
border-radius 2px
border none
background-color alpha(#1EC38B, 90%)
padding-left 20px
padding-right 20px
text-decoration none
color white
font-weight 600
font-size 16px
&:hover
background-color #1EC38B
transition 0.2s
.policy-confirm .policy-confirm
margin-top 10px margin-top 10px
@@ -60,11 +56,14 @@
body[data-theme="dark"] body[data-theme="dark"]
.root .root
color alpha($tab--dark-text-color, 80%) color alpha($tab--dark-text-color, 80%)
.appId
color $ui-dark-text-color
body[data-theme="solarized-dark"] body[data-theme="solarized-dark"]
.root .root
color $ui-solarized-dark-text-color color $ui-solarized-dark-text-color
.appId
color $ui-solarized-dark-text-color
.list .list
a a
color $ui-solarized-dark-active-color color $ui-solarized-dark-active-color
@@ -72,6 +71,8 @@ body[data-theme="solarized-dark"]
body[data-theme="monokai"] body[data-theme="monokai"]
.root .root
color $ui-monokai-text-color color $ui-monokai-text-color
.appId
color $ui-monokai-text-color
.list .list
a a
color $ui-monokai-active-color color $ui-monokai-active-color
@@ -79,6 +80,8 @@ body[data-theme="monokai"]
body[data-theme="dracula"] body[data-theme="dracula"]
.root .root
color $ui-dracula-text-color color $ui-dracula-text-color
.appId
color $ui-dracula-text-color
.list .list
a a
color $ui-dracula-active-color color $ui-dracula-active-color

View File

@@ -4,6 +4,7 @@ import _ from 'lodash'
import styles from './SnippetTab.styl' import styles from './SnippetTab.styl'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import dataApi from 'browser/main/lib/dataApi' import dataApi from 'browser/main/lib/dataApi'
import snippetManager from '../../../lib/SnippetManager'
const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace'] const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
const buildCMRulers = (rulers, enableRulers) => const buildCMRulers = (rulers, enableRulers) =>
@@ -64,7 +65,9 @@ class SnippetEditor extends React.Component {
} }
saveSnippet () { saveSnippet () {
dataApi.updateSnippet(this.snippet).catch((err) => { throw err }) dataApi.updateSnippet(this.snippet)
.then(snippets => snippetManager.assignSnippets(snippets))
.catch((err) => { throw err })
} }
render () { render () {

View File

@@ -91,7 +91,7 @@ class SnippetTab extends React.Component {
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4 if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
return ( return (
<div styleName='root'> <div styleName='root'>
<div styleName='header'>{i18n.__('Snippets')}</div> <div styleName='group-header'>{i18n.__('Snippets')}</div>
<SnippetList <SnippetList
onSnippetSelect={this.handleSnippetSelect.bind(this)} onSnippetSelect={this.handleSnippetSelect.bind(this)}
onSnippetDeleted={this.handleDeleteSnippet.bind(this)} onSnippetDeleted={this.handleDeleteSnippet.bind(this)}

View File

@@ -1,14 +1,5 @@
@import('./Tab')
@import('./ConfigTab') @import('./ConfigTab')
.root
padding 15px
white-space pre
line-height 1.4
color alpha($ui-text-color, 90%)
width 100%
font-size 14px
.group .group
margin-bottom 45px margin-bottom 45px
@@ -127,7 +118,7 @@
background darken(#f5f5f5, 5) background darken(#f5f5f5, 5)
.snippet-detail .snippet-detail
width 70% width 67%
height calc(100% - 200px) height calc(100% - 200px)
position absolute position absolute
left 33% left 33%

View File

@@ -4,7 +4,7 @@ import CSSModules from 'browser/lib/CSSModules'
import styles from './StorageItem.styl' import styles from './StorageItem.styl'
import consts from 'browser/lib/consts' import consts from 'browser/lib/consts'
import dataApi from 'browser/main/lib/dataApi' import dataApi from 'browser/main/lib/dataApi'
import store from 'browser/main/store' import { store } from 'browser/main/store'
import FolderList from './FolderList' import FolderList from './FolderList'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'

View File

@@ -1,8 +1,4 @@
@import('./Tab') @import('./ConfigTab')
.root
padding 15px
color $ui-text-color
.list .list
margin-bottom 15px margin-bottom 15px

View File

@@ -3,7 +3,7 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './ConfigTab.styl' import styles from './ConfigTab.styl'
import ConfigManager from 'browser/main/lib/ConfigManager' import ConfigManager from 'browser/main/lib/ConfigManager'
import store from 'browser/main/store' import { store } from 'browser/main/store'
import consts from 'browser/lib/consts' import consts from 'browser/lib/consts'
import ReactCodeMirror from 'react-codemirror' import ReactCodeMirror from 'react-codemirror'
import CodeMirror from 'codemirror' import CodeMirror from 'codemirror'
@@ -30,7 +30,9 @@ class UiTab extends React.Component {
componentDidMount () { componentDidMount () {
CodeMirror.autoLoadMode(this.codeMirrorInstance.getCodeMirror(), 'javascript') CodeMirror.autoLoadMode(this.codeMirrorInstance.getCodeMirror(), 'javascript')
CodeMirror.autoLoadMode(this.customCSSCM.getCodeMirror(), 'css') CodeMirror.autoLoadMode(this.customCSSCM.getCodeMirror(), 'css')
CodeMirror.autoLoadMode(this.customMarkdownLintConfigCM.getCodeMirror(), 'javascript')
this.customCSSCM.getCodeMirror().setSize('400px', '400px') this.customCSSCM.getCodeMirror().setSize('400px', '400px')
this.customMarkdownLintConfigCM.getCodeMirror().setSize('400px', '200px')
this.handleSettingDone = () => { this.handleSettingDone = () => {
this.setState({UiAlert: { this.setState({UiAlert: {
type: 'success', type: 'success',
@@ -101,7 +103,9 @@ class UiTab extends React.Component {
matchingTriples: this.refs.matchingTriples.value, matchingTriples: this.refs.matchingTriples.value,
explodingPairs: this.refs.explodingPairs.value, explodingPairs: this.refs.explodingPairs.value,
spellcheck: this.refs.spellcheck.checked, spellcheck: this.refs.spellcheck.checked,
enableSmartPaste: this.refs.enableSmartPaste.checked enableSmartPaste: this.refs.enableSmartPaste.checked,
enableMarkdownLint: this.refs.enableMarkdownLint.checked,
customMarkdownLintConfig: this.customMarkdownLintConfigCM.getCodeMirror().getValue()
}, },
preview: { preview: {
fontSize: this.refs.previewFontSize.value, fontSize: this.refs.previewFontSize.value,
@@ -128,8 +132,13 @@ class UiTab extends React.Component {
const newCodemirrorTheme = this.refs.editorTheme.value const newCodemirrorTheme = this.refs.editorTheme.value
if (newCodemirrorTheme !== codemirrorTheme) { if (newCodemirrorTheme !== codemirrorTheme) {
checkHighLight.setAttribute('href', `../node_modules/codemirror/theme/${newCodemirrorTheme.split(' ')[0]}.css`) const theme = consts.THEMES.find(theme => theme.name === newCodemirrorTheme)
if (theme) {
checkHighLight.setAttribute('href', `../${theme.path}`)
}
} }
this.setState({ config: newConfig, codemirrorTheme: newCodemirrorTheme }, () => { this.setState({ config: newConfig, codemirrorTheme: newCodemirrorTheme }, () => {
const {ui, editor, preview} = this.props.config const {ui, editor, preview} = this.props.config
this.currentConfig = {ui, editor, preview} this.currentConfig = {ui, editor, preview}
@@ -355,7 +364,7 @@ class UiTab extends React.Component {
> >
{ {
themes.map((theme) => { themes.map((theme) => {
return (<option value={theme} key={theme}>{theme}</option>) return (<option value={theme.name} key={theme.name}>{theme.name}</option>)
}) })
} }
</select> </select>
@@ -492,7 +501,7 @@ class UiTab extends React.Component {
ref='editorSnippetDefaultLanguage' ref='editorSnippetDefaultLanguage'
onChange={(e) => this.handleUIChange(e)} onChange={(e) => this.handleUIChange(e)}
> >
<option key='Auto Detect' value='Auto Detect'>Auto Detect</option> <option key='Auto Detect' value='Auto Detect'>{i18n.__('Auto Detect')}</option>
{ {
_.sortBy(CodeMirror.modeInfo.map(mode => mode.name)).map(name => (<option key={name} value={name}>{name}</option>)) _.sortBy(CodeMirror.modeInfo.map(mode => mode.name)).map(name => (<option key={name} value={name}>{name}</option>))
} }
@@ -632,6 +641,34 @@ class UiTab extends React.Component {
/> />
</div> </div>
</div> </div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Custom MarkdownLint Rules')}
</div>
<div styleName='group-section-control'>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.editor.enableMarkdownLint}
ref='enableMarkdownLint'
type='checkbox'
/>&nbsp;
{i18n.__('Enable MarkdownLint')}
<div style={{fontFamily, display: this.state.config.editor.enableMarkdownLint ? 'block' : 'none'}}>
<ReactCodeMirror
width='400px'
height='200px'
onChange={e => this.handleUIChange(e)}
ref={e => (this.customMarkdownLintConfigCM = e)}
value={config.editor.customMarkdownLintConfig}
options={{
lineNumbers: true,
mode: 'application/json',
theme: codemirrorTheme,
lint: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers']
}} />
</div>
</div>
</div>
<div styleName='group-header2'>{i18n.__('Preview')}</div> <div styleName='group-header2'>{i18n.__('Preview')}</div>
<div styleName='group-section'> <div styleName='group-section'>
@@ -670,7 +707,7 @@ class UiTab extends React.Component {
> >
{ {
themes.map((theme) => { themes.map((theme) => {
return (<option value={theme} key={theme}>{theme}</option>) return (<option value={theme.name} key={theme.name}>{theme.name}</option>)
}) })
} }
</select> </select>
@@ -846,6 +883,7 @@ class UiTab extends React.Component {
onChange={e => this.handleUIChange(e)} onChange={e => this.handleUIChange(e)}
ref={e => (this.customCSSCM = e)} ref={e => (this.customCSSCM = e)}
value={config.preview.customCSS} value={config.preview.customCSS}
defaultValue={'/* Drop Your Custom CSS Code Here */\n'}
options={{ options={{
lineNumbers: true, lineNumbers: true,
mode: 'css', mode: 'css',

View File

@@ -3,7 +3,7 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './RenameFolderModal.styl' import styles from './RenameFolderModal.styl'
import dataApi from 'browser/main/lib/dataApi' import dataApi from 'browser/main/lib/dataApi'
import store from 'browser/main/store' import { store } from 'browser/main/store'
import ModalEscButton from 'browser/components/ModalEscButton' import ModalEscButton from 'browser/components/ModalEscButton'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'

View File

@@ -1,8 +1,10 @@
import { combineReducers, createStore } from 'redux' import { combineReducers, createStore, compose, applyMiddleware } from 'redux'
import { routerReducer } from 'react-router-redux' import { connectRouter, routerMiddleware } from 'connected-react-router'
import { createHashHistory as createHistory } from 'history'
import ConfigManager from 'browser/main/lib/ConfigManager' import ConfigManager from 'browser/main/lib/ConfigManager'
import { Map, Set } from 'browser/lib/Mutable' import { Map, Set } from 'browser/lib/Mutable'
import _ from 'lodash' import _ from 'lodash'
import DevTools from './DevTools'
function defaultDataMap () { function defaultDataMap () {
return { return {
@@ -44,7 +46,9 @@ function data (state = defaultDataMap(), action) {
const folderNoteSet = getOrInitItem(state.folderNoteMap, folderKey) const folderNoteSet = getOrInitItem(state.folderNoteMap, folderKey)
folderNoteSet.add(uniqueKey) folderNoteSet.add(uniqueKey)
assignToTags(note.tags, state, uniqueKey) if (!note.isTrashed) {
assignToTags(note.tags, state, uniqueKey)
}
}) })
return state return state
case 'UPDATE_NOTE': case 'UPDATE_NOTE':
@@ -463,13 +467,16 @@ function getOrInitItem (target, key) {
return results return results
} }
const history = createHistory()
const reducer = combineReducers({ const reducer = combineReducers({
data, data,
config, config,
status, status,
routing: routerReducer router: connectRouter(history)
}) })
const store = createStore(reducer) const store = createStore(reducer, undefined, compose(
applyMiddleware(routerMiddleware(history)), DevTools.instrument()))
export default store export { store, history }

View File

@@ -80,3 +80,10 @@ class MyComponent extends React.Component {
} }
} }
``` ```
## React Hooks
Existing code will be kept class-based and will only be changed to functional components with hooks if it improves readability or makes things more reusable.
For new components it's OK to use hooks with functional components but don't mix hooks & class-based components within a feature - just for code style / readability reasons.
Read more about hooks in the [React hooks introduction](https://reactjs.org/docs/hooks-intro.html).

25
extra_scripts/codemirror/theme/nord.css vendored Normal file
View File

@@ -0,0 +1,25 @@
/* Theme: nord */
.cm-s-nord.CodeMirror { color: #d8dee9; }
.cm-s-nord.CodeMirror { background: #2e3440; }
.cm-s-nord .CodeMirror-cursor { color: #d8dee9; border-color: #d8dee9; }
.cm-s-nord .CodeMirror-activeline-background { background: #434c5e52 !important; }
.cm-s-nord .CodeMirror-selected { background: undefined; }
.cm-s-nord .cm-comment { color: #4c566a; }
.cm-s-nord .cm-string { color: #a3be8c; }
.cm-s-nord .cm-string-2 { color: #8fbcbb; }
.cm-s-nord .cm-property { color: #8fbcbb; }
.cm-s-nord .cm-qualifier { color: #8fbcbb; }
.cm-s-nord .cm-tag { color: #81a1c1; }
.cm-s-nord .cm-attribute { color: #8fbcbb; }
.cm-s-nord .cm-number { color: #b48ead; }
.cm-s-nord .cm-keyword { color: #81a1c1; }
.cm-s-nord .cm-operator { color: #81a1c1; }
.cm-s-nord .cm-error { background: #bf616a; color: #d8dee9; }
.cm-s-nord .cm-invalidchar { background: #bf616a; color: #d8dee9; }
.cm-s-nord .cm-variable { color: #d8dee9; }
.cm-s-nord .cm-variable-2 { color: #8fbcbb; }
.cm-s-nord .CodeMirror-gutters {
background: #3b4252;
color: #d8dee9;
}

View File

@@ -39,7 +39,7 @@ module.exports = function (grunt) {
name: 'boostnote', name: 'boostnote',
productName: 'Boostnote', productName: 'Boostnote',
genericName: 'Boostnote', genericName: 'Boostnote',
productDescription: 'The opensource note app for developer.', productDescription: 'The opensource note app for developers.',
arch: 'amd64', arch: 'amd64',
categories: [ categories: [
'Development', 'Development',
@@ -58,7 +58,7 @@ module.exports = function (grunt) {
name: 'boostnote', name: 'boostnote',
productName: 'Boostnote', productName: 'Boostnote',
genericName: 'Boostnote', genericName: 'Boostnote',
productDescription: 'The opensource note app for developer.', productDescription: 'The opensource note app for developers.',
arch: 'x86_64', arch: 'x86_64',
categories: [ categories: [
'Development', 'Development',
@@ -149,6 +149,7 @@ module.exports = function (grunt) {
case 'osx': case 'osx':
Object.assign(opts, { Object.assign(opts, {
platform: 'darwin', platform: 'darwin',
darwinDarkModeSupport: true,
icon: path.join(__dirname, 'resources/app.icns'), icon: path.join(__dirname, 'resources/app.icns'),
'app-category-type': 'public.app-category.developer-tools' 'app-category-type': 'public.app-category.developer-tools'
}) })

View File

@@ -141,6 +141,13 @@ const file = {
mainWindow.webContents.send('list:isMarkdownNote', 'export-html') mainWindow.webContents.send('list:isMarkdownNote', 'export-html')
mainWindow.webContents.send('export:save-html') mainWindow.webContents.send('export:save-html')
} }
},
{
label: 'PDF (.pdf)',
click () {
mainWindow.webContents.send('list:isMarkdownNote', 'export-pdf')
mainWindow.webContents.send('export:save-pdf')
}
} }
] ]
}, },

View File

@@ -6,6 +6,33 @@ const Config = require('electron-config')
const config = new Config() const config = new Config()
const _ = require('lodash') const _ = require('lodash')
// set up some chrome extensions
if (process.env.NODE_ENV === 'development') {
const {
default: installExtension,
REACT_DEVELOPER_TOOLS,
REACT_PERF
} = require('electron-devtools-installer')
require('electron-debug')({ showDevTools: false })
const ChromeLens = {
// ID of the extension (https://chrome.google.com/webstore/detail/chromelens/idikgljglpfilbhaboonnpnnincjhjkd)
id: 'idikgljglpfilbhaboonnpnnincjhjkd',
electron: '>=1.2.1'
}
const extensions = [REACT_DEVELOPER_TOOLS, REACT_PERF, ChromeLens]
for (const extension of extensions) {
try {
installExtension(extension)
} catch (e) {
console.error(`[ELECTRON] Extension installation failed`, e)
}
}
}
const windowSize = config.get('windowsize') || { const windowSize = config.get('windowsize') || {
x: null, x: null,
y: null, y: null,
@@ -27,8 +54,7 @@ const mainWindow = new BrowserWindow({
}, },
icon: path.resolve(__dirname, '../resources/app.png') icon: path.resolve(__dirname, '../resources/app.png')
}) })
const url = path.resolve(__dirname, process.env.NODE_ENV === 'production' ? './main.production.html' : './main.development.html')
const url = path.resolve(__dirname, './main.html')
mainWindow.loadURL('file://' + url) mainWindow.loadURL('file://' + url)
mainWindow.setMenuBarVisibility(false) mainWindow.setMenuBarVisibility(false)

166
lib/main.development.html Normal file
View File

@@ -0,0 +1,166 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
<link rel="stylesheet" href="../node_modules/font-awesome/css/font-awesome.min.css" media="screen" charset="utf-8">
<link rel="shortcut icon" href="../resources/favicon.ico">
<link rel="stylesheet" href="../node_modules/codemirror/lib/codemirror.css">
<link rel="stylesheet" href="../node_modules/katex/dist/katex.min.css">
<link rel="stylesheet" href="../node_modules/codemirror/addon/dialog/dialog.css">
<link rel="stylesheet" href="../node_modules/codemirror/addon/lint/lint.css">
<link rel="stylesheet" href="../extra_scripts/codemirror/mode/bfm/bfm.css">
<title>Boostnote</title>
<style>
@font-face {
font-family: 'OpenSans';
src: url('../resources/fonts/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
url('../resources/fonts/Lato-Regular.woff') format('woff'), /* Modern Browsers */
url('../resources/fonts/Lato-Regular.ttf') format('truetype');
font-style: normal;
font-weight: normal;
text-rendering: optimizeLegibility;
}
@font-face {
font-family: 'Lato';
src: url('../resources/fonts/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
url('../resources/fonts/Lato-Regular.woff') format('woff'), /* Modern Browsers */
url('../resources/fonts/Lato-Regular.ttf') format('truetype');
font-style: normal;
font-weight: normal;
text-rendering: optimizeLegibility;
}
#loadingCover {
background-color: #f4f4f4;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
box-sizing: border-box;
padding: 65px 0;
font-family: sans-serif;
}
#loadingCover img {
display: block;
margin: 75px auto 5px;
width: 160px;
height: 160px;
}
#loadingCover .message {
font-size: 30px;
text-align: center;
line-height: 1.6;
font-weight: 100;
color: #888;
}
.CodeEditor {
opacity: 1 !important;
pointer-events: auto !important;
}
.CodeMirror-ruler {
border-left-color: rgba(142, 142, 142, 0.5);
mix-blend-mode: difference;
}
.CodeMirror-lint-tooltip {
z-index: 1003;
}
</style>
</head>
<body>
<div id="loadingCover">
<img src="../resources/app.png">
<div class='message'>
<i class="fa fa-spinner fa-spin" spin></i>
</div>
</div>
<div id="content"></div>
<script src="../node_modules/codemirror/lib/codemirror.js"></script>
<script src="../node_modules/codemirror/mode/meta.js"></script>
<script src="../node_modules/codemirror-mode-elixir/dist/elixir.js"></script>
<script src="../node_modules/codemirror/addon/mode/overlay.js"></script>
<script src="../node_modules/codemirror/addon/mode/loadmode.js"></script>
<script src="../node_modules/codemirror/addon/mode/simple.js"></script>
<script src="../node_modules/codemirror/addon/mode/multiplex.js"></script>
<script src="../node_modules/codemirror/keymap/sublime.js"></script>
<script src="../node_modules/codemirror/keymap/vim.js"></script>
<script src="../node_modules/codemirror/keymap/emacs.js"></script>
<script src="../node_modules/codemirror/addon/runmode/runmode.js"></script>
<script src="../node_modules/codemirror/addon/display/panel.js"></script>
<script src="../node_modules/codemirror/mode/xml/xml.js"></script>
<script src="../node_modules/codemirror/mode/markdown/markdown.js"></script>
<script src="../node_modules/codemirror/mode/gfm/gfm.js"></script>
<script src="../node_modules/codemirror/mode/yaml/yaml.js"></script>
<script src="../node_modules/codemirror/mode/yaml-frontmatter/yaml-frontmatter.js"></script>
<script src="../extra_scripts/boost/boostNewLineIndentContinueMarkdownList.js"></script>
<script src="../extra_scripts/codemirror/mode/bfm/bfm.js"></script>
<script src="../extra_scripts/codemirror/addon/hyperlink/hyperlink.js"></script>
<script src="../extra_scripts/codemirror/mode/bfm/bfm.js"></script>
<script src="../node_modules/codemirror/addon/edit/closebrackets.js"></script>
<script src="../node_modules/codemirror/addon/edit/matchbrackets.js"></script>
<script src="../node_modules/codemirror/addon/search/search.js"></script>
<script src="../node_modules/codemirror/addon/search/searchcursor.js"></script>
<script src="../node_modules/codemirror/addon/scroll/annotatescrollbar.js"></script>
<script src="../node_modules/codemirror/addon/scroll/scrollpastend.js"></script>
<script src="../node_modules/codemirror/addon/search/matchesonscrollbar.js"></script>
<script src="../node_modules/codemirror/addon/search/jump-to-line.js"></script>
<script src="../node_modules/codemirror/addon/fold/brace-fold.js"></script>
<script src="../node_modules/codemirror/addon/fold/markdown-fold.js"></script>
<script src="../node_modules/codemirror/addon/fold/foldgutter.js"></script>
<script src="../node_modules/codemirror/addon/fold/foldcode.js"></script>
<script src="../node_modules/codemirror/addon/dialog/dialog.js"></script>
<script src="../node_modules/codemirror/addon/display/rulers.js"></script>
<script src="../node_modules/jsonlint-mod/lib/jsonlint.js"></script>
<script src="../node_modules/codemirror/addon/lint/lint.js"></script>
<script src="../node_modules/codemirror/addon/lint/json-lint.js"></script>
<script src="../node_modules/raphael/raphael.min.js"></script>
<script src="../node_modules/flowchart.js/release/flowchart.min.js"></script>
<script>
window._ = require('lodash')
</script>
<script src="../node_modules/@rokt33r/js-sequence-diagrams/dist/sequence-diagram-min.js"></script>
<script src="../node_modules/react/umd/react.development.js"></script>
<script src="../node_modules/react-dom/umd/react-dom.development.js"></script>
<script src="../node_modules/redux/dist/redux.min.js"></script>
<script src="../node_modules/react-redux/dist/react-redux.min.js"></script>
<script type='text/javascript'>
const electron = require('electron')
electron.webFrame.setVisualZoomLevelLimits(1, 1)
var scriptUrl = window._.find(electron.remote.process.argv, (a) => a === '--hot')
? 'http://localhost:8080/assets/main.js'
: '../compiled/main.js'
var scriptEl = document.createElement('script')
scriptEl.setAttribute('type', 'text/javascript')
scriptEl.setAttribute('src', scriptUrl)
document.body.appendChild(scriptEl)
</script>
<style>
.ace_search {
background-color: #d9d9d9;
}
</style>
</body>
</html>

View File

@@ -131,9 +131,9 @@
window._ = require('lodash') window._ = require('lodash')
</script> </script>
<script src="../node_modules/js-sequence-diagrams/fucknpm/sequence-diagram-min.js"></script> <script src="../node_modules/@rokt33r/js-sequence-diagrams/dist/sequence-diagram-min.js"></script>
<script src="../node_modules/react/dist/react.min.js"></script> <script src="../node_modules/react/umd/react.production.min.js"></script>
<script src="../node_modules/react-dom/dist/react-dom.min.js"></script> <script src="../node_modules/react-dom/umd/react-dom.production.min.js"></script>
<script src="../node_modules/redux/dist/redux.min.js"></script> <script src="../node_modules/redux/dist/redux.min.js"></script>
<script src="../node_modules/react-redux/dist/react-redux.min.js"></script> <script src="../node_modules/react-redux/dist/react-redux.min.js"></script>
<script type='text/javascript'> <script type='text/javascript'>

View File

@@ -18,6 +18,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "Print", "Print": "Print",
"Your preferences for Boostnote": "Your preferences for Boostnote", "Your preferences for Boostnote": "Your preferences for Boostnote",
"Storage Locations": "Storage Locations", "Storage Locations": "Storage Locations",
@@ -75,7 +76,7 @@
"Website": "Website", "Website": "Website",
"Development": "Development", "Development": "Development",
" : Development configurations for Boostnote.": " : Development configurations for Boostnote.", " : Development configurations for Boostnote.": " : Development configurations for Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "License: GPL v3", "License: GPL v3": "License: GPL v3",
"Analytics": "Analytics", "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.", "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.",
@@ -153,5 +154,7 @@
"Allow dangerous html tags": "Allow dangerous html tags", "Allow dangerous html tags": "Allow dangerous html tags",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.", "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠", "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
"Disabled": "Disabled" "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect"
} }

View File

@@ -18,6 +18,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "Drucken", "Print": "Drucken",
"Your preferences for Boostnote": "Boostnote Einstellungen", "Your preferences for Boostnote": "Boostnote Einstellungen",
"Storage Locations": "Speicherverwaltung", "Storage Locations": "Speicherverwaltung",
@@ -75,7 +76,7 @@
"Website": "Website", "Website": "Website",
"Development": "Entwicklung", "Development": "Entwicklung",
" : Development configurations for Boostnote.": " : Entwicklungseinstellungen für Boostnote.", " : Development configurations for Boostnote.": " : Entwicklungseinstellungen für Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "License: GPL v3", "License: GPL v3": "License: GPL v3",
"Analytics": "Analytics", "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 sammelt anonyme Daten, um die App zu verbessern. Persönliche Informationen, wie z.B. der Inhalt deiner Notizen, werden dabei nicht erfasst.", "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 sammelt anonyme Daten, um die App zu verbessern. Persönliche Informationen, wie z.B. der Inhalt deiner Notizen, werden dabei nicht erfasst.",
@@ -209,5 +210,7 @@
"No tags": "Keine Tags", "No tags": "Keine Tags",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.", "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠", "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
"Disabled": "Disabled" "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect"
} }

View File

@@ -19,6 +19,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "Print", "Print": "Print",
"Your preferences for Boostnote": "Your preferences for Boostnote", "Your preferences for Boostnote": "Your preferences for Boostnote",
"Help": "Help", "Help": "Help",
@@ -82,7 +83,7 @@
"Website": "Website", "Website": "Website",
"Development": "Development", "Development": "Development",
" : Development configurations for Boostnote.": " : Development configurations for Boostnote.", " : Development configurations for Boostnote.": " : Development configurations for Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "License: GPL v3", "License: GPL v3": "License: GPL v3",
"Analytics": "Analytics", "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.", "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.",
@@ -136,7 +137,7 @@
"Hotkeys": "Hotkeys", "Hotkeys": "Hotkeys",
"Show/Hide Boostnote": "Show/Hide Boostnote", "Show/Hide Boostnote": "Show/Hide Boostnote",
"Toggle Editor Mode": "Toggle Editor Mode", "Toggle Editor Mode": "Toggle Editor Mode",
"Delete Note": "Delete Note", "Delete Note": "Delete Note",
"Restore": "Restore", "Restore": "Restore",
"Permanent Delete": "Permanent Delete", "Permanent Delete": "Permanent Delete",
"Confirm note deletion": "Confirm note deletion", "Confirm note deletion": "Confirm note deletion",
@@ -179,10 +180,12 @@
"Allow dangerous html tags": "Allow dangerous html tags", "Allow dangerous html tags": "Allow dangerous html tags",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.", "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠", "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
"Disabled": "Disabled", "Spellcheck disabled": "Spellcheck disabled",
"Save tags of a note in alphabetical order": "Save tags of a note in alphabetical order", "Save tags of a note in alphabetical order": "Save tags of a note in alphabetical order",
"Enable live count of notes": "Enable live count of notes", "Enable live count of notes": "Enable live count of notes",
"Enable smart table editor": "Enable smart table editor", "Enable smart table editor": "Enable smart table editor",
"Snippet Default Language": "Snippet Default Language", "Snippet Default Language": "Snippet Default Language",
"New notes are tagged with the filtering tags": "New notes are tagged with the filtering tags" "New notes are tagged with the filtering tags": "New notes are tagged with the filtering tags",
"Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect"
} }

View File

@@ -18,6 +18,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "Imprimir", "Print": "Imprimir",
"Your preferences for Boostnote": "Tus preferencias para Boostnote", "Your preferences for Boostnote": "Tus preferencias para Boostnote",
"Storage Locations": "Almacenamientos", "Storage Locations": "Almacenamientos",
@@ -75,7 +76,7 @@
"Website": "Página web", "Website": "Página web",
"Development": "Desarrollo", "Development": "Desarrollo",
" : Development configurations for Boostnote.": " : Configuraciones de desarrollo para Boostnote.", " : Development configurations for Boostnote.": " : Configuraciones de desarrollo para Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "Licencia: GPL v3", "License: GPL v3": "Licencia: GPL v3",
"Analytics": "Analítica", "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, y de ninguna manera recopila información personal como 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.",
@@ -133,7 +134,7 @@
"Successfully applied!": "¡Aplicado con éxito!", "Successfully applied!": "¡Aplicado con éxito!",
"Albanian": "Albanés", "Albanian": "Albanés",
"Chinese (zh-CN)": "Chino - China", "Chinese (zh-CN)": "Chino - China",
"Chinese (zh-TW)": "Chino - Taiwan", "Chinese (zh-TW)": "Chino - Taiwán",
"Danish": "Danés", "Danish": "Danés",
"Japanese": "Japonés", "Japanese": "Japonés",
"Korean": "Coreano", "Korean": "Coreano",
@@ -154,6 +155,8 @@
"Allow dangerous html tags": "Permitir etiquetas html peligrosas", "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, mejor arrastre el archivo! ⚠", "⚠ 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, mejor arrastre el archivo! ⚠",
"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.",
"Disabled": "Disabled", "Spellcheck disabled": "Deshabilitar corrector ortográfico",
"Snippet Default Language": "Lenguaje por defecto de los fragmentos de código" "Show menu bar": "Mostrar barra del menú",
"Auto Detect": "Detección automática",
"Snippet Default Language": "Lenguaje por defecto de los fragmentos de código"
} }

View File

@@ -1,161 +1,164 @@
{ {
"Notes": "یادداشت ها", "Notes": "یادداشت ها",
"Tags": "تگ ها", "Tags": "تگ ها",
"Preferences": "تنظیمات", "Preferences": "تنظیمات",
"Make a note": "یک یادداشت بنویس", "Make a note": "یک یادداشت بنویس",
"Ctrl": "Ctrl", "Ctrl": "Ctrl",
"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": "لینک یادداشت",
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
"Print": "پرینت", ".pdf": ".pdf",
"Your preferences for Boostnote": "تنظیمات شما برای boostnote", "Print": "پرینت",
"Storage Locations": "ذخیره سازی", "Your preferences for Boostnote": "تنظیمات شما برای boostnote",
"Add Storage Location": "افزودن محل ذخیره سازی", "Storage Locations": "ذخیره سازی",
"Add Folder": "ساخت پوشه", "Add Storage Location": "افزودن محل ذخیره سازی",
"Open Storage folder": "بازکردن پوشه ذخیره سازی", "Add Folder": "ساخت پوشه",
"Unlink": "حذف لینک", "Open Storage folder": "بازکردن پوشه ذخیره سازی",
"Edit": "ویرایش", "Unlink": "حذف لینک",
"Delete": "حذف", "Edit": "ویرایش",
"Interface": "رابط کاربری", "Delete": "حذف",
"Interface Theme": "تم رابط کاربری", "Interface": "رابط کاربری",
"Default": "پیش فرض", "Interface Theme": "تم رابط کاربری",
"White": "روشن", "Default": "پیش فرض",
"Solarized Dark": "سولارایز", "White": "روشن",
"Dark": "تاریک", "Solarized Dark": "سولارایز",
"Show a confirmation dialog when deleting notes": "هنگام حذف یادداشت ها یک پیام تایید نمایش بده.", "Dark": "تاریک",
"Editor Theme": "تم ویرایشگر", "Show a confirmation dialog when deleting notes": "هنگام حذف یادداشت ها یک پیام تایید نمایش بده.",
"Editor Font Size": "اندازه فونت ویرایشگر", "Editor Theme": م ویرایشگر",
"Editor Font Family": "فونت ویرایشگر", "Editor Font Size": "اندازه فونت ویرایشگر",
"Editor Indent Style": "حالت فاصله گذاری ویرایشگر", "Editor Font Family": "فونت ویرایشگر",
"Spaces": "Spaces", "Editor Indent Style": "حالت فاصله گذاری ویرایشگر",
"Tabs": "Tabs", "Spaces": "Spaces",
"Switch to Preview": "دیدن پیش نمایش", "Tabs": "Tabs",
"When Editor Blurred": "وقتی ویرایشگر از حالت ویرایش خارج شد ", "Switch to Preview": "دیدن پیش نمایش",
"When Editor Blurred, Edit On Double Click": "وقتی ویرایشگر از حالت ویرایش خارج شد و با دبل کلیک ویرایش کنید.", "When Editor Blurred": "وقتی ویرایشگر از حالت ویرایش خارج شد ",
"On Right Click": "راست کلیک", "When Editor Blurred, Edit On Double Click": "وقتی ویرایشگر از حالت ویرایش خارج شد و با دبل کلیک ویرایش کنید.",
"Editor Keymap": "ویرایشگر Keymap", "On Right Click": "راست کلیک",
"default": "پیش فرض", "Editor Keymap": "ویرایشگر Keymap",
"vim": "vim", "default": "پیش فرض",
"emacs": "emacs", "vim": "vim",
"⚠️ Please restart boostnote after you change the keymap": "⚠️ برنامه را دوباره راه اندازی کنید keymap لطفا بعد از تغییر", "emacs": "emacs",
"Show line numbers in the editor": "شماره خطوط در ویرایشگر را نمایش بده.", "⚠️ Please restart boostnote after you change the keymap": "⚠️ برنامه را دوباره راه اندازی کنید keymap لطفا بعد از تغییر",
"Allow editor to scroll past the last line": "اجازه بده ویرایشگر بعد از آخرین خط اسکرول کند.", "Show line numbers in the editor": "شماره خطوط در ویرایشگر را نمایش بده.",
"Bring in web page title when pasting URL on editor": "هنگامی که آدرس اینترنتی در ویرایشگر اضافه شد عنوان آنرا نمایش بده", "Allow editor to scroll past the last line": "اجازه بده ویرایشگر بعد از آخرین خط اسکرول کند.",
"Preview": "پیش نمایش", "Bring in web page title when pasting URL on editor": "هنگامی که آدرس اینترنتی در ویرایشگر اضافه شد عنوان آنرا نمایش بده",
"Preview Font Size": "اندازه فونتِ پیش نمایش", "Preview": "پیش نمایش",
"Preview Font Family": " فونتِ پیش نمایش", "Preview Font Size": "اندازه فونتِ پیش نمایش",
"Code Block Theme": "تم بخش کد", "Preview Font Family": " فونتِ پیش نمایش",
"Allow preview to scroll past the last line": "اجازه بده پیش نمایش بعد از آخرین خط اسکرول کند.", "Code Block Theme": "تم بخش کد",
"Show line numbers for preview code blocks": "شماره خطوط در پیش نمایش را نمایش بده.", "Allow preview to scroll past the last line": "اجازه بده پیش نمایش بعد از آخرین خط اسکرول کند.",
"LaTeX Inline Open Delimiter": "جداکننده آغازین لاتکس خطی", "Show line numbers for preview code blocks": "شماره خطوط در پیش نمایش را نمایش بده.",
"LaTeX Inline Close Delimiter": "جداکننده پایانی لاتکس خطی", "LaTeX Inline Open Delimiter": "جداکننده آغازین لاتکس خطی",
"LaTeX Block Open Delimiter": "جداکننده آغازین بلوک لاتکس ", "LaTeX Inline Close Delimiter": "جداکننده پایانی لاتکس خطی",
"LaTeX Block Close Delimiter": "جداکننده آغازین بلوک لاتکس ", "LaTeX Block Open Delimiter": "جداکننده آغازین بلوک لاتکس ",
"PlantUML Server": "PlantUML Server", "LaTeX Block Close Delimiter": "جداکننده آغازین بلوک لاتکس ",
"Community": "کامینیتی", "PlantUML Server": "PlantUML Server",
"Subscribe to Newsletter": "اشتراک در خبرنامه", "Community": "کامینیتی",
"GitHub": "گیت هاب", "Subscribe to Newsletter": "اشتراک در خبرنامه",
"Blog": "بلاگ", "GitHub": یت هاب",
"Facebook Group": "گروه فیسبوک", "Blog": "بلاگ",
"Twitter": "توییتر", "Facebook Group": "گروه فیسبوک",
"About": "درباره", "Twitter": "توییتر",
"Boostnote": "Boostnote", "About": "درباره",
"An open source note-taking app made for programmers just like you.": "یک دفترچه یادداشت متن باز ساخته شده برای برنامه نویسانی مثل تو.", "Boostnote": "Boostnote",
"Website": "وبسایت", "An open source note-taking app made for programmers just like you.": "یک دفترچه یادداشت متن باز ساخته شده برای برنامه نویسانی مثل تو.",
"Development": "توسعه", "Website": "وبسایت",
" : Development configurations for Boostnote.": " : پیکربندی توسعه برای Boostnote.", "Development": "توسعه",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", " : Development configurations for Boostnote.": " : پیکربندی توسعه برای Boostnote.",
"License: GPL v3": "لایسنس: GPL v3", "Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"Analytics": "تجزیه و تحلیل", "License: GPL v3": "لایسنس: GPL v3",
"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.": "Bosstnote اطلاعات ناشناس را برای بهبود عملکرد برنامه جمع آوری می‌کند.اطلاعات شخصی شما مثل محتوای یادداشت ها هرگز برای هیچ هدفی جمع آوری نمی‌شوند", "Analytics": "تجزیه و تحلیل",
"You can see how it works on ": "میتوانید ببینید چگونه کار میکند. ", "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.": "Bosstnote اطلاعات ناشناس را برای بهبود عملکرد برنامه جمع آوری می‌کند.اطلاعات شخصی شما مثل محتوای یادداشت ها هرگز برای هیچ هدفی جمع آوری نمی‌شوند",
"You can choose to enable or disable this option.": "میتوانید این گزینه را فعال یا غیرفعال کنید.", "You can see how it works on ": "میتوانید ببینید چگونه کار میکند. ",
"Enable analytics to help improve Boostnote":".تجزیه تحلیل داده ها را برای کمک به بهبود برنامه فعال کن", "You can choose to enable or disable this option.": "میتوانید این گزینه را فعال یا غیرفعال کنید.",
"Crowdfunding": "جمع سپاری (سرمایه گذاری جمعی )", "Enable analytics to help improve Boostnote":".تجزیه تحلیل داده ها را برای کمک به بهبود برنامه فعال کن",
"Dear Boostnote users,": "عزیزان,", "Crowdfunding": "جمع سپاری (سرمایه گذاری جمعی )",
"Thank you for using Boostnote!": "از شما بخاطر استفاده از boostnote ممنونیم!", "Dear Boostnote users,": "عزیزان,",
"Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "در ۲۰۰ کشور مختلف دنیا مورد توسط جمعی از برنامه نویسان بی نظیر مورد استفاده قرار میگیرد. Boostnote", "Thank you for using Boostnote!": "از شما بخاطر استفاده از boostnote ممنونیم!",
"To support our growing userbase, and satisfy community expectations,": "برای حمایت از این رشد ، و برآورده شدن انتظارات کامینیتی,", "Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "در ۲۰۰ کشور مختلف دنیا مورد توسط جمعی از برنامه نویسان بی نظیر مورد استفاده قرار میگیرد. Boostnote",
"we would like to invest more time and resources in this project.": "ما می خواهیم زمان و منابع بیشتری را در این پروژه سرمایه گذاری کنیم.", "To support our growing userbase, and satisfy community expectations,": "برای حمایت از این رشد ، و برآورده شدن انتظارات کامینیتی,",
"If you use Boostnote and see its potential, help us out by supporting the project on OpenCollective!": "اگر این پروژه را دوست دارید و پتانسیلی در آن می‌بینید، میتوانید مارا در اوپن‌ کالکتیو حمایت کنید.", "we would like to invest more time and resources in this project.": "ما می خواهیم زمان و منابع بیشتری را در این پروژه سرمایه گذاری کنیم.",
"Thanks,": "با تشکر,", "If you use Boostnote and see its potential, help us out by supporting the project on OpenCollective!": "اگر این پروژه را دوست دارید و پتانسیلی در آن می‌بینید، میتوانید مارا در اوپن‌ کالکتیو حمایت کنید.",
"The Boostnote Team": "Boostnote نگهدارندگان", "Thanks,": "با تشکر,",
"Support via OpenCollective": "حمایت کنید OpenCollective از طریق", "The Boostnote Team": "Boostnote نگهدارندگان",
"Language": "زبان", "Support via OpenCollective": "حمایت کنید OpenCollective از طریق",
"English": "انگلیسی", "Language": "زبان",
"German": "آلمانی", "English": "انگلیسی",
"French": "فرانسوی", "German": "آلمانی",
"Show \"Saved to Clipboard\" notification when copying": "نمایش \"ذخیره در کلیپ‌بورد\" اطلاع رسانی هنگام کپی کردن", "French": "فرانسوی",
"All Notes": "همه یادداشت ها", "Show \"Saved to Clipboard\" notification when copying": "نمایش \"ذخیره در کلیپ‌بورد\" اطلاع رسانی هنگام کپی کردن",
"Starred": "ستاره دار", "All Notes": "همه یادداشت ها",
"Are you sure to ": " مطمئن هستید که", "Starred": "ستاره دار",
" delete": "حذف ", "Are you sure to ": " مطمئن هستید که",
"this folder?": "این پوشه ؟", " delete": "حذف ",
"Confirm": "تایید", "this folder?": "این پوشه ؟",
"Cancel": "انصراف", "Confirm": "تایید",
"Markdown Note": "Markdown یادداشتِ", "Cancel": "انصراف",
"This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "این قالب برای ساخت سند های متنی است. چک لیست ها و تکه کد ها و بلاک های لاتکس قابل استفاده اند.", "Markdown Note": "Markdown یادداشتِ",
"Snippet Note": "Snippet یادداشتِ", "This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "این قالب برای ساخت سند های متنی است. چک لیست ها و تکه کد ها و بلاک های لاتکس قابل استفاده اند.",
"This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "این قالب برای ساخت تکه کد هاست. چند تکه کد میتوانند تبدیل به یک یادداشت شوند.", "Snippet Note": "Snippet یادداشتِ",
"Tab to switch format": "را بزنید Tab برای تغییر فرمت", "This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "این قالب برای ساخت تکه کد هاست. چند تکه کد میتوانند تبدیل به یک یادداشت شوند.",
"Updated": "بروزرسانی شد", "Tab to switch format": "را بزنید Tab برای تغییر فرمت",
"Created": "ایجاد شد", "Updated": "بروزرسانی شد",
"Alphabetically": "بر اساس حروف الفبا", "Created": "ایجاد شد",
"Counter": "شمارشگر", "Alphabetically": "بر اساس حروف الفبا",
"Default View": "نمایش پیش‌فرض", "Counter": "شمارشگر",
"Compressed View": "نمایش فشرده", "Default View": "نمایش پیش‌فرض",
"Search": "جستجو", "Compressed View": "نمایش فشرده",
"Blog Type": "نوع وبلاگ", "Search": "جستجو",
"Blog Address": "آدرس وبلاگ", "Blog Type": "نوع وبلاگ",
"Save": "ذخیره", "Blog Address": "آدرس وبلاگ",
"Auth": "هویت", "Save": "ذخیره",
"Authentication Method": "متد احراز هویت", "Auth": "هویت",
"JWT": "JWT", "Authentication Method": "متد احراز هویت",
"USER": "کاربر", "JWT": "JWT",
"Token": "توکن", "USER": "کاربر",
"Storage": "ذخیره سازی", "Token": "توکن",
"Hotkeys": "کلید های میانبر", "Storage": "ذخیره سازی",
"Show/Hide Boostnote": "Boostnote نمایش/پنهان کردن", "Hotkeys": "کلید های میانبر",
"Restore": "بازگرداندن به حالت اول", "Show/Hide Boostnote": "Boostnote نمایش/پنهان کردن",
"Permanent Delete": "حذف بدون بازگشت", "Restore": "بازگرداندن به حالت اول",
"Confirm note deletion": ".حذف یادداشت را تایید کنید", "Permanent Delete": "حذف بدون بازگشت",
"This will permanently remove this note.": ".این کار یادداشت را بطور دائمی حذف خواهد کرد", "Confirm note deletion": ".حذف یادداشت را تایید کنید",
"Successfully applied!": "!با موفقیت اجرا شد", "This will permanently remove this note.": ".این کار یادداشت را بطور دائمی حذف خواهد کرد",
"Albanian": "آلبانی", "Successfully applied!": "!با موفقیت اجرا شد",
"Chinese (zh-CN)": "چینی (zh-CN)", "Albanian": "آلبانی",
"Chinese (zh-TW)": "چینی (zh-TW)", "Chinese (zh-CN)": "چینی (zh-CN)",
"Danish": "دانمارکی", "Chinese (zh-TW)": "چینی (zh-TW)",
"Japanese": "ژاپنی", "Danish": "دانمارکی",
"Korean": "کره ای", "Japanese": "ژاپنی",
"Norwegian": "نروژی", "Korean": "کره ای",
"Polish": "لهستانی", "Norwegian": روژی",
"Portuguese": "پرتغالی", "Polish": "لهستانی",
"Spanish": "اسپانیایی", "Portuguese": "پرتغالی",
"Unsaved Changes!": "!باید ذخیره کنید", "Spanish": "اسپانیایی",
"UserName": "نام کاربری", "Unsaved Changes!": "!باید ذخیره کنید",
"Password": "رمز عبور", "UserName": "نام کاربری",
"Russian": وسی", "Password": مز عبور",
"Thai": "Thai (ภาษาไทย)", "Russian": "روسی",
"Command(⌘)": "Command(⌘)", "Thai": "Thai (ภาษาไทย)",
"Editor Rulers": "Editor Rulers", "Command(⌘)": "Command(⌘)",
"Enable": "فعال", "Editor Rulers": "Editor Rulers",
"Disable": "غیرفعال", "Enable": "فعال",
"Sanitization": "پاکسازی کردن", "Disable": "غیرفعال",
"Only allow secure html tags (recommended)": "(فقط تگ های امن اچ تی ام ال مجاز اند.(پیشنهاد میشود", "Sanitization": "پاکسازی کردن",
"Allow styles": "حالت های مجاز", "Only allow secure html tags (recommended)": "(فقط تگ های امن اچ تی ام ال مجاز اند.(پیشنهاد میشود",
"Allow dangerous html tags": گ های خطرناک اچ‌ تی ام ال مجاز اند", "Allow styles": "حالت های مجاز",
"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.", "Allow dangerous html tags": "تگ های خطرناک اچ‌ تی ام ال مجاز اند",
"⚠ 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.",
"Disabled": "Disabled" "⚠ 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! ⚠",
"Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect"
} }

View File

@@ -18,6 +18,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "Imprimer", "Print": "Imprimer",
"Your preferences for Boostnote": "Vos préférences pour Boostnote", "Your preferences for Boostnote": "Vos préférences pour Boostnote",
"Storage Locations": "Stockages", "Storage Locations": "Stockages",
@@ -76,7 +77,7 @@
"Website": "Site web", "Website": "Site web",
"Development": "Développement", "Development": "Développement",
" : Development configurations for Boostnote.": " : Configurations de développement pour Boostnote.", " : Development configurations for Boostnote.": " : Configurations de développement pour Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "License: GPL v3", "License: GPL v3": "License: GPL v3",
"Analytics": "Analytics", "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 collecte des données anonymisées dans le seul but d'améliorer l'application, et ne collecte aucune donnée personnelle telle que le contenu de vos 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.": "Boostnote collecte des données anonymisées dans le seul but d'améliorer l'application, et ne collecte aucune donnée personnelle telle que le contenu de vos notes.",
@@ -163,11 +164,13 @@
"Enable live count of notes": "Activer le comptage live des notes", "Enable live count of notes": "Activer le comptage live des notes",
"Enable smart table editor": "Activer l'intelligent éditeur de tableaux", "Enable smart table editor": "Activer l'intelligent éditeur de tableaux",
"Snippet Default Language": "Langage par défaut d'un snippet", "Snippet Default Language": "Langage par défaut d'un snippet",
"Disabled": "Disabled", "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect",
"New Snippet": "Nouveau snippet", "New Snippet": "Nouveau snippet",
"Custom CSS": "CSS personnalisé", "Custom CSS": "CSS personnalisé",
"Snippet name": "Nom du snippet", "Snippet name": "Nom du snippet",
"Snippet prefix": "Préfixe du snippet", "Snippet prefix": "Préfixe du snippet",
"Delete Note": "Supprimer la note", "Delete Note": "Supprimer la note",
"New notes are tagged with the filtering tags": "Les nouvelles notes sont taggées avec les tags de filtrage" "New notes are tagged with the filtering tags": "Les nouvelles notes sont taggées avec les tags de filtrage"
} }

View File

@@ -19,6 +19,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "Nyomtatás", "Print": "Nyomtatás",
"Your preferences for Boostnote": "Boostnote beállításaid", "Your preferences for Boostnote": "Boostnote beállításaid",
"Help": "Súgó", "Help": "Súgó",
@@ -26,7 +27,6 @@
"Storage Locations": "Tárolók", "Storage Locations": "Tárolók",
"Add Storage Location": "Tároló Hozzáadása", "Add Storage Location": "Tároló Hozzáadása",
"Add Folder": "Könyvtár Hozzáadása", "Add Folder": "Könyvtár Hozzáadása",
"Select Folder": "Könyvtár Kiválasztása",
"Open Storage folder": "Tároló Megnyitása", "Open Storage folder": "Tároló Megnyitása",
"Unlink": "Tároló Leválasztása", "Unlink": "Tároló Leválasztása",
"Edit": "Szerkesztés", "Edit": "Szerkesztés",
@@ -82,7 +82,7 @@
"Website": "Weboldal", "Website": "Weboldal",
"Development": "Fejlesztés", "Development": "Fejlesztés",
" : Development configurations for Boostnote.": " : Információk a Boostnote fejlesztéséről.", " : Development configurations for Boostnote.": " : Információk a Boostnote fejlesztéséről.",
"Copyright (C) 2017 - 2018 BoostIO": "Szerzői jog (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Szerzői jog (C) 2017 - 2019 BoostIO",
"License: GPL v3": "Licensz: GPL v3", "License: GPL v3": "Licensz: GPL v3",
"Analytics": "Adatok elemzése", "Analytics": "Adatok elemzése",
"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.": "A Boostnote névtelen adatokat gyűjt össze az alkalmazás tökéletesítése céljából, és szigorúan nem gyűjt semmilyen személyes adatot, például a jegyzetek tartalmát.", "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.": "A Boostnote névtelen adatokat gyűjt össze az alkalmazás tökéletesítése céljából, és szigorúan nem gyűjt semmilyen személyes adatot, például a jegyzetek tartalmát.",
@@ -178,5 +178,7 @@
"Allow dangerous html tags": "Veszélyes html tag-ek engedélyezése", "Allow dangerous html tags": "Veszélyes html tag-ek engedélyezése",
"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.", "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠", "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
"Disabled": "Disabled" "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect"
} }

View File

@@ -18,6 +18,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "Stampa", "Print": "Stampa",
"Your preferences for Boostnote": "Le tue preferenze per Boostnote", "Your preferences for Boostnote": "Le tue preferenze per Boostnote",
"Storage Locations": "Posizioni", "Storage Locations": "Posizioni",
@@ -75,7 +76,7 @@
"Website": "Sito Web", "Website": "Sito Web",
"Development": "Sviluppo", "Development": "Sviluppo",
" : Development configurations for Boostnote.": " : Configurazioni di sviluppo per Boostnote.", " : Development configurations for Boostnote.": " : Configurazioni di sviluppo per Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "Licenza: GPL v3", "License: GPL v3": "Licenza: GPL v3",
"Analytics": "Statistiche", "Analytics": "Statistiche",
"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 raccoglie dati anonimi al solo scopo di migliorare l'applicazione, e non raccoglie nessuna informazione personale rigurado il contenuto delle note.", "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 raccoglie dati anonimi al solo scopo di migliorare l'applicazione, e non raccoglie nessuna informazione personale rigurado il contenuto delle note.",
@@ -157,5 +158,7 @@
"Allow dangerous html tags": "Consenti tag HTML pericolosi", "Allow dangerous html tags": "Consenti tag HTML pericolosi",
"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.", "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠", "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
"Disabled": "Disabled" "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect"
} }

View File

@@ -8,10 +8,10 @@
"to create a new note": "ノートを新規に作成", "to create a new note": "ノートを新規に作成",
"Toggle Mode": "モード切替", "Toggle Mode": "モード切替",
"Add tag...": "タグを追加...", "Add tag...": "タグを追加...",
"Star": "お気に入り", "Star": "お気に入り",
"Fullscreen": "全画面", "Fullscreen": "全画面",
"Trash": "ゴミ箱", "Trash": "ゴミ箱",
"Info": "情報", "Info": "情報",
"MODIFICATION DATE": "修正日", "MODIFICATION DATE": "修正日",
"Words": "ワード", "Words": "ワード",
"Letters": "文字", "Letters": "文字",
@@ -22,6 +22,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "印刷", "Print": "印刷",
"Your preferences for Boostnote": "Boostnoteの個人設定", "Your preferences for Boostnote": "Boostnoteの個人設定",
"Help": "ヘルプ", "Help": "ヘルプ",
@@ -46,7 +47,7 @@
"Default New Note": "新規ノートの形式", "Default New Note": "新規ノートの形式",
"Always Ask": "作成時に聞く", "Always Ask": "作成時に聞く",
"Show a confirmation dialog when deleting notes": "ノートを削除する時に確認ダイアログを表示する", "Show a confirmation dialog when deleting notes": "ノートを削除する時に確認ダイアログを表示する",
"Disable Direct Write (It will be applied after restarting)": "Disable Direct Write (It will be applied after restarting)", "Disable Direct Write (It will be applied after restarting)": "直接編集を無効にする(設定反映には再起動が必要です)",
"Save tags of a note in alphabetical order": "ノートのタグをアルファベット順に保存する", "Save tags of a note in alphabetical order": "ノートのタグをアルファベット順に保存する",
"Show tags of a note in alphabetical order": "ノートのタグをアルファベット順に表示する", "Show tags of a note in alphabetical order": "ノートのタグをアルファベット順に表示する",
"Show only related tags": "関連するタグのみ表示する", "Show only related tags": "関連するタグのみ表示する",
@@ -78,6 +79,7 @@
"Matching character pairs": "自動補完する括弧ペアの列記", "Matching character pairs": "自動補完する括弧ペアの列記",
"Matching character triples": "自動補完する3文字括弧の列記", "Matching character triples": "自動補完する3文字括弧の列記",
"Exploding character pairs": "改行時に空行を挿入する括弧ペアの列記", "Exploding character pairs": "改行時に空行を挿入する括弧ペアの列記",
"Custom MarkdownLint Rules": "カスタムMarkdownLintルール",
"Preview": "プレビュー", "Preview": "プレビュー",
"Preview Font Size": "プレビュー時フォントサイズ", "Preview Font Size": "プレビュー時フォントサイズ",
"Preview Font Family": "プレビュー時フォント", "Preview Font Family": "プレビュー時フォント",
@@ -105,7 +107,7 @@
"Website": "ウェブサイト", "Website": "ウェブサイト",
"Development": "開発", "Development": "開発",
" : Development configurations for Boostnote.": " : Boostnote の開発構成", " : Development configurations for Boostnote.": " : Boostnote の開発構成",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "ライセンス: GPL v3", "License: GPL v3": "ライセンス: 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 はアプリケーションの機能向上だけを目的に匿名データを収集します。ノートの内容を含めた個人の情報は一切収集しません。", "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 はアプリケーションの機能向上だけを目的に匿名データを収集します。ノートの内容を含めた個人の情報は一切収集しません。",
@@ -199,7 +201,7 @@
"Type": "種類", "Type": "種類",
"File System": "ファイルシステム", "File System": "ファイルシステム",
"Setting up 3rd-party cloud storage integration:": "サードパーティのクラウドストレージとの統合を設定する:", "Setting up 3rd-party cloud storage integration:": "サードパーティのクラウドストレージとの統合を設定する:",
"Cloud-Syncing-and-Backup": "Cloud-Syncing-and-Backup", "Cloud-Syncing-and-Backup": "クラウド同期とバックアップ",
"Location": "ロケーション", "Location": "ロケーション",
"Add": "追加", "Add": "追加",
"Export Storage": "ストレージの書き出し", "Export Storage": "ストレージの書き出し",
@@ -215,5 +217,7 @@
"Allow dangerous html tags": "安全でないHTMLタグの利用を許可する", "Allow dangerous html tags": "安全でないHTMLタグの利用を許可する",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "テキストの矢印を綺麗な記号に変換する ⚠ この設定はMarkdown内でのHTMLコメントに干渉します。", "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "テキストの矢印を綺麗な記号に変換する ⚠ この設定はMarkdown内でのHTMLコメントに干渉します。",
"⚠ 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! ⚠": "⚠ このノートのストレージに存在しない添付ファイルへのリンクを貼り付けました。添付ファイルへのリンクの貼り付けは同一ストレージ内でのみサポートされています。代わりに添付ファイルをドラッグアンドドロップしてください! ⚠",
"Disabled": "Disabled" "Spellcheck disabled": "スペルチェック無効",
"Show menu bar": "メニューバーを表示",
"Auto Detect": "自動検出"
} }

View File

@@ -18,6 +18,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "인쇄", "Print": "인쇄",
"Your preferences for Boostnote": "Boostnote 설정", "Your preferences for Boostnote": "Boostnote 설정",
"Storage Locations": "저장소", "Storage Locations": "저장소",
@@ -75,7 +76,7 @@
"Website": "웹사이트", "Website": "웹사이트",
"Development": "개발", "Development": "개발",
" : Development configurations for Boostnote.": " : Boostnote 개발을 위한 설정들.", " : Development configurations for Boostnote.": " : Boostnote 개발을 위한 설정들.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "License: GPL v3", "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는 서비스개선을 위해 익명으로 데이터를 수집하며 노트의 내용과같은 일체의 개인정보는 수집하지 않습니다.", "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는 서비스개선을 위해 익명으로 데이터를 수집하며 노트의 내용과같은 일체의 개인정보는 수집하지 않습니다.",
@@ -160,5 +161,7 @@
"Allow dangerous html tags": "모든 위험한 태그 허용", "Allow dangerous html tags": "모든 위험한 태그 허용",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.", "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠", "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
"Disabled": "Disabled" "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect"
} }

View File

@@ -18,6 +18,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "Print", "Print": "Print",
"Your preferences for Boostnote": "Your preferences for Boostnote", "Your preferences for Boostnote": "Your preferences for Boostnote",
"Storage Locations": "Storage Locations", "Storage Locations": "Storage Locations",
@@ -75,7 +76,7 @@
"Website": "Website", "Website": "Website",
"Development": "Development", "Development": "Development",
" : Development configurations for Boostnote.": " : Development configurations for Boostnote.", " : Development configurations for Boostnote.": " : Development configurations for Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "License: GPL v3", "License: GPL v3": "License: GPL v3",
"Analytics": "Analytics", "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.", "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.",
@@ -153,5 +154,7 @@
"Allow dangerous html tags": "Allow dangerous html tags", "Allow dangerous html tags": "Allow dangerous html tags",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.", "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠", "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
"Disabled": "Disabled" "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect"
} }

View File

@@ -14,13 +14,14 @@
"STORAGE": "MIEJSCE ZAPISU", "STORAGE": "MIEJSCE ZAPISU",
"FOLDER": "FOLDER", "FOLDER": "FOLDER",
"CREATION DATE": "DATA UTWORZENIA", "CREATION DATE": "DATA UTWORZENIA",
"NOTE LINK": "LINK NOTATKI", "NOTE LINK": "LINK NOTATKI",
"Toggle Editor Mode": "Przełączanie trybu edytora", "Toggle Editor Mode": "Przełączanie trybu edytora",
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
"Print": "Drukuj", ".pdf": ".pdf",
"Help": "Pomoc", "Print": "Drukuj",
"Help": "Pomoc",
"Your preferences for Boostnote": "Twoje ustawienia dla Boostnote", "Your preferences for Boostnote": "Twoje ustawienia dla Boostnote",
"Storage Locations": "Storage Locations", "Storage Locations": "Storage Locations",
"Add Storage Location": "Dodaj miejsce zapisu", "Add Storage Location": "Dodaj miejsce zapisu",
@@ -35,12 +36,12 @@
"White": "Biały", "White": "Biały",
"Solarized Dark": "Solarized Dark", "Solarized Dark": "Solarized Dark",
"Dark": "Ciemny", "Dark": "Ciemny",
"Show a confirmation dialog when deleting notes": "Zatwierdzaj usunięcie notatek", "Show a confirmation dialog when deleting notes": "Zatwierdzaj usunięcie notatek",
"Show only related tags": "Pokazuj tylko powiązane tagi", "Show only related tags": "Pokazuj tylko powiązane tagi",
"Editor Theme": "Wygląd edytora", "Editor Theme": "Wygląd edytora",
"Editor Font Size": "Rozmiar czcionki", "Editor Font Size": "Rozmiar czcionki",
"Editor Font Family": "Czcionka", "Editor Font Family": "Czcionka",
"Snippet Default Language": "Domyślny język snippetów kodu", "Snippet Default Language": "Domyślny język snippetów kodu",
"Editor Indent Style": "Rodzaj wcięć", "Editor Indent Style": "Rodzaj wcięć",
"Spaces": "Spacje", "Spaces": "Spacje",
"Tabs": "Tabulatory", "Tabs": "Tabulatory",
@@ -57,9 +58,9 @@
"Allow editor to scroll past the last line": "Pozwalaj edytorowi na przewijanie poza końcową linię", "Allow editor to scroll past the last line": "Pozwalaj edytorowi na przewijanie poza końcową linię",
"Bring in web page title when pasting URL on editor": "Wprowadź tytuł wklejanej strony WWW do edytora", "Bring in web page title when pasting URL on editor": "Wprowadź tytuł wklejanej strony WWW do edytora",
"Preview": "Podgląd", "Preview": "Podgląd",
"Preview Font Size": "Rozmiar czcionki", "Preview Font Size": "Rozmiar czcionki",
"Enable smart quotes": "Włącz inteligentne cytowanie", "Enable smart quotes": "Włącz inteligentne cytowanie",
"Render newlines in Markdown paragraphs as <br>": "Dodawaj nowe linie w notatce jako znacznik <br>", "Render newlines in Markdown paragraphs as <br>": "Dodawaj nowe linie w notatce jako znacznik <br>",
"Preview Font Family": "Czcionka", "Preview Font Family": "Czcionka",
"Code Block Theme": "Styl bloku kodu", "Code Block Theme": "Styl bloku kodu",
"Allow preview to scroll past the last line": "Pozwalaj podglądowi na przewijanie poza końcową linię", "Allow preview to scroll past the last line": "Pozwalaj podglądowi na przewijanie poza końcową linię",
@@ -81,7 +82,7 @@
"Website": "Strona WWW", "Website": "Strona WWW",
"Development": "Development", "Development": "Development",
" : Development configurations for Boostnote.": " : Development configurations for Boostnote.", " : Development configurations for Boostnote.": " : Development configurations for Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "Licencja: GPL v3", "License: GPL v3": "Licencja: GPL v3",
"Analytics": "Statystyki", "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 dane wyłącznie w celu poprawy działania aplikacji, lecz nigdy nie pobiera prywatnych danych z Twoich notatek.", "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 dane wyłącznie w celu poprawy działania aplikacji, lecz nigdy nie pobiera prywatnych danych z Twoich notatek.",
@@ -159,8 +160,10 @@
"Allow dangerous html tags": "Zezwól na niebezpieczne tagi HTML", "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.": "Konwertuje tekstowe strzałki na znaki. ⚠ Wpłynie to na używanie komentarzy HTML w Twojej notatce.", "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Konwertuje tekstowe strzałki na znaki. ⚠ Wpłynie to na używanie komentarzy HTML w Twojej notatce.",
"⚠ 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! ⚠": "⚠ Wkleiłes link odnoszący się do załącznika, ktory nie może zostać znaleziony. Wklejanie linków do załączników jest możliwe tylko gdy notatka i załącznik są w tym samym folderze. Używaj opcji 'Przeciągaj i upuść' załącznik! ⚠", "⚠ 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! ⚠": "⚠ Wkleiłes link odnoszący się do załącznika, ktory nie może zostać znaleziony. Wklejanie linków do załączników jest możliwe tylko gdy notatka i załącznik są w tym samym folderze. Używaj opcji 'Przeciągaj i upuść' załącznik! ⚠",
"Star": "Oznacz", "Star": "Oznacz",
"Fullscreen": "Pełen ekran", "Fullscreen": "Pełen ekran",
"Add tag...": "Dodaj tag...", "Add tag...": "Dodaj tag...",
"Disabled": "Disabled" "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect"
} }

View File

@@ -18,6 +18,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "Imprimir", "Print": "Imprimir",
"Your preferences for Boostnote": "Suas preferências para o Boostnote", "Your preferences for Boostnote": "Suas preferências para o Boostnote",
"Storage Locations": "Armazenamentos", "Storage Locations": "Armazenamentos",
@@ -75,7 +76,7 @@
"Website": "Website", "Website": "Website",
"Development": "Desenvolvimento", "Development": "Desenvolvimento",
" : Development configurations for Boostnote.": " : Configurações de desenvolvimento para o Boostnote.", " : Development configurations for Boostnote.": " : Configurações de desenvolvimento para o Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Direitos Autorais (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Direitos Autorais (C) 2017 - 2019 BoostIO",
"License: GPL v3": "Licença: GPL v3", "License: GPL v3": "Licença: GPL v3",
"Analytics": "Técnicas analíticas", "Analytics": "Técnicas analíticas",
"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.": "O Boostnote coleta dados anônimos com o único propósito de melhorar o aplicativo e de modo algum coleta qualquer informação pessoal, bem como o conteúdo de suas anotações.", "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.": "O Boostnote coleta dados anônimos com o único propósito de melhorar o aplicativo e de modo algum coleta qualquer informação pessoal, bem como o conteúdo de suas anotações.",
@@ -153,5 +154,7 @@
"Allow dangerous html tags": "Permitir tags html perigosas", "Allow dangerous html tags": "Permitir tags html perigosas",
"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.", "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠", "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
"Disabled": "Disabled" "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect"
} }

View File

@@ -18,6 +18,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "Imprimir", "Print": "Imprimir",
"Your preferences for Boostnote": "As tuas definiçōes para Boostnote", "Your preferences for Boostnote": "As tuas definiçōes para Boostnote",
"Storage Locations": "Locais de Armazenamento", "Storage Locations": "Locais de Armazenamento",
@@ -75,7 +76,7 @@
"Website": "Website", "Website": "Website",
"Development": "Desenvolvimento", "Development": "Desenvolvimento",
" : Development configurations for Boostnote.": " : Configurações de desenvolvimento para o Boostnote.", " : Development configurations for Boostnote.": " : Configurações de desenvolvimento para o Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Direitos de Autor (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Direitos de Autor (C) 2017 - 2019 BoostIO",
"License: GPL v3": "Licença: GPL v3", "License: GPL v3": "Licença: GPL v3",
"Analytics": "Analíse de Data", "Analytics": "Analíse de Data",
"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.": "O Boostnote coleta dados anônimos com o único propósito de melhorar a aplicação e não adquire informação pessoal ou conteúdo das tuas 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.": "O Boostnote coleta dados anônimos com o único propósito de melhorar a aplicação e não adquire informação pessoal ou conteúdo das tuas notas.",
@@ -130,7 +131,7 @@
"Permanent Delete": "Apagar Permanentemente", "Permanent Delete": "Apagar Permanentemente",
"Confirm note deletion": "Confirmar o apagamento da nota", "Confirm note deletion": "Confirmar o apagamento da nota",
"This will permanently remove this note.": "Isto irá remover permanentemente esta nota.", "This will permanently remove this note.": "Isto irá remover permanentemente esta nota.",
"Successfully applied!": "Aplicado com Sucesso!", "Successfully applied!": "Aplicado com Sucesso!",
"Albanian": "Albanês", "Albanian": "Albanês",
"Chinese (zh-CN)": "Chinês (zh-CN)", "Chinese (zh-CN)": "Chinês (zh-CN)",
"Chinese (zh-TW)": "Chinês (zh-TW)", "Chinese (zh-TW)": "Chinês (zh-TW)",
@@ -152,5 +153,7 @@
"Allow dangerous html tags": "Permitir tags html perigosas", "Allow dangerous html tags": "Permitir tags html perigosas",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Converter setas de texto em simbolos. ⚠ Isto irá interferir no use de comentários em HTML em Markdown.", "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Converter setas de texto em simbolos. ⚠ Isto irá interferir no use de comentários em HTML em 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! ⚠": "⚠ Você colou um link referente a um anexo que não pôde ser encontrado no local de armazenamento desta nota. A vinculação de anexos de referência de links só é suportada se o local de origem e de destino for o mesmo de armazenamento. Por favor, arraste e solte o anexo na nota! ⚠", "⚠ 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! ⚠": "⚠ Você colou um link referente a um anexo que não pôde ser encontrado no local de armazenamento desta nota. A vinculação de anexos de referência de links só é suportada se o local de origem e de destino for o mesmo de armazenamento. Por favor, arraste e solte o anexo na nota! ⚠",
"Disabled": "Disabled" "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect"
} }

View File

@@ -18,6 +18,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "Print", "Print": "Print",
"Your preferences for Boostnote": "Настройки Boostnote", "Your preferences for Boostnote": "Настройки Boostnote",
"Storage Locations": "Хранилища", "Storage Locations": "Хранилища",
@@ -74,7 +75,7 @@
"Website": "Сайт", "Website": "Сайт",
"Development": "Разработка", "Development": "Разработка",
" : Development configurations for Boostnote.": " : Разработческие конфигурации для Boostnote.", " : Development configurations for Boostnote.": " : Разработческие конфигурации для Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "License: GPL v3", "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 собирает анонимные данные о пользовании приложением для того, чтобы улучшать пользовательский опыт. Мы не собираем личную информацию и содержание ваших записей.", "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 собирает анонимные данные о пользовании приложением для того, чтобы улучшать пользовательский опыт. Мы не собираем личную информацию и содержание ваших записей.",
@@ -150,5 +151,7 @@
"Disable": "Disable", "Disable": "Disable",
"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.", "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠", "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
"Disabled": "Disabled" "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect"
} }

View File

@@ -18,6 +18,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "Print", "Print": "Print",
"Your preferences for Boostnote": "Your preferences for Boostnote", "Your preferences for Boostnote": "Your preferences for Boostnote",
"Storage Locations": "Storage Locations", "Storage Locations": "Storage Locations",
@@ -74,7 +75,7 @@
"Website": "Website", "Website": "Website",
"Development": "Development", "Development": "Development",
" : Development configurations for Boostnote.": " : Development configurations for Boostnote.", " : Development configurations for Boostnote.": " : Development configurations for Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "License: GPL v3", "License: GPL v3": "License: GPL v3",
"Analytics": "Analytics", "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.", "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.",
@@ -152,5 +153,7 @@
"Allow dangerous html tags": "Allow dangerous html tags", "Allow dangerous html tags": "Allow dangerous html tags",
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.", "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠", "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
"Disabled": "Disabled" "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect"
} }

View File

@@ -19,6 +19,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "พิมพ์", "Print": "พิมพ์",
"Your preferences for Boostnote": "การตั้งค่าของคุณสำหรับ Boostnote", "Your preferences for Boostnote": "การตั้งค่าของคุณสำหรับ Boostnote",
"Help": "ช่วยเหลือ", "Help": "ช่วยเหลือ",
@@ -82,7 +83,7 @@
"Website": "เว็บไซต์", "Website": "เว็บไซต์",
"Development": "การพัฒนา", "Development": "การพัฒนา",
" : Development configurations for Boostnote.": " : การตั้งค่าต่างๆสำหรับการพัฒนา Boostnote.", " : Development configurations for Boostnote.": " : การตั้งค่าต่างๆสำหรับการพัฒนา Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "สงวนลิขสิทธิ์ (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "สงวนลิขสิทธิ์ (C) 2017 - 2019 BoostIO",
"License: GPL v3": "License: GPL v3", "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 จะเก็บข้อมูลแบบไม่ระบุตัวตนเพื่อนำไปใช้ในการปรับปรุงแอพพลิเคชันเท่านั้น, และจะไม่มีการเก็บข้อมูลส่วนตัวใดๆของคุณ เช่น ข้อมูลในโน๊ตของคุณอย่างเด็ดขาด.", "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 จะเก็บข้อมูลแบบไม่ระบุตัวตนเพื่อนำไปใช้ในการปรับปรุงแอพพลิเคชันเท่านั้น, และจะไม่มีการเก็บข้อมูลส่วนตัวใดๆของคุณ เช่น ข้อมูลในโน๊ตของคุณอย่างเด็ดขาด.",
@@ -178,5 +179,8 @@
"Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "แปลงลูกศรจากรูปแบบข้อความให้เป็นสัญลักษณ์. ⚠ สิ่งนี้จะเป็นการแทรกโดยใช้ HTML comment ลงไปใน Markdown ที่คุณเขียน.", "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "แปลงลูกศรจากรูปแบบข้อความให้เป็นสัญลักษณ์. ⚠ สิ่งนี้จะเป็นการแทรกโดยใช้ HTML comment ลงไปใน 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! ⚠": "⚠ ไม่พบไฟล์แนบในโน๊ตนี้ จากลิงค์ที่คุณได้วาง. คุณสามารถวางลิงค์ที่อ้างอิงไปยังไฟล์แนบ เฉพาะกรณีที่ต้นทาง และปลายทางที่อ้างถึงนั้นอยู่ใน 'แหล่งจัดเก็บ เดียวกัน. กรุณาใช้การลากและวางเพื่อใส่ไฟล์แนบแทน! ⚠",
"Enable smart table editor": "เปิดการใช้ Smart table editor", "Enable smart table editor": "เปิดการใช้ Smart table editor",
"Snippet Default Language": "ทำการ Snippet ภาษาที่เป็นค่าเริ่มต้น" "Snippet Default Language": "ทำการ Snippet ภาษาที่เป็นค่าเริ่มต้น",
"Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect"
} }

View File

@@ -18,6 +18,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "Yazdır", "Print": "Yazdır",
"Your preferences for Boostnote": "Boostnote tercihleriniz", "Your preferences for Boostnote": "Boostnote tercihleriniz",
"Storage Locations": "Saklama Alanları", "Storage Locations": "Saklama Alanları",
@@ -74,7 +75,7 @@
"Website": "Websitesi", "Website": "Websitesi",
"Development": "Geliştirme", "Development": "Geliştirme",
" : Development configurations for Boostnote.": " : Boostnote için geliştirme ayarları.", " : Development configurations for Boostnote.": " : Boostnote için geliştirme ayarları.",
"Copyright (C) 2017 - 2018 BoostIO": "Her hakkı saklıdır. (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Her hakkı saklıdır. (C) 2017 - 2019 BoostIO",
"License: GPL v3": "Lisans: GPL v3", "License: GPL v3": "Lisans: GPL v3",
"Analytics": "Analizler", "Analytics": "Analizler",
"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, uygulamanın geliştirilmesi amacıyla anonim veriler toplar. Notlarınızın içeriği gibi kişisel bilgiler kesinlikle toplanmaz.", "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, uygulamanın geliştirilmesi amacıyla anonim veriler toplar. Notlarınızın içeriği gibi kişisel bilgiler kesinlikle toplanmaz.",
@@ -152,5 +153,7 @@
"Only allow secure html tags (recommended)": "Sadece güvenli html etiketlerine izin ver (tavsiye edilen)", "Only allow secure html tags (recommended)": "Sadece güvenli html etiketlerine izin ver (tavsiye edilen)",
"Allow styles": "Stillere izin ver", "Allow styles": "Stillere izin ver",
"Allow dangerous html tags": "Tehlikeli html etiketlerine izin ver", "Allow dangerous html tags": "Tehlikeli html etiketlerine izin ver",
"Disabled": "Disabled" "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect"
} }

View File

@@ -18,6 +18,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "打印", "Print": "打印",
"Your preferences for Boostnote": "个性设置", "Your preferences for Boostnote": "个性设置",
"Storage Locations": "本地存储", "Storage Locations": "本地存储",
@@ -76,7 +77,7 @@
"Website": "官网", "Website": "官网",
"Development": "开发", "Development": "开发",
" : Development configurations for Boostnote.": " : Boostnote 的开发配置", " : Development configurations for Boostnote.": " : Boostnote 的开发配置",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "License: GPL v3", "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 收集匿名数据只为了提升软件使用体验,绝对不收集任何个人信息(包括笔记内容)", "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 收集匿名数据只为了提升软件使用体验,绝对不收集任何个人信息(包括笔记内容)",
@@ -205,8 +206,19 @@
"Rename": "重命名", "Rename": "重命名",
"Folder Name": "文件夹名称", "Folder Name": "文件夹名称",
"No tags":"无标签", "No tags":"无标签",
"Render newlines in Markdown paragraphs as <br>":"在 Markdown 段落中使用 <br> 换行", "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "将文本箭头转换为完整符号。 ⚠ 注意这会影响 Markdown 的 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! ⚠", "⚠ 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! ⚠",
"Disabled": "Disabled" "Default New Note":"预设新笔记类型",
"Show only related tags": "只显示相关标签",
"Snippet Default Language": "程式码片段预设语言",
"Disable Direct Write (It will be applied after restarting)": "停用直接编辑 (重启后生效)",
"Enable smart table editor": "启用智能表格编辑器",
"Enable smart quotes": "启用智能引号",
"Allow line through checkbox": "替标示为完成的选框添加删除线",
"Custom CSS": "自定义 CSS",
"Allow custom CSS for preview": "允许预览自定义 CSS",
"Render newlines in Markdown paragraphs as <br>":"在 Markdown 段落中使用 <br> 换行",
"Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect"
} }

View File

@@ -18,6 +18,7 @@
".md": ".md", ".md": ".md",
".txt": ".txt", ".txt": ".txt",
".html": ".html", ".html": ".html",
".pdf": ".pdf",
"Print": "列印", "Print": "列印",
"Your preferences for Boostnote": "Boostnote 偏好設定", "Your preferences for Boostnote": "Boostnote 偏好設定",
"Storage Locations": "儲存空間", "Storage Locations": "儲存空間",
@@ -74,7 +75,7 @@
"Website": "官網", "Website": "官網",
"Development": "開發", "Development": "開發",
" : Development configurations for Boostnote.": " : Boostnote 的開發組態", " : Development configurations for Boostnote.": " : Boostnote 的開發組態",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO", "Copyright (C) 2017 - 2019 BoostIO": "Copyright (C) 2017 - 2019 BoostIO",
"License: GPL v3": "License: GPL v3", "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 收集匿名資料單純只為了提升軟體使用體驗,絕對不收集任何個人資料(包括筆記內容)", "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 收集匿名資料單純只為了提升軟體使用體驗,絕對不收集任何個人資料(包括筆記內容)",
@@ -149,7 +150,19 @@
"Only allow secure html tags (recommended)": "只允許安全的 HTML 標籤 (建議)", "Only allow secure html tags (recommended)": "只允許安全的 HTML 標籤 (建議)",
"Allow styles": "允許樣式", "Allow styles": "允許樣式",
"Allow dangerous html tags": "允許危險的 HTML 標籤", "Allow dangerous html tags": "允許危險的 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.", "Convert textual arrows to beautiful signs. ⚠ This will interfere with using HTML comments in your Markdown.": "將文本箭頭轉換為完整符號。 ⚠ 注意這會影響 Markdown 的 HTML 注釋。",
"Default New Note":"預設新筆記類型",
"Show only related tags": "只顯示相關標籤",
"Snippet Default Language": "程式碼片段預設語言",
"Disable Direct Write (It will be applied after restarting)": "停用直接編輯 (重啟後生效)",
"Enable smart table editor": "啟用智能表格編輯器",
"Enable smart quotes": "啟用智能引號",
"Allow line through checkbox": "替標示為完成的選框添加刪除線",
"Custom CSS": "自定義 CSS",
"Allow custom CSS for preview": "允許預覽自定義 CSS",
"Render newlines in Markdown paragraphs as <br>":"在 Markdown 段落中使用 <br> 換行",
"⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠", "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠": "⚠ You have pasted a link referring an attachment that could not be found in the storage location of this note. Pasting links referring attachments is only supported if the source and destination location is the same storage. Please Drag&Drop the attachment instead! ⚠",
"Disabled": "Disabled" "Spellcheck disabled": "Spellcheck disabled",
"Show menu bar": "Show menu bar",
"Auto Detect": "Auto Detect"
} }

View File

@@ -1,18 +1,18 @@
{ {
"name": "boost", "name": "boost",
"productName": "Boostnote", "productName": "Boostnote",
"version": "0.11.15", "version": "0.11.17",
"main": "index.js", "main": "index.js",
"description": "Boostnote", "description": "Boostnote",
"license": "GPL-3.0", "license": "GPL-3.0",
"scripts": { "scripts": {
"start": "electron ./index.js", "start": "electron ./index.js",
"compile": "grunt compile", "compile": "grunt compile",
"test": "PWD=$(pwd) NODE_ENV=test ava --serial", "test": "cross-env NODE_ENV=test ava --serial",
"jest": "jest", "jest": "jest",
"fix": "eslint . --fix", "fix": "eslint . --fix",
"lint": "eslint .", "lint": "eslint .",
"dev": "node dev-scripts/dev.js", "dev": "cross-env NODE_ENV=development node dev-scripts/dev.js",
"watch": "webpack-dev-server --hot" "watch": "webpack-dev-server --hot"
}, },
"config": { "config": {
@@ -50,6 +50,7 @@
"homepage": "https://boostnote.io", "homepage": "https://boostnote.io",
"dependencies": { "dependencies": {
"@enyaxu/markdown-it-anchor": "^5.0.2", "@enyaxu/markdown-it-anchor": "^5.0.2",
"@rokt33r/js-sequence-diagrams": "^2.0.6-2",
"@rokt33r/markdown-it-math": "^4.0.1", "@rokt33r/markdown-it-math": "^4.0.1",
"@rokt33r/season": "^5.3.0", "@rokt33r/season": "^5.3.0",
"@susisu/mte-kernel": "^2.0.0", "@susisu/mte-kernel": "^2.0.0",
@@ -58,6 +59,7 @@
"chart.js": "^2.7.2", "chart.js": "^2.7.2",
"codemirror": "^5.40.2", "codemirror": "^5.40.2",
"codemirror-mode-elixir": "^1.1.1", "codemirror-mode-elixir": "^1.1.1",
"connected-react-router": "^6.4.0",
"electron-config": "^1.0.0", "electron-config": "^1.0.0",
"electron-gh-releases": "^2.0.4", "electron-gh-releases": "^2.0.4",
"escape-string-regexp": "^1.0.5", "escape-string-regexp": "^1.0.5",
@@ -72,9 +74,9 @@
"iconv-lite": "^0.4.19", "iconv-lite": "^0.4.19",
"immutable": "^3.8.1", "immutable": "^3.8.1",
"invert-color": "^2.0.0", "invert-color": "^2.0.0",
"js-sequence-diagrams": "^1000000.0.6",
"js-yaml": "^3.12.0", "js-yaml": "^3.12.0",
"katex": "^0.9.0", "jsonlint-mod": "^1.7.4",
"katex": "^0.10.1",
"lodash": "^4.11.1", "lodash": "^4.11.1",
"lodash-move": "^1.1.1", "lodash-move": "^1.1.1",
"markdown-it": "^6.0.1", "markdown-it": "^6.0.1",
@@ -96,15 +98,18 @@
"mousetrap": "^1.6.2", "mousetrap": "^1.6.2",
"mousetrap-global-bind": "^1.1.0", "mousetrap-global-bind": "^1.1.0",
"node-ipc": "^8.1.0", "node-ipc": "^8.1.0",
"prop-types": "^15.7.2",
"query-string": "^6.5.0",
"raphael": "^2.2.7", "raphael": "^2.2.7",
"react": "^15.5.4", "react": "^16.8.6",
"react-autosuggest": "^9.4.0", "react-autosuggest": "^9.4.0",
"react-codemirror": "^0.3.0", "react-codemirror": "^1.0.0",
"react-color": "^2.2.2", "react-color": "^2.2.2",
"react-debounce-render": "^4.0.1", "react-debounce-render": "^4.0.1",
"react-dom": "^15.0.2", "react-dom": "^16.8.6",
"react-image-carousel": "^2.0.18", "react-image-carousel": "^2.0.18",
"react-redux": "^4.4.5", "react-redux": "^7.0.3",
"react-router-dom": "^5.0.0",
"react-sortable-hoc": "^0.6.7", "react-sortable-hoc": "^0.6.7",
"react-transition-group": "^2.5.0", "react-transition-group": "^2.5.0",
"redux": "^3.5.2", "redux": "^3.5.2",
@@ -133,12 +138,15 @@
"color": "^3.0.0", "color": "^3.0.0",
"concurrently": "^3.4.0", "concurrently": "^3.4.0",
"copy-to-clipboard": "^3.0.6", "copy-to-clipboard": "^3.0.6",
"cross-env": "^5.2.0",
"css": "^2.2.4", "css": "^2.2.4",
"css-loader": "^0.19.0", "css-loader": "^0.19.0",
"devtron": "^1.1.0", "devtron": "^1.1.0",
"dom-storage": "^2.0.2", "dom-storage": "^2.0.2",
"electron": "3.0.8", "electron": "3.0.8",
"electron-packager": "^12.0.0", "electron-debug": "^2.2.0",
"electron-devtools-installer": "^2.2.4",
"electron-packager": "^12.2.0",
"eslint": "^3.13.1", "eslint": "^3.13.1",
"eslint-config-standard": "^6.2.1", "eslint-config-standard": "^6.2.1",
"eslint-config-standard-jsx": "^3.2.0", "eslint-config-standard-jsx": "^3.2.0",
@@ -147,21 +155,23 @@
"faker": "^3.1.0", "faker": "^3.1.0",
"grunt": "^0.4.5", "grunt": "^0.4.5",
"grunt-electron-installer": "2.1.0", "grunt-electron-installer": "2.1.0",
"history": "^1.17.0", "history": "^4.9.0",
"husky": "^1.1.0", "husky": "^1.1.0",
"identity-obj-proxy": "^3.0.0", "identity-obj-proxy": "^3.0.0",
"jest": "^22.4.3", "jest": "^22.4.3",
"jest-localstorage-mock": "^2.2.0", "jest-localstorage-mock": "^2.2.0",
"jsdom": "^9.4.2", "jsdom": "^9.4.2",
"json-loader": "^0.5.4", "json-loader": "^0.5.4",
"markdownlint": "^0.11.0",
"merge-stream": "^1.0.0", "merge-stream": "^1.0.0",
"mock-require": "^3.0.1", "mock-require": "^3.0.1",
"nib": "^1.1.0", "nib": "^1.1.0",
"react-css-modules": "^3.7.6", "react-css-modules": "^4.7.9",
"react-input-autosize": "^1.1.0", "react-input-autosize": "^1.1.0",
"react-router": "^2.4.0", "react-test-renderer": "^16.8.6",
"react-router-redux": "^4.0.4", "redux-devtools": "^3.5.0",
"react-test-renderer": "^15.6.2", "redux-devtools-dock-monitor": "^1.1.3",
"redux-devtools-log-monitor": "^1.4.0",
"signale": "^1.2.1", "signale": "^1.2.1",
"standard": "^8.4.0", "standard": "^8.4.0",
"style-loader": "^0.12.4", "style-loader": "^0.12.4",

View File

@@ -3,7 +3,7 @@
![Boostnote app screenshot](./resources/repository/top.png) ![Boostnote app screenshot](./resources/repository/top.png)
<h4 align="center">Note-taking app for programmers. </h4> <h4 align="center">Note-taking app for programmers. </h4>
<h5 align="center">Apps available for Mac, Windows, Linux, Android, and iOS.</h5> <h5 align="center">Apps available for Mac, Windows and Linux.</h5>
<h5 align="center">Built with Electron, React + Redux, Webpack, and CSSModules.</h5> <h5 align="center">Built with Electron, React + Redux, Webpack, and CSSModules.</h5>
<p align="center"> <p align="center">
<a href="https://travis-ci.org/BoostIO/Boostnote"> <a href="https://travis-ci.org/BoostIO/Boostnote">

View File

@@ -28,7 +28,14 @@ test('getTodoStatus should return a correct hash object', t => {
['- [x] `- [x] a`\n', { total: 1, completed: 1 }], ['- [x] `- [x] a`\n', { total: 1, completed: 1 }],
['- [X] `- [X] a`\n', { total: 1, completed: 1 }], ['- [X] `- [X] a`\n', { total: 1, completed: 1 }],
[' \t - [X] `- [X] a`\n', { total: 1, completed: 1 }], [' \t - [X] `- [X] a`\n', { total: 1, completed: 1 }],
[' \t - [X] `- [X] a`\n \t - [ ] `- [X] a`\n', { total: 2, completed: 1 }] [' \t - [X] `- [X] a`\n \t - [ ] `- [X] a`\n', { total: 2, completed: 1 }],
['> - [ ] a\n', { total: 1, completed: 0 }],
['> - [ ] a\n- [x] a\n', { total: 2, completed: 1 }],
['> + [ ] a\n+ foo [x]bar a\n', { total: 1, completed: 0 }],
['> - [X] `- [X] a`\n', { total: 1, completed: 1 }],
['> \t - [X] `- [X] a`\n', { total: 1, completed: 1 }],
['> > - [ ] a\n', { total: 1, completed: 0 }],
['> > > - [ ] a\n- [x] a\n', { total: 2, completed: 1 }]
] ]
testCases.forEach(testCase => { testCases.forEach(testCase => {

View File

@@ -30,7 +30,7 @@ Generated by [AVA](https://ava.li).
> Snapshot 1 > Snapshot 1
`<span class="katex-display"><span class="katex"><span class="katex-mathml"><math><semantics><mrow><mi>c</mi><mo>=</mo><mi>p</mi><mi>m</mi><mi>s</mi><mi>q</mi><mi>r</mi><mi>t</mi><mrow><msup><mi>a</mi><mn>2</mn></msup><mo>+</mo><msup><mi>b</mi><mn>2</mn></msup></mrow></mrow><annotation encoding="application/x-tex">c = pmsqrt{a^2 + b^2}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="strut" style="height:0.8641079999999999em;"></span><span class="strut bottom" style="height:1.0585479999999998em;vertical-align:-0.19444em;"></span><span class="base"><span class="mord mathit">c</span><span class="mord rule" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mord rule" style="margin-right:0.2777777777777778em;"></span><span class="mord mathit">p</span><span class="mord mathit">m</span><span class="mord mathit">s</span><span class="mord mathit" style="margin-right:0.03588em;">q</span><span class="mord mathit" style="margin-right:0.02778em;">r</span><span class="mord mathit">t</span><span class="mord"><span class="mord"><span class="mord mathit">a</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641079999999999em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span><span class="mord rule" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mord rule" style="margin-right:0.2222222222222222em;"></span><span class="mord"><span class="mord mathit">b</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641079999999999em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span></span></span></span>␊ `<span class="katex-display"><span class="katex"><span class="katex-mathml"><math><semantics><mrow><mi>c</mi><mo>=</mo><mi>p</mi><mi>m</mi><mi>s</mi><mi>q</mi><mi>r</mi><mi>t</mi><mrow><msup><mi>a</mi><mn>2</mn></msup><mo>+</mo><msup><mi>b</mi><mn>2</mn></msup></mrow></mrow><annotation encoding="application/x-tex">c = pmsqrt{a^2 + b^2}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.43056em;vertical-align:0em;"></span><span class="mord mathdefault">c</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1.0585479999999998em;vertical-align:-0.19444em;"></span><span class="mord mathdefault">p</span><span class="mord mathdefault">m</span><span class="mord mathdefault">s</span><span class="mord mathdefault" style="margin-right:0.03588em;">q</span><span class="mord mathdefault" style="margin-right:0.02778em;">r</span><span class="mord mathdefault">t</span><span class="mord"><span class="mord"><span class="mord mathdefault">a</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641079999999999em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mord"><span class="mord mathdefault">b</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641079999999999em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span></span></span></span>␊
` `
## Markdown.render() should renders abbrevations correctly ## Markdown.render() should renders abbrevations correctly

15
tests/lib/utils.test.js Normal file
View File

@@ -0,0 +1,15 @@
import { isMarkdownTitleURL } from '../../browser/lib/utils'
describe('isMarkdownTitleURL', () => {
it('returns true for valid Markdown title with url', () => {
expect(isMarkdownTitleURL('# https://validurl.com')).toBe(true)
expect(isMarkdownTitleURL('## https://validurl.com')).toBe(true)
expect(isMarkdownTitleURL('###### https://validurl.com')).toBe(true)
})
it('returns true for invalid Markdown title with url', () => {
expect(isMarkdownTitleURL('1 https://validurl.com')).toBe(false)
expect(isMarkdownTitleURL('24 https://validurl.com')).toBe(false)
expect(isMarkdownTitleURL('####### https://validurl.com')).toBe(false)
})
})

654
yarn.lock

File diff suppressed because it is too large Load Diff