1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-14 10:16:26 +00:00

Compare commits

..

5 Commits

Author SHA1 Message Date
Masahide Morio
9d98f0cb03 for v0.9 2018-02-04 05:51:39 +09:00
Masahide Morio
3503233631 Merge remote-tracking branch 'upstream/master' 2018-02-04 05:40:24 +09:00
Masahide Morio
c39393c453 for Windows 32bit 2018-02-04 05:40:15 +09:00
Masahide Morio
564cc80ef7 for Windows 32bit 2018-01-17 01:01:45 +09:00
Masahide.MORIO
77f7144fbf test 2018-01-16 10:01:20 +09:00
120 changed files with 2201 additions and 6448 deletions

View File

@@ -1,4 +1,3 @@
node_modules/
compiled/
dist/
extra_scripts/

View File

@@ -3,9 +3,7 @@
"plugins": ["react"],
"rules": {
"no-useless-escape": 0,
"prefer-const": ["warn", {
"destructuring": "all"
}],
"prefer-const": "warn",
"no-unused-vars": "warn",
"no-undef": "warn",
"no-lone-blocks": "warn",

View File

@@ -17,3 +17,4 @@ deploy:
script: if [ ${TRAVIS_NODE_VERSION} = "stable" ];then docker run -v $(pwd):$(pwd) -t snapcore/snapcraft sh -c "apt update -qq
&& cd $(pwd) && snapcraft && snapcraft push *.snap --release edge"; fi
skip_cleanup: true

View File

@@ -2,7 +2,7 @@ GPL-3.0
Boostnote - an open source note-taking app made for programmers just like you.
Copyright (C) 2017 - 2018 BoostIO
Copyright (C) 2017 Maisin&Co., Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -7,15 +7,10 @@ import path from 'path'
import copyImage from 'browser/main/lib/dataApi/copyImage'
import { findStorage } from 'browser/lib/findStorage'
import fs from 'fs'
import eventEmitter from 'browser/main/lib/eventEmitter'
import iconv from 'iconv-lite'
const { ipcRenderer } = require('electron')
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
const buildCMRulers = (rulers, enableRulers) =>
enableRulers ? rulers.map(ruler => ({ column: ruler })) : []
function pass (name) {
switch (name) {
@@ -36,13 +31,8 @@ export default class CodeEditor extends React.Component {
constructor (props) {
super(props)
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {leading: false, trailing: true})
this.changeHandler = (e) => this.handleChange(e)
this.focusHandler = () => {
ipcRenderer.send('editor:focused', true)
}
this.blurHandler = (editor, e) => {
ipcRenderer.send('editor:focused', false)
if (e == null) return null
let el = e.relatedTarget
while (el != null) {
@@ -57,47 +47,12 @@ export default class CodeEditor extends React.Component {
this.loadStyleHandler = (e) => {
this.editor.refresh()
}
this.searchHandler = (e, msg) => this.handleSearch(msg)
this.searchState = null
}
handleSearch (msg) {
const cm = this.editor
const component = this
if (component.searchState) cm.removeOverlay(component.searchState)
if (msg.length < 3) return
cm.operation(function () {
component.searchState = makeOverlay(msg, 'searching')
cm.addOverlay(component.searchState)
function makeOverlay (query, style) {
query = new RegExp(query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'), 'gi')
return {
token: function (stream) {
query.lastIndex = stream.pos
var match = query.exec(stream.string)
if (match && match.index === stream.pos) {
stream.pos += match[0].length || 1
return style
} else if (match) {
stream.pos = match.index
} else {
stream.skipToEnd()
}
}
}
}
})
}
componentDidMount () {
const { rulers, enableRulers } = this.props
this.value = this.props.value
this.editor = CodeMirror(this.refs.root, {
rulers: buildCMRulers(rulers, enableRulers),
value: this.props.value,
lineNumbers: this.props.displayLineNumbers,
lineWrapping: true,
@@ -137,7 +92,7 @@ export default class CodeEditor extends React.Component {
'Cmd-T': function (cm) {
// Do nothing
},
Enter: 'boostNewLineAndIndentContinueMarkdownList',
Enter: 'newlineAndIndentContinueMarkdownList',
'Ctrl-C': (cm) => {
if (cm.getOption('keyMap').substr(0, 3) === 'vim') {
document.execCommand('copy')
@@ -149,14 +104,9 @@ export default class CodeEditor extends React.Component {
this.setMode(this.props.mode)
this.editor.on('focus', this.focusHandler)
this.editor.on('blur', this.blurHandler)
this.editor.on('change', this.changeHandler)
this.editor.on('paste', this.pasteHandler)
eventEmitter.on('top:search', this.searchHandler)
eventEmitter.emit('code:init')
this.editor.on('scroll', this.scrollHandler)
const editorTheme = document.getElementById('editorTheme')
editorTheme.addEventListener('load', this.loadStyleHandler)
@@ -173,19 +123,15 @@ export default class CodeEditor extends React.Component {
}
componentWillUnmount () {
this.editor.off('focus', this.focusHandler)
this.editor.off('blur', this.blurHandler)
this.editor.off('change', this.changeHandler)
this.editor.off('paste', this.pasteHandler)
eventEmitter.off('top:search', this.searchHandler)
this.editor.off('scroll', this.scrollHandler)
const editorTheme = document.getElementById('editorTheme')
editorTheme.removeEventListener('load', this.loadStyleHandler)
}
componentDidUpdate (prevProps, prevState) {
let needRefresh = false
const { rulers, enableRulers } = this.props
if (prevProps.mode !== this.props.mode) {
this.setMode(this.props.mode)
}
@@ -203,10 +149,6 @@ export default class CodeEditor extends React.Component {
needRefresh = true
}
if (prevProps.enableRulers !== enableRulers || prevProps.rulers !== rulers) {
this.editor.setOption('rulers', buildCMRulers(rulers, enableRulers))
}
if (prevProps.indentSize !== this.props.indentSize) {
this.editor.setOption('indentUnit', this.props.indentSize)
this.editor.setOption('tabSize', this.props.indentSize)
@@ -289,105 +231,27 @@ export default class CodeEditor extends React.Component {
}
handlePaste (editor, e) {
const clipboardData = e.clipboardData
const dataTransferItem = clipboardData.items[0]
const pastedTxt = clipboardData.getData('text')
const isURL = (str) => {
const matcher = /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/
return matcher.test(str)
const dataTransferItem = e.clipboardData.items[0]
if (!dataTransferItem.type.match('image')) return
const blob = dataTransferItem.getAsFile()
const reader = new window.FileReader()
let base64data
reader.readAsDataURL(blob)
reader.onloadend = () => {
base64data = reader.result.replace(/^data:image\/png;base64,/, '')
base64data += base64data.replace('+', ' ')
const binaryData = new Buffer(base64data, 'base64').toString('binary')
const imageName = Math.random().toString(36).slice(-16)
const storagePath = findStorage(this.props.storageKey).path
const imageDir = path.join(storagePath, 'images')
if (!fs.existsSync(imageDir)) fs.mkdirSync(imageDir)
const imagePath = path.join(imageDir, `${imageName}.png`)
fs.writeFile(imagePath, binaryData, 'binary')
const imageMd = `![${imageName}](${path.join('/:storage', `${imageName}.png`)})`
this.insertImageMd(imageMd)
}
const isInLinkTag = (editor) => {
const startCursor = editor.getCursor('start')
const prevChar = editor.getRange(
{ line: startCursor.line, ch: startCursor.ch - 2 },
{ line: startCursor.line, ch: startCursor.ch }
)
const endCursor = editor.getCursor('end')
const nextChar = editor.getRange(
{ line: endCursor.line, ch: endCursor.ch },
{ line: endCursor.line, ch: endCursor.ch + 1 }
)
return prevChar === '](' && nextChar === ')'
}
if (dataTransferItem.type.match('image')) {
const blob = dataTransferItem.getAsFile()
const reader = new FileReader()
let base64data
reader.readAsDataURL(blob)
reader.onloadend = () => {
base64data = reader.result.replace(/^data:image\/png;base64,/, '')
base64data += base64data.replace('+', ' ')
const binaryData = new Buffer(base64data, 'base64').toString('binary')
const imageName = Math.random().toString(36).slice(-16)
const storagePath = findStorage(this.props.storageKey).path
const imageDir = path.join(storagePath, 'images')
if (!fs.existsSync(imageDir)) fs.mkdirSync(imageDir)
const imagePath = path.join(imageDir, `${imageName}.png`)
fs.writeFile(imagePath, binaryData, 'binary')
const imageMd = `![${imageName}](${path.join('/:storage', `${imageName}.png`)})`
this.insertImageMd(imageMd)
}
} else if (this.props.fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) {
this.handlePasteUrl(e, editor, pastedTxt)
}
}
handleScroll (e) {
if (this.props.onScroll) {
this.props.onScroll(e)
}
}
handlePasteUrl (e, editor, pastedTxt) {
e.preventDefault()
const taggedUrl = `<${pastedTxt}>`
editor.replaceSelection(taggedUrl)
fetch(pastedTxt, {
method: 'get'
}).then((response) => {
return this.decodeResponse(response)
}).then((response) => {
const parsedResponse = (new window.DOMParser()).parseFromString(response, 'text/html')
const value = editor.getValue()
const cursor = editor.getCursor()
const LinkWithTitle = `[${parsedResponse.title}](${pastedTxt})`
const newValue = value.replace(taggedUrl, LinkWithTitle)
editor.setValue(newValue)
editor.setCursor(cursor)
}).catch((e) => {
const value = editor.getValue()
const newValue = value.replace(taggedUrl, pastedTxt)
const cursor = editor.getCursor()
editor.setValue(newValue)
editor.setCursor(cursor)
})
}
decodeResponse (response) {
const headers = response.headers
const _charset = headers.has('content-type')
? this.extractContentTypeCharset(headers.get('content-type'))
: undefined
return response.arrayBuffer().then((buff) => {
return new Promise((resolve, reject) => {
try {
const charset = _charset !== undefined && iconv.encodingExists(_charset) ? _charset : 'utf-8'
resolve(iconv.decode(new Buffer(buff), charset).toString())
} catch (e) {
reject(e)
}
})
})
}
extractContentTypeCharset (contentType) {
return contentType.split(';').filter((str) => {
return str.trim().toLowerCase().startsWith('charset')
}).map((str) => {
return str.replace(/['"]/g, '').split('=')[1]
})[0]
}
render () {
@@ -416,8 +280,6 @@ export default class CodeEditor extends React.Component {
CodeEditor.propTypes = {
value: PropTypes.string,
enableRulers: PropTypes.bool,
rulers: PropTypes.arrayOf(Number),
mode: PropTypes.string,
className: PropTypes.string,
onBlur: PropTypes.func,

View File

@@ -5,7 +5,7 @@ import styles from './MarkdownEditor.styl'
import CodeEditor from 'browser/components/CodeEditor'
import MarkdownPreview from 'browser/components/MarkdownPreview'
import eventEmitter from 'browser/main/lib/eventEmitter'
import {findStorage} from 'browser/lib/findStorage'
import { findStorage } from 'browser/lib/findStorage'
class MarkdownEditor extends React.Component {
constructor (props) {
@@ -92,9 +92,7 @@ class MarkdownEditor extends React.Component {
if (this.state.isLocked) return
this.setState({ keyPressed: new Set() })
const { config } = this.props
if (config.editor.switchPreview === 'BLUR' ||
(config.editor.switchPreview === 'DBL_CLICK' && this.state.status === 'CODE')
) {
if (config.editor.switchPreview === 'BLUR') {
const cursorPosition = this.refs.code.editor.getCursor()
this.setState({
status: 'PREVIEW'
@@ -106,20 +104,6 @@ class MarkdownEditor extends React.Component {
}
}
handleDoubleClick (e) {
if (this.state.isLocked) return
this.setState({keyPressed: new Set()})
const { config } = this.props
if (config.editor.switchPreview === 'DBL_CLICK') {
this.setState({
status: 'CODE'
}, () => {
this.refs.code.focus()
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
})
}
}
handlePreviewMouseDown (e) {
this.previewMouseDownedAt = new Date()
}
@@ -258,12 +242,9 @@ class MarkdownEditor extends React.Component {
fontSize={editorFontSize}
indentType={config.editor.indentType}
indentSize={editorIndentSize}
enableRulers={config.editor.enableRulers}
rulers={config.editor.rulers}
displayLineNumbers={config.editor.displayLineNumbers}
scrollPastEnd={config.editor.scrollPastEnd}
storageKey={storageKey}
fetchUrlTitle={config.editor.fetchUrlTitle}
onChange={(e) => this.handleChange(e)}
onBlur={(e) => this.handleBlur(e)}
/>
@@ -281,11 +262,8 @@ class MarkdownEditor extends React.Component {
lineNumber={config.preview.lineNumber}
indentSize={editorIndentSize}
scrollPastEnd={config.preview.scrollPastEnd}
smartQuotes={config.preview.smartQuotes}
sanitize={config.preview.sanitize}
ref='preview'
onContextMenu={(e) => this.handleContextMenu(e)}
onDoubleClick={(e) => this.handleDoubleClick(e)}
tabIndex='0'
value={this.state.renderValue}
onMouseUp={(e) => this.handlePreviewMouseUp(e)}

159
browser/components/MarkdownPreview.js Executable file → Normal file
View File

@@ -1,6 +1,6 @@
import PropTypes from 'prop-types'
import React from 'react'
import Markdown from 'browser/lib/markdown'
import markdown from 'browser/lib/markdown'
import _ from 'lodash'
import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir'
@@ -9,10 +9,10 @@ import Raphael from 'raphael'
import flowchart from 'flowchart'
import SequenceDiagram from 'js-sequence-diagrams'
import eventEmitter from 'browser/main/lib/eventEmitter'
import fs from 'fs'
import htmlTextHelper from 'browser/lib/htmlTextHelper'
import copy from 'copy-to-clipboard'
import mdurl from 'mdurl'
import exportNote from 'browser/main/lib/dataApi/exportNote'
const { remote } = require('electron')
const { app } = remote
@@ -23,10 +23,6 @@ const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
const appPath = 'file://' + (process.env.NODE_ENV === 'production'
? app.getAppPath()
: path.resolve())
const CSS_FILES = [
`${appPath}/node_modules/katex/dist/katex.min.css`,
`${appPath}/node_modules/codemirror/lib/codemirror.css`
]
function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd) {
return `
@@ -120,8 +116,6 @@ export default class MarkdownPreview extends React.Component {
this.contextMenuHandler = (e) => this.handleContextMenu(e)
this.mouseDownHandler = (e) => this.handleMouseDown(e)
this.mouseUpHandler = (e) => this.handleMouseUp(e)
this.DoubleClickHandler = (e) => this.handleDoubleClick(e)
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {leading: false, trailing: true})
this.anchorClickHandler = (e) => this.handlePreviewAnchorClick(e)
this.checkboxClickHandler = (e) => this.handleCheckboxClick(e)
this.saveAsTextHandler = () => this.handleSaveAsText()
@@ -130,16 +124,6 @@ export default class MarkdownPreview extends React.Component {
this.printHandler = () => this.handlePrint()
this.linkClickHandler = this.handlelinkClick.bind(this)
this.initMarkdown = this.initMarkdown.bind(this)
this.initMarkdown()
}
initMarkdown () {
const { smartQuotes, sanitize } = this.props
this.markdown = new Markdown({
typographer: smartQuotes,
sanitize
})
}
handlePreviewAnchorClick (e) {
@@ -162,21 +146,13 @@ export default class MarkdownPreview extends React.Component {
this.props.onCheckboxClick(e)
}
handleScroll (e) {
if (this.props.onScroll) {
this.props.onScroll(e)
}
}
handleContextMenu (e) {
if (!this.props.onContextMenu) return
this.props.onContextMenu(e)
}
handleDoubleClick (e) {
if (this.props.onDoubleClick != null) this.props.onDoubleClick(e)
}
handleMouseDown (e) {
if (!this.props.onMouseDown) return
if (e.target != null) {
switch (e.target.tagName) {
case 'A':
@@ -204,35 +180,8 @@ export default class MarkdownPreview extends React.Component {
}
handleSaveAsHtml () {
this.exportAsDocument('html', (noteContent, exportTasks) => {
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme} = this.getStyleParams()
const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, lineNumber)
const body = this.markdown.render(noteContent)
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
files.forEach((file) => {
file = file.replace('file://', '')
exportTasks.push({
src: file,
dst: 'css'
})
})
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>`
this.exportAsDocument('html', (value) => {
return this.refs.root.contentWindow.document.documentElement.outerHTML
})
}
@@ -240,29 +189,23 @@ export default class MarkdownPreview extends React.Component {
this.refs.root.contentWindow.print()
}
exportAsDocument (fileType, contentFormatter) {
exportAsDocument (fileType, formatter) {
const options = {
filters: [
{name: 'Documents', extensions: [fileType]}
{ name: 'Documents', extensions: [fileType] }
],
properties: ['openFile', 'createDirectory']
}
const value = formatter ? formatter.call(this, this.props.value) : this.props.value
dialog.showSaveDialog(remote.getCurrentWindow(), options,
(filename) => {
if (filename) {
const content = this.props.value
const storage = this.props.storagePath
exportNote(storage, content, filename, contentFormatter)
.then((res) => {
dialog.showMessageBox(remote.getCurrentWindow(), {type: 'info', message: `Exported to ${filename}`})
}).catch((err) => {
dialog.showErrorBox('Export error', err ? err.message || err : 'Unexpected error during export')
throw err
})
}
(filename) => {
if (filename) {
fs.writeFile(filename, value, (err) => {
if (err) throw err
})
}
})
}
fixDecodedURI (node) {
@@ -279,26 +222,20 @@ export default class MarkdownPreview extends React.Component {
this.refs.root.setAttribute('sandbox', 'allow-scripts')
this.refs.root.contentWindow.document.body.addEventListener('contextmenu', this.contextMenuHandler)
let styles = `
this.refs.root.contentWindow.document.head.innerHTML = `
<style id='style'></style>
<link rel="stylesheet" href="${appPath}/node_modules/katex/dist/katex.min.css">
<link rel="stylesheet" href="${appPath}/node_modules/codemirror/lib/codemirror.css">
<link rel="stylesheet" id="codeTheme">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
`
CSS_FILES.forEach((file) => {
styles += `<link rel="stylesheet" href="${file}">`
})
this.refs.root.contentWindow.document.head.innerHTML = styles
this.rewriteIframe()
this.applyStyle()
this.refs.root.contentWindow.document.addEventListener('mousedown', this.mouseDownHandler)
this.refs.root.contentWindow.document.addEventListener('mouseup', this.mouseUpHandler)
this.refs.root.contentWindow.document.addEventListener('dblclick', this.DoubleClickHandler)
this.refs.root.contentWindow.document.addEventListener('drop', this.preventImageDroppedHandler)
this.refs.root.contentWindow.document.addEventListener('dragover', this.preventImageDroppedHandler)
this.refs.root.contentWindow.document.addEventListener('scroll', this.scrollHandler)
eventEmitter.on('export:save-text', this.saveAsTextHandler)
eventEmitter.on('export:save-md', this.saveAsMdHandler)
eventEmitter.on('export:save-html', this.saveAsHtmlHandler)
@@ -309,10 +246,8 @@ export default class MarkdownPreview extends React.Component {
this.refs.root.contentWindow.document.body.removeEventListener('contextmenu', this.contextMenuHandler)
this.refs.root.contentWindow.document.removeEventListener('mousedown', this.mouseDownHandler)
this.refs.root.contentWindow.document.removeEventListener('mouseup', this.mouseUpHandler)
this.refs.root.contentWindow.document.removeEventListener('dblclick', this.DoubleClickHandler)
this.refs.root.contentWindow.document.removeEventListener('drop', this.preventImageDroppedHandler)
this.refs.root.contentWindow.document.removeEventListener('dragover', this.preventImageDroppedHandler)
this.refs.root.contentWindow.document.removeEventListener('scroll', this.scrollHandler)
eventEmitter.off('export:save-text', this.saveAsTextHandler)
eventEmitter.off('export:save-md', this.saveAsMdHandler)
eventEmitter.off('export:save-html', this.saveAsHtmlHandler)
@@ -321,10 +256,6 @@ export default class MarkdownPreview extends React.Component {
componentDidUpdate (prevProps) {
if (prevProps.value !== this.props.value) this.rewriteIframe()
if (prevProps.smartQuotes !== this.props.smartQuotes || prevProps.sanitize !== this.props.sanitize) {
this.initMarkdown()
this.rewriteIframe()
}
if (prevProps.fontFamily !== this.props.fontFamily ||
prevProps.fontSize !== this.props.fontSize ||
prevProps.codeBlockFontFamily !== this.props.codeBlockFontFamily ||
@@ -338,31 +269,25 @@ export default class MarkdownPreview extends React.Component {
}
}
getStyleParams () {
applyStyle () {
const { fontSize, lineNumber, codeBlockTheme, scrollPastEnd } = this.props
let { fontFamily, codeBlockFontFamily } = this.props
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
? fontFamily.split(',').map(fontName => fontName.trim()).concat(defaultFontFamily)
: defaultFontFamily
? fontFamily.split(',').map(fontName => fontName.trim()).concat(defaultFontFamily)
: defaultFontFamily
codeBlockFontFamily = _.isString(codeBlockFontFamily) && codeBlockFontFamily.trim().length > 0
? codeBlockFontFamily.split(',').map(fontName => fontName.trim()).concat(defaultCodeBlockFontFamily)
: defaultCodeBlockFontFamily
? codeBlockFontFamily.split(',').map(fontName => fontName.trim()).concat(defaultCodeBlockFontFamily)
: defaultCodeBlockFontFamily
return {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd}
}
applyStyle () {
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd} = this.getStyleParams()
this.getWindow().document.getElementById('codeTheme').href = this.GetCodeThemeLink(codeBlockTheme)
this.setCodeTheme(codeBlockTheme)
this.getWindow().document.getElementById('style').innerHTML = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd)
}
GetCodeThemeLink (theme) {
setCodeTheme (theme) {
theme = consts.THEMES.some((_theme) => _theme === theme) && theme !== 'default'
? theme
: 'elegant'
return theme.startsWith('solarized')
this.getWindow().document.getElementById('codeTheme').href = theme.startsWith('solarized')
? `${appPath}/node_modules/codemirror/theme/solarized.css`
: `${appPath}/node_modules/codemirror/theme/${theme}.css`
}
@@ -390,7 +315,11 @@ export default class MarkdownPreview extends React.Component {
value = value.replace(codeBlock, htmlTextHelper.encodeEntities(codeBlock))
})
}
this.refs.root.contentWindow.document.body.innerHTML = this.markdown.render(value)
this.refs.root.contentWindow.document.body.innerHTML = markdown.render(value)
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.taskListItem'), (el) => {
el.parentNode.parentNode.style.listStyleType = 'none'
})
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
this.fixDecodedURI(el)
@@ -406,9 +335,9 @@ export default class MarkdownPreview extends React.Component {
})
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('img'), (el) => {
el.src = this.markdown.normalizeLinkText(el.src)
el.src = markdown.normalizeLinkText(el.src)
if (!/\/:storage/.test(el.src)) return
el.src = `file:///${this.markdown.normalizeLinkText(path.join(storagePath, 'images', path.basename(el.src)))}`
el.src = `file:///${markdown.normalizeLinkText(path.join(storagePath, 'images', path.basename(el.src)))}`
})
codeBlockTheme = consts.THEMES.some((_theme) => _theme === codeBlockTheme)
@@ -435,9 +364,9 @@ export default class MarkdownPreview extends React.Component {
el.innerHTML = ''
if (codeBlockTheme.indexOf('solarized') === 0) {
const [refThema, color] = codeBlockTheme.split(' ')
el.parentNode.className += ` cm-s-${refThema} cm-s-${color}`
el.parentNode.className += ` cm-s-${refThema} cm-s-${color} CodeMirror`
} else {
el.parentNode.className += ` cm-s-${codeBlockTheme}`
el.parentNode.className += ` cm-s-${codeBlockTheme} CodeMirror`
}
CodeMirror.runMode(content, syntax.mime, el, {
tabSize: indentSize
@@ -520,20 +449,9 @@ export default class MarkdownPreview extends React.Component {
handlelinkClick (e) {
const noteHash = e.target.href.split('/').pop()
// this will match the new uuid v4 hash and the old hash
// e.g.
// :note:1c211eb7dcb463de6490 and
// :note:7dd23275-f2b4-49cb-9e93-3454daf1af9c
const regexIsNoteLink = /^:note:([a-zA-Z0-9-]{20,36})$/
const regexIsNoteLink = /^(.{20})-(.{20})$/
if (regexIsNoteLink.test(noteHash)) {
eventEmitter.emit('list:jump', noteHash.replace(':note:', ''))
}
// this will match the old link format storage.key-note.key
// e.g.
// 877f99c3268608328037-1c211eb7dcb463de6490
const regexIsLegacyNoteLink = /^(.{20})-(.{20})$/
if (regexIsLegacyNoteLink.test(noteHash)) {
eventEmitter.emit('list:jump', noteHash.split('-')[1])
eventEmitter.emit('list:jump', noteHash)
}
}
@@ -560,6 +478,5 @@ MarkdownPreview.propTypes = {
className: PropTypes.string,
value: PropTypes.string,
showCopyNotification: PropTypes.bool,
storagePath: PropTypes.string,
smartQuotes: PropTypes.bool
storagePath: PropTypes.string
}

View File

@@ -2,7 +2,6 @@ import React from 'react'
import CodeEditor from 'browser/components/CodeEditor'
import MarkdownPreview from 'browser/components/MarkdownPreview'
import { findStorage } from 'browser/lib/findStorage'
import _ from 'lodash'
import styles from './MarkdownSplitEditor.styl'
import CSSModules from 'browser/lib/CSSModules'
@@ -13,7 +12,6 @@ class MarkdownSplitEditor extends React.Component {
this.value = props.value
this.focus = () => this.refs.code.focus()
this.reload = () => this.refs.code.reload()
this.userScroll = true
}
handleOnChange () {
@@ -21,49 +19,6 @@ class MarkdownSplitEditor extends React.Component {
this.props.onChange()
}
handleScroll (e) {
const previewDoc = _.get(this, 'refs.preview.refs.root.contentWindow.document')
const codeDoc = _.get(this, 'refs.code.editor.doc')
let srcTop, srcHeight, targetTop, targetHeight
if (this.userScroll) {
if (e.doc) {
srcTop = _.get(e, 'doc.scrollTop')
srcHeight = _.get(e, 'doc.height')
targetTop = _.get(previewDoc, 'body.scrollTop')
targetHeight = _.get(previewDoc, 'body.scrollHeight')
} else {
srcTop = _.get(previewDoc, 'body.scrollTop')
srcHeight = _.get(previewDoc, 'body.scrollHeight')
targetTop = _.get(codeDoc, 'scrollTop')
targetHeight = _.get(codeDoc, 'height')
}
const distance = (targetHeight * srcTop / srcHeight) - targetTop
const framerate = 1000 / 60
const frames = 20
const refractory = frames * framerate
this.userScroll = false
let frame = 0
let scrollPos, time
const timer = setInterval(() => {
time = frame / frames
scrollPos = time < 0.5
? 2 * time * time // ease in
: -1 + (4 - 2 * time) * time // ease out
if (e.doc) _.set(previewDoc, 'body.scrollTop', targetTop + scrollPos * distance)
else _.get(this, 'refs.code.editor').scrollTo(0, targetTop + scrollPos * distance)
if (frame >= frames) {
clearInterval(timer)
setTimeout(() => { this.userScroll = true }, refractory)
}
frame++
}, framerate)
}
}
handleCheckboxClick (e) {
e.preventDefault()
e.stopPropagation()
@@ -110,13 +65,9 @@ class MarkdownSplitEditor extends React.Component {
displayLineNumbers={config.editor.displayLineNumbers}
indentType={config.editor.indentType}
indentSize={editorIndentSize}
enableRulers={config.editor.enableRulers}
rulers={config.editor.rulers}
scrollPastEnd={config.editor.scrollPastEnd}
fetchUrlTitle={config.editor.fetchUrlTitle}
storageKey={storageKey}
onChange={this.handleOnChange.bind(this)}
onScroll={this.handleScroll.bind(this)}
/>
<MarkdownPreview
style={previewStyle}
@@ -129,13 +80,10 @@ class MarkdownSplitEditor extends React.Component {
codeBlockFontFamily={config.editor.fontFamily}
lineNumber={config.preview.lineNumber}
scrollPastEnd={config.preview.scrollPastEnd}
smartQuotes={config.preview.smartQuotes}
sanitize={config.preview.sanitize}
ref='preview'
tabInde='0'
value={value}
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
onScroll={this.handleScroll.bind(this)}
showCopyNotification={config.ui.showCopyNotification}
storagePath={storage.path}
/>

View File

@@ -46,25 +46,14 @@ const TagElementList = (tags) => {
* @param {Function} handleDragStart
* @param {string} dateDisplay
*/
const NoteItem = ({
isActive,
note,
dateDisplay,
handleNoteClick,
handleNoteContextMenu,
handleDragStart,
pathname,
storageName,
folderName,
viewType
}) => (
const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleNoteContextMenu, handleDragStart, pathname }) => (
<div styleName={isActive
? 'item--active'
: 'item'
}
key={note.key}
onClick={e => handleNoteClick(e, note.key)}
onContextMenu={e => handleNoteContextMenu(e, note.key)}
? 'item--active'
: 'item'
}
key={`${note.storage}-${note.key}`}
onClick={e => handleNoteClick(e, `${note.storage}-${note.key}`)}
onContextMenu={e => handleNoteContextMenu(e, `${note.storage}-${note.key}`)}
onDragStart={e => handleDragStart(e, note)}
draggable='true'
>
@@ -79,33 +68,23 @@ const NoteItem = ({
: <span styleName='item-title-empty'>Empty</span>
}
</div>
{['ALL', 'STORAGE'].includes(viewType) && <div styleName='item-middle'>
<div styleName='item-middle-time'>{dateDisplay}</div>
<div styleName='item-middle-app-meta'>
<div title={viewType === 'ALL' ? storageName : viewType === 'STORAGE' ? folderName : null} styleName='item-middle-app-meta-label'>
{viewType === 'ALL' && storageName}
{viewType === 'STORAGE' && folderName}
</div>
</div>
</div>}
<div styleName='item-bottom-time'>{dateDisplay}</div>
{note.isStarred
? <img styleName='item-star' src='../resources/icon/icon-starred.svg' /> : ''
}
{note.isPinned && !pathname.match(/\/home|\/starred|\/trash/)
? <i styleName='item-pin' className='fa fa-thumb-tack' /> : ''
}
{note.type === 'MARKDOWN_NOTE'
? <TodoProcess todoStatus={getTodoStatus(note.content)} />
: ''
}
<div styleName='item-bottom'>
<div styleName='item-bottom-tagList'>
{note.tags.length > 0
? TagElementList(note.tags)
: <span style={{ fontStyle: 'italic', opacity: 0.5 }} styleName='item-bottom-tagList-empty'>No tags</span>
}
</div>
<div>
{note.isStarred
? <img styleName='item-star' src='../resources/icon/icon-starred.svg' /> : ''
}
{note.isPinned && !pathname.match(/\/starred|\/trash/)
? <i styleName='item-pin' className='fa fa-thumb-tack' /> : ''
}
{note.type === 'MARKDOWN_NOTE'
? <TodoProcess todoStatus={getTodoStatus(note.content)} />
: ''
: <span styleName='item-bottom-tagList-empty' />
}
</div>
</div>
@@ -123,11 +102,7 @@ NoteItem.propTypes = {
title: PropTypes.string.isrequired,
tags: PropTypes.array,
isStarred: PropTypes.bool.isRequired,
isTrashed: PropTypes.bool.isRequired,
blog: {
blogLink: PropTypes.string,
blogId: PropTypes.number
}
isTrashed: PropTypes.bool.isRequired
}),
handleNoteClick: PropTypes.func.isRequired,
handleNoteContextMenu: PropTypes.func.isRequired,

View File

@@ -90,26 +90,6 @@ $control-height = 30px
font-weight normal
color $ui-inactive-text-color
.item-middle
font-size 13px
padding-left 2px
padding-bottom 2px
.item-middle-time
color $ui-inactive-text-color
display inline-block
.item-middle-app-meta
float right
.item-middle-app-meta-label
opacity 0.4
color $ui-inactive-text-color
padding 0 3px
white-space nowrap
text-overflow ellipsis
overflow hidden
max-width 200px
.item-bottom
position relative
bottom 0px
@@ -117,7 +97,7 @@ $control-height = 30px
font-size 12px
line-height 20px
overflow ellipsis
display block
display flex
.item-bottom-tagList
flex 1
@@ -145,8 +125,10 @@ $control-height = 30px
.item-star
position absolute
right 2px
top 5px
right -6px
bottom 23px
width 16px
height 16px
color alpha($ui-favorite-star-button-color, 60%)
font-size 12px
padding 0
@@ -154,8 +136,10 @@ $control-height = 30px
.item-pin
position absolute
right 25px
top 7px
right 0px
bottom 2px
width 34px
height 34px
color #E54D42
font-size 14px
padding 0
@@ -208,7 +192,7 @@ body[data-theme="dark"]
.item-bottom-tagList-item
transition 0.15s
background-color alpha(white, 10%)
color $ui-dark-text-color
color $ui-dark-text-color
.item-wrapper
border-color alpha($ui-dark-button--active-backgroundColor, 60%)
@@ -282,7 +266,7 @@ body[data-theme="solarized-dark"]
.item-bottom-tagList-item
transition 0.15s
background-color alpha($ui-solarized-dark-noteList-backgroundColor, 10%)
color $ui-solarized-dark-text-color
color $ui-solarized-dark-text-color
.item-wrapper
border-color alpha($ui-solarized-dark-button--active-backgroundColor, 60%)
@@ -320,4 +304,4 @@ body[data-theme="solarized-dark"]
.item-bottom-tagList-empty
color $ui-inactive-text-color
vertical-align middle
vertical-align middle

View File

@@ -14,23 +14,14 @@ import styles from './NoteItemSimple.styl'
* @param {Function} handleNoteContextMenu
* @param {Function} handleDragStart
*/
const NoteItemSimple = ({
isActive,
isAllNotesView,
note,
handleNoteClick,
handleNoteContextMenu,
handleDragStart,
pathname,
storage
}) => (
const NoteItemSimple = ({ isActive, note, handleNoteClick, handleNoteContextMenu, handleDragStart, pathname }) => (
<div styleName={isActive
? 'item-simple--active'
: 'item-simple'
}
key={note.key}
onClick={e => handleNoteClick(e, note.key)}
onContextMenu={e => handleNoteContextMenu(e, note.key)}
? 'item-simple--active'
: 'item-simple'
}
key={`${note.storage}-${note.key}`}
onClick={e => handleNoteClick(e, `${note.storage}-${note.key}`)}
onContextMenu={e => handleNoteContextMenu(e, `${note.storage}-${note.key}`)}
onDragStart={e => handleDragStart(e, note)}
draggable='true'
>
@@ -39,7 +30,7 @@ const NoteItemSimple = ({
? <i styleName='item-simple-title-icon' className='fa fa-fw fa-code' />
: <i styleName='item-simple-title-icon' className='fa fa-fw fa-file-text-o' />
}
{note.isPinned && !pathname.match(/\/starred|\/trash/)
{note.isPinned && !pathname.match(/\/home|\/starred|\/trash/)
? <i styleName='item-pin' className='fa fa-thumb-tack' />
: ''
}
@@ -47,11 +38,6 @@ const NoteItemSimple = ({
? note.title
: <span styleName='item-simple-title-empty'>Empty</span>
}
{isAllNotesView && <div styleName='item-simple-right'>
<span styleName='item-simple-right-storageName'>
{storage.name}
</span>
</div>}
</div>
</div>
)

View File

@@ -124,7 +124,7 @@ body[data-theme="dark"]
.item-simple-bottom-tagList-item
transition 0.15s
background-color alpha(white, 10%)
color $ui-dark-text-color
color $ui-dark-text-color
.item-simple--active
border-color $ui-dark-borderColor
@@ -188,7 +188,7 @@ body[data-theme="solarized-dark"]
.item-simple-bottom-tagList-item
transition 0.15s
background-color alpha(white, 10%)
color $ui-solarized-dark-text-color
color $ui-solarized-dark-text-color
.item-simple--active
border-color $ui-solarized-dark-borderColor
@@ -206,9 +206,4 @@ body[data-theme="solarized-dark"]
// background-color alpha($ui-dark-button--active-backgroundColor, 60%)
color #c0392b
.item-simple-bottom-tagList-item
background-color alpha(#fff, 20%)
.item-simple-right
float right
.item-simple-right-storageName
padding-left 4px
opacity 0.4
background-color alpha(#fff, 20%)

View File

@@ -5,7 +5,6 @@ import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './SideNavFilter.styl'
import i18n from 'browser/lib/i18n'
/**
* @param {boolean} isFolded
@@ -18,7 +17,7 @@ import i18n from 'browser/lib/i18n'
const SideNavFilter = ({
isFolded, isHomeActive, handleAllNotesButtonClick,
isStarredActive, handleStarredButtonClick, isTrashedActive, handleTrashedButtonClick, counterDelNote,
counterTotalNote, counterStarredNote, handleFilterButtonContextMenu
counterTotalNote, counterStarredNote
}) => (
<div styleName={isFolded ? 'menu--folded' : 'menu'}>
@@ -27,12 +26,12 @@ const SideNavFilter = ({
>
<div styleName='iconWrap'>
<img src={isHomeActive
? '../resources/icon/icon-all-active.svg'
: '../resources/icon/icon-all.svg'
}
? '../resources/icon/icon-all-active.svg'
: '../resources/icon/icon-all.svg'
}
/>
</div>
<span styleName='menu-button-label'>{i18n.__('All Notes')}</span>
<span styleName='menu-button-label'>All Notes</span>
<span styleName='counters'>{counterTotalNote}</span>
</button>
@@ -41,12 +40,12 @@ const SideNavFilter = ({
>
<div styleName='iconWrap'>
<img src={isStarredActive
? '../resources/icon/icon-star-active.svg'
: '../resources/icon/icon-star-sidenav.svg'
}
? '../resources/icon/icon-star-active.svg'
: '../resources/icon/icon-star-sidenav.svg'
}
/>
</div>
<span styleName='menu-button-label'>{i18n.__('Starred')}</span>
<span styleName='menu-button-label'>Starred</span>
<span styleName='counters'>{counterStarredNote}</span>
</button>
@@ -55,12 +54,12 @@ const SideNavFilter = ({
>
<div styleName='iconWrap'>
<img src={isTrashedActive
? '../resources/icon/icon-trash-active.svg'
: '../resources/icon/icon-trash-sidenav.svg'
}
? '../resources/icon/icon-trash-active.svg'
: '../resources/icon/icon-trash-sidenav.svg'
}
/>
</div>
<span onContextMenu={handleFilterButtonContextMenu} styleName='menu-button-label'>{i18n.__('Trash')}</span>
<span styleName='menu-button-label'>Trash</span>
<span styleName='counters'>{counterDelNote}</span>
</button>

View File

@@ -1,7 +1,6 @@
.root
position relative
flex 1
min-width 70px
overflow hidden
&:hover
.deleteButton
@@ -22,7 +21,7 @@
height 29px
overflow ellipsis
text-align left
padding-right 23px
padding-right 30px
border none
background-color transparent
transition 0.15s

View File

@@ -6,18 +6,6 @@ import React from 'react'
import styles from './StorageItem.styl'
import CSSModules from 'browser/lib/CSSModules'
import _ from 'lodash'
import { SortableHandle } from 'react-sortable-hoc'
const DraggableIcon = SortableHandle(({ className }) => (
<i className={`fa ${className}`} />
))
const FolderIcon = ({ className, color, isActive }) => {
const iconStyle = isActive ? 'fa-folder-open-o' : 'fa-folder-o'
return (
<i className={`fa ${iconStyle} ${className}`} style={{ color: color }} />
)
}
/**
* @param {boolean} isActive
@@ -33,54 +21,34 @@ const FolderIcon = ({ className, color, isActive }) => {
* @return {React.Component}
*/
const StorageItem = ({
styles,
isActive,
handleButtonClick,
handleContextMenu,
folderName,
folderColor,
isFolded,
noteCount,
handleDrop,
handleDragEnter,
handleDragLeave
}) => {
return (
<button
styleName={isActive ? 'folderList-item--active' : 'folderList-item'}
onClick={handleButtonClick}
onContextMenu={handleContextMenu}
onDrop={handleDrop}
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
>
{!isFolded && (
<DraggableIcon className={styles['folderList-item-reorder']} />
)}
<span
styleName={
isFolded ? 'folderList-item-name--folded' : 'folderList-item-name'
}
>
<FolderIcon
styleName='folderList-item-icon'
color={folderColor}
isActive={isActive}
/>
{isFolded
? _.truncate(folderName, { length: 1, omission: '' })
: folderName}
isActive, handleButtonClick, handleContextMenu, folderName,
folderColor, isFolded, noteCount, handleDrop, handleDragEnter, handleDragLeave
}) => (
<button styleName={isActive
? 'folderList-item--active'
: 'folderList-item'
}
onClick={handleButtonClick}
onContextMenu={handleContextMenu}
onDrop={handleDrop}
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
>
<span styleName={isFolded
? 'folderList-item-name--folded' : 'folderList-item-name'
}>
<text style={{color: folderColor, paddingRight: '10px'}}>{isActive ? <i className='fa fa-folder-open-o' /> : <i className='fa fa-folder-o' />}</text>{isFolded ? _.truncate(folderName, {length: 1, omission: ''}) : folderName}
</span>
{(!isFolded && _.isNumber(noteCount)) &&
<span styleName='folderList-item-noteCount'>{noteCount}</span>
}
{isFolded &&
<span styleName='folderList-item-tooltip'>
{folderName}
</span>
{!isFolded &&
_.isNumber(noteCount) && (
<span styleName='folderList-item-noteCount'>{noteCount}</span>
)}
{isFolded && (
<span styleName='folderList-item-tooltip'>{folderName}</span>
)}
</button>
)
}
}
</button>
)
StorageItem.propTypes = {
isActive: PropTypes.bool.isRequired,

View File

@@ -13,7 +13,6 @@
border none
overflow ellipsis
font-size 14px
align-items: center
&:first-child
margin-top 0
&:hover
@@ -23,7 +22,7 @@
&:active
color $$ui-button-default-color
background-color alpha($ui-button-default--active-backgroundColor, 20%)
.folderList-item--active
@extend .folderList-item
color #1EC38B
@@ -35,7 +34,9 @@
.folderList-item-name
display block
flex 1
padding-right: 10px
padding 0 12px
height 26px
line-height 26px
border-width 0 0 0 2px
border-style solid
border-color transparent
@@ -68,20 +69,9 @@
.folderList-item-name--folded
@extend .folderList-item-name
padding-left 7px
.folderList-item-icon
text
font-size 9px
.folderList-item-icon
padding-right: 10px
.folderList-item-reorder
font-size: 9px
padding: 10px 8px 10px 9px;
color: rgba(147, 147, 149, 0.3)
cursor: ns-resize
&:before
content: "\f142 \f142"
body[data-theme="white"]
.folderList-item
color $ui-inactive-text-color
@@ -137,4 +127,4 @@ body[data-theme="solarized-dark"]
background-color $ui-solarized-dark-button-backgroundColor
&:hover
color $ui-solarized-dark-text-color
background-color $ui-solarized-dark-button-backgroundColor
background-color $ui-solarized-dark-button-backgroundColor

View File

@@ -12,11 +12,10 @@ import CSSModules from 'browser/lib/CSSModules'
* @param {bool} isActive
*/
const TagListItem = ({name, handleClickTagListItem, isActive, count}) => (
const TagListItem = ({name, handleClickTagListItem, isActive}) => (
<button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}>
<span styleName='tagList-item-name'>
{`# ${name}`}
<span styleName='tagList-item-count'> {count}</span>
</span>
</button>
)

View File

@@ -48,9 +48,6 @@
overflow hidden
text-overflow ellipsis
.tagList-item-count
padding 0 3px
body[data-theme="white"]
.tagList-item
color $ui-inactive-text-color
@@ -66,8 +63,6 @@ body[data-theme="white"]
color $ui-text-color
&:hover
background-color alpha($ui-button--active-backgroundColor, 60%)
.tagList-item-count
color $ui-text-color
body[data-theme="dark"]
.tagList-item
@@ -86,6 +81,4 @@ body[data-theme="dark"]
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
&:hover
color $ui-dark-text-color
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
.tagList-item-count
color $ui-dark-button--active-color
background-color alpha($ui-dark-button--active-backgroundColor, 50%)

View File

@@ -58,7 +58,7 @@ body
.katex
font 400 1.2em 'KaTeX_Main'
line-height 1.2em
white-space initial
white-space nowrap
text-indent 0
.katex .mfrac>.vlist>span:nth-child(2)
top 0 !important
@@ -76,7 +76,7 @@ body
justify-content left
li
label.taskListItem
margin-left -1.8em
margin-left -2em
&.checked
text-decoration line-through
opacity 0.5
@@ -178,8 +178,6 @@ ul
margin-bottom 1em
li
display list-item
&.taskListItem
list-style none
p
margin 0
&>li>ul, &>li>ol
@@ -220,7 +218,6 @@ pre
background-color white
&.CodeMirror
height initial
flex-wrap wrap
&>code
flex 1
overflow-x auto
@@ -230,13 +227,6 @@ pre
padding 0
border none
border-radius 0
&>span.filename
width 100%
border-radius: 5px 0px 0px 0px
margin -8px 100% 8px -8px
padding 0px 6px
background-color #777;
color white
&>span.lineNumber
display none
font-size 1em

View File

@@ -1,9 +0,0 @@
// load package for localization
const i18n = new (require('i18n-2'))({
// setup some locales - other locales default to the first locale
locales: ['en', 'sq', 'zh-CN', 'zh-TW', 'da', 'fr', 'de', 'hu', 'ja', 'ko', 'no', 'pl', 'pt', 'es'],
extension: '.json',
devMode: false
})
export default i18n

View File

@@ -1,11 +1,7 @@
const crypto = require('crypto')
const _ = require('lodash')
const uuidv4 = require('uuid/v4')
module.exports = function (uuid) {
if (typeof uuid === typeof true && uuid) {
return uuidv4()
}
const length = 10
module.exports = function (length) {
if (!_.isFinite(length)) length = 10
return crypto.randomBytes(length).toString('hex')
}

View File

@@ -1,23 +0,0 @@
'use strict'
import sanitizeHtml from 'sanitize-html'
module.exports = function sanitizePlugin (md, options) {
options = options || {}
md.core.ruler.after('linkify', 'sanitize_inline', state => {
for (let tokenIdx = 0; tokenIdx < state.tokens.length; tokenIdx++) {
if (state.tokens[tokenIdx].type === 'html_block') {
state.tokens[tokenIdx].content = sanitizeHtml(state.tokens[tokenIdx].content, options)
}
if (state.tokens[tokenIdx].type === 'inline') {
const inlineTokens = state.tokens[tokenIdx].children
for (let childIdx = 0; childIdx < inlineTokens.length; childIdx++) {
if (inlineTokens[childIdx].type === 'html_inline') {
inlineTokens[childIdx].content = sanitizeHtml(inlineTokens[childIdx].content, options)
}
}
}
}
})
}

View File

@@ -1,244 +1,173 @@
import markdownit from 'markdown-it'
import sanitize from './markdown-it-sanitize-html'
import emoji from 'markdown-it-emoji'
import math from '@rokt33r/markdown-it-math'
import _ from 'lodash'
import ConfigManager from 'browser/main/lib/ConfigManager'
import katex from 'katex'
import {lastFindInArray} from './utils'
function createGutter (str, firstLineNumber) {
if (Number.isNaN(firstLineNumber)) firstLineNumber = 1
const lastLineNumber = (str.match(/\n/g) || []).length + firstLineNumber - 1
// FIXME We should not depend on global variable.
const katex = window.katex
const config = ConfigManager.get()
function createGutter (str) {
const lc = (str.match(/\n/g) || []).length
const lines = []
for (let i = firstLineNumber; i <= lastLineNumber; i++) {
for (let i = 1; i <= lc; i++) {
lines.push('<span class="CodeMirror-linenumber">' + i + '</span>')
}
return '<span class="lineNumber CodeMirror-gutters">' + lines.join('') + '</span>'
}
class Markdown {
constructor (options = {}) {
const config = ConfigManager.get()
const defaultOptions = {
typographer: config.preview.smartQuotes,
linkify: true,
html: true,
xhtmlOut: true,
breaks: true,
highlight: function (str, lang) {
const delimiter = ':'
const langInfo = lang.split(delimiter)
const langType = langInfo[0]
const fileName = langInfo[1] || ''
const firstLineNumber = parseInt(langInfo[2], 10)
if (langType === 'flowchart') {
return `<pre class="flowchart">${str}</pre>`
}
if (langType === 'sequence') {
return `<pre class="sequence">${str}</pre>`
}
return '<pre class="code CodeMirror">' +
'<span class="filename">' + fileName + '</span>' +
createGutter(str, firstLineNumber) +
'<code class="' + langType + '">' +
str +
'</code></pre>'
},
sanitize: 'STRICT'
var md = markdownit({
typographer: true,
linkify: true,
html: true,
xhtmlOut: true,
breaks: true,
highlight: function (str, lang) {
if (lang === 'flowchart') {
return `<pre class="flowchart">${str}</pre>`
}
const updatedOptions = Object.assign(defaultOptions, options)
this.md = markdownit(updatedOptions)
if (updatedOptions.sanitize !== 'NONE') {
const allowedTags = ['iframe', 'input', 'b',
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7', 'h8', 'br', 'b', 'i', 'strong', 'em', 'a', 'pre', 'code', 'img', 'tt',
'div', 'ins', 'del', 'sup', 'sub', 'p', 'ol', 'ul', 'table', 'thead', 'tbody', 'tfoot', 'blockquote',
'dl', 'dt', 'dd', 'kbd', 'q', 'samp', 'var', 'hr', 'ruby', 'rt', 'rp', 'li', 'tr', 'td', 'th', 's', 'strike', 'summary', 'details'
]
const allowedAttributes = [
'abbr', 'accept', 'accept-charset',
'accesskey', 'action', 'align', 'alt', 'axis',
'border', 'cellpadding', 'cellspacing', 'char',
'charoff', 'charset', 'checked',
'clear', 'cols', 'colspan', 'color',
'compact', 'coords', 'datetime', 'dir',
'disabled', 'enctype', 'for', 'frame',
'headers', 'height', 'hreflang',
'hspace', 'ismap', 'label', 'lang',
'maxlength', 'media', 'method',
'multiple', 'name', 'nohref', 'noshade',
'nowrap', 'open', 'prompt', 'readonly', 'rel', 'rev',
'rows', 'rowspan', 'rules', 'scope',
'selected', 'shape', 'size', 'span',
'start', 'summary', 'tabindex', 'target',
'title', 'type', 'usemap', 'valign', 'value',
'vspace', 'width', 'itemprop'
]
if (updatedOptions.sanitize === 'ALLOW_STYLES') {
allowedTags.push('style')
allowedAttributes.push('style')
}
// Sanitize use rinput before other plugins
this.md.use(sanitize, {
allowedTags,
allowedAttributes: {
'*': allowedAttributes,
'a': ['href'],
'div': ['itemscope', 'itemtype'],
'blockquote': ['cite'],
'del': ['cite'],
'ins': ['cite'],
'q': ['cite'],
'img': ['src', 'width', 'height'],
'iframe': ['src', 'width', 'height', 'frameborder', 'allowfullscreen'],
'input': ['type', 'id', 'checked']
},
allowedIframeHostnames: ['www.youtube.com']
})
if (lang === 'sequence') {
return `<pre class="sequence">${str}</pre>`
}
this.md.use(emoji, {
shortcuts: {}
})
this.md.use(math, {
inlineOpen: config.preview.latexInlineOpen,
inlineClose: config.preview.latexInlineClose,
blockOpen: config.preview.latexBlockOpen,
blockClose: config.preview.latexBlockClose,
inlineRenderer: function (str) {
let output = ''
try {
output = katex.renderToString(str.trim())
} catch (err) {
output = `<span class="katex-error">${err.message}</span>`
}
return output
},
blockRenderer: function (str) {
let output = ''
try {
output = katex.renderToString(str.trim(), { displayMode: true })
} catch (err) {
output = `<div class="katex-error">${err.message}</div>`
}
return output
}
})
this.md.use(require('markdown-it-imsize'))
this.md.use(require('markdown-it-footnote'))
this.md.use(require('markdown-it-multimd-table'))
this.md.use(require('markdown-it-named-headers'), {
slugify: (header) => {
return encodeURI(header.trim()
.replace(/[\]\[\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~]/g, '')
.replace(/\s+/g, '-'))
.replace(/\-+$/, '')
}
})
this.md.use(require('markdown-it-kbd'))
const deflate = require('markdown-it-plantuml/lib/deflate')
this.md.use(require('markdown-it-plantuml'), '', {
generateSource: function (umlCode) {
const s = unescape(encodeURIComponent(umlCode))
const zippedCode = deflate.encode64(
deflate.zip_deflate(`@startuml\n${s}\n@enduml`, 9)
)
return `http://www.plantuml.com/plantuml/svg/${zippedCode}`
}
})
// Override task item
this.md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
let content, terminate, i, l, token
let nextLine = startLine + 1
const terminatorRules = state.md.block.ruler.getRules('paragraph')
const endLine = state.lineMax
// jump line-by-line until empty one or EOF
for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {
// this would be a code block normally, but after paragraph
// it's considered a lazy continuation regardless of what's there
if (state.sCount[nextLine] - state.blkIndent > 3) { continue }
// quirk for blockquotes, this line should already be checked by that rule
if (state.sCount[nextLine] < 0) { continue }
// Some tags can terminate paragraph without empty line.
terminate = false
for (i = 0, l = terminatorRules.length; i < l; i++) {
if (terminatorRules[i](state, nextLine, endLine, true)) {
terminate = true
break
}
}
if (terminate) { break }
}
content = state.getLines(startLine, nextLine, state.blkIndent, false).trim()
state.line = nextLine
token = state.push('paragraph_open', 'p', 1)
token.map = [startLine, state.line]
if (state.parentType === 'list') {
const match = content.match(/^\[( |x)\] ?(.+)/i)
if (match) {
const liToken = lastFindInArray(state.tokens, token => token.type === 'list_item_open')
if (liToken) {
if (!liToken.attrs) {
liToken.attrs = []
}
liToken.attrs.push(['class', 'taskListItem'])
}
content = `<label class='taskListItem${match[1] !== ' ' ? ' checked' : ''}' for='checkbox-${startLine + 1}'><input type='checkbox'${match[1] !== ' ' ? ' checked' : ''} id='checkbox-${startLine + 1}'/> ${content.substring(4, content.length)}</label>`
}
}
token = state.push('inline', '', 0)
token.content = content
token.map = [startLine, state.line]
token.children = []
token = state.push('paragraph_close', 'p', -1)
return true
})
// Add line number attribute for scrolling
const originalRender = this.md.renderer.render
this.md.renderer.render = (tokens, options, env) => {
tokens.forEach((token) => {
switch (token.type) {
case 'heading_open':
case 'paragraph_open':
case 'blockquote_open':
case 'table_open':
token.attrPush(['data-line', token.map[0]])
}
})
const result = originalRender.call(this.md.renderer, tokens, options, env)
return result
return '<pre class="code">' +
createGutter(str) +
'<code class="' + lang + '">' +
str +
'</code></pre>'
}
})
md.use(emoji, {
shortcuts: {}
})
md.use(math, {
inlineOpen: config.preview.latexInlineOpen,
inlineClose: config.preview.latexInlineClose,
blockOpen: config.preview.latexBlockOpen,
blockClose: config.preview.latexBlockClose,
inlineRenderer: function (str) {
let output = ''
try {
output = katex.renderToString(str.trim())
} catch (err) {
output = `<span class="katex-error">${err.message}</span>`
}
// FIXME We should not depend on global variable.
window.md = this.md
return output
},
blockRenderer: function (str) {
let output = ''
try {
output = katex.renderToString(str.trim(), { displayMode: true })
} catch (err) {
output = `<div class="katex-error">${err.message}</div>`
}
return output
}
})
md.use(require('markdown-it-imsize'))
md.use(require('markdown-it-footnote'))
md.use(require('markdown-it-multimd-table'))
md.use(require('markdown-it-named-headers'), {
slugify: (header) => {
return encodeURI(header.trim()
.replace(/[\]\[\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\_\{\|\}\~]/g, '')
.replace(/\s+/g, '-'))
.replace(/\-+$/, '')
}
})
md.use(require('markdown-it-kbd'))
const deflate = require('markdown-it-plantuml/lib/deflate')
md.use(require('markdown-it-plantuml'), '', {
generateSource: function (umlCode) {
const s = unescape(encodeURIComponent(umlCode))
const zippedCode = deflate.encode64(
deflate.zip_deflate(`@startuml\n${s}\n@enduml`, 9)
)
return `http://www.plantuml.com/plantuml/svg/${zippedCode}`
}
})
// Override task item
md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
let content, terminate, i, l, token
let nextLine = startLine + 1
const terminatorRules = state.md.block.ruler.getRules('paragraph')
const endLine = state.lineMax
// jump line-by-line until empty one or EOF
for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {
// this would be a code block normally, but after paragraph
// it's considered a lazy continuation regardless of what's there
if (state.sCount[nextLine] - state.blkIndent > 3) { continue }
// quirk for blockquotes, this line should already be checked by that rule
if (state.sCount[nextLine] < 0) { continue }
// Some tags can terminate paragraph without empty line.
terminate = false
for (i = 0, l = terminatorRules.length; i < l; i++) {
if (terminatorRules[i](state, nextLine, endLine, true)) {
terminate = true
break
}
}
if (terminate) { break }
}
render (content) {
if (!_.isString(content)) content = ''
return this.md.render(content)
content = state.getLines(startLine, nextLine, state.blkIndent, false).trim()
state.line = nextLine
token = state.push('paragraph_open', 'p', 1)
token.map = [startLine, state.line]
if (state.parentType === 'list') {
const match = content.match(/^\[( |x)\] ?(.+)/i)
if (match) {
content = `<label class='taskListItem${match[1] !== ' ' ? ' checked' : ''}' for='checkbox-${startLine + 1}'><input type='checkbox'${match[1] !== ' ' ? ' checked' : ''} id='checkbox-${startLine + 1}'/> ${content.substring(4, content.length)}</label>`
}
}
normalizeLinkText (linkText) {
return this.md.normalizeLinkText(linkText)
}
token = state.push('inline', '', 0)
token.content = content
token.map = [startLine, state.line]
token.children = []
token = state.push('paragraph_close', 'p', -1)
return true
})
// Add line number attribute for scrolling
const originalRender = md.renderer.render
md.renderer.render = function render (tokens, options, env) {
tokens.forEach((token) => {
switch (token.type) {
case 'heading_open':
case 'paragraph_open':
case 'blockquote_open':
case 'table_open':
token.attrPush(['data-line', token.map[0]])
}
})
const result = originalRender.call(md.renderer, tokens, options, env)
return result
}
// FIXME We should not depend on global variable.
window.md = md
function normalizeLinkText (linkText) {
return md.normalizeLinkText(linkText)
}
export default Markdown
const markdown = {
render: function markdown (content) {
if (!_.isString(content)) content = ''
const renderedContent = md.render(content)
return renderedContent
},
normalizeLinkText
}
export default markdown

View File

@@ -1,11 +0,0 @@
export function lastFindInArray (array, callback) {
for (let i = array.length - 1; i >= 0; --i) {
if (callback(array[i], i, array)) {
return array[i]
}
}
}
export default {
lastFindInArray
}

View File

@@ -3,7 +3,6 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './FolderSelect.styl'
import _ from 'lodash'
import i18n from 'browser/lib/i18n'
class FolderSelect extends React.Component {
constructor (props) {
@@ -250,7 +249,7 @@ class FolderSelect extends React.Component {
<input styleName='search-input'
ref='search'
value={this.state.search}
placeholder={i18n.__('Folder...')}
placeholder='Folder...'
onChange={(e) => this.handleSearchInputChange(e)}
onBlur={(e) => this.handleSearchInputBlur(e)}
onKeyDown={(e) => this.handleSearchInputKeyDown(e)}

View File

@@ -2,14 +2,13 @@ import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './FullscreenButton.styl'
import i18n from 'browser/lib/i18n'
const FullscreenButton = ({
onClick
}) => (
<button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')} onMouseDown={(e) => onClick(e)}>
<button styleName='control-fullScreenButton' title='Fullscreen' onMouseDown={(e) => onClick(e)}>
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
<span styleName='tooltip'>{i18n.__('Fullscreen')}</span>
<span styleName='tooltip'>Fullscreen</span>
</button>
)

View File

@@ -2,7 +2,6 @@ import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './InfoButton.styl'
import i18n from 'browser/lib/i18n'
const InfoButton = ({
onClick
@@ -11,7 +10,7 @@ const InfoButton = ({
onClick={(e) => onClick(e)}
>
<img className='infoButton' src='../resources/icon/icon-info.svg' />
<span styleName='tooltip'>{i18n.__('Info')}</span>
<span styleName='tooltip'>Info</span>
</button>
)

View File

@@ -3,7 +3,6 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './InfoPanel.styl'
import copy from 'copy-to-clipboard'
import i18n from 'browser/lib/i18n'
class InfoPanel extends React.Component {
copyNoteLink () {
@@ -20,7 +19,7 @@ class InfoPanel extends React.Component {
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
<div>
<p styleName='modification-date'>{updatedAt}</p>
<p styleName='modification-date-desc'>{i18n.__('MODIFICATION DATE')}</p>
<p styleName='modification-date-desc'>MODIFICATION DATE</p>
</div>
<hr />
@@ -30,11 +29,11 @@ class InfoPanel extends React.Component {
: <div styleName='count-wrap'>
<div styleName='count-number'>
<p styleName='infoPanel-defaul-count'>{wordCount}</p>
<p styleName='infoPanel-sub-count'>{i18n.__('Words')}</p>
<p styleName='infoPanel-sub-count'>Words</p>
</div>
<div styleName='count-number'>
<p styleName='infoPanel-defaul-count'>{letterCount}</p>
<p styleName='infoPanel-sub-count'>{i18n.__('Letters')}</p>
<p styleName='infoPanel-sub-count'>Letters</p>
</div>
</div>
}
@@ -46,17 +45,17 @@ class InfoPanel extends React.Component {
<div>
<p styleName='infoPanel-default'>{storageName}</p>
<p styleName='infoPanel-sub'>{i18n.__('STORAGE')}</p>
<p styleName='infoPanel-sub'>STORAGE</p>
</div>
<div>
<p styleName='infoPanel-default'>{folderName}</p>
<p styleName='infoPanel-sub'>{i18n.__('FOLDER')}</p>
<p styleName='infoPanel-sub'>FOLDER</p>
</div>
<div>
<p styleName='infoPanel-default'>{createdAt}</p>
<p styleName='infoPanel-sub'>{i18n.__('CREATION DATE')}</p>
<p styleName='infoPanel-sub'>CREATION DATE</p>
</div>
<div>
@@ -64,7 +63,7 @@ class InfoPanel extends React.Component {
<button onClick={() => this.copyNoteLink()} styleName='infoPanel-copyButton'>
<i className='fa fa-clipboard' />
</button>
<p styleName='infoPanel-sub'>{i18n.__('NOTE LINK')}</p>
<p styleName='infoPanel-sub'>NOTE LINK</p>
</div>
<hr />
@@ -72,22 +71,22 @@ class InfoPanel extends React.Component {
<div id='export-wrap'>
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
<i className='fa fa-file-code-o' />
<p>{i18n.__('.md')}</p>
<p>.md</p>
</button>
<button styleName='export--enable' onClick={(e) => exportAsTxt(e)}>
<i className='fa fa-file-text-o' />
<p>{i18n.__('.txt')}</p>
<p>.txt</p>
</button>
<button styleName='export--enable' onClick={(e) => exportAsHtml(e)}>
<i className='fa fa-html5' />
<p>{i18n.__('.html')}</p>
<p>.html</p>
</button>
<button styleName='export--enable' onClick={(e) => print(e)}>
<i className='fa fa-print' />
<p>{i18n.__('Print')}</p>
<p>Print</p>
</button>
</div>
</div>

View File

@@ -2,7 +2,6 @@ import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './InfoPanel.styl'
import i18n from 'browser/lib/i18n'
const InfoPanelTrashed = ({
storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml
@@ -10,24 +9,24 @@ const InfoPanelTrashed = ({
<div className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}>
<div>
<p styleName='modification-date'>{updatedAt}</p>
<p styleName='modification-date-desc'>{i18n.__('MODIFICATION DATE')}</p>
<p styleName='modification-date-desc'>MODIFICATION DATE</p>
</div>
<hr />
<div>
<p styleName='infoPanel-default'>{storageName}</p>
<p styleName='infoPanel-sub'>{i18n.__('STORAGE')}</p>
<p styleName='infoPanel-sub'>STORAGE</p>
</div>
<div>
<p styleName='infoPanel-default'><text styleName='infoPanel-trash'>Trash</text>{folderName}</p>
<p styleName='infoPanel-sub'>{i18n.__('FOLDER')}</p>
<p styleName='infoPanel-sub'>FOLDER</p>
</div>
<div>
<p styleName='infoPanel-default'>{createdAt}</p>
<p styleName='infoPanel-sub'>{i18n.__('CREATION DATE')}</p>
<p styleName='infoPanel-sub'>CREATION DATE</p>
</div>
<div id='export-wrap'>

22
browser/main/Detail/MarkdownNoteDetail.js Executable file → Normal file
View File

@@ -19,7 +19,6 @@ import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import ConfigManager from 'browser/main/lib/ConfigManager'
import TrashButton from './TrashButton'
import FullscreenButton from './FullscreenButton'
import RestoreButton from './RestoreButton'
import PermanentDeleteButton from './PermanentDeleteButton'
import InfoButton from './InfoButton'
import ToggleModeButton from './ToggleModeButton'
@@ -69,10 +68,13 @@ class MarkdownNoteDetail extends React.Component {
}
componentWillUnmount () {
ee.off('topbar:togglelockbutton', this.toggleLockButton)
if (this.saveQueue != null) this.saveNow()
}
componentDidUnmount () {
ee.off('topbar:togglelockbutton', this.toggleLockButton)
}
handleUpdateTag () {
const { note } = this.state
if (this.refs.tags) note.tags = this.refs.tags.value
@@ -139,7 +141,7 @@ class MarkdownNoteDetail extends React.Component {
hashHistory.replace({
pathname: location.pathname,
query: {
key: newNote.key
key: newNote.storage + '-' + newNote.key
}
})
this.setState({
@@ -196,9 +198,8 @@ class MarkdownNoteDetail extends React.Component {
noteKey: data.noteKey
})
}
ee.once('list:next', dispatchHandler)
ee.once('list:moved', dispatchHandler)
})
.then(() => ee.emit('list:next'))
}
} else {
if (confirmDeletion()) {
@@ -322,7 +323,10 @@ class MarkdownNoteDetail extends React.Component {
const trashTopBar = <div styleName='info'>
<div styleName='info-left'>
<RestoreButton onClick={(e) => this.handleUndoButtonClick(e)} />
<i styleName='undo-button'
className='fa fa-undo fa-fw'
onClick={(e) => this.handleUndoButtonClick(e)}
/>
</div>
<div styleName='info-right'>
<PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} />
@@ -357,10 +361,12 @@ class MarkdownNoteDetail extends React.Component {
value={this.state.note.tags}
onChange={this.handleUpdateTag.bind(this)}
/>
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
<TodoListPercentage percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
</div>
<div styleName='info-right'>
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
<StarButton
onClick={(e) => this.handleStarButtonClick(e)}
isActive={note.isStarred}
@@ -393,7 +399,7 @@ class MarkdownNoteDetail extends React.Component {
<InfoPanel
storageName={currentOption.storage.name}
folderName={currentOption.folder.name}
noteLink={`[${note.title}](:note:${location.query.key})`}
noteLink={`[${note.title}](${location.query.key})`}
updatedAt={formatDate(note.updatedAt)}
createdAt={formatDate(note.createdAt)}
exportAsMd={this.exportAsMd}

View File

@@ -7,7 +7,6 @@
background-color $ui-noteDetail-backgroundColor
box-shadow none
padding 20px 40px
overflow hidden
.lock-button
padding-bottom 3px
@@ -45,7 +44,7 @@
margin 0 30px
.body-noteEditor
absolute top bottom left right
body[data-theme="white"]
.root
box-shadow $note-detail-box-shadow

View File

@@ -2,7 +2,6 @@ import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './TrashButton.styl'
import i18n from 'browser/lib/i18n'
const PermanentDeleteButton = ({
onClick
@@ -11,7 +10,7 @@ const PermanentDeleteButton = ({
onClick={(e) => onClick(e)}
>
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
<span styleName='tooltip'>{i18n.__('Permanent Delete')}</span>
<span styleName='tooltip'>Permanent Delete</span>
</button>
)

View File

@@ -1,22 +0,0 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './RestoreButton.styl'
import i18n from 'browser/lib/i18n'
const RestoreButton = ({
onClick
}) => (
<button styleName='control-restoreButton'
onClick={onClick}
>
<i className='fa fa-undo fa-fw' styleName='iconRestore' />
<span styleName='tooltip'>{i18n.__('Restore')}</span>
</button>
)
RestoreButton.propTypes = {
onClick: PropTypes.func.isRequired
}
export default CSSModules(RestoreButton, styles)

View File

@@ -1,22 +0,0 @@
.control-restoreButton
top 115px
topBarButtonRight()
&:hover .tooltip
opacity 1
.tooltip
tooltip()
position absolute
pointer-events none
top 50px
left 25px
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s
body[data-theme="dark"]
.control-restoreButton
topBarButtonDark()

View File

@@ -8,7 +8,7 @@ import StarButton from './StarButton'
import TagSelect from './TagSelect'
import FolderSelect from './FolderSelect'
import dataApi from 'browser/main/lib/dataApi'
import {hashHistory} from 'react-router'
import { hashHistory } from 'react-router'
import ee from 'browser/main/lib/eventEmitter'
import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir'
@@ -17,16 +17,14 @@ import StatusBar from '../StatusBar'
import context from 'browser/lib/context'
import ConfigManager from 'browser/main/lib/ConfigManager'
import _ from 'lodash'
import {findNoteTitle} from 'browser/lib/findNoteTitle'
import { findNoteTitle } from 'browser/lib/findNoteTitle'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import TrashButton from './TrashButton'
import RestoreButton from './RestoreButton'
import PermanentDeleteButton from './PermanentDeleteButton'
import InfoButton from './InfoButton'
import InfoPanel from './InfoPanel'
import InfoPanelTrashed from './InfoPanelTrashed'
import { formatDate } from 'browser/lib/date-formatter'
import i18n from 'browser/lib/i18n'
function pass (name) {
switch (name) {
@@ -54,30 +52,12 @@ class SnippetNoteDetail extends React.Component {
this.state = {
isMovingNote: false,
snippetIndex: 0,
showArrows: false,
enableLeftArrow: false,
enableRightArrow: false,
note: Object.assign({
description: ''
}, props.note, {
snippets: props.note.snippets.map((snippet) => Object.assign({}, snippet))
})
}
this.scrollToNextTabThreshold = 0.7
}
componentDidMount () {
const visibleTabs = this.visibleTabs
const allTabs = this.allTabs
if (visibleTabs.offsetWidth < allTabs.scrollWidth) {
this.setState({
showArrows: visibleTabs.offsetWidth < allTabs.scrollWidth,
enableRightArrow: allTabs.offsetLeft !== visibleTabs.offsetWidth - allTabs.scrollWidth,
enableLeftArrow: allTabs.offsetLeft !== 0
})
}
}
componentWillReceiveProps (nextProps) {
@@ -97,7 +77,6 @@ class SnippetNoteDetail extends React.Component {
this.refs['code-' + index].reload()
})
if (this.refs.tags) this.refs.tags.reset()
this.setState(this.getArrowsState())
})
}
}
@@ -167,7 +146,7 @@ class SnippetNoteDetail extends React.Component {
hashHistory.replace({
pathname: location.pathname,
query: {
key: newNote.key
key: newNote.storage + '-' + newNote.key
}
})
this.setState({
@@ -212,9 +191,8 @@ class SnippetNoteDetail extends React.Component {
noteKey: data.noteKey
})
}
ee.once('list:next', dispatchHandler)
ee.once('list:moved', dispatchHandler)
})
.then(() => ee.emit('list:next'))
}
} else {
if (confirmDeletion()) {
@@ -248,51 +226,6 @@ class SnippetNoteDetail extends React.Component {
ee.emit('editor:fullscreen')
}
handleTabMoveLeftButtonClick (e) {
{
const left = this.visibleTabs.scrollLeft
const tabs = this.allTabs.querySelectorAll('div')
const lastVisibleTab = Array.from(tabs).find((tab) => {
return tab.offsetLeft + tab.offsetWidth >= left
})
if (lastVisibleTab) {
const visiblePart = lastVisibleTab.offsetWidth + lastVisibleTab.offsetLeft - left
const isFullyVisible = visiblePart > lastVisibleTab.offsetWidth * this.scrollToNextTabThreshold
const scrollToTab = (isFullyVisible && lastVisibleTab.previousSibling)
? lastVisibleTab.previousSibling
: lastVisibleTab
// FIXME use `scrollIntoView()` instead of custom method after update to Electron2.0 (with Chrome 61 its possible animate the scroll)
this.moveToTab(scrollToTab)
// scrollToTab.scrollIntoView({behavior: 'smooth', inline: 'start', block: 'start'})
}
}
}
handleTabMoveRightButtonClick (e) {
const left = this.visibleTabs.scrollLeft
const width = this.visibleTabs.offsetWidth
const tabs = this.allTabs.querySelectorAll('div')
const lastVisibleTab = Array.from(tabs).find((tab) => {
return tab.offsetLeft + tab.offsetWidth >= width + left
})
if (lastVisibleTab) {
const visiblePart = width + left - lastVisibleTab.offsetLeft
const isFullyVisible = visiblePart > lastVisibleTab.offsetWidth * this.scrollToNextTabThreshold
const scrollToTab = (isFullyVisible && lastVisibleTab.nextSibling)
? lastVisibleTab.nextSibling
: lastVisibleTab
// FIXME use `scrollIntoView()` instead of custom method after update to Electron2.0 (with Chrome 61 its possible animate the scroll)
this.moveToTab(scrollToTab)
// scrollToTab.scrollIntoView({behavior: 'smooth', inline: 'end', block: 'end'})
}
}
handleTabPlusButtonClick (e) {
this.addSnippet()
}
@@ -329,9 +262,9 @@ class SnippetNoteDetail extends React.Component {
if (this.state.note.snippets[index].content.trim().length > 0) {
const dialogIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: i18n.__('Delete a snippet'),
detail: i18n.__('This work cannot be undone.'),
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
message: 'Delete a snippet',
detail: 'This work cannot be undone.',
buttons: ['Confirm', 'Cancel']
})
if (dialogIndex === 0) {
this.deleteSnippetByIndex(index)
@@ -352,21 +285,6 @@ class SnippetNoteDetail extends React.Component {
this.setState({ note, snippetIndex }, () => {
this.save()
this.refs['code-' + this.state.snippetIndex].reload()
if (this.visibleTabs.offsetWidth > this.allTabs.scrollWidth) {
console.log('no need for arrows')
this.moveTabBarBy(0)
} else {
const lastTab = this.allTabs.lastChild
if (lastTab.offsetLeft + lastTab.offsetWidth < this.visibleTabs.offsetWidth) {
console.log('need to scroll')
const width = this.visibleTabs.offsetWidth
const newLeft = lastTab.offsetLeft + lastTab.offsetWidth - width
this.moveTabBarBy(newLeft > 0 ? -newLeft : 0)
} else {
this.setState(this.getArrowsState())
}
}
})
}
@@ -423,7 +341,6 @@ class SnippetNoteDetail extends React.Component {
handleKeyDown (e) {
switch (e.keyCode) {
// tab key
case 9:
if (e.ctrlKey && !e.shiftKey) {
e.preventDefault()
@@ -436,7 +353,6 @@ class SnippetNoteDetail extends React.Component {
this.focusEditor()
}
break
// L key
case 76:
{
const isSuper = global.process.platform === 'darwin'
@@ -448,7 +364,6 @@ class SnippetNoteDetail extends React.Component {
}
}
break
// T key
case 84:
{
const isSuper = global.process.platform === 'darwin'
@@ -540,51 +455,6 @@ class SnippetNoteDetail extends React.Component {
this.refs.description.focus()
}
moveToTab (tab) {
const easeOutCubic = t => (--t) * t * t + 1
const startScrollPosition = this.visibleTabs.scrollLeft
const animationTiming = 300
const scrollMoreCoeff = 1.4 // introduce coefficient, because we want to scroll a bit further to see next tab
let scrollBy = (tab.offsetLeft - startScrollPosition)
if (tab.offsetLeft > startScrollPosition) {
// if tab is on the right side and we want to show the whole tab in visible area,
// we need to include width of the tab and visible area in the formula
// ___________________________________________
// |____|_______|________|________|_show_this_|
// ↑_____________________↑
// visible area
scrollBy += (tab.offsetWidth - this.visibleTabs.offsetWidth)
}
let startTime = null
const scrollAnimation = time => {
startTime = startTime || time
const elapsed = (time - startTime) / animationTiming
this.visibleTabs.scrollLeft = startScrollPosition + easeOutCubic(elapsed) * scrollBy * scrollMoreCoeff
if (elapsed < 1) {
window.requestAnimationFrame(scrollAnimation)
} else {
this.setState(this.getArrowsState())
}
}
window.requestAnimationFrame(scrollAnimation)
}
getArrowsState () {
const allTabs = this.allTabs
const visibleTabs = this.visibleTabs
const showArrows = visibleTabs.offsetWidth < allTabs.scrollWidth
const enableRightArrow = visibleTabs.scrollLeft !== allTabs.scrollWidth - visibleTabs.offsetWidth
const enableLeftArrow = visibleTabs.scrollLeft !== 0
return {showArrows, enableRightArrow, enableLeftArrow}
}
addSnippet () {
const { note } = this.state
@@ -595,16 +465,10 @@ class SnippetNoteDetail extends React.Component {
}])
const snippetIndex = note.snippets.length - 1
this.setState(Object.assign({
this.setState({
note,
snippetIndex
}, this.getArrowsState()), () => {
if (this.state.showArrows) {
const tabs = this.allTabs.querySelectorAll('div')
if (tabs) {
this.moveToTab(tabs[snippetIndex])
}
}
}, () => {
this.refs['tab-' + snippetIndex].startRenaming()
})
}
@@ -638,9 +502,9 @@ class SnippetNoteDetail extends React.Component {
showWarning () {
dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: i18n.__('Sorry!'),
detail: i18n.__('md/text import is available only a markdown note.'),
buttons: [i18n.__('OK')]
message: 'Sorry!',
detail: 'md/text import is available only a markdown note.',
buttons: ['OK']
})
}
@@ -703,7 +567,6 @@ class SnippetNoteDetail extends React.Component {
displayLineNumbers={config.editor.displayLineNumbers}
keyMap={config.editor.keyMap}
scrollPastEnd={config.editor.scrollPastEnd}
fetchUrlTitle={config.editor.fetchUrlTitle}
onChange={(e) => this.handleCodeChange(index)(e)}
ref={'code-' + index}
/>
@@ -724,7 +587,10 @@ class SnippetNoteDetail extends React.Component {
const trashTopBar = <div styleName='info'>
<div styleName='info-left'>
<RestoreButton onClick={(e) => this.handleUndoButtonClick(e)} />
<i styleName='undo-button'
className='fa fa-undo fa-fw'
onClick={(e) => this.handleUndoButtonClick(e)}
/>
</div>
<div styleName='info-right'>
<PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} />
@@ -766,10 +632,10 @@ class SnippetNoteDetail extends React.Component {
isActive={note.isStarred}
/>
<button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')}
<button styleName='control-fullScreenButton' title='Fullscreen'
onMouseDown={(e) => this.handleFullScreenButton(e)}>
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
<span styleName='tooltip'>{i18n.__('Fullscreen')}</span>
<span styleName='tooltip'>Fullscreen</span>
</button>
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
@@ -781,7 +647,7 @@ class SnippetNoteDetail extends React.Component {
<InfoPanel
storageName={currentOption.storage.name}
folderName={currentOption.folder.name}
noteLink={`[${note.title}](:note:${location.query.key})`}
noteLink={`[${note.title}](${location.query.key})`}
updatedAt={formatDate(note.updatedAt)}
createdAt={formatDate(note.createdAt)}
exportAsMd={this.showWarning}
@@ -807,32 +673,16 @@ class SnippetNoteDetail extends React.Component {
fontSize: parseInt(config.preview.fontSize, 10)
}}
ref='description'
placeholder={i18n.__('Description...')}
placeholder='Description...'
value={this.state.note.description}
onChange={(e) => this.handleChange(e)}
/>
</div>
<div styleName='tabList'>
<button styleName='tabButton'
hidden={!this.state.showArrows}
disabled={!this.state.enableLeftArrow}
onClick={(e) => this.handleTabMoveLeftButtonClick(e)}
>
<i className='fa fa-chevron-left' />
</button>
<div styleName='list' onScroll={(e) => { this.setState(this.getArrowsState()) }} ref={(tabs) => { this.visibleTabs = tabs }}>
<div styleName='allTabs' ref={(tabs) => { this.allTabs = tabs }}>
{tabList}
</div>
<div styleName='list'>
{tabList}
</div>
<button styleName='tabButton'
hidden={!this.state.showArrows}
disabled={!this.state.enableRightArrow}
onClick={(e) => this.handleTabMoveRightButtonClick(e)}
>
<i className='fa fa-chevron-right' />
</button>
<button styleName='tabButton'
<button styleName='plusButton'
onClick={(e) => this.handleTabPlusButtonClick(e)}
>
<i className='fa fa-plus' />
@@ -846,7 +696,7 @@ class SnippetNoteDetail extends React.Component {
onClick={(e) => this.handleModeButtonClick(e, this.state.snippetIndex)}
>
{this.state.note.snippets[this.state.snippetIndex].mode == null
? i18n.__('Select Syntax...')
? 'Select Syntax...'
: this.state.note.snippets[this.state.snippetIndex].mode
}&nbsp;
<i className='fa fa-caret-down' />

View File

@@ -35,26 +35,13 @@
height 30px
display flex
background-color $ui-noteDetail-backgroundColor
overflow hidden
.tabList .list
flex 1
display flex
overflow hidden
overflow-x scroll
position relative
&::-webkit-scrollbar {
display: none;
}
.allTabs
display flex
position relative
overflow visible
left 0
transition left 0.1s
.tabList .tabButton
.tabList .plusButton
navWhiteButtonColor()
width 30px

View File

@@ -3,7 +3,6 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './StarButton.styl'
import _ from 'lodash'
import i18n from 'browser/lib/i18n'
class StarButton extends React.Component {
constructor (props) {
@@ -54,7 +53,7 @@ class StarButton extends React.Component {
: '../resources/icon/icon-star.svg'
}
/>
<span styleName='tooltip'>{i18n.__('Star')}</span>
<span styleName='tooltip'>Star</span>
</button>
)
}

View File

@@ -4,7 +4,6 @@ import CSSModules from 'browser/lib/CSSModules'
import styles from './TagSelect.styl'
import _ from 'lodash'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import i18n from 'browser/lib/i18n'
class TagSelect extends React.Component {
constructor (props) {
@@ -138,7 +137,7 @@ class TagSelect extends React.Component {
<input styleName='newTag'
ref='newTag'
value={this.state.newTag}
placeholder={i18n.__('Add tag...')}
placeholder='Add tag...'
onChange={(e) => this.handleNewTagInputChange(e)}
onKeyDown={(e) => this.handleNewTagInputKeyDown(e)}
onBlur={(e) => this.handleNewTagBlur(e)}

View File

@@ -2,7 +2,6 @@ import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './ToggleModeButton.styl'
import i18n from 'browser/lib/i18n'
const ToggleModeButton = ({
onClick, editorType
@@ -14,7 +13,7 @@ const ToggleModeButton = ({
<div styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : 'non-active'} onClick={() => onClick('EDITOR_PREVIEW')}>
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '' : '../resources/icon/icon-mode-split-on-active.svg'} />
</div>
<span styleName='tooltip'>{i18n.__('Toggle Mode')}</span>
<span styleName='tooltip'>Toggle Mode</span>
</div>
)

View File

@@ -5,8 +5,8 @@
width 52px
display flex
align-items center
position: relative
top 2px
position absolute
right 165px
.active
background-color #1EC38B
width 33px
@@ -55,4 +55,4 @@ body[data-theme="solarized-dark"]
background-color #002B36
.active
background-color #1EC38B
box-shadow 2px 0px 7px #222222
box-shadow 2px 0px 7px #222222

View File

@@ -2,7 +2,6 @@ import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './TrashButton.styl'
import i18n from 'browser/lib/i18n'
const TrashButton = ({
onClick
@@ -11,7 +10,7 @@ const TrashButton = ({
onClick={(e) => onClick(e)}
>
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
<span styleName='tooltip'>{i18n.__('Trash')}</span>
<span styleName='tooltip'>Trash</span>
</button>
)

View File

@@ -7,7 +7,6 @@ import MarkdownNoteDetail from './MarkdownNoteDetail'
import SnippetNoteDetail from './SnippetNoteDetail'
import ee from 'browser/main/lib/eventEmitter'
import StatusBar from '../StatusBar'
import i18n from 'browser/lib/i18n'
const OSX = global.process.platform === 'darwin'
@@ -41,9 +40,9 @@ class Detail extends React.Component {
const alertConfig = {
type: 'warning',
message: i18n.__('Confirm note deletion'),
detail: i18n.__('This will permanently remove this note.'),
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
message: 'Confirm note deletion',
detail: 'This will permanently remove this note.',
buttons: ['Confirm', 'Cancel']
}
const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), alertConfig)
@@ -57,8 +56,11 @@ class Detail extends React.Component {
const { location, data, config } = this.props
let note = null
if (location.query.key != null) {
const noteKey = location.query.key
note = data.noteMap.get(noteKey)
const splitted = location.query.key.split('-')
const storageKey = splitted.shift()
const noteKey = splitted.shift()
note = data.noteMap.get(storageKey + '-' + noteKey)
}
if (note == null) {
@@ -68,7 +70,7 @@ class Detail extends React.Component {
tabIndex='0'
>
<div styleName='empty'>
<div styleName='empty-message'>{OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')} + N<br />{i18n.__('to create a new note')}</div>
<div styleName='empty-message'>{OSX ? 'Command(⌘)' : 'Ctrl(^)'} + N<br />to create a new note</div>
</div>
<StatusBar
{..._.pick(this.props, ['config', 'location', 'dispatch'])}

View File

@@ -14,7 +14,6 @@ import mobileAnalytics from 'browser/main/lib/AwsMobileAnalyticsConfig'
import eventEmitter from 'browser/main/lib/eventEmitter'
import { hashHistory } from 'react-router'
import store from 'browser/main/store'
import i18n from 'browser/lib/i18n'
const path = require('path')
const electron = require('electron')
const { remote } = electron
@@ -149,37 +148,6 @@ class Main extends React.Component {
} else {
document.body.setAttribute('data-theme', 'default')
}
if (config.ui.language === 'sq') {
i18n.setLocale('sq')
} else if (config.ui.language === 'zh-CN') {
i18n.setLocale('zh-CN')
} else if (config.ui.language === 'zh-TW') {
i18n.setLocale('zh-TW')
} else if (config.ui.language === 'da') {
i18n.setLocale('da')
} else if (config.ui.language === 'fr') {
i18n.setLocale('fr')
} else if (config.ui.language === 'de') {
i18n.setLocale('de')
} else if (config.ui.language === 'hu') {
i18n.setLocale('hu')
} else if (config.ui.language === 'ja') {
i18n.setLocale('ja')
} else if (config.ui.language === 'ko') {
i18n.setLocale('ko')
} else if (config.ui.language === 'no') {
i18n.setLocale('no')
} else if (config.ui.language === 'pl') {
i18n.setLocale('pl')
} else if (config.ui.language === 'pt') {
i18n.setLocale('pt')
} else if (config.ui.language === 'ru') {
i18n.setLocale('ru')
} else if (config.ui.language === 'es') {
i18n.setLocale('es')
} else {
i18n.setLocale('en')
}
// Reload all data
dataApi.init()

View File

@@ -6,7 +6,6 @@ import _ from 'lodash'
import modal from 'browser/main/lib/modal'
import NewNoteModal from 'browser/main/modals/NewNoteModal'
import eventEmitter from 'browser/main/lib/eventEmitter'
import i18n from 'browser/lib/i18n'
const { remote } = require('electron')
const { dialog } = remote
@@ -57,9 +56,9 @@ class NewNoteButton extends React.Component {
}
}
if (storage == null) this.showMessageBox(i18n.__('No storage to create a note'))
if (storage == null) this.showMessageBox('No storage to create a note')
const folder = _.find(storage.folders, {key: params.folderKey}) || storage.folders[0]
if (folder == null) this.showMessageBox(i18n.__('No folder to create a note'))
if (folder == null) this.showMessageBox('No folder to create a note')
return {
storage,
@@ -87,7 +86,7 @@ class NewNoteButton extends React.Component {
onClick={(e) => this.handleNewNoteButtonClick(e)}>
<img styleName='iconTag' src='../resources/icon/icon-newnote.svg' />
<span styleName='control-newNoteButton-tooltip'>
{i18n.__('Make a note')} {OSX ? '⌘' : i18n.__('Ctrl')} + N
Make a note {OSX ? '⌘' : 'Ctrl'} + N
</span>
</button>
</div>

View File

@@ -1,4 +1,3 @@
/* global electron */
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
@@ -14,14 +13,10 @@ import searchFromNotes from 'browser/lib/search'
import fs from 'fs'
import path from 'path'
import { hashHistory } from 'react-router'
import copy from 'copy-to-clipboard'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import Markdown from '../../lib/markdown'
import i18n from 'browser/lib/i18n'
const { remote } = require('electron')
const { Menu, MenuItem, dialog } = remote
const WP_POST_PATH = '/wp/v2/posts'
function sortByCreatedAt (a, b) {
return new Date(b.createdAt) - new Date(a.createdAt)
@@ -36,7 +31,7 @@ function sortByUpdatedAt (a, b) {
}
function findNoteByKey (notes, noteKey) {
return notes.find((note) => note.key === noteKey)
return notes.find((note) => `${note.storage}-${note.key}` === noteKey)
}
function findNotesByKeys (notes, noteKeys) {
@@ -44,7 +39,7 @@ function findNotesByKeys (notes, noteKeys) {
}
function getNoteKey (note) {
return note.key
return `${note.storage}-${note.key}`
}
class NoteList extends React.Component {
@@ -71,11 +66,6 @@ class NoteList extends React.Component {
this.deleteNote = this.deleteNote.bind(this)
this.focusNote = this.focusNote.bind(this)
this.pinToTop = this.pinToTop.bind(this)
this.getNoteStorage = this.getNoteStorage.bind(this)
this.getNoteFolder = this.getNoteFolder.bind(this)
this.getViewType = this.getViewType.bind(this)
this.restoreNote = this.restoreNote.bind(this)
this.copyNoteLink = this.copyNoteLink.bind(this)
// TODO: not Selected noteKeys but SelectedNote(for reusing)
this.state = {
@@ -119,27 +109,14 @@ class NoteList extends React.Component {
componentDidUpdate (prevProps) {
const { location } = this.props
const { selectedNoteKeys } = this.state
const visibleNoteKeys = this.notes.map(note => note.key)
const note = this.notes[0]
const prevKey = prevProps.location.query.key
const noteKey = visibleNoteKeys.includes(prevKey) ? prevKey : note && note.key
if (note && location.query.key == null) {
if (this.notes.length > 0 && location.query.key == null) {
const { router } = this.context
if (!location.pathname.match(/\/searched/)) this.contextNotes = this.getContextNotes()
// A visible note is an active note
if (!selectedNoteKeys.includes(noteKey)) {
if (selectedNoteKeys.length === 1) selectedNoteKeys.pop()
selectedNoteKeys.push(noteKey)
ee.emit('list:moved')
}
router.replace({
pathname: location.pathname,
query: {
key: noteKey
key: this.notes[0].storage + '-' + this.notes[0].key
}
})
return
@@ -263,38 +240,27 @@ class NoteList extends React.Component {
handleNoteListKeyDown (e) {
if (e.metaKey || e.ctrlKey) return true
// A key
if (e.keyCode === 65 && !e.shiftKey) {
e.preventDefault()
ee.emit('top:new-note')
}
// D key
if (e.keyCode === 68) {
e.preventDefault()
this.deleteNote()
}
// E key
if (e.keyCode === 69) {
e.preventDefault()
ee.emit('detail:focus')
}
// F or S key
if (e.keyCode === 70 || e.keyCode === 83) {
e.preventDefault()
ee.emit('top:focus-search')
}
// UP or K key
if (e.keyCode === 38 || e.keyCode === 75) {
if (e.keyCode === 38) {
e.preventDefault()
this.selectPriorNote()
}
// DOWN or J key
if (e.keyCode === 40 || e.keyCode === 74) {
if (e.keyCode === 40) {
e.preventDefault()
this.selectNextNote()
}
@@ -445,9 +411,9 @@ class NoteList extends React.Component {
if (this.notes[targetIndex].type === 'SNIPPET_NOTE') {
dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: i18n.__('Sorry!'),
detail: i18n.__('md/text import is available only a markdown note.'),
buttons: [i18n.__('OK'), i18n.__('Cancel')]
message: 'Sorry!',
detail: 'md/text import is available only a markdown note.',
buttons: ['OK', 'Cancel']
})
}
}
@@ -471,30 +437,17 @@ class NoteList extends React.Component {
this.handleNoteClick(e, uniqueKey)
}
const pinLabel = note.isPinned ? i18n.__('Remove pin') : i18n.__('Pin to Top')
const deleteLabel = i18n.__('Delete Note')
const cloneNote = i18n.__('Clone Note')
const restoreNote = i18n.__('Restore Note')
const copyNoteLink = i18n.__('Copy Note Link')
const publishLabel = i18n.__('Publish Blog')
const updateLabel = i18n.__('Update Blog')
const openBlogLabel = i18n.__('Open Blog')
const pinLabel = note.isPinned ? 'Remove pin' : 'Pin to Top'
const deleteLabel = 'Delete Note'
const cloneNote = 'Clone Note'
const menu = new Menu()
if (!location.pathname.match(/\/starred|\/trash/)) {
if (!location.pathname.match(/\/home|\/starred|\/trash/)) {
menu.append(new MenuItem({
label: pinLabel,
click: this.pinToTop
}))
}
if (location.pathname.match(/\/trash/)) {
menu.append(new MenuItem({
label: restoreNote,
click: this.restoreNote
}))
}
menu.append(new MenuItem({
label: deleteLabel,
click: this.deleteNote
@@ -503,75 +456,31 @@ class NoteList extends React.Component {
label: cloneNote,
click: this.cloneNote.bind(this)
}))
menu.append(new MenuItem({
label: copyNoteLink,
click: this.copyNoteLink(note)
}))
if (note.type === 'MARKDOWN_NOTE') {
if (note.blog && note.blog.blogLink && note.blog.blogId) {
menu.append(new MenuItem({
label: updateLabel,
click: this.publishMarkdown.bind(this)
}))
menu.append(new MenuItem({
label: openBlogLabel,
click: () => this.openBlog.bind(this)(note)
}))
} else {
menu.append(new MenuItem({
label: publishLabel,
click: this.publishMarkdown.bind(this)
}))
}
}
menu.popup()
}
updateSelectedNotes (updateFunc, cleanSelection = true) {
pinToTop () {
const { selectedNoteKeys } = this.state
const { dispatch } = this.props
const notes = this.notes.map((note) => Object.assign({}, note))
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
if (!_.isFunction(updateFunc)) {
console.warn('Update function is not defined. No update will happen')
updateFunc = (note) => { return note }
}
Promise.all(
selectedNotes.map((note) => {
note = updateFunc(note)
return dataApi
.updateNote(note.storage, note.key, note)
})
selectedNotes.map((note) => {
note.isPinned = !note.isPinned
return dataApi
.updateNote(note.storage, note.key, note)
})
)
.then((updatedNotes) => {
updatedNotes.forEach((note) => {
dispatch({
type: 'UPDATE_NOTE',
note
})
})
.then((updatedNotes) => {
updatedNotes.forEach((note) => {
dispatch({
type: 'UPDATE_NOTE',
note
})
if (cleanSelection) {
this.selectNextNote()
}
}
pinToTop () {
this.updateSelectedNotes((note) => {
note.isPinned = !note.isPinned
return note
})
}
restoreNote () {
this.updateSelectedNotes((note) => {
note.isTrashed = false
return note
})
})
this.setState({ selectedNoteKeys: [] })
}
deleteNote () {
@@ -585,15 +494,17 @@ class NoteList extends React.Component {
const noteExp = selectedNotes.length > 1 ? 'notes' : 'note'
const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: i18n.__('Confirm note deletion'),
message: 'Confirm note deletion',
detail: `This will permanently remove ${selectedNotes.length} ${noteExp}.`,
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
buttons: ['Confirm', 'Cancel']
})
if (dialogueButtonIndex === 1) return
Promise.all(
selectedNotes.map((note) => {
selectedNoteKeys.map((uniqueKey) => {
const storageKey = uniqueKey.split('-')[0]
const noteKey = uniqueKey.split('-')[1]
return dataApi
.deleteNote(note.storage, note.key)
.deleteNote(storageKey, noteKey)
})
)
.then((data) => {
@@ -654,134 +565,23 @@ class NoteList extends React.Component {
content: firstNote.content
})
.then((note) => {
const uniqueKey = note.storage + '-' + note.key
dispatch({
type: 'UPDATE_NOTE',
note: note
})
this.setState({
selectedNoteKeys: [note.key]
selectedNoteKeys: [uniqueKey]
})
hashHistory.push({
pathname: location.pathname,
query: {key: note.key}
query: {key: uniqueKey}
})
})
}
copyNoteLink (note) {
const noteLink = `[${note.title}](:note:${note.key})`
return copy(noteLink)
}
save (note) {
const { dispatch } = this.props
dataApi
.updateNote(note.storage, note.key, note)
.then((note) => {
dispatch({
type: 'UPDATE_NOTE',
note: note
})
})
}
publishMarkdown () {
if (this.pendingPublish) {
clearTimeout(this.pendingPublish)
}
this.pendingPublish = setTimeout(() => {
this.publishMarkdownNow()
}, 1000)
}
publishMarkdownNow () {
const {selectedNoteKeys} = this.state
const notes = this.notes.map((note) => Object.assign({}, note))
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
const firstNote = selectedNotes[0]
const config = ConfigManager.get()
const {address, token, authMethod, username, password} = config.blog
let authToken = ''
if (authMethod === 'USER') {
authToken = `Basic ${window.btoa(`${username}:${password}`)}`
} else {
authToken = `Bearer ${token}`
}
const contentToRender = firstNote.content.replace(`# ${firstNote.title}`, '')
const markdown = new Markdown()
const data = {
title: firstNote.title,
content: markdown.render(contentToRender),
status: 'publish'
}
let url = ''
let method = ''
if (firstNote.blog && firstNote.blog.blogId) {
url = `${address}${WP_POST_PATH}/${firstNote.blog.blogId}`
method = 'PUT'
} else {
url = `${address}${WP_POST_PATH}`
method = 'POST'
}
// eslint-disable-next-line no-undef
fetch(url, {
method: method,
body: JSON.stringify(data),
headers: {
'Authorization': authToken,
'Content-Type': 'application/json'
}
}).then(res => res.json())
.then(response => {
if (_.isNil(response.link) || _.isNil(response.id)) {
return Promise.reject()
}
firstNote.blog = {
blogLink: response.link,
blogId: response.id
}
this.save(firstNote)
this.confirmPublish(firstNote)
})
.catch((error) => {
console.error(error)
this.confirmPublishError()
})
}
confirmPublishError () {
const { remote } = electron
const { dialog } = remote
const alertError = {
type: 'warning',
message: i18n.__('Publish Failed'),
detail: i18n.__('Check and update your blog setting and try again.'),
buttons: [i18n.__('Confirm')]
}
dialog.showMessageBox(remote.getCurrentWindow(), alertError)
}
confirmPublish (note) {
const buttonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: i18n.__('Publish Succeeded'),
detail: `${note.title} is published at ${note.blog.blogLink}`,
buttons: [i18n.__('Confirm'), i18n.__('Open Blog')]
})
if (buttonIndex === 1) {
this.openBlog(note)
}
}
openBlog (note) {
const { shell } = electron
shell.openExternal(note.blog.blogLink)
}
importFromFile () {
const options = {
filters: [
@@ -874,28 +674,10 @@ class NoteList extends React.Component {
dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: message,
buttons: [i18n.__('OK')]
buttons: ['OK']
})
}
getNoteStorage (note) { // note.storage = storage key
return this.props.data.storageMap.toJS()[note.storage]
}
getNoteFolder (note) { // note.folder = folder key
return _.find(this.getNoteStorage(note).folders, ({ key }) => key === note.folder)
}
getViewType () {
const { pathname } = this.props.location
const folder = /\/folders\/[a-zA-Z0-9]+/.test(pathname)
const storage = /\/storages\/[a-zA-Z0-9]+/.test(pathname) && !folder
const allNotes = pathname === '/home'
if (allNotes) return 'ALL'
if (folder) return 'FOLDER'
if (storage) return 'STORAGE'
}
render () {
const { location, config } = this.props
let { notes } = this.props
@@ -905,7 +687,7 @@ class NoteList extends React.Component {
: config.sortBy === 'ALPHABETICAL'
? sortByAlphabetical
: sortByUpdatedAt
const sortedNotes = location.pathname.match(/\/starred|\/trash/)
const sortedNotes = location.pathname.match(/\/home|\/starred|\/trash/)
? this.getNotes().sort(sortFunc)
: this.sortByPin(this.getNotes().sort(sortFunc))
this.notes = notes = sortedNotes.filter((note) => {
@@ -932,8 +714,6 @@ class NoteList extends React.Component {
}
})
const viewType = this.getViewType()
const noteList = notes
.map(note => {
if (note == null) {
@@ -959,9 +739,6 @@ class NoteList extends React.Component {
handleNoteClick={this.handleNoteClick.bind(this)}
handleDragStart={this.handleDragStart.bind(this)}
pathname={location.pathname}
folderName={this.getNoteFolder(note).name}
storageName={this.getNoteStorage(note).name}
viewType={viewType}
/>
)
}
@@ -975,9 +752,6 @@ class NoteList extends React.Component {
handleNoteClick={this.handleNoteClick.bind(this)}
handleDragStart={this.handleDragStart.bind(this)}
pathname={location.pathname}
folderName={this.getNoteFolder(note).name}
storageName={this.getNoteStorage(note).name}
viewType={viewType}
/>
)
})
@@ -992,17 +766,16 @@ class NoteList extends React.Component {
<div styleName='control-sortBy'>
<i className='fa fa-angle-down' />
<select styleName='control-sortBy-select'
title={i18n.__('Select filter mode')}
value={config.sortBy}
onChange={(e) => this.handleSortByChange(e)}
>
<option title='Sort by update time' value='UPDATED_AT'>{i18n.__('Updated')}</option>
<option title='Sort by create time' value='CREATED_AT'>{i18n.__('Created')}</option>
<option title='Sort alphabetically' value='ALPHABETICAL'>{i18n.__('Alphabetically')}</option>
<option value='UPDATED_AT'>Updated</option>
<option value='CREATED_AT'>Created</option>
<option value='ALPHABETICAL'>Alphabetically</option>
</select>
</div>
<div styleName='control-button-area'>
<button title={i18n.__('Default View')} styleName={config.listStyle === 'DEFAULT'
<button styleName={config.listStyle === 'DEFAULT'
? 'control-button--active'
: 'control-button'
}
@@ -1010,7 +783,7 @@ class NoteList extends React.Component {
>
<img styleName='iconTag' src='../resources/icon/icon-column.svg' />
</button>
<button title={i18n.__('Compressed View')} styleName={config.listStyle === 'SMALL'
<button styleName={config.listStyle === 'SMALL'
? 'control-button--active'
: 'control-button'
}

View File

@@ -2,7 +2,6 @@ import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './SwitchButton.styl'
import i18n from 'browser/lib/i18n'
const ListButton = ({
onClick, isTagActive
@@ -13,7 +12,7 @@ const ListButton = ({
: '../resources/icon/icon-list-active.svg'
}
/>
<span styleName='tooltip'>{i18n.__('Notes')}</span>
<span styleName='tooltip'>Notes</span>
</button>
)

View File

@@ -2,14 +2,13 @@ import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './PreferenceButton.styl'
import i18n from 'browser/lib/i18n'
const PreferenceButton = ({
onClick
}) => (
<button styleName='top-menu-preference' onClick={(e) => onClick(e)}>
<img styleName='iconTag' src='../resources/icon/icon-setting.svg' />
<span styleName='tooltip'>{i18n.__('Preferences')}</span>
<span styleName='tooltip'>Preferences</span>
</button>
)

View File

@@ -9,8 +9,6 @@ import RenameFolderModal from 'browser/main/modals/RenameFolderModal'
import dataApi from 'browser/main/lib/dataApi'
import StorageItemChild from 'browser/components/StorageItem'
import _ from 'lodash'
import { SortableElement } from 'react-sortable-hoc'
import i18n from 'browser/lib/i18n'
const { remote } = require('electron')
const { Menu, dialog } = remote
@@ -27,14 +25,14 @@ class StorageItem extends React.Component {
handleHeaderContextMenu (e) {
const menu = Menu.buildFromTemplate([
{
label: i18n.__('Add Folder'),
label: 'Add Folder',
click: (e) => this.handleAddFolderButtonClick(e)
},
{
type: 'separator'
},
{
label: i18n.__('Unlink Storage'),
label: 'Unlink Storage',
click: (e) => this.handleUnlinkStorageClick(e)
}
])
@@ -45,9 +43,9 @@ class StorageItem extends React.Component {
handleUnlinkStorageClick (e) {
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: i18n.__('Unlink Storage'),
detail: i18n.__('This work will just detatches a storage from Boostnote. (Any data won\'t be deleted.)'),
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
message: 'Unlink Storage',
detail: 'This work will just detatches a storage from Boostnote. (Any data won\'t be deleted.)',
buttons: ['Confirm', 'Cancel']
})
if (index === 0) {
@@ -94,21 +92,21 @@ class StorageItem extends React.Component {
handleFolderButtonContextMenu (e, folder) {
const menu = Menu.buildFromTemplate([
{
label: i18n.__('Rename Folder'),
label: 'Rename Folder',
click: (e) => this.handleRenameFolderClick(e, folder)
},
{
type: 'separator'
},
{
label: i18n.__('Export Folder'),
label: 'Export Folder',
submenu: [
{
label: i18n.__('Export as txt'),
label: 'Export as txt',
click: (e) => this.handleExportFolderClick(e, folder, 'txt')
},
{
label: i18n.__('Export as md'),
label: 'Export as md',
click: (e) => this.handleExportFolderClick(e, folder, 'md')
}
]
@@ -117,7 +115,7 @@ class StorageItem extends React.Component {
type: 'separator'
},
{
label: i18n.__('Delete Folder'),
label: 'Delete Folder',
click: (e) => this.handleFolderDeleteClick(e, folder)
}
])
@@ -136,8 +134,8 @@ class StorageItem extends React.Component {
handleExportFolderClick (e, folder, fileType) {
const options = {
properties: ['openDirectory', 'createDirectory'],
buttonLabel: i18n.__('Select directory'),
title: i18n.__('Select a folder to export the files to'),
buttonLabel: 'Select directory',
title: 'Select a folder to export the files to',
multiSelections: false
}
dialog.showOpenDialog(remote.getCurrentWindow(), options,
@@ -161,9 +159,9 @@ class StorageItem extends React.Component {
handleFolderDeleteClick (e, folder) {
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: i18n.__('Delete Folder'),
detail: i18n.__('This will delete all notes in the folder and can not be undone.'),
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
message: 'Delete Folder',
detail: 'This will delete all notes in the folder and can not be undone.',
buttons: ['Confirm', 'Cancel']
})
if (index === 0) {
@@ -193,16 +191,33 @@ class StorageItem extends React.Component {
dropNote (storage, folder, dispatch, location, noteData) {
noteData = noteData.filter((note) => folder.key !== note.folder)
if (noteData.length === 0) return
const newNoteData = noteData.map((note) => Object.assign({}, note, {storage: storage, folder: folder.key}))
Promise.all(
noteData.map((note) => dataApi.moveNote(note.storage, note.key, storage.key, folder.key))
newNoteData.map((note) => dataApi.createNote(storage.key, note))
)
.then((createdNoteData) => {
createdNoteData.forEach((newNote) => {
createdNoteData.forEach((note) => {
dispatch({
type: 'MOVE_NOTE',
originNote: noteData.find((note) => note.content === newNote.content),
note: newNote
type: 'UPDATE_NOTE',
note: note
})
})
})
.catch((err) => {
console.error(`error on create notes: ${err}`)
})
.then(() => {
return Promise.all(
noteData.map((note) => dataApi.deleteNote(note.storage, note.key))
)
})
.then((deletedNoteData) => {
deletedNoteData.forEach((note) => {
dispatch({
type: 'DELETE_NOTE',
storageKey: note.storageKey,
noteKey: note.noteKey
})
})
})
@@ -221,8 +236,7 @@ class StorageItem extends React.Component {
render () {
const { storage, location, isFolded, data, dispatch } = this.props
const { folderNoteMap, trashedSet } = data
const SortableStorageItemChild = SortableElement(StorageItemChild)
const folderList = storage.folders.map((folder, index) => {
const folderList = storage.folders.map((folder) => {
const isActive = !!(location.pathname.match(new RegExp('\/storages\/' + storage.key + '\/folders\/' + folder.key)))
const noteSet = folderNoteMap.get(storage.key + '-' + folder.key)
@@ -236,9 +250,8 @@ class StorageItem extends React.Component {
noteCount = noteSet.size - trashedNoteCount
}
return (
<SortableStorageItemChild
<StorageItemChild
key={folder.key}
index={index}
isActive={isActive}
handleButtonClick={(e) => this.handleFolderButtonClick(folder.key)(e)}
handleContextMenu={(e) => this.handleFolderButtonContextMenu(e, folder)}
@@ -260,9 +273,9 @@ class StorageItem extends React.Component {
key={storage.key}
>
<div styleName={isActive
? 'header--active'
: 'header'
}
? 'header--active'
: 'header'
}
onContextMenu={(e) => this.handleHeaderContextMenu(e)}
>
<button styleName='header-toggleButton'
@@ -271,7 +284,7 @@ class StorageItem extends React.Component {
<img src={this.state.isOpen
? '../resources/icon/icon-down.svg'
: '../resources/icon/icon-right.svg'
}
}
/>
</button>

View File

@@ -2,7 +2,6 @@ import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './SwitchButton.styl'
import i18n from 'browser/lib/i18n'
const TagButton = ({
onClick, isTagActive
@@ -13,7 +12,7 @@ const TagButton = ({
: '../resources/icon/icon-tag.svg'
}
/>
<span styleName='tooltip'>{i18n.__('Tags')}</span>
<span styleName='tooltip'>Tags</span>
</button>
)

View File

@@ -1,9 +1,6 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
const { remote } = require('electron')
const { Menu } = remote
import dataApi from 'browser/main/lib/dataApi'
import styles from './SideNav.styl'
import { openModal } from 'browser/main/lib/modal'
import PreferencesModal from '../modals/PreferencesModal'
@@ -17,8 +14,6 @@ import EventEmitter from 'browser/main/lib/eventEmitter'
import PreferenceButton from './PreferenceButton'
import ListButton from './ListButton'
import TagButton from './TagButton'
import {SortableContainer} from 'react-sortable-hoc'
import i18n from 'browser/lib/i18n'
class SideNav extends React.Component {
// TODO: should not use electron stuff v0.7
@@ -70,17 +65,6 @@ class SideNav extends React.Component {
router.push('/alltags')
}
onSortEnd (storage) {
return ({oldIndex, newIndex}) => {
const { dispatch } = this.props
dataApi
.reorderFolder(storage.key, oldIndex, newIndex)
.then((data) => {
dispatch({ type: 'REORDER_FOLDER', storage: data.storage })
})
}
}
SideNavComponent (isFolded, storageList) {
const { location, data } = this.props
@@ -102,10 +86,9 @@ class SideNav extends React.Component {
isTrashedActive={isTrashedActive}
handleStarredButtonClick={(e) => this.handleStarredButtonClick(e)}
handleTrashedButtonClick={(e) => this.handleTrashedButtonClick(e)}
counterTotalNote={data.noteMap._map.size - data.trashedSet._set.size}
counterTotalNote={data.noteMap._map.size}
counterStarredNote={data.starredSet._set.size}
counterDelNote={data.trashedSet._set.size}
handleFilterButtonContextMenu={this.handleFilterButtonContextMenu.bind(this)}
/>
<StorageList storageList={storageList} />
@@ -116,7 +99,7 @@ class SideNav extends React.Component {
component = (
<div styleName='tabBody'>
<div styleName='tag-title'>
<p>{i18n.__('Tags')}</p>
<p>Tags</p>
</div>
<div styleName='tagList'>
{this.tagListComponent(data)}
@@ -130,21 +113,18 @@ class SideNav extends React.Component {
tagListComponent () {
const { data, location } = this.props
const tagList = _.sortBy(data.tagNoteMap.map((tag, name) => {
return { name, size: tag.size }
}), ['name'])
const tagList = data.tagNoteMap.map((tag, key) => {
return key
})
return (
tagList.map(tag => {
return (
<TagListItem
name={tag.name}
handleClickTagListItem={this.handleClickTagListItem.bind(this)}
isActive={this.getTagActive(location.pathname, tag)}
key={tag.name}
count={tag.size}
/>
)
})
tagList.map(tag => (
<TagListItem
name={tag}
handleClickTagListItem={this.handleClickTagListItem.bind(this)}
isActive={this.getTagActive(location.pathname, tag)}
key={tag}
/>
))
)
}
@@ -159,48 +139,19 @@ class SideNav extends React.Component {
router.push(`/tags/${name}`)
}
emptyTrash (entries) {
const { dispatch } = this.props
const deletionPromises = entries.map((note) => {
return dataApi.deleteNote(note.storage, note.key)
})
Promise.all(deletionPromises)
.then((arrayOfStorageAndNoteKeys) => {
arrayOfStorageAndNoteKeys.forEach(({ storageKey, noteKey }) => {
dispatch({ type: 'DELETE_NOTE', storageKey, noteKey })
})
})
.catch((err) => {
console.error('Cannot Delete note: ' + err)
})
console.log('Trash emptied')
}
handleFilterButtonContextMenu (event) {
const { data } = this.props
const trashedNotes = data.trashedSet.toJS().map((uniqueKey) => data.noteMap.get(uniqueKey))
const menu = Menu.buildFromTemplate([
{ label: i18n.__('Empty Trash'), click: () => this.emptyTrash(trashedNotes) }
])
menu.popup()
}
render () {
const { data, location, config, dispatch } = this.props
const isFolded = config.isSideNavFolded
const storageList = data.storageMap.map((storage, key) => {
const SortableStorageItem = SortableContainer(StorageItem)
return <SortableStorageItem
return <StorageItem
key={storage.key}
storage={storage}
data={data}
location={location}
isFolded={isFolded}
dispatch={dispatch}
onSortEnd={this.onSortEnd.bind(this)(storage)}
useDragHandle
/>
})
const style = {}

View File

@@ -21,19 +21,20 @@
color white
.zoom
navButtonColor()
color rgba(0,0,0,.54)
height 20px
display flex
padding 0
align-items center
background-color transparent
&:hover
color $ui-active-color
&:active
color $ui-active-color
span
margin-left 5px
display none
// navButtonColor()
// color rgba(0,0,0,.54)
// height 20px
// display flex
// padding 0
// align-items center
// background-color transparent
// &:hover
// color $ui-active-color
// &:active
// color $ui-active-color
// span
// margin-left 5px
.update
navButtonColor()

View File

@@ -3,7 +3,6 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './StatusBar.styl'
import ZoomManager from 'browser/main/lib/ZoomManager'
import i18n from 'browser/lib/i18n'
const electron = require('electron')
const { remote, ipcRenderer } = electron
@@ -15,9 +14,9 @@ class StatusBar extends React.Component {
updateApp () {
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: i18n.__('Update Boostnote'),
detail: i18n.__('New Boostnote is ready to be installed.'),
buttons: [i18n.__('Restart & Install'), i18n.__('Not Now')]
message: 'Update Boostnote',
detail: 'New Boostnote is ready to be installed.',
buttons: ['Restart & Install', 'Not Now']
})
if (index === 0) {
@@ -63,7 +62,7 @@ class StatusBar extends React.Component {
{status.updateReady
? <button onClick={this.updateApp} styleName='update'>
<i styleName='update-icon' className='fa fa-cloud-download' /> {i18n.__('Ready to Update!')}
<i styleName='update-icon' className='fa fa-cloud-download' /> Ready to Update!
</button>
: null
}

View File

@@ -40,32 +40,6 @@ $control-height = 34px
padding-bottom 2px
background-color $ui-noteList-backgroundColor
.control-search-input-clear
height 16px
width 16px
position absolute
right 40px
top 10px
z-index 300
border none
background-color transparent
color #999
&:hover .control-search-input-clear-tooltip
opacity 1
.control-search-input-clear-tooltip
tooltip()
position fixed
pointer-events none
top 50px
left 433px
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s
.control-search-optionList
position fixed
z-index 200
@@ -233,4 +207,4 @@ body[data-theme="solarized-dark"]
background-color $ui-solarized-dark-noteList-backgroundColor
input
background-color $ui-solarized-dark-noteList-backgroundColor
color $ui-solarized-dark-text-color
color $ui-solarized-dark-text-color

View File

@@ -5,7 +5,6 @@ import styles from './TopBar.styl'
import _ from 'lodash'
import ee from 'browser/main/lib/eventEmitter'
import NewNoteButton from 'browser/main/NewNoteButton'
import i18n from 'browser/lib/i18n'
class TopBar extends React.Component {
constructor (props) {
@@ -23,29 +22,14 @@ class TopBar extends React.Component {
this.focusSearchHandler = () => {
this.handleOnSearchFocus()
}
this.codeInitHandler = this.handleCodeInit.bind(this)
}
componentDidMount () {
ee.on('top:focus-search', this.focusSearchHandler)
ee.on('code:init', this.codeInitHandler)
}
componentWillUnmount () {
ee.off('top:focus-search', this.focusSearchHandler)
ee.off('code:init', this.codeInitHandler)
}
handleSearchClearButton (e) {
const { router } = this.context
this.setState({
search: '',
isSearching: false
})
this.refs.search.childNodes[0].blur
router.push('/searched')
e.preventDefault()
}
handleKeyDown (e) {
@@ -55,23 +39,6 @@ class TopBar extends React.Component {
isIME: false
})
// Clear search on ESC
if (e.keyCode === 27) {
return this.handleSearchClearButton(e)
}
// Next note on DOWN key
if (e.keyCode === 40) {
ee.emit('list:next')
e.preventDefault()
}
// Prev note on UP key
if (e.keyCode === 38) {
ee.emit('list:prior')
e.preventDefault()
}
// When the key is an alphabet, del, enter or ctr
if (e.keyCode <= 90 || e.keyCode >= 186 && e.keyCode <= 222) {
this.setState({
@@ -106,16 +73,14 @@ class TopBar extends React.Component {
handleSearchChange (e) {
const { router } = this.context
const keyword = this.refs.searchInput.value
if (this.state.isAlphabet || this.state.isConfirmTranslation) {
router.push('/searched')
} else {
e.preventDefault()
}
this.setState({
search: keyword
search: this.refs.searchInput.value
})
ee.emit('top:search', keyword)
}
handleSearchFocus (e) {
@@ -143,19 +108,13 @@ class TopBar extends React.Component {
}
handleOnSearchFocus () {
const el = this.refs.search.childNodes[0]
if (this.state.isSearching) {
el.blur()
this.refs.search.childNodes[0].blur()
} else {
el.focus()
el.setSelectionRange(0, el.value.length)
this.refs.search.childNodes[0].focus()
}
}
handleCodeInit () {
ee.emit('top:search', this.refs.searchInput.value)
}
render () {
const { config, style, location } = this.props
return (
@@ -177,19 +136,19 @@ class TopBar extends React.Component {
onChange={(e) => this.handleSearchChange(e)}
onKeyDown={(e) => this.handleKeyDown(e)}
onKeyUp={(e) => this.handleKeyUp(e)}
placeholder={i18n.__('Search')}
placeholder='Search'
type='text'
className='searchInput'
/>
{this.state.search !== '' &&
<button styleName='control-search-input-clear'
onClick={(e) => this.handleSearchClearButton(e)}
>
<i className='fa fa-fw fa-times' />
<span styleName='control-search-input-clear-tooltip'>{i18n.__('Clear Search')}</span>
</button>
}
</div>
{this.state.search > 0 &&
<button styleName='left-search-clearButton'
onClick={(e) => this.handleSearchClearButton(e)}
>
<i className='fa fa-times' />
</button>
}
</div>
</div>
{location.pathname === '/trashed' ? ''

View File

@@ -8,7 +8,6 @@ import { Router, Route, IndexRoute, IndexRedirect, hashHistory } from 'react-rou
import { syncHistoryWithStore } from 'react-router-redux'
require('./lib/ipcClient')
require('../lib/customMeta')
import i18n from 'browser/lib/i18n'
const electron = require('electron')
@@ -47,9 +46,9 @@ function notify (...args) {
function updateApp () {
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: i18n.__('Update Boostnote'),
detail: i18n.__('New Boostnote is ready to be installed.'),
buttons: [i18n.__('Restart & Install'), i18n.__('Not Now')]
message: 'Update Boostnote',
detail: 'New Boostnote is ready to be installed.',
buttons: ['Restart & Install', 'Not Now']
})
if (index === 0) {

View File

@@ -1,6 +1,5 @@
import _ from 'lodash'
import RcParser from 'browser/lib/RcParser'
import i18n from 'browser/lib/i18n'
const OSX = global.process.platform === 'darwin'
const win = global.process.platform === 'win32'
@@ -22,7 +21,6 @@ export const DEFAULT_CONFIG = {
toggleMain: OSX ? 'Cmd + Alt + L' : 'Super + Alt + E'
},
ui: {
language: 'en',
theme: 'default',
showCopyNotification: true,
disableDirectWrite: false,
@@ -35,13 +33,10 @@ export const DEFAULT_CONFIG = {
fontFamily: win ? 'Segoe UI' : 'Monaco, Consolas',
indentType: 'space',
indentSize: '2',
enableRulers: false,
rulers: [80, 120],
displayLineNumbers: true,
switchPreview: 'BLUR', // Available value: RIGHTCLICK, BLUR
scrollPastEnd: false,
type: 'SPLIT',
fetchUrlTitle: true
type: 'SPLIT'
},
preview: {
fontSize: '14',
@@ -52,17 +47,7 @@ export const DEFAULT_CONFIG = {
latexInlineClose: '$',
latexBlockOpen: '$$',
latexBlockClose: '$$',
scrollPastEnd: false,
smartQuotes: true,
sanitize: 'STRICT' // 'STRICT', 'ALLOW_STYLES', 'NONE'
},
blog: {
type: 'wordpress', // Available value: wordpress, add more types in the future plz
address: 'http://wordpress.com/wp-json',
authMethod: 'JWT', // Available value: JWT, USER
token: '',
username: '',
password: ''
scrollPastEnd: false
}
}
@@ -138,38 +123,6 @@ function set (updates) {
document.body.setAttribute('data-theme', 'default')
}
if (newConfig.ui.language === 'sq') {
i18n.setLocale('sq')
} else if (newConfig.ui.language === 'zh-CN') {
i18n.setLocale('zh-CN')
} else if (newConfig.ui.language === 'zh-TW') {
i18n.setLocale('zh-TW')
} else if (newConfig.ui.language === 'da') {
i18n.setLocale('da')
} else if (newConfig.ui.language === 'fr') {
i18n.setLocale('fr')
} else if (newConfig.ui.language === 'de') {
i18n.setLocale('de')
} else if (newConfig.ui.language === 'hu') {
i18n.setLocale('hu')
} else if (newConfig.ui.language === 'ja') {
i18n.setLocale('ja')
} else if (newConfig.ui.language === 'ko') {
i18n.setLocale('ko')
} else if (newConfig.ui.language === 'no') {
i18n.setLocale('no')
} else if (newConfig.ui.language === 'pl') {
i18n.setLocale('pl')
} else if (newConfig.ui.language === 'pt') {
i18n.setLocale('pt')
} else if (newConfig.ui.language === 'ru') {
i18n.setLocale('ru')
} else if (newConfig.ui.language === 'es') {
i18n.setLocale('es')
} else {
i18n.setLocale('en')
}
let editorTheme = document.getElementById('editorTheme')
if (editorTheme == null) {
editorTheme = document.createElement('link')
@@ -197,7 +150,6 @@ function set (updates) {
function assignConfigValues (originalConfig, rcConfig) {
const config = Object.assign({}, DEFAULT_CONFIG, originalConfig, rcConfig)
config.hotkey = Object.assign({}, DEFAULT_CONFIG.hotkey, originalConfig.hotkey, rcConfig.hotkey)
config.blog = Object.assign({}, DEFAULT_CONFIG.blog, originalConfig.blog, rcConfig.blog)
config.ui = Object.assign({}, DEFAULT_CONFIG.ui, originalConfig.ui, rcConfig.ui)
config.editor = Object.assign({}, DEFAULT_CONFIG.editor, originalConfig.editor, rcConfig.editor)
config.preview = Object.assign({}, DEFAULT_CONFIG.preview, originalConfig.preview, rcConfig.preview)

View File

@@ -1,31 +0,0 @@
const fs = require('fs')
const path = require('path')
/**
* @description Copy a file from source to destination
* @param {String} srcPath
* @param {String} dstPath
* @return {Promise} an image path
*/
function copyFile (srcPath, dstPath) {
if (!path.extname(dstPath)) {
dstPath = path.join(dstPath, path.basename(srcPath))
}
return new Promise((resolve, reject) => {
const dstFolder = path.dirname(dstPath)
if (!fs.existsSync(dstFolder)) fs.mkdirSync(dstFolder)
const input = fs.createReadStream(srcPath)
const output = fs.createWriteStream(dstPath)
output.on('error', reject)
input.on('error', reject)
input.on('end', () => {
resolve(dstPath)
})
input.pipe(output)
})
}
module.exports = copyFile

View File

@@ -3,20 +3,19 @@ const path = require('path')
const { findStorage } = require('browser/lib/findStorage')
/**
* @description Copy an image and return the path.
* @description To copy an image and return the path.
* @param {String} filePath
* @param {String} storageKey
* @param {Boolean} rename create new filename or leave the old one
* @return {Promise<any>} an image path
* @return {String} an image path
*/
function copyImage (filePath, storageKey, rename = true) {
function copyImage (filePath, storageKey) {
return new Promise((resolve, reject) => {
try {
const targetStorage = findStorage(storageKey)
const inputImage = fs.createReadStream(filePath)
const imageExt = path.extname(filePath)
const imageName = rename ? Math.random().toString(36).slice(-16) : path.basename(filePath, imageExt)
const imageName = Math.random().toString(36).slice(-16)
const basename = `${imageName}${imageExt}`
const imageDir = path.join(targetStorage.path, 'images')
if (!fs.existsSync(imageDir)) fs.mkdirSync(imageDir)

View File

@@ -52,12 +52,12 @@ function createNote (storageKey, input) {
return storage
})
.then(function saveNote (storage) {
let key = keygen(true)
let key = keygen()
let isUnique = false
while (!isUnique) {
try {
sander.statSync(path.join(storage.path, 'notes', key + '.cson'))
key = keygen(true)
key = keygen()
} catch (err) {
if (err.code === 'ENOENT') {
isUnique = true

View File

@@ -1,7 +1,6 @@
import { findStorage } from 'browser/lib/findStorage'
import resolveStorageData from './resolveStorageData'
import resolveStorageNotes from './resolveStorageNotes'
import filenamify from 'filenamify'
import * as path from 'path'
import * as fs from 'fs'
@@ -46,7 +45,7 @@ function exportFolder (storageKey, folderKey, fileType, exportDir) {
notes
.filter(note => note.folder === folderKey && note.isTrashed === false && note.type === 'MARKDOWN_NOTE')
.forEach(snippet => {
const notePath = path.join(exportDir, `${filenamify(snippet.title, {replacement: '_'})}.${fileType}`)
const notePath = path.join(exportDir, `${snippet.title}.${fileType}`)
fs.writeFileSync(notePath, snippet.content)
})

View File

@@ -1,112 +0,0 @@
import copyFile from 'browser/main/lib/dataApi/copyFile'
import {findStorage} from 'browser/lib/findStorage'
import filenamify from 'filenamify'
const fs = require('fs')
const path = require('path')
const LOCAL_STORED_REGEX = /!\[(.*?)]\(\s*?\/:storage\/(.*\.\S*?)\)/gi
const IMAGES_FOLDER_NAME = 'images'
/**
* Export note together with images
*
* If images is stored in the storage, creates 'images' subfolder in target directory
* and copies images to it. Changes links to images in the content of the note
*
* @param {String} storageKey or storage path
* @param {String} noteContent Content to export
* @param {String} targetPath Path to exported file
* @param {function} outputFormatter
* @return {Promise.<*[]>}
*/
function exportNote (storageKey, noteContent, targetPath, outputFormatter) {
const storagePath = path.isAbsolute(storageKey) ? storageKey : findStorage(storageKey).path
const exportTasks = []
if (!storagePath) {
throw new Error('Storage path is not found')
}
let exportedData = noteContent.replace(LOCAL_STORED_REGEX, (match, dstFilename, srcFilename) => {
dstFilename = filenamify(dstFilename, {replacement: '_'})
if (!path.extname(dstFilename)) {
dstFilename += path.extname(srcFilename)
}
const dstRelativePath = path.join(IMAGES_FOLDER_NAME, dstFilename)
exportTasks.push({
src: path.join(IMAGES_FOLDER_NAME, srcFilename),
dst: dstRelativePath
})
return `![${dstFilename}](${dstRelativePath})`
})
if (outputFormatter) {
exportedData = outputFormatter(exportedData, exportTasks)
}
const tasks = prepareTasks(exportTasks, storagePath, path.dirname(targetPath))
return Promise.all(tasks.map((task) => copyFile(task.src, task.dst)))
.then(() => {
return saveToFile(exportedData, targetPath)
}).catch((err) => {
rollbackExport(tasks)
throw err
})
}
function prepareTasks (tasks, storagePath, targetPath) {
return tasks.map((task) => {
if (!path.isAbsolute(task.src)) {
task.src = path.join(storagePath, task.src)
}
if (!path.isAbsolute(task.dst)) {
task.dst = path.join(targetPath, task.dst)
}
return task
})
}
function saveToFile (data, filename) {
return new Promise((resolve, reject) => {
fs.writeFile(filename, data, (err) => {
if (err) return reject(err)
resolve(filename)
})
})
}
/**
* Remove exported files
* @param tasks Array of copy task objects. Object consists of two mandatory fields `src` and `dst`
*/
function rollbackExport (tasks) {
const folders = new Set()
tasks.forEach((task) => {
let fullpath = task.dst
if (!path.extname(task.dst)) {
fullpath = path.join(task.dst, path.basename(task.src))
}
if (fs.existsSync(fullpath)) {
fs.unlink(fullpath)
folders.add(path.dirname(fullpath))
}
})
folders.forEach((folder) => {
if (fs.readdirSync(folder).length === 0) {
fs.rmdir(folder)
}
})
}
export default exportNote

View File

@@ -1,12 +1,10 @@
const resolveStorageData = require('./resolveStorageData')
const _ = require('lodash')
const path = require('path')
const fs = require('fs')
const CSON = require('@rokt33r/season')
const keygen = require('browser/lib/keygen')
const sander = require('sander')
const { findStorage } = require('browser/lib/findStorage')
const copyImage = require('./copyImage')
function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
let oldStorage, newStorage
@@ -39,12 +37,12 @@ function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
return resolveStorageData(newStorage)
.then(function findNewNoteKey (_newStorage) {
newStorage = _newStorage
newNoteKey = keygen(true)
newNoteKey = keygen()
let isUnique = false
while (!isUnique) {
try {
sander.statSync(path.join(newStorage.path, 'notes', newNoteKey + '.cson'))
newNoteKey = keygen(true)
newNoteKey = keygen()
} catch (err) {
if (err.code === 'ENOENT') {
isUnique = true
@@ -67,27 +65,6 @@ function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
return noteData
})
.then(function moveImages (noteData) {
const searchImagesRegex = /!\[.*?]\(\s*?\/:storage\/(.*\.\S*?)\)/gi
let match = searchImagesRegex.exec(noteData.content)
const moveTasks = []
while (match != null) {
const [, filename] = match
const oldPath = path.join(oldStorage.path, 'images', filename)
moveTasks.push(
copyImage(oldPath, noteData.storage, false)
.then(() => {
fs.unlinkSync(oldPath)
})
)
// find next occurence
match = searchImagesRegex.exec(noteData.content)
}
return Promise.all(moveTasks).then(() => noteData)
})
.then(function writeAndReturn (noteData) {
CSON.writeFileSync(path.join(newStorage.path, 'notes', noteData.key + '.cson'), _.omit(noteData, ['key', 'storage']))
return noteData

View File

@@ -30,9 +30,6 @@ function validateInput (input) {
validatedInput.isPinned = !!input.isPinned
}
if (!_.isNil(input.blog)) {
validatedInput.blog = input.blog
}
validatedInput.type = input.type
switch (input.type) {
case 'MARKDOWN_NOTE':

View File

@@ -1,4 +1,5 @@
import ConfigManager from './ConfigManager'
import store from 'browser/main/store'
const nodeIpc = require('node-ipc')
const { remote, ipcRenderer } = require('electron')
@@ -17,7 +18,7 @@ nodeIpc.connectTo(
console.log(err)
})
nodeIpc.of.node.on('connect', function () {
console.log('Connected successfully')
console.log('Conncted successfully')
ipcRenderer.send('config-renew', {config: ConfigManager.get()})
})
nodeIpc.of.node.on('disconnect', function () {

View File

@@ -7,7 +7,6 @@ import store from 'browser/main/store'
import consts from 'browser/lib/consts'
import ModalEscButton from 'browser/components/ModalEscButton'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import i18n from 'browser/lib/i18n'
class CreateFolderModal extends React.Component {
constructor (props) {
@@ -80,12 +79,12 @@ class CreateFolderModal extends React.Component {
onKeyDown={(e) => this.handleKeyDown(e)}
>
<div styleName='header'>
<div styleName='title'>{i18n.__('Create new folder')}</div>
<div styleName='title'>Create new folder</div>
</div>
<ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} />
<div styleName='control'>
<div styleName='control-folder'>
<div styleName='control-folder-label'>{i18n.__('Folder name')}</div>
<div styleName='control-folder-label'>Folder name</div>
<input styleName='control-folder-input'
ref='name'
value={this.state.name}

View File

@@ -6,7 +6,6 @@ import { hashHistory } from 'react-router'
import ee from 'browser/main/lib/eventEmitter'
import ModalEscButton from 'browser/components/ModalEscButton'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import i18n from 'browser/lib/i18n'
class NewNoteModal extends React.Component {
constructor (props) {
@@ -36,16 +35,14 @@ class NewNoteModal extends React.Component {
content: ''
})
.then((note) => {
const noteHash = note.key
dispatch({
type: 'UPDATE_NOTE',
note: note
})
hashHistory.push({
pathname: location.pathname,
query: {key: noteHash}
query: {key: note.storage + '-' + note.key}
})
ee.emit('list:jump', noteHash)
ee.emit('detail:focus')
this.props.close()
})
@@ -76,16 +73,14 @@ class NewNoteModal extends React.Component {
}]
})
.then((note) => {
const noteHash = note.key
dispatch({
type: 'UPDATE_NOTE',
note: note
})
hashHistory.push({
pathname: location.pathname,
query: {key: noteHash}
query: {key: note.storage + '-' + note.key}
})
ee.emit('list:jump', noteHash)
ee.emit('detail:focus')
this.props.close()
})
@@ -111,7 +106,7 @@ class NewNoteModal extends React.Component {
onKeyDown={(e) => this.handleKeyDown(e)}
>
<div styleName='header'>
<div styleName='title'>{i18n.__('Make a note')}</div>
<div styleName='title'>Make a note</div>
</div>
<ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} />
<div styleName='control'>
@@ -123,8 +118,8 @@ class NewNoteModal extends React.Component {
<i styleName='control-button-icon'
className='fa fa-file-text-o'
/><br />
<span styleName='control-button-label'>{i18n.__('Markdown Note')}</span><br />
<span styleName='control-button-description'>{i18n.__('This format is for creating text documents. Checklists, code blocks and Latex blocks are available.')}</span>
<span styleName='control-button-label'>Markdown Note</span><br />
<span styleName='control-button-description'>This format is for creating text documents. Checklists, code blocks and Latex blocks are available.</span>
</button>
<button styleName='control-button'
@@ -135,13 +130,13 @@ class NewNoteModal extends React.Component {
<i styleName='control-button-icon'
className='fa fa-code'
/><br />
<span styleName='control-button-label'>{i18n.__('Snippet Note')}</span><br />
<span styleName='control-button-description'>{i18n.__('This format is for creating code snippets. Multiple snippets can be grouped into a single note.')}
<span styleName='control-button-label'>Snippet Note</span><br />
<span styleName='control-button-description'>This format is for creating code snippets. Multiple snippets can be grouped into a single note.
</span>
</button>
</div>
<div styleName='description'><i className='fa fa-arrows-h' />{i18n.__('Tab to switch format')}</div>
<div styleName='description'><i className='fa fa-arrows-h' /> Tab to switch format</div>
</div>
)

View File

@@ -1,199 +0,0 @@
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './ConfigTab.styl'
import ConfigManager from 'browser/main/lib/ConfigManager'
import store from 'browser/main/store'
import PropTypes from 'prop-types'
import _ from 'lodash'
import i18n from 'browser/lib/i18n'
const electron = require('electron')
const { shell } = electron
const ipc = electron.ipcRenderer
class Blog extends React.Component {
constructor (props) {
super(props)
this.state = {
config: props.config,
BlogAlert: null
}
}
handleLinkClick (e) {
shell.openExternal(e.currentTarget.href)
e.preventDefault()
}
clearMessage () {
_.debounce(() => {
this.setState({
BlogAlert: null
})
}, 2000)()
}
componentDidMount () {
this.handleSettingDone = () => {
this.setState({BlogAlert: {
type: 'success',
message: i18n.__('Successfully applied!')
}})
}
this.handleSettingError = (err) => {
this.setState({BlogAlert: {
type: 'error',
message: err.message != null ? err.message : i18n.__('Error occurs!')
}})
}
this.oldBlog = this.state.config.blog
ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
ipc.addListener('APP_SETTING_ERROR', this.handleSettingError)
}
handleBlogChange (e) {
const { config } = this.state
config.blog = {
password: !_.isNil(this.refs.passwordInput) ? this.refs.passwordInput.value : config.blog.password,
username: !_.isNil(this.refs.usernameInput) ? this.refs.usernameInput.value : config.blog.username,
token: !_.isNil(this.refs.tokenInput) ? this.refs.tokenInput.value : config.blog.token,
authMethod: this.refs.authMethodDropdown.value,
address: this.refs.addressInput.value,
type: this.refs.typeDropdown.value
}
this.setState({
config
})
if (_.isEqual(this.oldBlog, config.blog)) {
this.props.haveToSave()
} else {
this.props.haveToSave({
tab: 'Blog',
type: 'warning',
message: i18n.__('You have to save!')
})
}
}
handleSaveButtonClick (e) {
const newConfig = {
blog: this.state.config.blog
}
ConfigManager.set(newConfig)
store.dispatch({
type: 'SET_UI',
config: newConfig
})
this.clearMessage()
this.props.haveToSave()
}
render () {
const {config, BlogAlert} = this.state
const blogAlertElement = BlogAlert != null
? <p className={`alert ${BlogAlert.type}`}>
{BlogAlert.message}
</p>
: null
return (
<div styleName='root'>
<div styleName='group'>
<div styleName='group-header'>{i18n.__('Blog')}</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Blog Type')}
</div>
<div styleName='group-section-control'>
<select
value={config.blog.type}
ref='typeDropdown'
onChange={(e) => this.handleBlogChange(e)}
>
<option value='wordpress' key='wordpress'>{i18n.__('wordpress')}</option>
</select>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Blog Address')}</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
onChange={(e) => this.handleBlogChange(e)}
ref='addressInput'
value={config.blog.address}
type='text'
/>
</div>
</div>
<div styleName='group-control'>
<button styleName='group-control-rightButton'
onClick={(e) => this.handleSaveButtonClick(e)}>{i18n.__('Save')}
</button>
{blogAlertElement}
</div>
</div>
<div styleName='group-header2'>{i18n.__('Auth')}</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Authentication Method')}
</div>
<div styleName='group-section-control'>
<select
value={config.blog.authMethod}
ref='authMethodDropdown'
onChange={(e) => this.handleBlogChange(e)}
>
<option value='JWT' key='JWT'>{i18n.__('JWT')}</option>
<option value='USER' key='USER'>{i18n.__('USER')}</option>
</select>
</div>
</div>
{ config.blog.authMethod === 'JWT' &&
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Token')}</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
onChange={(e) => this.handleBlogChange(e)}
ref='tokenInput'
value={config.blog.token}
type='text' />
</div>
</div>
}
{ config.blog.authMethod === 'USER' &&
<div>
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('UserName')}</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
onChange={(e) => this.handleBlogChange(e)}
ref='usernameInput'
value={config.blog.username}
type='text' />
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Password')}</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
onChange={(e) => this.handleBlogChange(e)}
ref='passwordInput'
value={config.blog.password}
type='password' />
</div>
</div>
</div>
}
</div>
)
}
}
Blog.propTypes = {
dispatch: PropTypes.func,
haveToSave: PropTypes.func
}
export default CSSModules(Blog, styles)

View File

@@ -1,7 +1,6 @@
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './Crowdfunding.styl'
import i18n from 'browser/lib/i18n'
const electron = require('electron')
const { shell } = electron
@@ -22,22 +21,22 @@ class Crowdfunding extends React.Component {
render () {
return (
<div styleName='root'>
<div styleName='header'>{i18n.__('Crowdfunding')}</div>
<p>{i18n.__('Dear everyone,')}</p>
<div styleName='header'>Crowdfunding</div>
<p>Dear everyone,</p>
<br />
<p>{i18n.__('Thank you for using Boostnote!')}</p>
<p>{i18n.__('Boostnote is used in about 200 different countries and regions by an awesome community of developers.')}</p>
<p>Thank you for using Boostnote!</p>
<p>Boostnote is used in about 200 different countries and regions by an awesome community of developers.</p>
<br />
<p>{i18n.__('To continue supporting this growth, and to satisfy community expectations,')}</p>
<p>{i18n.__('we would like to invest more time and resources in this project.')}</p>
<p>To continue supporting this growth, and to satisfy community expectations,</p>
<p>we would like to invest more time and resources in this project.</p>
<br />
<p>{i18n.__('If you like this project and see its potential, you can help by supporting us on OpenCollective!')}</p>
<p>If you like this project and see its potential, you can help by supporting us on OpenCollective!</p>
<br />
<p>{i18n.__('Thanks,')}</p>
<p>{i18n.__('Boostnote maintainers')}</p>
<p>Thanks,</p>
<p>Boostnote maintainers</p>
<br />
<button styleName='cf-link'>
<a href='https://opencollective.com/boostnoteio' onClick={(e) => this.handleLinkClick(e)}>{i18n.__('Support via OpenCollective')}</a>
<a href='https://opencollective.com/boostnoteio' onClick={(e) => this.handleLinkClick(e)}>Support via OpenCollective</a>
</button>
</div>
)

View File

@@ -7,7 +7,6 @@ import dataApi from 'browser/main/lib/dataApi'
import store from 'browser/main/store'
import { SketchPicker } from 'react-color'
import { SortableElement, SortableHandle } from 'react-sortable-hoc'
import i18n from 'browser/lib/i18n'
class FolderItem extends React.Component {
constructor (props) {
@@ -180,18 +179,18 @@ class FolderItem extends React.Component {
return (
<div styleName='folderItem'>
<div styleName='folderItem-left'>
{i18n.__('Are you sure to ')} <span styleName='folderItem-left-danger'>{i18n.__(' delete')}</span> {i18n.__('this folder?')}
Are you sure to <span styleName='folderItem-left-danger'>delete</span> this folder?
</div>
<div styleName='folderItem-right'>
<button styleName='folderItem-right-dangerButton'
onClick={(e) => this.handleDeleteConfirmButtonClick(e)}
>
{i18n.__('Confirm')}
Confirm
</button>
<button styleName='folderItem-right-button'
onClick={(e) => this.handleCancelButtonClick(e)}
>
{i18n.__('Cancel')}
Cancel
</button>
</div>
</div>
@@ -232,12 +231,12 @@ class FolderItem extends React.Component {
<button styleName='folderItem-right-button'
onClick={(e) => this.handleEditButtonClick(e)}
>
{i18n.__('Edit')}
Edit
</button>
<button styleName='folderItem-right-button'
onClick={(e) => this.handleDeleteButtonClick(e)}
>
{i18n.__('Delete')}
Delete
</button>
</div>
</div>

View File

@@ -6,7 +6,6 @@ import styles from './FolderList.styl'
import store from 'browser/main/store'
import FolderItem from './FolderItem'
import { SortableContainer } from 'react-sortable-hoc'
import i18n from 'browser/lib/i18n'
class FolderList extends React.Component {
render () {
@@ -25,7 +24,7 @@ class FolderList extends React.Component {
<div styleName='folderList'>
{folderList.length > 0
? folderList
: <div styleName='folderList-empty'>{i18n.__('No Folders')}</div>
: <div styleName='folderList-empty'>No Folders</div>
}
</div>
)

View File

@@ -5,7 +5,6 @@ import styles from './ConfigTab.styl'
import ConfigManager from 'browser/main/lib/ConfigManager'
import store from 'browser/main/store'
import _ from 'lodash'
import i18n from 'browser/lib/i18n'
const electron = require('electron')
const ipc = electron.ipcRenderer
@@ -24,13 +23,13 @@ class HotkeyTab extends React.Component {
this.handleSettingDone = () => {
this.setState({keymapAlert: {
type: 'success',
message: i18n.__('Successfully applied!')
message: 'Successfully applied!'
}})
}
this.handleSettingError = (err) => {
this.setState({keymapAlert: {
type: 'error',
message: err.message != null ? err.message : i18n.__('Error occurs!')
message: err.message != null ? err.message : 'Error occurs!'
}})
}
this.oldHotkey = this.state.config.hotkey
@@ -78,7 +77,7 @@ class HotkeyTab extends React.Component {
this.props.haveToSave({
tab: 'Hotkey',
type: 'warning',
message: i18n.__('You have to save!')
message: 'You have to save!'
})
}
}
@@ -103,9 +102,9 @@ class HotkeyTab extends React.Component {
return (
<div styleName='root'>
<div styleName='group'>
<div styleName='group-header'>{i18n.__('Hotkeys')}</div>
<div styleName='group-header'>Hotkeys</div>
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Show/Hide Boostnote')}</div>
<div styleName='group-section-label'>Show/Hide Boostnote</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
onChange={(e) => this.handleHotkeyChange(e)}
@@ -125,7 +124,7 @@ class HotkeyTab extends React.Component {
}
</button>
<button styleName='group-control-rightButton'
onClick={(e) => this.handleSaveButtonClick(e)}>{i18n.__('Save')}
onClick={(e) => this.handleSaveButtonClick(e)}>Save
</button>
{keymapAlertElement}
</div>

View File

@@ -5,7 +5,6 @@ import ConfigManager from 'browser/main/lib/ConfigManager'
import store from 'browser/main/store'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import _ from 'lodash'
import i18n from 'browser/lib/i18n'
const electron = require('electron')
const { shell, remote } = electron
@@ -39,11 +38,11 @@ class InfoTab extends React.Component {
if (!newConfig.amaEnabled) {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('DISABLE_AMA')
this.setState({
amaMessage: i18n.__('We hope we will gain your trust')
amaMessage: 'We hope we will gain your trust'
})
} else {
this.setState({
amaMessage: i18n.__('Thank\'s for trusting us')
amaMessage: 'Thank\'s for trust us'
})
}
@@ -70,48 +69,48 @@ class InfoTab extends React.Component {
return (
<div styleName='root'>
<div styleName='header--sub'>{i18n.__('Community')}</div>
<div styleName='header--sub'>Community</div>
<div styleName='top'>
<ul styleName='list'>
<li>
<a href='https://boostnote.io/#subscribe'
onClick={(e) => this.handleLinkClick(e)}
>{i18n.__('Subscribe to Newsletter')}</a>
>Subscribe to Newsletter</a>
</li>
<li>
<a href='https://github.com/BoostIO/Boostnote/issues'
onClick={(e) => this.handleLinkClick(e)}
>{i18n.__('GitHub')}</a>
>GitHub</a>
</li>
<li>
<a href='https://boostlog.io/@junp1234'
<a href='https://medium.com/boostnote'
onClick={(e) => this.handleLinkClick(e)}
>{i18n.__('Blog')}</a>
>Blog</a>
</li>
<li>
<a href='https://www.facebook.com/groups/boostnote'
onClick={(e) => this.handleLinkClick(e)}
>{i18n.__('Facebook Group')}</a>
>Facebook Group</a>
</li>
<li>
<a href='https://twitter.com/boostnoteapp'
onClick={(e) => this.handleLinkClick(e)}
>{i18n.__('Twitter')}</a>
>Twitter</a>
</li>
</ul>
</div>
<hr />
<div styleName='header--sub'>{i18n.__('About')}</div>
<div styleName='header--sub'>About</div>
<div styleName='top'>
<div styleName='icon-space'>
<img styleName='icon' src='../resources/app.png' width='92' height='92' />
<div styleName='icon-right'>
<div styleName='appId'>{i18n.__('Boostnote')} {appVersion}</div>
<div styleName='appId'>Boostnote {appVersion}</div>
<div styleName='description'>
{i18n.__('An open source note-taking app made for programmers just like you.')}
An open source note-taking app made for programmers just like you.
</div>
</div>
</div>
@@ -121,35 +120,35 @@ class InfoTab extends React.Component {
<li>
<a href='https://boostnote.io'
onClick={(e) => this.handleLinkClick(e)}
>{i18n.__('Website')}</a>
>Website</a>
</li>
<li>
<a href='https://github.com/BoostIO/Boostnote/blob/master/docs/build.md'
onClick={(e) => this.handleLinkClick(e)}
>{i18n.__('Development')}</a>{i18n.__(' : Development configurations for Boostnote.')}
>Development</a> : Development configurations for Boostnote.
</li>
<li styleName='cc'>
{i18n.__('Copyright (C) 2017 - 2018 BoostIO')}
Copyright (C) 2017 Maisin&Co.
</li>
<li styleName='cc'>
{i18n.__('License: GPL v3')}
License: GPL v3
</li>
</ul>
<hr styleName='separate-line' />
<div styleName='policy'>{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.__('You can see how it works on ')}<a href='https://github.com/BoostIO/Boostnote' onClick={(e) => this.handleLinkClick(e)}>GitHub</a>.</div>
<div styleName='policy'>Analytics</div>
<div>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>You can see how it works on <a href='https://github.com/BoostIO/Boostnote' onClick={(e) => this.handleLinkClick(e)}>GitHub</a>.</div>
<br />
<div>{i18n.__('You can choose to enable or disable this option.')}</div>
<div>You can choose to enable or disable this option.</div>
<input onChange={(e) => this.handleConfigChange(e)}
checked={this.state.config.amaEnabled}
ref='amaEnabled'
type='checkbox'
/>
{i18n.__('Enable analytics to help improve Boostnote')}<br />
<button styleName='policy-submit' onClick={(e) => this.handleSaveButtonClick(e)}>{i18n.__('Save')}</button>
Enable analytics to help improve Boostnote<br />
<button styleName='policy-submit' onClick={(e) => this.handleSaveButtonClick(e)}>Save</button>
<br />
{this.infoMessage()}
</div>

View File

@@ -6,7 +6,6 @@ import consts from 'browser/lib/consts'
import dataApi from 'browser/main/lib/dataApi'
import store from 'browser/main/store'
import FolderList from './FolderList'
import i18n from 'browser/lib/i18n'
const { shell, remote } = require('electron')
const { dialog } = remote
@@ -47,9 +46,9 @@ class StorageItem extends React.Component {
handleUnlinkButtonClick (e) {
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: i18n.__('Unlink Storage'),
detail: i18n.__('Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.'),
buttons: [i18n.__('Unlink'), i18n.__('Cancel')]
message: 'Unlink Storage',
detail: 'Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.',
buttons: ['Unlink', 'Cancel']
})
if (index === 0) {
@@ -128,7 +127,7 @@ class StorageItem extends React.Component {
<i className='fa fa-plus' />
<span styleName='header-control-button-tooltip'
style={{left: -20}}
>{i18n.__('Add Folder')}</span>
>Add Folder</span>
</button>
<button styleName='header-control-button'
onClick={(e) => this.handleExternalButtonClick(e)}
@@ -136,7 +135,7 @@ class StorageItem extends React.Component {
<i className='fa fa-external-link' />
<span styleName='header-control-button-tooltip'
style={{left: -50}}
>{i18n.__('Open Storage folder')}</span>
>Open Storage folder</span>
</button>
<button styleName='header-control-button'
onClick={(e) => this.handleUnlinkButtonClick(e)}
@@ -144,7 +143,7 @@ class StorageItem extends React.Component {
<i className='fa fa-unlink' />
<span styleName='header-control-button-tooltip'
style={{left: -10}}
>{i18n.__('Unlink')}</span>
>Unlink</span>
</button>
</div>
</div>

View File

@@ -4,7 +4,6 @@ import CSSModules from 'browser/lib/CSSModules'
import styles from './StoragesTab.styl'
import dataApi from 'browser/main/lib/dataApi'
import StorageItem from './StorageItem'
import i18n from 'browser/lib/i18n'
const electron = require('electron')
const { shell, remote } = electron
@@ -15,7 +14,7 @@ function browseFolder () {
const defaultPath = remote.app.getPath('home')
return new Promise((resolve, reject) => {
dialog.showOpenDialog({
title: i18n.__('Select Directory'),
title: 'Select Directory',
defaultPath,
properties: ['openDirectory', 'createDirectory']
}, function (targetPaths) {
@@ -70,16 +69,16 @@ class StoragesTab extends React.Component {
})
return (
<div styleName='list'>
<div styleName='header'>{i18n.__('Storages')}</div>
<div styleName='header'>Storages</div>
{storageList.length > 0
? storageList
: <div styleName='list-empty'>{i18n.__('No storage found.')}</div>
: <div styleName='list-empty'>No storage found.</div>
}
<div styleName='list-control'>
<button styleName='list-control-addStorageButton'
onClick={(e) => this.handleAddStorageButton(e)}
>
<i className='fa fa-plus' /> {i18n.__('Add Storage Location')}
<i className='fa fa-plus' /> Add Storage Location
</button>
</div>
</div>
@@ -141,13 +140,13 @@ class StoragesTab extends React.Component {
return (
<div styleName='addStorage'>
<div styleName='addStorage-header'>{i18n.__('Add Storage')}</div>
<div styleName='addStorage-header'>Add Storage</div>
<div styleName='addStorage-body'>
<div styleName='addStorage-body-section'>
<div styleName='addStorage-body-section-label'>
{i18n.__('Name')}
Name
</div>
<div styleName='addStorage-body-section-name'>
<input styleName='addStorage-body-section-name-input'
@@ -159,25 +158,25 @@ class StoragesTab extends React.Component {
</div>
<div styleName='addStorage-body-section'>
<div styleName='addStorage-body-section-label'>{i18n.__('Type')}</div>
<div styleName='addStorage-body-section-label'>Type</div>
<div styleName='addStorage-body-section-type'>
<select styleName='addStorage-body-section-type-select'
value={this.state.newStorage.type}
readOnly
>
<option value='FILESYSTEM'>{i18n.__('File System')}</option>
<option value='FILESYSTEM'>File System</option>
</select>
<div styleName='addStorage-body-section-type-description'>
{i18n.__('Setting up 3rd-party cloud storage integration:')}{' '}
Setting up 3rd-party cloud storage integration:{' '}
<a href='https://github.com/BoostIO/Boostnote/wiki/Cloud-Syncing-and-Backup'
onClick={(e) => this.handleLinkClick(e)}
>{i18n.__('Cloud-Syncing-and-Backup')}</a>
>Cloud-Syncing-and-Backup</a>
</div>
</div>
</div>
<div styleName='addStorage-body-section'>
<div styleName='addStorage-body-section-label'>{i18n.__('Location')}
<div styleName='addStorage-body-section-label'>Location
</div>
<div styleName='addStorage-body-section-path'>
<input styleName='addStorage-body-section-path-input'
@@ -197,10 +196,10 @@ class StoragesTab extends React.Component {
<div styleName='addStorage-body-control'>
<button styleName='addStorage-body-control-createButton'
onClick={(e) => this.handleAddStorageCreateButton(e)}
>{i18n.__('Add')}</button>
>Add</button>
<button styleName='addStorage-body-control-cancelButton'
onClick={(e) => this.handleAddStorageCancelButton(e)}
>{i18n.__('Cancel')}</button>
>Cancel</button>
</div>
</div>

View File

@@ -9,7 +9,6 @@ import ReactCodeMirror from 'react-codemirror'
import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir'
import _ from 'lodash'
import i18n from 'browser/lib/i18n'
const OSX = global.process.platform === 'darwin'
@@ -30,13 +29,13 @@ class UiTab extends React.Component {
this.handleSettingDone = () => {
this.setState({UiAlert: {
type: 'success',
message: i18n.__('Successfully applied!')
message: 'Successfully applied!'
}})
}
this.handleSettingError = (err) => {
this.setState({UiAlert: {
type: 'error',
message: err.message != null ? err.message : i18n.__('Error occurs!')
message: err.message != null ? err.message : 'Error occurs!'
}})
}
ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
@@ -62,7 +61,6 @@ class UiTab extends React.Component {
const newConfig = {
ui: {
theme: this.refs.uiTheme.value,
language: this.refs.uiLanguage.value,
showCopyNotification: this.refs.showCopyNotification.checked,
confirmDeletion: this.refs.confirmDeletion.checked,
disableDirectWrite: this.refs.uiD2w != null
@@ -75,13 +73,10 @@ class UiTab extends React.Component {
fontFamily: this.refs.editorFontFamily.value,
indentType: this.refs.editorIndentType.value,
indentSize: this.refs.editorIndentSize.value,
enableRulers: this.refs.enableEditorRulers.value === 'true',
rulers: this.refs.editorRulers.value.replace(/[^0-9,]/g, '').split(','),
displayLineNumbers: this.refs.editorDisplayLineNumbers.checked,
switchPreview: this.refs.editorSwitchPreview.value,
keyMap: this.refs.editorKeyMap.value,
scrollPastEnd: this.refs.scrollPastEnd.checked,
fetchUrlTitle: this.refs.editorFetchUrlTitle.checked
scrollPastEnd: this.refs.scrollPastEnd.checked
},
preview: {
fontSize: this.refs.previewFontSize.value,
@@ -92,9 +87,7 @@ class UiTab extends React.Component {
latexInlineClose: this.refs.previewLatexInlineClose.value,
latexBlockOpen: this.refs.previewLatexBlockOpen.value,
latexBlockClose: this.refs.previewLatexBlockClose.value,
scrollPastEnd: this.refs.previewScrollPastEnd.checked,
smartQuotes: this.refs.previewSmartQuotes.checked,
sanitize: this.refs.previewSanitize.value
scrollPastEnd: this.refs.previewScrollPastEnd.checked
}
}
@@ -112,7 +105,7 @@ class UiTab extends React.Component {
this.props.haveToSave({
tab: 'UI',
type: 'warning',
message: i18n.__('You have to save!')
message: 'You have to save!'
})
}
})
@@ -154,53 +147,25 @@ class UiTab extends React.Component {
const themes = consts.THEMES
const { config, codemirrorTheme } = this.state
const codemirrorSampleCode = 'function iamHappy (happy) {\n\tif (happy) {\n\t console.log("I am Happy!")\n\t} else {\n\t console.log("I am not Happy!")\n\t}\n};'
const enableEditRulersStyle = config.editor.enableRulers ? 'block' : 'none'
return (
<div styleName='root'>
<div styleName='group'>
<div styleName='group-header'>{i18n.__('Interface')}</div>
<div styleName='group-header'>Interface</div>
<div styleName='group-section'>
{i18n.__('Interface Theme')}
Interface Theme
<div styleName='group-section-control'>
<select value={config.ui.theme}
onChange={(e) => this.handleUIChange(e)}
ref='uiTheme'
>
<option value='default'>{i18n.__('Default')}</option>
<option value='white'>{i18n.__('White')}</option>
<option value='solarized-dark'>{i18n.__('Solarized Dark')}</option>
<option value='dark'>{i18n.__('Dark')}</option>
<option value='default'>Default</option>
<option value='white'>White</option>
<option value='solarized-dark'>Solarized Dark</option>
<option value='dark'>Dark</option>
</select>
</div>
</div>
<div styleName='group-section'>
{i18n.__('Language')}
<div styleName='group-section-control'>
<select value={config.ui.language}
onChange={(e) => this.handleUIChange(e)}
ref='uiLanguage'
>
<option value='sq'>{i18n.__('Albanian')}</option>
<option value='zh-CN'>{i18n.__('Chinese (zh-CN)')}</option>
<option value='zh-TW'>{i18n.__('Chinese (zh-TW)')}</option>
<option value='da'>{i18n.__('Danish')}</option>
<option value='en'>{i18n.__('English')}</option>
<option value='fr'>{i18n.__('French')}</option>
<option value='de'>{i18n.__('German')}</option>
<option value='hu'>{i18n.__('Hungarian')}</option>
<option value='ja'>{i18n.__('Japanese')}</option>
<option value='ko'>{i18n.__('Korean')}</option>
<option value='no'>{i18n.__('Norwegian')}</option>
<option value='pl'>{i18n.__('Polish')}</option>
<option value='pt'>{i18n.__('Portuguese')}</option>
<option value='ru'>{i18n.__('Russian')}</option>
<option value='es'>{i18n.__('Spanish')}</option>
</select>
</div>
</div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
@@ -208,7 +173,7 @@ class UiTab extends React.Component {
ref='showCopyNotification'
type='checkbox'
/>&nbsp;
{i18n.__('Show "Saved to Clipboard" notification when copying')}
Show &quot;Saved to Clipboard&quot; notification when copying
</label>
</div>
<div styleName='group-checkBoxSection'>
@@ -218,7 +183,7 @@ class UiTab extends React.Component {
ref='confirmDeletion'
type='checkbox'
/>&nbsp;
{i18n.__('Show a confirmation dialog when deleting notes')}
Show a confirmation dialog when deleting notes
</label>
</div>
{
@@ -240,7 +205,7 @@ class UiTab extends React.Component {
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Editor Theme')}
Editor Theme
</div>
<div styleName='group-section-control'>
<select value={config.editor.theme}
@@ -260,7 +225,7 @@ class UiTab extends React.Component {
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Editor Font Size')}
Editor Font Size
</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
@@ -273,7 +238,7 @@ class UiTab extends React.Component {
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Editor Font Family')}
Editor Font Family
</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
@@ -286,7 +251,7 @@ class UiTab extends React.Component {
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Editor Indent Style')}
Editor Indent Style
</div>
<div styleName='group-section-control'>
<select value={config.editor.indentSize}
@@ -302,70 +267,41 @@ class UiTab extends React.Component {
ref='editorIndentType'
onChange={(e) => this.handleUIChange(e)}
>
<option value='space'>{i18n.__('Spaces')}</option>
<option value='tab'>{i18n.__('Tabs')}</option>
<option value='space'>Spaces</option>
<option value='tab'>Tabs</option>
</select>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Editor Rulers')}
</div>
<div styleName='group-section-control'>
<div>
<select value={config.editor.enableRulers}
ref='enableEditorRulers'
onChange={(e) => this.handleUIChange(e)}
>
<option value='true'>
{i18n.__('Enable')}
</option>
<option value='false'>
{i18n.__('Disable')}
</option>
</select>
</div>
<input styleName='group-section-control-input'
style={{ display: enableEditRulersStyle }}
ref='editorRulers'
value={config.editor.rulers}
onChange={(e) => this.handleUIChange(e)}
type='text'
/>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Switch to Preview')}
Switch to Preview
</div>
<div styleName='group-section-control'>
<select value={config.editor.switchPreview}
ref='editorSwitchPreview'
onChange={(e) => this.handleUIChange(e)}
>
<option value='BLUR'>{i18n.__('When Editor Blurred')}</option>
<option value='DBL_CLICK'>{i18n.__('When Editor Blurred, Edit On Double Click')}</option>
<option value='RIGHTCLICK'>{i18n.__('On Right Click')}</option>
<option value='BLUR'>When Editor Blurred</option>
<option value='RIGHTCLICK'>On Right Click</option>
</select>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Editor Keymap')}
Editor Keymap
</div>
<div styleName='group-section-control'>
<select value={config.editor.keyMap}
ref='editorKeyMap'
onChange={(e) => this.handleUIChange(e)}
>
<option value='sublime'>{i18n.__('default')}</option>
<option value='vim'>{i18n.__('vim')}</option>
<option value='emacs'>{i18n.__('emacs')}</option>
<option value='sublime'>default</option>
<option value='vim'>vim</option>
<option value='emacs'>emacs</option>
</select>
<p styleName='note-for-keymap'>{i18n.__('⚠️ Please restart boostnote after you change the keymap')}</p>
<p styleName='note-for-keymap'> Please restart boostnote after you change the keymap</p>
</div>
</div>
@@ -376,7 +312,7 @@ class UiTab extends React.Component {
ref='editorDisplayLineNumbers'
type='checkbox'
/>&nbsp;
{i18n.__('Show line numbers in the editor')}
Show line numbers in the editor
</label>
</div>
@@ -387,25 +323,14 @@ class UiTab extends React.Component {
ref='scrollPastEnd'
type='checkbox'
/>&nbsp;
{i18n.__('Allow editor to scroll past the last line')}
Allow editor to scroll past the last line
</label>
</div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.editor.fetchUrlTitle}
ref='editorFetchUrlTitle'
type='checkbox'
/>&nbsp;
{i18n.__('Bring in web page title when pasting URL on editor')}
</label>
</div>
<div styleName='group-header2'>{i18n.__('Preview')}</div>
<div styleName='group-header2'>Preview</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Preview Font Size')}
Preview Font Size
</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
@@ -418,7 +343,7 @@ class UiTab extends React.Component {
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Preview Font Family')}
Preview Font Family
</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
@@ -430,7 +355,7 @@ class UiTab extends React.Component {
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Code block Theme')}</div>
<div styleName='group-section-label'>Code block Theme</div>
<div styleName='group-section-control'>
<select value={config.preview.codeBlockTheme}
ref='previewCodeBlockTheme'
@@ -451,7 +376,7 @@ class UiTab extends React.Component {
ref='previewScrollPastEnd'
type='checkbox'
/>&nbsp;
{i18n.__('Allow preview to scroll past the last line')}
Allow preview to scroll past the last line
</label>
</div>
<div styleName='group-checkBoxSection'>
@@ -461,39 +386,12 @@ class UiTab extends React.Component {
ref='previewLineNumber'
type='checkbox'
/>&nbsp;
{i18n.__('Show line numbers for preview code blocks')}
Show line numbers for preview code blocks
</label>
</div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.preview.smartQuotes}
ref='previewSmartQuotes'
type='checkbox'
/>&nbsp;
Enable smart quotes
</label>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Sanitization')}
</div>
<div styleName='group-section-control'>
<select value={config.preview.sanitize}
ref='previewSanitize'
onChange={(e) => this.handleUIChange(e)}
>
<option value='STRICT'> {i18n.__('Only allow secure html tags (recommended)')}
</option>
<option value='ALLOW_STYLES'> {i18n.__('Allow styles')}</option>
<option value='NONE'> {i18n.__('Allow dangerous html tags')}</option>
</select>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('LaTeX Inline Open Delimiter')}
LaTeX Inline Open Delimiter
</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
@@ -506,7 +404,7 @@ class UiTab extends React.Component {
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('LaTeX Inline Close Delimiter')}
LaTeX Inline Close Delimiter
</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
@@ -519,7 +417,7 @@ class UiTab extends React.Component {
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('LaTeX Block Open Delimiter')}
LaTeX Block Open Delimiter
</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
@@ -532,7 +430,7 @@ class UiTab extends React.Component {
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('LaTeX Block Close Delimiter')}
LaTeX Block Close Delimiter
</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
@@ -546,7 +444,7 @@ class UiTab extends React.Component {
<div styleName='group-control'>
<button styleName='group-control-rightButton'
onClick={(e) => this.handleSaveUIClick(e)}>{i18n.__('Save')}
onClick={(e) => this.handleSaveUIClick(e)}>Save
</button>
{UiAlertElement}
</div>

View File

@@ -6,13 +6,11 @@ import UiTab from './UiTab'
import InfoTab from './InfoTab'
import Crowdfunding from './Crowdfunding'
import StoragesTab from './StoragesTab'
import Blog from './Blog'
import ModalEscButton from 'browser/components/ModalEscButton'
import CSSModules from 'browser/lib/CSSModules'
import styles from './PreferencesModal.styl'
import RealtimeNotification from 'browser/components/RealtimeNotification'
import _ from 'lodash'
import i18n from 'browser/lib/i18n'
class Preferences extends React.Component {
constructor (props) {
@@ -21,8 +19,7 @@ class Preferences extends React.Component {
this.state = {
currentTab: 'STORAGES',
UIAlert: '',
HotkeyAlert: '',
BlogAlert: ''
HotkeyAlert: ''
}
}
@@ -78,14 +75,6 @@ class Preferences extends React.Component {
return (
<Crowdfunding />
)
case 'BLOG':
return (
<Blog
dispatch={dispatch}
config={config}
haveToSave={alert => this.setState({BlogAlert: alert})}
/>
)
case 'STORAGES':
default:
return (
@@ -118,12 +107,11 @@ class Preferences extends React.Component {
const content = this.renderContent()
const tabs = [
{target: 'STORAGES', label: i18n.__('Storage')},
{target: 'HOTKEY', label: i18n.__('Hotkeys'), Hotkey: this.state.HotkeyAlert},
{target: 'UI', label: i18n.__('Interface'), UI: this.state.UIAlert},
{target: 'INFO', label: i18n.__('About')},
{target: 'CROWDFUNDING', label: i18n.__('Crowdfunding')},
{target: 'BLOG', label: i18n.__('Blog'), Blog: this.state.BlogAlert}
{target: 'STORAGES', label: 'Storage'},
{target: 'HOTKEY', label: 'Hotkeys', Hotkey: this.state.HotkeyAlert},
{target: 'UI', label: 'Interface', UI: this.state.UIAlert},
{target: 'INFO', label: 'About'},
{target: 'CROWDFUNDING', label: 'Crowdfunding'}
]
const navButtons = tabs.map((tab) => {
@@ -152,7 +140,7 @@ class Preferences extends React.Component {
onKeyDown={(e) => this.handleKeyDown(e)}
>
<div styleName='top-bar'>
<p>{i18n.__('Your preferences for Boostnote')}</p>
<p>Your preferences for Boostnote</p>
</div>
<ModalEscButton handleEscButtonClick={(e) => this.handleEscButtonClick(e)} />
<div styleName='nav'>

View File

@@ -5,7 +5,6 @@ import styles from './RenameFolderModal.styl'
import dataApi from 'browser/main/lib/dataApi'
import store from 'browser/main/store'
import ModalEscButton from 'browser/components/ModalEscButton'
import i18n from 'browser/lib/i18n'
class RenameFolderModal extends React.Component {
constructor (props) {
@@ -73,13 +72,13 @@ class RenameFolderModal extends React.Component {
onKeyDown={(e) => this.handleKeyDown(e)}
>
<div styleName='header'>
<div styleName='title'>{i18n.__('Rename Folder')}</div>
<div styleName='title'>Rename Folder</div>
</div>
<ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} />
<div styleName='control'>
<input styleName='control-input'
placeholder={i18n.__('Folder Name')}
placeholder='Folder Name'
ref='name'
value={this.state.name}
onChange={(e) => this.handleChange(e)}

View File

@@ -27,7 +27,7 @@ function data (state = defaultDataMap(), action) {
action.notes.some((note) => {
if (note === undefined) return true
const uniqueKey = note.key
const uniqueKey = note.storage + '-' + note.key
const folderKey = note.storage + '-' + note.folder
state.noteMap.set(uniqueKey, note)
@@ -66,7 +66,7 @@ function data (state = defaultDataMap(), action) {
case 'UPDATE_NOTE':
{
const note = action.note
const uniqueKey = note.key
const uniqueKey = note.storage + '-' + note.key
const folderKey = note.storage + '-' + note.folder
const oldNote = state.noteMap.get(uniqueKey)
@@ -162,9 +162,9 @@ function data (state = defaultDataMap(), action) {
case 'MOVE_NOTE':
{
const originNote = action.originNote
const originKey = originNote.key
const originKey = originNote.storage + '-' + originNote.key
const note = action.note
const uniqueKey = note.key
const uniqueKey = note.storage + '-' + note.key
const folderKey = note.storage + '-' + note.folder
const oldNote = state.noteMap.get(uniqueKey)
@@ -297,7 +297,7 @@ function data (state = defaultDataMap(), action) {
}
case 'DELETE_NOTE':
{
const uniqueKey = action.noteKey
const uniqueKey = action.storageKey + '-' + action.noteKey
const targetNote = state.noteMap.get(uniqueKey)
state = Object.assign({}, state)
@@ -423,7 +423,7 @@ function data (state = defaultDataMap(), action) {
state.folderNoteMap = new Map(state.folderNoteMap)
state.tagNoteMap = new Map(state.tagNoteMap)
action.notes.forEach((note) => {
const uniqueKey = note.key
const uniqueKey = note.storage + '-' + note.key
const folderKey = note.storage + '-' + note.folder
state.noteMap.set(uniqueKey, note)
@@ -483,7 +483,7 @@ function data (state = defaultDataMap(), action) {
state.tagNoteMap = new Map(state.tagNoteMap)
state.starredSet = new Set(state.starredSet)
notes.forEach((note) => {
const noteKey = note.key
const noteKey = storage.key + '-' + note.key
state.noteMap.delete(noteKey)
state.starredSet.delete(noteKey)
note.tags.forEach((tag) => {

View File

@@ -5,7 +5,7 @@ $danger-color = #c9302c
$danger-lighten-color = lighten(#c9302c, 5%)
// Layouts
$statusBar-height = 22px
$statusBar-height = 0px
$sideNav-width = 200px
$sideNav--folded-width = 44px
$topBar-height = 60px
@@ -347,4 +347,4 @@ modalSolarizedDark()
width 100%
background-color $ui-solarized-dark-backgroundColor
overflow hidden
border-radius $modal-border-radius
border-radius $modal-border-radius

View File

@@ -9,7 +9,7 @@ Thank you for your help in advance.
### About copyright of Pull Request
If you make a pull request, It means you agree to transfer the copyright of the code changes to BoostIO.
If you make a pull request, It means you agree to transfer the copyright of the code changes to Maisin&Co.
It doesn't mean Boostnote will become a paid app. If we want to earn some money, We will try other way, which is some kind of cloud storage, Mobile app integration or some SPECIAL features.
Because GPL v3 is too strict to be compatible with any other License, We thought this is needed to replace the license with much freer one(like BSD, MIT) somewhen.
@@ -27,7 +27,7 @@ Because GPL v3 is too strict to be compatible with any other License, We thought
### Об авторских правах Pull Request
Если вы делаете pull request, значит вы согласны передать авторские права на изменения кода в BoostIO.
Если вы делаете pull request, значит вы согласны передать авторские права на изменения кода в Maisin&Co.
Это не означает, что Boostnote станет платным приложением. Если мы захотим заработать немного денег, мы найдем другой способ. Например, использование облачного хранилища, интеграцией мобильных приложений или другими специальными функциями.
Так как лицензия GPL v3 слишком строгая, чтобы быть совместимой с любой другой лицензией, мы думаем, что нужно заменить лицензию на более свободную (например, BSD, MIT).
@@ -45,7 +45,7 @@ Because GPL v3 is too strict to be compatible with any other License, We thought
### Pull Request의 저작권에 관하여
당신이 pull request를 요청하면, 코드 변경에 대한 저작권을 BoostIO에 양도한다는 것에 동의한다는 의미입니다.
당신이 pull request를 요청하면, 코드 변경에 대한 저작권을 Maisin&Co에 양도한다는 것에 동의한다는 의미입니다.
이것은 Boostnote가 유료화가 되는 것을 의미하는 건 아닙니다. 만약 우리가 자금이 필요하다면, 우리는 클라우드 연동, 모바일 앱 통합 혹은 특수한 기능 같은 것을 사용해 수입 창출을 시도할 것입니다.
GPL v3 라이센스는 다른 라이센스와 혼합해 사용하기엔 너무 엄격하므로, 우리는 BSD, MIT 라이센스와 같은 더 자유로운 라이센스로 교체하는 것을 생각하고 있습니다.
@@ -63,7 +63,7 @@ GPL v3 라이센스는 다른 라이센스와 혼합해 사용하기엔 너무
### Pull requestの著作権について
Pull requestをすることはその変化分のコードの著作権をBoostIOに譲渡することに同意することになります。
Pull requestをすることはその変化分のコードの著作権をMaisin&Co.に譲渡することに同意することになります。
アプリケーションのLicenseをいつでも変える選択肢を残したいと思うからです。
これはいずれかBoostnoteが有料の商用アプリになる可能性がある話ではありません。
@@ -83,7 +83,7 @@ Pull requestをすることはその変化分のコードの著作権をBoostIO
感谢您对我们的支持。
### 关于您提供的Pull Request的著作权版权问题
如果您提供了一个Pull Request这表示您将您所修改的代码的著作权移交给BoostIO
如果您提供了一个Pull Request这表示您将您所修改的代码的著作权移交给Maisin&Co
这并不表示Boostnote会成为一个需要付费的软件。如果我们想获得收益我们会尝试一些其他的方法比如说云存储、绑定手机软件等。
因为GPLv3过于严格不能和其他的一些协议兼容所以我们有可能在将来会把BoostNote的协议改为一些较为宽松的协议比如说BSD、MIT。

View File

@@ -1,5 +1,5 @@
# Build
Diese Seite ist auch verfügbar in [Japanisch](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [Koreanisch](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [Russisch](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), [Vereinfachtem Chinesisch](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md), [Französisch](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/build.md) und [Deutsch](https://github.com/BoostIO/Boostnote/blob/master/docs/de/build.md).
Diese Seite ist auch verfügbar in [Japanese](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [Korean](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [Russain](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), [Simplified Chinese](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md), [French](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/build.md) and [German](https://github.com/BoostIO/Boostnote/blob/master/docs/de/build.md).
## Umgebungen
* npm: 4.x

View File

@@ -1,31 +1,25 @@
# How to debug Boostnote (Electron app)
Diese Seite ist auch verfügbar in [Japanese](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/debug.md), [Korean](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/debug.md), [Russain](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/debug.md), [Simplified Chinese](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/debug.md), [French](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/debug.md) and [German](https://github.com/BoostIO/Boostnote/blob/add-german-documents/docs/de/debug.md).
Diese Seite ist auch verfügbar in [Japanisch](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/debug.md), [Koreanisch](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/debug.md), [Russisch](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/debug.md), [Vereinfachtem Chinesisch](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/debug.md), [Französisch](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/debug.md) und [Deutsch](https://github.com/BoostIO/Boostnote/blob/master/docs/de/debug.md).
Boostnote ist eine Electron App und basiert auf Chromium.
Zum Entwicklen verwendest du am Besten die `Developer Tools` von Google Chrome verwenden. Diese kannst du ganz einfach im unter dem Menüpunkt `View` mit `Toggle Developer Tools` aktivieren:
Boostnote is eine Electron app, somit basiert sie auf Chromium; Entwickler können die `Developer Tools` verwenden, wie Google Chrome.
Du kannst die `Developer Tools` so einschalten:
![how_to_toggle_devTools](https://cloud.githubusercontent.com/assets/11307908/24343585/162187e2-127c-11e7-9c01-23578db03ecf.png)
Die Anzeige der `Developer Tools` sieht in etwa so aus:
Die `Developer Tools` schauen dann ungefähr so aus:
![Developer_Tools](https://cloud.githubusercontent.com/assets/11307908/24343545/eff9f3a6-127b-11e7-94cf-cb67bfda634a.png)
Wenn Fehler vorkommen, werden die Fehlermeldungen in der `console` ausgegeben.
## Debugging
Fehlermeldungen werden in der Regel in der `console` ausgegeben, die du über den gleichnamigen Reiter der `Developer Tools` anzeigen lassen kannst. Zum Debuggen kannst du beispielsweise über den `debugger` Haltepunkte im Code setzen.
Zum Beispiel kannst du mit dem `debugger` Haltepunkte im Code setzen wie hier veranschaulicht:
![debugger](https://cloud.githubusercontent.com/assets/11307908/24343879/9459efea-127d-11e7-9943-f60bf7f66d4a.png)
Du kannst aber natürlich auch die Art von Debugging verwenden mit der du am besten zurecht kommst.
Das ist ledigtlich ein Beispiel, du kannst die Art von Debugging verwenden die für dich am besten ist.
## Referenz
* [Official document of Google Chrome about debugging](https://developer.chrome.com/devtools)
---
Special thanks: Translated by [gino909](https://github.com/gino909), [mdeuerlein](https://github.com/mdeuerlein)
Special thanks: Translated by [gino909](https://github.com/gino909)

View File

@@ -1,5 +1,5 @@
# How to debug Boostnote (Electron app)
This page is also available in [Japanese](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/debug.md), [Korean](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/debug.md), [Russain](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/debug.md), [Simplified Chinese](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/debug.md), [French](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/debug.md) and [German](https://github.com/BoostIO/Boostnote/blob/master/docs/de/debug.md).
This page is also available in [Japanese](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/debug.md), [Korean](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/debug.md), [Russain](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/debug.md), [Simplified Chinese](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/debug.md), [French](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/debug.md) and [German](https://github.com/BoostIO/Boostnote/blob/add-german-documents/docs/de/debug.md).
Boostnote is an Electron app so it's based on Chromium; developers can use `Developer Tools` just like Google Chrome.

View File

@@ -1,5 +1,5 @@
# Build
Cette page est également disponible en [Anglais](https://github.com/BoostIO/Boostnote/blob/master/docs/build.md), [Japonais](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [Coréen](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [Russe](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), [Chinois Simplifié](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md) et en [Allemand](https://github.com/BoostIO/Boostnote/blob/master/docs/de/build.md)
Cette page est également disponible en [Japonais](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [Coréen](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [Russe](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), et en [Chinois Simplifié](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md).
## Environnements
* npm: 4.x

View File

@@ -1,5 +1,5 @@
# Comment débugger Boostnote (Application Electron)
Cette page est également disponible en [Angalis](https://github.com/BoostIO/Boostnote/blob/master/docs/debug.md), [Japonais](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/debug.md), [Coréen](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/debug.md), [Russe](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/debug.md), [Chinois Simplifié](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/debug.md) et en [Allemand](https://github.com/BoostIO/Boostnote/blob/master/docs/de/debug.md)
Cette page est également disponible en [Japonais](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/debug.md), [Coréen](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/debug.md), [Russe](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/debug.md), et en [Chinois Simplifié](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/debug.md)
Boostnote est une application Electron donc basée sur Chromium. Il est possible d'utiliser les `Developer Tools` comme dans Google Chrome.
@@ -19,4 +19,4 @@ Par exemple, vous pouvez utiliser le `debugger` pour placer un point d'arrêt da
C'est une façon comme une autre de faire, vous pouvez trouver une façon de débugger que vous trouverez plus adaptée.
## Références
* [Documentation officiel de Google Chrome sur le debugging](https://developer.chrome.com/devtools)
* [Documentation officiel de Google Chrome sur le debugging](https://developer.chrome.com/devtools)

View File

@@ -1,86 +0,0 @@
# 編譯
此文件還提供下列的語言 [日文](https://github.com/BoostIO/Boostnote/blob/master/docs/jp/build.md), [韓文](https://github.com/BoostIO/Boostnote/blob/master/docs/ko/build.md), [俄文](https://github.com/BoostIO/Boostnote/blob/master/docs/ru/build.md), [簡體中文](https://github.com/BoostIO/Boostnote/blob/master/docs/zh_CN/build.md), [法文](https://github.com/BoostIO/Boostnote/blob/master/docs/fr/build.md) and [德文](https://github.com/BoostIO/Boostnote/blob/master/docs/de/build.md).
## 環境
* npm: 4.x
* node: 7.x
`$ grunt pre-build``npm v5.x` 有問題,所以只能用 `npm v4.x`
## 開發
我們使用 Webpack HMR 來開發 Boostnote。

在專案根目錄底下執行下列指令,將會以原始設置啟動 Boostnote。
**用 yarn 來安裝必要 packages**
```bash
$ yarn
```
**開始開發**
```
$ yarn run dev-start
```
上述指令同時運行了 `yarn run webpack``yarn run hot`,相當於將這兩個指令在不同的 terminal 中運行。
`webpack` 會同時監控修改過的程式碼,並
The `webpack` will watch for code changes and then apply them automatically.
If the following error occurs: `Failed to load resource: net::ERR_CONNECTION_REFUSED`, please reload Boostnote.
![net::ERR_CONNECTION_REFUSED](https://cloud.githubusercontent.com/assets/11307908/24343004/081e66ae-1279-11e7-8d9e-7f478043d835.png)
> ### Notice
> There are some cases where you have to refresh the app manually.
> 1. When editing a constructor method of a component
> 2. When adding a new css class (similar to 1: the CSS class is re-written by each component. This process occurs at the Constructor method.)
## Deploy
We use Grunt to automate deployment.
You can build the program by using `grunt`. However, we don't recommend this because the default task includes codesign and authenticode.
So, we've prepared a separate script which just makes an executable file.
This build doesn't work on npm v5.3.0. So you need to use v5.2.0 when you build it.
```
grunt pre-build
```
You will find the executable in the `dist` directory. Note, the auto updater won't work because the app isn't signed.
If you find it necessary, you can use codesign or authenticode with this executable.
## Make own distribution packages (deb, rpm)
Distribution packages are created by exec `grunt build` on Linux platform (e.g. Ubuntu, Fedora).
> Note: You can create both `.deb` and `.rpm` in a single environment.
After installing the supported version of `node` and `npm`, install build dependency packages.
Ubuntu/Debian:
```
$ sudo apt-get install -y rpm fakeroot
```
Fedora:
```
$ sudo dnf install -y dpkg dpkg-dev rpm-build fakeroot
```
Then execute `grunt build`.
```
$ grunt build
```
You will find `.deb` and `.rpm` in the `dist` directory.

View File

@@ -1,52 +0,0 @@
(function (mod) {
if (typeof exports === 'object' && typeof module === 'object') { // Common JS
mod(require('../codemirror/lib/codemirror'))
} else if (typeof define === 'function' && define.amd) { // AMD
define(['../codemirror/lib/codemirror'], mod)
} else { // Plain browser env
mod(CodeMirror)
}
})(function (CodeMirror) {
'use strict'
var listRE = /^(\s*)(>[> ]*|[*+-] \[[x ]\]\s|[*+-]\s|(\d+)([.)]))(\s*)/
var emptyListRE = /^(\s*)(>[> ]*|[*+-] \[[x ]\]|[*+-]|(\d+)[.)])(\s*)$/
var unorderedListRE = /[*+-]\s/
CodeMirror.commands.boostNewLineAndIndentContinueMarkdownList = function (cm) {
if (cm.getOption('disableInput')) return CodeMirror.Pass
var ranges = cm.listSelections()
var replacements = []
for (var i = 0; i < ranges.length; i++) {
var pos = ranges[i].head
var eolState = cm.getStateAfter(pos.line)
var inList = eolState.list !== false
var inQuote = eolState.quote !== 0
var line = cm.getLine(pos.line)
var match = listRE.exec(line)
if (!ranges[i].empty() || (!inList && !inQuote) || !match || pos.ch < match[2].length - 1) {
cm.execCommand('newlineAndIndent')
return
}
if (emptyListRE.test(line)) {
if (!/>\s*$/.test(line)) {
cm.replaceRange('', {
line: pos.line, ch: 0
}, {
line: pos.line, ch: pos.ch + 1
})
}
replacements[i] = '\n'
} else {
var indent = match[1]
var after = match[5]
var bullet = unorderedListRE.test(match[2]) || match[2].indexOf('>') >= 0
? match[2].replace('x', ' ')
: (parseInt(match[3], 10) + 1) + match[4]
replacements[i] = '\n' + indent + bullet + after
}
}
cm.replaceSelections(replacements)
}
})

View File

@@ -19,7 +19,7 @@ module.exports = function (grunt) {
var initConfig = {
pkg: grunt.file.readJSON('package.json'),
'create-windows-installer': {
x64: {
ia32: {
appDirectory: path.join(__dirname, 'dist', 'Boostnote-win32-x64'),
outputDirectory: path.join(__dirname, 'dist'),
authors: 'MAISIN&CO., Inc.',
@@ -109,7 +109,7 @@ module.exports = function (grunt) {
var done = this.async()
var opts = {
name: 'Boostnote',
arch: 'x64',
arch: 'ia32',
dir: __dirname,
version: grunt.config.get('pkg.config.electron-version'),
'app-version': grunt.config.get('pkg.version'),

View File

@@ -69,10 +69,6 @@ ipc.on('update-app-confirm', function (event, msg) {
}
})
app.on('window-all-closed', function () {
app.quit()
})
app.on('ready', function () {
mainWindow = require('./main-window')

View File

@@ -1,7 +1,6 @@
const electron = require('electron')
const BrowserWindow = electron.BrowserWindow
const shell = electron.shell
const ipc = electron.ipcMain
const mainWindow = require('./main-window')
const macOS = process.platform === 'darwin'
@@ -260,23 +259,6 @@ const view = {
]
}
let editorFocused
// Define extra shortcut keys
mainWindow.webContents.on('before-input-event', (event, input) => {
// Synonyms for Search (Find)
if (input.control && input.key === 'f' && input.type === 'keyDown') {
if (!editorFocused) {
mainWindow.webContents.send('top:focus-search')
event.preventDefault()
}
}
})
ipc.on('editor:focused', (event, isFocused) => {
editorFocused = isFocused
})
const window = {
label: 'Window',
submenu: [
@@ -285,11 +267,6 @@ const window = {
accelerator: 'Command+M',
selector: 'performMiniaturize:'
},
{
label: 'Toggle Full Screen',
accelerator: 'Command+Control+F',
selector: 'toggleFullScreen:'
},
{
label: 'Close',
accelerator: 'Command+W',

View File

@@ -56,8 +56,11 @@ if (process.platform === 'darwin' || process.env.DESKTOP_SESSION === 'cinnamon')
app.on('before-quit', function (e) {
mainWindow.removeAllListeners()
})
} else {
app.on('window-all-closed', function () {
app.quit()
})
}
mainWindow.on('resize', _.throttle(storeWindowSize, 500))
function storeWindowSize () {

View File

@@ -57,10 +57,6 @@
opacity: 1 !important;
pointer-events: auto !important;
}
.CodeMirror-ruler {
border-left-color: rgba(142, 142, 142, 0.5);
mix-blend-mode: difference;
}
</style>
</head>
<body>
@@ -82,7 +78,7 @@
<script src="../node_modules/codemirror/keymap/emacs.js"></script>
<script src="../node_modules/codemirror/addon/runmode/runmode.js"></script>
<script src="../extra_scripts/boost/boostNewLineIndentContinueMarkdownList.js"></script>
<script src="../node_modules/codemirror/addon/edit/continuelist.js"></script>
<script src="../node_modules/codemirror/addon/edit/closebrackets.js"></script>
@@ -93,7 +89,6 @@
<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/dialog/dialog.js"></script>
<script src="../node_modules/codemirror/addon/display/rulers.js"></script>
<script src="../node_modules/raphael/raphael.min.js"></script>
<script src="../node_modules/flowchart.js/release/flowchart.min.js"></script>
@@ -102,6 +97,7 @@
</script>
<script src="../node_modules/js-sequence-diagrams/fucknpm/sequence-diagram-min.js"></script>
<script src="../node_modules/katex/dist/katex.min.js"></script>
<script src="../node_modules/react/dist/react.min.js"></script>
<script src="../node_modules/react-dom/dist/react-dom.min.js"></script>
<script src="../node_modules/redux/dist/redux.min.js"></script>

View File

@@ -1,153 +0,0 @@
{
"Notes": "Notes",
"Tags": "Tags",
"Preferences": "Preferences",
"Make a note": "Make a note",
"Ctrl": "Ctrl",
"Ctrl(^)": "Ctrl",
"to create a new note": "to create a new note",
"Toggle Mode": "Toggle Mode",
"Trash": "Trash",
"MODIFICATION DATE": "MODIFICATION DATE",
"Words": "Words",
"Letters": "Letters",
"STORAGE": "STORAGE",
"FOLDER": "FOLDER",
"CREATION DATE": "CREATION DATE",
"NOTE LINK": "NOTE LINK",
".md": ".md",
".txt": ".txt",
".html": ".html",
"Print": "Print",
"Your preferences for Boostnote": "Your preferences for Boostnote",
"Storages": "Storages",
"Add Storage Location": "Add Storage Location",
"Add Folder": "Add Folder",
"Open Storage folder": "Open Storage folder",
"Unlink": "Unlink",
"Edit": "Edit",
"Delete": "Delete",
"Interface": "Interface",
"Interface Theme": "Interface Theme",
"Default": "Delete",
"White": "White",
"Solarized Dark": "Solarized Dark",
"Dark": "Dark",
"Show a confirmation dialog when deleting notes": "Show a confirmation dialog when deleting notes",
"Editor Theme": "Editor Theme",
"Editor Font Size": "Editor Font Size",
"Editor Font Family": "Editor Font Family",
"Editor Indent Style": "Editor Indent Style",
"Spaces": "Spaces",
"Tabs": "Tabs",
"Switch to Preview": "Switch to Preview",
"When Editor Blurred": "When Editor Blurred",
"When Editor Blurred, Edit On Double Click": "When Editor Blurred, Edit On Double Click",
"On Right Click": "On Right Click",
"Editor Keymap": "Editor Keymap",
"default": "default",
"vim": "vim",
"emacs": "emacs",
"⚠️ Please restart boostnote after you change the keymap": "⚠️ Please restart boostnote after you change the keymap",
"Show line numbers in the editor": "Show line numbers in the editor",
"Allow editor to scroll past the last line": "Allow editor to scroll past the last line",
"Bring in web page title when pasting URL on editor": "Bring in web page title when pasting URL on editor",
"Preview": "Preview",
"Preview Font Size": "Preview Font Size",
"Preview Font Family": "Preview Font Family",
"Code block Theme": "Code block Theme",
"Allow preview to scroll past the last line": "Allow preview to scroll past the last line",
"Show line numbers for preview code blocks": "Show line numbers for preview code blocks",
"LaTeX Inline Open Delimiter": "LaTeX Inline Open Delimiter",
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
"Community": "Community",
"Subscribe to Newsletter": "Subscribe to Newsletter",
"GitHub": "GitHub",
"Blog": "Blog",
"Facebook Group": "Facebook Group",
"Twitter": "Twitter",
"About": "About",
"Boostnote": "Boostnote",
"An open source note-taking app made for programmers just like you.": "An open source note-taking app made for programmers just like you.",
"Website": "Website",
"Development": "Development",
" : Development configurations for Boostnote.": " : Development configurations for Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO",
"License: GPL v3": "License: GPL v3",
"Analytics": "Analytics",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.",
"You can see how it works on ": "You can see how it works on ",
"You can choose to enable or disable this option.": "You can choose to enable or disable this option.",
"Enable analytics to help improve Boostnote": "Enable analytics to help improve Boostnote",
"Crowdfunding": "Crowdfunding",
"Dear everyone,": "Dear everyone,",
"Thank you for using Boostnote!": "Thank you for using Boostnote!",
"Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "Boostnote is used in about 200 different countries and regions by an awesome community of developers.",
"To continue supporting this growth, and to satisfy community expectations,": "To continue supporting this growth, and to satisfy community expectations,",
"we would like to invest more time and resources in this project.": "we would like to invest more time and resources in this project.",
"If you like this project and see its potential, you can help by supporting us on OpenCollective!": "If you like this project and see its potential, you can help by supporting us on OpenCollective!",
"Thanks,": "Thanks,",
"Boostnote maintainers": "Boostnote maintainers",
"Support via OpenCollective": "Support via OpenCollective",
"Language": "Language",
"English": "English",
"German": "German",
"French": "French",
"Show \"Saved to Clipboard\" notification when copying": "Show \"Saved to Clipboard\" notification when copying",
"All Notes": "All Notes",
"Starred": "Starred",
"Are you sure to ": "Are you sure to ",
" delete": " delete",
"this folder?": "this folder?",
"Confirm": "Confirm",
"Cancel": "Cancel",
"Markdown Note": "Markdown Note",
"This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "This format is for creating text documents. Checklists, code blocks and Latex blocks are available.",
"Snippet Note": "Snippet Note",
"This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "This format is for creating code snippets. Multiple snippets can be grouped into a single note.",
"Tab to switch format": "Tab to switch format",
"Updated": "Updated",
"Created": "Created",
"Alphabetically": "Alphabetically",
"Default View": "Default View",
"Compressed View": "Compressed View",
"Search": "Search",
"Blog Type": "Blog Type",
"Blog Address": "Blog Address",
"Save": "Save",
"Auth": "Auth",
"Authentication Method": "Authentication Method",
"JWT": "JWT",
"USER": "USER",
"Token": "Token",
"Storage": "Storage",
"Hotkeys": "Hotkeys",
"Show/Hide Boostnote": "Show/Hide Boostnote",
"Restore": "Restore",
"Permanent Delete": "Permanent Delete",
"Confirm note deletion": "Confirm note deletion",
"This will permanently remove this note.": "This will permanently remove this note.",
"Successfully applied!": "Successfully applied!",
"Albanian": "Albanian",
"Chinese (zh-CN)": "Chinese (zh-CN)",
"Chinese (zh-TW)": "Chinese (zh-TW)",
"Danish": "Danish",
"Japanese": "Japanese",
"Korean": "Korean",
"Norwegian": "Norwegian",
"Polish": "Polish",
"Portuguese": "Portuguese",
"Spanish": "Spanish",
"You have to save!": "You have to save!",
"Russian": "Russian",
"Command(⌘)": "Command(⌘)",
"Editor Rulers": "Editor Rulers",
"Enable": "Enable",
"Disable": "Disable",
"Sanitization": "Sanitization",
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
"Allow styles": "Allow styles",
"Allow dangerous html tags": "Allow dangerous html tags"
}

View File

@@ -1,155 +0,0 @@
{
"Notes": "Notizen",
"Tags": "Tags",
"Preferences": "Einstellungen",
"Make a note": "Notiz erstellen",
"Ctrl": "Ctrl",
"Ctrl(^)": "Ctrl",
"to create a new note": "um eine neue Notiz zu erstellen",
"Toggle Mode": "Modus umschalten",
"Trash": "Papierkorb",
"MODIFICATION DATE": "ÄNDERUNGSDATUM",
"Words": "Wörter",
"Letters": "Buchstaben",
"STORAGE": "SPEICHERORT",
"FOLDER": "ORDNER",
"CREATION DATE": "ERSTELLUNGSDATUM",
"NOTE LINK": "NOTIZ LINK",
".md": ".md",
".txt": ".txt",
".html": ".html",
"Print": "Print",
"Your preferences for Boostnote": "Deine Boostnote Einstellungen",
"Storages": "Speicherorte",
"Add Storage Location": "Speicherort Hinzufügen",
"Add Folder": "Ordner Hinzufügen",
"Open Storage folder": "Speicherort Öffnen",
"Unlink": "Verknüpfung aufheben",
"Edit": "Bearbeiten",
"Delete": "Löschen",
"Interface": "Interface",
"Interface Theme": "Interface-Thema",
"Default": "Standard",
"White": "Weiß",
"Solarized Dark": "Solarized Dark",
"Dark": "Dark",
"Show a confirmation dialog when deleting notes": "Bestätigungsdialog beim Löschen von Notizen anzeigen",
"Editor Theme": "Editor Theme",
"Editor Font Size": "Editor Schriftgröße",
"Editor Font Family": "Editor Schriftart",
"Editor Indent Style": "Editor Einrückestil",
"Spaces": "Leerzeichen",
"Tabs": "Tabs",
"Switch to Preview": "Zur Vorschau wechseln",
"When Editor Blurred": "Wenn Editor verschwommen ist",
"When Editor Blurred, Edit On Double Click": "Mit Doppelklick bearbeiten, wenn Editor verschwommen ist",
"On Right Click": "Mit Rechtsklcik",
"Editor Keymap": "Editor Tastenbelegung",
"default": "standard",
"vim": "vim",
"emacs": "emacs",
"⚠️ Please restart boostnote after you change the keymap": "⚠️ Boostnote nach Änderung der Tastenbelegung neu starten",
"Show line numbers in the editor": "Zeilennummern im Editor anzeigen",
"Allow editor to scroll past the last line": "Editor das Scrollen über das Ende hinaus erlauben",
"Bring in web page title when pasting URL on editor": "Websitetitel beim Einfügen in den Editor anzeigen",
"Preview": "Vorschau",
"Preview Font Size": "Vorschau Schriftgröße",
"Preview Font Family": "Vorschau Schriftart",
"Code block Theme": "Code block Theme",
"Allow preview to scroll past the last line": "Vorschau das Scrollen über das Ende hinaus erlauben",
"Show line numbers for preview code blocks": "Zeilennummern in Vorschau-Code-Blöcken anzeigen",
"LaTeX Inline Open Delimiter": "LaTeX Inline Open Delimiter",
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
"Community": "Community",
"Subscribe to Newsletter": "Newsletter abonnieren",
"GitHub": "GitHub",
"Blog": "Blog",
"Facebook Group": "Facebook Group",
"Twitter": "Twitter",
"About": "About",
"Boostnote": "Boostnote",
"An open source note-taking app made for programmers just like you.": "Eine OpenSource-Notizapp für Programmierer wie du und ich.",
"Website": "Website",
"Development": "Entwicklung",
" : Development configurations for Boostnote.": " : Entwicklungseinstellungen für Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO",
"License: GPL v3": "License: GPL v3",
"Analytics": "Analytics",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote sammelt anonyme Daten, um die App zu verbessern und nicht persönliche Informationen, wie z.B. der Inhalt deiner Notizen.",
"You can see how it works on ": "You can see how it works on ",
"You can choose to enable or disable this option.": "Du kannst wählen, ob du diese Option aktivierst oder daektivierst.",
"Enable analytics to help improve Boostnote": "Datenerhebung zur Verbesserung von Boostnote aktivieren",
"Crowdfunding": "Crowdfunding",
"Dear everyone,": "Dear everyone,",
"Thank you for using Boostnote!": "Thank you for using Boostnote!",
"Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "Boostnote is used in about 200 different countries and regions by an awesome community of developers.",
"To continue supporting this growth, and to satisfy community expectations,": "To continue supporting this growth, and to satisfy community expectations,",
"we would like to invest more time and resources in this project.": "we would like to invest more time and resources in this project.",
"If you like this project and see its potential, you can help by supporting us on OpenCollective!": "If you like this project and see its potential, you can help by supporting us on OpenCollective!",
"Thanks,": "Thanks,",
"Boostnote maintainers": "Boostnote Betreuer",
"Support via OpenCollective": "Support per OpenCollective",
"Language": "Sprache",
"English": "Englisch",
"German": "Deutsch",
"French": "Französisch",
"Show \"Saved to Clipboard\" notification when copying": "\"Auf Clipboard gespeichert\" Benachrichtigungen beim kopieren anzeigen",
"All Notes": "Alle Notizen",
"Starred": "Starred",
"Are you sure to ": "Sind sie sicher ",
" delete": " zu löschen",
"this folder?": "diesen Ordner?",
"Confirm": "Bestätigen",
"Cancel": "Abbrechen",
"Markdown Note": "Markdown Notiz",
"This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "Dieses Format dient der Erstellung von Text Dokumenten. Checklisten, Code-Blöcke und Latex-Blöcke sind verfügbar.",
"Snippet Note": "Snippet Notiz",
"This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "Dieses Format dient der Erstellung von Code-Schnipslen. Mehrere snippets können zu einer Notiz zusammengefasst werden.",
"Tab to switch format": "Tab drücken, um das Format zu wechseln",
"Updated": "Bearbeitet",
"Created": "Erstellt",
"Alphabetically": "Alphabetisch",
"Default View": "Standardansicht",
"Compressed View": "Komprimierte Ansicht",
"Search": "Suchen",
"Blog Type": "Blog-Typ",
"Blog Address": "Blog Adresse",
"Save": "Speichern",
"Auth": "Authentifizierung",
"Authentication Method": "Authentifizierungsmethode",
"JWT": "JWT",
"USER": "BENUTZER",
"Token": "Token",
"Storage": "Speicherorte",
"Hotkeys": "Hotkeys",
"Show/Hide Boostnote": "Boostnote anzeigen/verstecken",
"Restore": "Restore",
"Permanent Delete": "Dauerhaft Löschen",
"Confirm note deletion": "Löschen bestätigen",
"This will permanently remove this note.": "Notiz wird dauerhaft gelöscht",
"You have to save!": "Es muss gespeichert werden",
"Albanian": "Albanisch",
"Danish": "Dänisch",
"Japanese": "Japanisch",
"Korean": "Koreanisch",
"Norwegian": "Norwegisch",
"Polish": "Polnish",
"Portuguese": "Portugiesisch",
"Spanish": "Spanisch",
"Chinese (zh-CN)": "Chinesisch (zh-CN)",
"Chinese (zh-TW)": "Chinesisch (zh-TW)",
"Successfully applied!": "Erfolgreich angewendet!",
"UserName": "UserName",
"Password": "Password",
"Russian": "Russian",
"Command(⌘)": "Command(⌘)",
"Editor Rulers": "Editor Rulers",
"Enable": "Enable",
"Disable": "Disable",
"Sanitization": "Sanitization",
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
"Allow styles": "Allow styles",
"Allow dangerous html tags": "Allow dangerous html tags"
}

View File

@@ -1,155 +0,0 @@
{
"Notes": "Notes",
"Tags": "Tags",
"Preferences": "Preferences",
"Make a note": "Make a note",
"Ctrl": "Ctrl",
"Ctrl(^)": "Ctrl",
"to create a new note": "to create a new note",
"Toggle Mode": "Toggle Mode",
"Trash": "Trash",
"MODIFICATION DATE": "MODIFICATION DATE",
"Words": "Words",
"Letters": "Letters",
"STORAGE": "STORAGE",
"FOLDER": "FOLDER",
"CREATION DATE": "CREATION DATE",
"NOTE LINK": "NOTE LINK",
".md": ".md",
".txt": ".txt",
".html": ".html",
"Print": "Print",
"Your preferences for Boostnote": "Your preferences for Boostnote",
"Storages": "Storages",
"Add Storage Location": "Add Storage Location",
"Add Folder": "Add Folder",
"Open Storage folder": "Open Storage folder",
"Unlink": "Unlink",
"Edit": "Edit",
"Delete": "Delete",
"Interface": "Interface",
"Interface Theme": "Interface Theme",
"Default": "Delete",
"White": "White",
"Solarized Dark": "Solarized Dark",
"Dark": "Dark",
"Show a confirmation dialog when deleting notes": "Show a confirmation dialog when deleting notes",
"Editor Theme": "Editor Theme",
"Editor Font Size": "Editor Font Size",
"Editor Font Family": "Editor Font Family",
"Editor Indent Style": "Editor Indent Style",
"Spaces": "Spaces",
"Tabs": "Tabs",
"Switch to Preview": "Switch to Preview",
"When Editor Blurred": "When Editor Blurred",
"When Editor Blurred, Edit On Double Click": "When Editor Blurred, Edit On Double Click",
"On Right Click": "On Right Click",
"Editor Keymap": "Editor Keymap",
"default": "default",
"vim": "vim",
"emacs": "emacs",
"⚠️ Please restart boostnote after you change the keymap": "⚠️ Please restart boostnote after you change the keymap",
"Show line numbers in the editor": "Show line numbers in the editor",
"Allow editor to scroll past the last line": "Allow editor to scroll past the last line",
"Bring in web page title when pasting URL on editor": "Bring in web page title when pasting URL on editor",
"Preview": "Preview",
"Preview Font Size": "Preview Font Size",
"Preview Font Family": "Preview Font Family",
"Code block Theme": "Code block Theme",
"Allow preview to scroll past the last line": "Allow preview to scroll past the last line",
"Show line numbers for preview code blocks": "Show line numbers for preview code blocks",
"LaTeX Inline Open Delimiter": "LaTeX Inline Open Delimiter",
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
"Community": "Community",
"Subscribe to Newsletter": "Subscribe to Newsletter",
"GitHub": "GitHub",
"Blog": "Blog",
"Facebook Group": "Facebook Group",
"Twitter": "Twitter",
"About": "About",
"Boostnote": "Boostnote",
"An open source note-taking app made for programmers just like you.": "An open source note-taking app made for programmers just like you.",
"Website": "Website",
"Development": "Development",
" : Development configurations for Boostnote.": " : Development configurations for Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO",
"License: GPL v3": "License: GPL v3",
"Analytics": "Analytics",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.",
"You can see how it works on ": "You can see how it works on ",
"You can choose to enable or disable this option.": "You can choose to enable or disable this option.",
"Enable analytics to help improve Boostnote": "Enable analytics to help improve Boostnote",
"Crowdfunding": "Crowdfunding",
"Dear everyone,": "Dear everyone,",
"Thank you for using Boostnote!": "Thank you for using Boostnote!",
"Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "Boostnote is used in about 200 different countries and regions by an awesome community of developers.",
"To continue supporting this growth, and to satisfy community expectations,": "To continue supporting this growth, and to satisfy community expectations,",
"we would like to invest more time and resources in this project.": "we would like to invest more time and resources in this project.",
"If you like this project and see its potential, you can help by supporting us on OpenCollective!": "If you like this project and see its potential, you can help by supporting us on OpenCollective!",
"Thanks,": "Thanks,",
"Boostnote maintainers": "Boostnote maintainers",
"Support via OpenCollective": "Support via OpenCollective",
"Language": "Language",
"English": "English",
"German": "German",
"French": "French",
"Show \"Saved to Clipboard\" notification when copying": "Show \"Saved to Clipboard\" notification when copying",
"All Notes": "All Notes",
"Starred": "Starred",
"Are you sure to ": "Are you sure to ",
" delete": " delete",
"this folder?": "this folder?",
"Confirm": "Confirm",
"Cancel": "Cancel",
"Markdown Note": "Markdown Note",
"This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "This format is for creating text documents. Checklists, code blocks and Latex blocks are available.",
"Snippet Note": "Snippet Note",
"This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "This format is for creating code snippets. Multiple snippets can be grouped into a single note.",
"Tab to switch format": "Tab to switch format",
"Updated": "Updated",
"Created": "Created",
"Alphabetically": "Alphabetically",
"Default View": "Default View",
"Compressed View": "Compressed View",
"Search": "Search",
"Blog Type": "Blog Type",
"Blog Address": "Blog Address",
"Save": "Save",
"Auth": "Auth",
"Authentication Method": "Authentication Method",
"JWT": "JWT",
"USER": "USER",
"Token": "Token",
"Storage": "Storage",
"Hotkeys": "Hotkeys",
"Show/Hide Boostnote": "Show/Hide Boostnote",
"Restore": "Restore",
"Permanent Delete": "Permanent Delete",
"Confirm note deletion": "Confirm note deletion",
"This will permanently remove this note.": "This will permanently remove this note.",
"Successfully applied!": "Successfully applied!",
"Albanian": "Albanian",
"Chinese (zh-CN)": "Chinese (zh-CN)",
"Chinese (zh-TW)": "Chinese (zh-TW)",
"Danish": "Danish",
"Japanese": "Japanese",
"Korean": "Korean",
"Norwegian": "Norwegian",
"Polish": "Polish",
"Portuguese": "Portuguese",
"Spanish": "Spanish",
"You have to save!": "You have to save!",
"UserName": "UserName",
"Password": "Password",
"Russian": "Russian",
"Command(⌘)": "Command(⌘)",
"Editor Rulers": "Editor Rulers",
"Enable": "Enable",
"Disable": "Disable",
"Sanitization": "Sanitization",
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
"Allow styles": "Allow styles",
"Allow dangerous html tags": "Allow dangerous html tags"
}

View File

@@ -1,153 +0,0 @@
{
"Notes": "Notes",
"Tags": "Tags",
"Preferences": "Preferences",
"Make a note": "Make a note",
"Ctrl": "Ctrl",
"Ctrl(^)": "Ctrl",
"to create a new note": "to create a new note",
"Toggle Mode": "Toggle Mode",
"Trash": "Trash",
"MODIFICATION DATE": "MODIFICATION DATE",
"Words": "Words",
"Letters": "Letters",
"STORAGE": "STORAGE",
"FOLDER": "FOLDER",
"CREATION DATE": "CREATION DATE",
"NOTE LINK": "NOTE LINK",
".md": ".md",
".txt": ".txt",
".html": ".html",
"Print": "Print",
"Your preferences for Boostnote": "Your preferences for Boostnote",
"Storages": "Storages",
"Add Storage Location": "Add Storage Location",
"Add Folder": "Add Folder",
"Open Storage folder": "Open Storage folder",
"Unlink": "Unlink",
"Edit": "Edit",
"Delete": "Delete",
"Interface": "Interface",
"Interface Theme": "Interface Theme",
"Default": "Delete",
"White": "White",
"Solarized Dark": "Solarized Dark",
"Dark": "Dark",
"Show a confirmation dialog when deleting notes": "Show a confirmation dialog when deleting notes",
"Editor Theme": "Editor Theme",
"Editor Font Size": "Editor Font Size",
"Editor Font Family": "Editor Font Family",
"Editor Indent Style": "Editor Indent Style",
"Spaces": "Spaces",
"Tabs": "Tabs",
"Switch to Preview": "Switch to Preview",
"When Editor Blurred": "When Editor Blurred",
"When Editor Blurred, Edit On Double Click": "When Editor Blurred, Edit On Double Click",
"On Right Click": "On Right Click",
"Editor Keymap": "Editor Keymap",
"default": "default",
"vim": "vim",
"emacs": "emacs",
"⚠️ Please restart boostnote after you change the keymap": "⚠️ Please restart boostnote after you change the keymap",
"Show line numbers in the editor": "Show line numbers in the editor",
"Allow editor to scroll past the last line": "Allow editor to scroll past the last line",
"Bring in web page title when pasting URL on editor": "Bring in web page title when pasting URL on editor",
"Preview": "Preview",
"Preview Font Size": "Preview Font Size",
"Preview Font Family": "Preview Font Family",
"Code block Theme": "Code block Theme",
"Allow preview to scroll past the last line": "Allow preview to scroll past the last line",
"Show line numbers for preview code blocks": "Show line numbers for preview code blocks",
"LaTeX Inline Open Delimiter": "LaTeX Inline Open Delimiter",
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
"Community": "Community",
"Subscribe to Newsletter": "Subscribe to Newsletter",
"GitHub": "GitHub",
"Blog": "Blog",
"Facebook Group": "Facebook Group",
"Twitter": "Twitter",
"About": "About",
"Boostnote": "Boostnote",
"An open source note-taking app made for programmers just like you.": "An open source note-taking app made for programmers just like you.",
"Website": "Website",
"Development": "Development",
" : Development configurations for Boostnote.": " : Development configurations for Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO",
"License: GPL v3": "License: GPL v3",
"Analytics": "Analytics",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.",
"You can see how it works on ": "You can see how it works on ",
"You can choose to enable or disable this option.": "You can choose to enable or disable this option.",
"Enable analytics to help improve Boostnote": "Enable analytics to help improve Boostnote",
"Crowdfunding": "Crowdfunding",
"Dear everyone,": "Dear everyone,",
"Thank you for using Boostnote!": "Thank you for using Boostnote!",
"Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "Boostnote is used in about 200 different countries and regions by an awesome community of developers.",
"To continue supporting this growth, and to satisfy community expectations,": "To continue supporting this growth, and to satisfy community expectations,",
"we would like to invest more time and resources in this project.": "we would like to invest more time and resources in this project.",
"If you like this project and see its potential, you can help by supporting us on OpenCollective!": "If you like this project and see its potential, you can help by supporting us on OpenCollective!",
"Thanks,": "Thanks,",
"Boostnote maintainers": "Boostnote maintainers",
"Support via OpenCollective": "Support via OpenCollective",
"Language": "Language",
"English": "English",
"German": "German",
"French": "French",
"Show \"Saved to Clipboard\" notification when copying": "Show \"Saved to Clipboard\" notification when copying",
"All Notes": "All Notes",
"Starred": "Starred",
"Are you sure to ": "Are you sure to ",
" delete": " delete",
"this folder?": "this folder?",
"Confirm": "Confirm",
"Cancel": "Cancel",
"Markdown Note": "Markdown Note",
"This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "This format is for creating text documents. Checklists, code blocks and Latex blocks are available.",
"Snippet Note": "Snippet Note",
"This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "This format is for creating code snippets. Multiple snippets can be grouped into a single note.",
"Tab to switch format": "Tab to switch format",
"Updated": "Updated",
"Created": "Created",
"Alphabetically": "Alphabetically",
"Default View": "Default View",
"Compressed View": "Compressed View",
"Search": "Search",
"Blog Type": "Blog Type",
"Blog Address": "Blog Address",
"Save": "Save",
"Auth": "Auth",
"Authentication Method": "Authentication Method",
"JWT": "JWT",
"USER": "USER",
"Token": "Token",
"Storage": "Storage",
"Hotkeys": "Hotkeys",
"Show/Hide Boostnote": "Show/Hide Boostnote",
"Restore": "Restore",
"Permanent Delete": "Permanent Delete",
"Confirm note deletion": "Confirm note deletion",
"This will permanently remove this note.": "This will permanently remove this note.",
"Successfully applied!": "Successfully applied!",
"Albanian": "Albanian",
"Chinese (zh-CN)": "Chinese (zh-CN)",
"Chinese (zh-TW)": "Chinese (zh-TW)",
"Danish": "Danish",
"Japanese": "Japanese",
"Korean": "Korean",
"Norwegian": "Norwegian",
"Polish": "Polish",
"Portuguese": "Portuguese",
"Spanish": "Spanish",
"You have to save!": "You have to save!",
"Russian": "Russian",
"Command(⌘)": "Command(⌘)",
"Editor Rulers": "Editor Rulers",
"Enable": "Enable",
"Disable": "Disable",
"Sanitization": "Sanitization",
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
"Allow styles": "Allow styles",
"Allow dangerous html tags": "Allow dangerous html tags"
}

View File

@@ -1,153 +0,0 @@
{
"Notes": "Notes",
"Tags": "Tags",
"Preferences": "Préférences",
"Make a note": "Créer une note",
"Ctrl": "Ctrl",
"Ctrl(^)": "Ctrl",
"to create a new note": "pour créer une nouvelle note",
"Toggle Mode": "Toggle Mode",
"Trash": "Poubelle",
"MODIFICATION DATE": "DATE DE MODIFICATION",
"Words": "Mots",
"Letters": "Lettres",
"STORAGE": "STOCKAGE",
"FOLDER": "DOSSIER",
"CREATION DATE": "DATE DE CREATION",
"NOTE LINK": "LIEN DE LA NOTE",
".md": ".md",
".txt": ".txt",
".html": ".html",
"Print": "Imprimer",
"Your preferences for Boostnote": "Vos préférences pour Boostnote",
"Storages": "Stockages",
"Add Storage Location": "Ajouter un espace de stockage",
"Add Folder": "Ajouter un dossier",
"Open Storage folder": "Ouvrir un dossier de stockage",
"Unlink": "Délier",
"Edit": "Editer",
"Delete": "Supprimer",
"Interface": "Interface",
"Interface Theme": "Thème d'interface",
"Default": "Effacer",
"White": "Blanc",
"Solarized Dark": "Foncé solarisé",
"Dark": "Foncé",
"Show a confirmation dialog when deleting notes": "Montrer une alerte de confirmation lors de la suppression de notes",
"Editor Theme": "Theme d'éditeur",
"Editor Font Size": "Taille de police de l'éditeur",
"Editor Font Family": "Police de l'éditeur",
"Editor Indent Style": "Style d'indentation de l'éditeur",
"Spaces": "Espaces",
"Tabs": "Tabulations",
"Switch to Preview": "Switcher vers l'aperçu",
"When Editor Blurred": "Quand l'éditeur n'est pas sélectionné",
"When Editor Blurred, Edit On Double Click": "Quand l'éditeur n'est pas sélectionné, éditer avec un double clic",
"On Right Click": "Avec un clic droit",
"Editor Keymap": "Keymap de l'éditeur",
"default": "Par défaut",
"vim": "vim",
"emacs": "emacs",
"⚠️ Please restart boostnote after you change the keymap": "⚠️ Redémarrez Boostnote après avoir changé la keymap",
"Show line numbers in the editor": "Montrer les numéros de lignes dans l'éditeur",
"Allow editor to scroll past the last line": "Contrôle si l'éditeur défile au-delà de la dernière ligne",
"Bring in web page title when pasting URL on editor": "Mettre le titre de la page lors d'un collé d'une URL dans l'éditeur",
"Preview": "Aperçu",
"Preview Font Size": "Taille de police de l'aperçu",
"Preview Font Family": "Police de l'aperçu",
"Code block Theme": "Thème des blocs de code",
"Show line numbers for preview code blocks": "Montrer les numéros de lignes dans les blocs de code dans l'aperçu",
"LaTeX Inline Open Delimiter": "LaTeX Inline Open Delimiter",
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
"Community": "Communauté",
"Subscribe to Newsletter": "Souscrire à la newsletter",
"GitHub": "GitHub",
"Blog": "Blog",
"Facebook Group": "Groupe Facebook",
"Twitter": "Twitter",
"About": "A propos",
"Boostnote": "Boostnote",
"An open source note-taking app made for programmers just like you.": "Une appli de prise de notes open-source faite pour les développeurs comme vous.",
"Website": "Site web",
"Development": "Développement",
" : Development configurations for Boostnote.": " : Configurations de développement pour Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO",
"License: GPL v3": "License: GPL v3",
"Analytics": "Analytics",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote 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.",
"You can see how it works on ": "Vous pouvez voir comment ça marche sur",
"You can choose to enable or disable this option.": "Vous pouvez choisir d'activer/désactiver cette option.",
"Enable analytics to help improve Boostnote": "Activer la collecte de données anonymisées pour améliorer Boostnote",
"Crowdfunding": "Crowdfunding",
"Dear everyone,": "Cher utilisateur,",
"Thank you for using Boostnote!": "Merci d'utiliser Boostnote !",
"Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "Boostnote est utilisé dans plus de 200 pays et régions par une impressionnante communauté de développeurs.",
"To continue supporting this growth, and to satisfy community expectations,": "Afin de continuer à grandir, et de satisfaire les attentes de la communauté,",
"we would like to invest more time and resources in this project.": "nous aimerions investir d'avantage de temps et de ressources dans ce proje.",
"If you like this project and see its potential, you can help by supporting us on OpenCollective!": "Si vous aimez ce projet et que vous en voyez tout le potentiel, vous pouvez aider par un support sur OpenCollective !",
"Thanks,": "Merci,",
"Boostnote maintainers": "Les mainteneurs de Boostnote",
"Support via OpenCollective": "Support via OpenCollective",
"Language": "Langues",
"English": "Anglais",
"German": "Allemand",
"French": "Français",
"Show \"Saved to Clipboard\" notification when copying": "Montrer la notification \"Sauvegardé dans le presse-papiers\" lors de la copie",
"All Notes": "Toutes les notes",
"Starred": "Favoris",
"Are you sure to ": "Etes-vous sûr de ",
" delete": " supprimer",
"this folder?": "ce dossier ?",
"Confirm": "Confimer",
"Cancel": "Annuler",
"Markdown Note": "Note Markdown",
"This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "Ce format est pour créer des documents texte. Checklists, blocks de code et blocks Latex sont disponibles.",
"Snippet Note": "Note Snippet",
"This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "Ce format est pour créer des snippets de code. Plusieurs snippets peuvent être groupés en une seule note.",
"Tab to switch format": "Tab pour changer de format",
"Updated": "Mis à jour",
"Created": "Créé",
"Alphabetically": "De manière alphabétique",
"Default View": "Vue par défaut",
"Compressed View": "Vue compressée",
"Search": "Chercher",
"Blog Type": "Blog Type",
"Blog Address": "Blog Address",
"Save": "Sauvegarder",
"Auth": "Auth",
"Authentication Method": "Méthode d'Authentification",
"JWT": "JWT",
"USER": "USER",
"Token": "Token",
"Storage": "Stockage",
"Hotkeys": "Raccourcis",
"Show/Hide Boostnote": "Montrer/Cacher Boostnote",
"Restore": "Restaurer",
"Permanent Delete": "Supprimer définivitement",
"Confirm note deletion": "Confirmer la suppression de la note",
"This will permanently remove this note.": "Cela va supprimer cette note définitivement.",
"Successfully applied!": " Succès !",
"Albanian": "Albanais",
"Chinese (zh-CN)": "Chinois (zh-CN)",
"Chinese (zh-TW)": "Chinois (zh-TW)",
"Danish": "Danois",
"Japanese": "Japonais",
"Korean": "Coréen",
"Norwegian": "Norvégien",
"Polish": "Polonais",
"Portuguese": "Portugais",
"Spanish": "Espagnol",
"You have to save!": "Il faut sauvegarder !",
"Russian": "Russian",
"Command(⌘)": "Command(⌘)",
"Editor Rulers": "Editor Rulers",
"Enable": "Enable",
"Disable": "Disable",
"Allow preview to scroll past the last line": "Allow preview to scroll past the last line",
"Sanitization": "Sanitization",
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
"Allow styles": "Allow styles",
"Allow dangerous html tags": "Allow dangerous html tags"
}

View File

@@ -1,159 +0,0 @@
{
"Notes": "Jegyzetek",
"Tags": "Címkék",
"Preferences": "Beállítások",
"Make a note": "Új jegyzet",
"Ctrl": "Ctrl",
"Ctrl(^)": "Ctrl",
"to create a new note": "hogy létrehozz egy jegyzetet",
"Toggle Mode": "Mód Váltás",
"Trash": "Lomtár",
"MODIFICATION DATE": "MÓDOSÍTÁS DÁTUMA",
"Words": "Szó",
"Letters": "Betű",
"STORAGE": "TÁROLÓ",
"FOLDER": "KÖNYVTÁR",
"CREATION DATE": "LÉTREHOZÁS DÁTUMA",
"NOTE LINK": "JEGYZET LINKJE",
".md": ".md",
".txt": ".txt",
".html": ".html",
"Print": "Nyomtatás",
"Your preferences for Boostnote": "Boostnote beállításaid",
"Storages": "Tárolók",
"Add Storage Location": "Tároló Hozzáadása",
"Add Folder": "Könyvtár Hozzáadása",
"Open Storage folder": "Tároló Megnyitása",
"Unlink": "Tároló Leválasztása",
"Edit": "Szerkesztés",
"Delete": "Törlés",
"Interface": "Felület",
"Interface Theme": "Felület Témája",
"Default": "Alapértelmezett",
"White": "Világos",
"Solarized Dark": "Solarized Dark",
"Dark": "Sötét",
"Show a confirmation dialog when deleting notes": "Kérjen megerősítést a jegyzetek törlése előtt",
"Editor Theme": "Szerkesztő Témája",
"Editor Font Size": "Szerkesztő Betűmérete",
"Editor Font Family": "Szerkesztő Betűtípusa",
"Editor Indent Style": "Szerkesztő Behúzása",
"Spaces": "Szóközök",
"Tabs": "Tabulátor karakterek",
"Switch to Preview": "Váltás Megtekintésre",
"When Editor Blurred": "Szerkesztő Elhagyásakor",
"When Editor Blurred, Edit On Double Click": "Szerkesztő Elhagyásakor, Szerkesztő Megnyitása Dupla Kattintással",
"On Right Click": "Jobb Egérgombbal",
"Editor Keymap": "Szerkesztő Billentyűzetkiosztása",
"default": "alapértelmezett",
"vim": "vim",
"emacs": "emacs",
"⚠️ Please restart boostnote after you change the keymap": "⚠️ Kérlek, indítsd újra a programot a kiosztás megváltoztatása után",
"Show line numbers in the editor": "Mutatassa a sorszámokat a szerkesztőben",
"Allow editor to scroll past the last line": "A szerkesztőben az utolsó sor alá is lehessen görgetni",
"Bring in web page title when pasting URL on editor": "Weboldal főcímének lekérdezése URL cím beillesztésekor",
"Preview": "Megtekintés",
"Preview Font Size": "Megtekintés Betűmérete",
"Preview Font Family": "Megtekintés Betűtípusa",
"Code block Theme": "Kódblokk Témája",
"Allow preview to scroll past the last line": "Megtekintésben az utolsó sor alá is lehessen görgetni",
"Show line numbers for preview code blocks": "Mutatassa a sorszámokat a megtekintett kódblokkokban",
"LaTeX Inline Open Delimiter": "LaTeX Inline Nyitó Határolója",
"LaTeX Inline Close Delimiter": "LaTeX Inline Záró Határolója",
"LaTeX Block Open Delimiter": "LaTeX Blokk Nyitó Határolója",
"LaTeX Block Close Delimiter": "LaTeX Blokk Záró Határolója",
"Community": "Közösség",
"Subscribe to Newsletter": "Feliratkozás a Hírlevélre",
"GitHub": "GitHub",
"Blog": "Blog",
"Facebook Group": "Facebook Csoport",
"Twitter": "Twitter",
"About": "Névjegy",
"Boostnote": "Boostnote",
"An open source note-taking app made for programmers just like you.": "Nyílt forráskódú jegyzetkészítő program a hozzád hasonló programozóknak.",
"Website": "Weboldal",
"Development": "Fejlesztés",
" : 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",
"License: GPL v3": "Licensz: GPL v3",
"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.",
"You can see how it works on ": "A működéséről további információkat itt találsz: ",
"You can choose to enable or disable this option.": "Kiválaszthatod, hogy engedélyezed, vagy tiltod ezt az opciót.",
"Enable analytics to help improve Boostnote": "Adatok elemzésének engedélyezése a Boostnote tökéletesítésének céljából",
"Crowdfunding": "Közösségi finanszírozás",
"Dear everyone,": "Kedves felhasználók!",
"Thank you for using Boostnote!": "Köszönjük, hogy a Boostnote-ot használjátok!",
"Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "A Boostnote-ot több, mint 200 ország és régió fantasztikus fejlesztői használják.",
"To continue supporting this growth, and to satisfy community expectations,": "Hogy folytathassuk ezt a fejlődést és kielégíthessük a felhasználói elvárásokat,",
"we would like to invest more time and resources in this project.": "több időt és erőforrást szeretnénk a projektbe fektetni.",
"If you like this project and see its potential, you can help by supporting us on OpenCollective!": "Ha tetszik a projekt és hasznosnak találod, te is segíthetsz ebben az OpenCollective-en keresztül küldött támogatásoddal.",
"Thanks,": "Köszönjük!",
"Boostnote maintainers": "A Boostnote csapata",
"Support via OpenCollective": "Támogatás Küldése",
"Language": "Nyelv",
"English": "English",
"German": "German",
"French": "French",
"Show \"Saved to Clipboard\" notification when copying": "Mutassa a \"Vágólapra Másolva\" üzenetet másoláskor",
"All Notes": "Minden Jegyzet",
"Starred": "Kiemelt",
"Are you sure to ": "Biztos, hogy ",
" delete": " törölni",
"this folder?": "szeretnéd a könyvtárat?",
"Confirm": "Igen",
"Cancel": "Mégse",
"Markdown Note": "Markdown Jegyzet",
"This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "Ez a formátum szöveges dokumentumok készítésére használható. Jelölőnégyzeteket, kódblokkokat és Latex blokkokat is tartalmazhat.",
"Snippet Note": "Kód Jegyzet",
"This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "Ez a formátum kódrészletek készítésére használható. Több kódrészlet tárolására is alkalmas (pl. HTML + CSS).",
"Tab to switch format": "Formátum váltásához nyomd le a Tabulátor billentyűt!",
"Updated": "Módosítás",
"Created": "Létrehozás",
"Alphabetically": "Ábécé sorrendben",
"Default View": "Alapértelmezett Nézet",
"Compressed View": "Tömörített Nézet",
"Search": "Keresés",
"Blog Type": "Blog Típusa",
"Blog Address": "Blog Címe",
"Save": "Mentés",
"Auth": "Hitelesítés",
"Authentication Method": "Hitelesítési Módszer",
"JWT": "JWT",
"USER": "USER",
"Token": "Token",
"Storage": "Tároló",
"Hotkeys": "Gyorsbillentyűk",
"Show/Hide Boostnote": "Boostnote Megjelenítése/Elrejtése",
"Restore": "Visszaállítás",
"Permanent Delete": "Végleges Törlés",
"Confirm note deletion": "Törlés megerősítése",
"This will permanently remove this note.": "A jegyzet véglegesen törölve lesz.",
"Successfully applied!": "Sikeresen alkalmazva.",
"Albanian": "Albanian",
"Chinese (zh-CN)": "Chinese (zh-CN)",
"Chinese (zh-TW)": "Chinese (zh-TW)",
"Danish": "Danish",
"Japanese": "Japanese",
"Korean": "Korean",
"Norwegian": "Norwegian",
"Polish": "Polish",
"Portuguese": "Portuguese",
"Spanish": "Spanish",
"You have to save!": "Mentened kell!",
"UserName": "FelhasznaloNev",
"Password": "Jelszo",
"Russian": "Russian",
"Command(⌘)": "Command(⌘)",
"Hungarian": "Hungarian",
"Add Storage": "Tároló hozzáadása",
"Name": "Név",
"Type": "Típus",
"File System": "Fájlrendszer",
"Setting up 3rd-party cloud storage integration:": "Harmadik féltől származó felhőtárolási integráció beállítása:",
"Cloud-Syncing-and-Backup": "Cloud-Syncing-and-Backup",
"Location": "Hely",
"Add": "Hozzáadás",
"Unlink Storage": "Tároló Leválasztása",
"Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.": "A leválasztás eltávolítja ezt a tárolót a Boostnote-ból. Az adatok nem lesznek törölve, kérlek manuálisan töröld a könyvtárat a merevlemezről, ha szükséges."
}

View File

@@ -1,153 +0,0 @@
{
"Notes": "Notes",
"Tags": "Tags",
"Preferences": "Preferences",
"Make a note": "Make a note",
"Ctrl": "Ctrl",
"Ctrl(^)": "Ctrl",
"to create a new note": "to create a new note",
"Toggle Mode": "Toggle Mode",
"Trash": "Trash",
"MODIFICATION DATE": "MODIFICATION DATE",
"Words": "Words",
"Letters": "Letters",
"STORAGE": "STORAGE",
"FOLDER": "FOLDER",
"CREATION DATE": "CREATION DATE",
"NOTE LINK": "NOTE LINK",
".md": ".md",
".txt": ".txt",
".html": ".html",
"Print": "Print",
"Your preferences for Boostnote": "Your preferences for Boostnote",
"Storages": "Storages",
"Add Storage Location": "Add Storage Location",
"Add Folder": "Add Folder",
"Open Storage folder": "Open Storage folder",
"Unlink": "Unlink",
"Edit": "Edit",
"Delete": "Delete",
"Interface": "Interface",
"Interface Theme": "Interface Theme",
"Default": "Delete",
"White": "White",
"Solarized Dark": "Solarized Dark",
"Dark": "Dark",
"Show a confirmation dialog when deleting notes": "Show a confirmation dialog when deleting notes",
"Editor Theme": "Editor Theme",
"Editor Font Size": "Editor Font Size",
"Editor Font Family": "Editor Font Family",
"Editor Indent Style": "Editor Indent Style",
"Spaces": "Spaces",
"Tabs": "Tabs",
"Switch to Preview": "Switch to Preview",
"When Editor Blurred": "When Editor Blurred",
"When Editor Blurred, Edit On Double Click": "When Editor Blurred, Edit On Double Click",
"On Right Click": "On Right Click",
"Editor Keymap": "Editor Keymap",
"default": "default",
"vim": "vim",
"emacs": "emacs",
"⚠️ Please restart boostnote after you change the keymap": "⚠️ Please restart boostnote after you change the keymap",
"Show line numbers in the editor": "Show line numbers in the editor",
"Allow editor to scroll past the last line": "Allow editor to scroll past the last line",
"Bring in web page title when pasting URL on editor": "Bring in web page title when pasting URL on editor",
"Preview": "Preview",
"Preview Font Size": "Preview Font Size",
"Preview Font Family": "Preview Font Family",
"Code block Theme": "Code block Theme",
"Allow preview to scroll past the last line": "Allow preview to scroll past the last line",
"Show line numbers for preview code blocks": "Show line numbers for preview code blocks",
"LaTeX Inline Open Delimiter": "LaTeX Inline Open Delimiter",
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
"Community": "Community",
"Subscribe to Newsletter": "Subscribe to Newsletter",
"GitHub": "GitHub",
"Blog": "Blog",
"Facebook Group": "Facebook Group",
"Twitter": "Twitter",
"About": "About",
"Boostnote": "Boostnote",
"An open source note-taking app made for programmers just like you.": "An open source note-taking app made for programmers just like you.",
"Website": "Website",
"Development": "Development",
" : Development configurations for Boostnote.": " : Development configurations for Boostnote.",
"Copyright (C) 2017 - 2018 BoostIO": "Copyright (C) 2017 - 2018 BoostIO",
"License: GPL v3": "License: GPL v3",
"Analytics": "Analytics",
"Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.": "Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.",
"You can see how it works on ": "You can see how it works on ",
"You can choose to enable or disable this option.": "You can choose to enable or disable this option.",
"Enable analytics to help improve Boostnote": "Enable analytics to help improve Boostnote",
"Crowdfunding": "Crowdfunding",
"Dear everyone,": "Dear everyone,",
"Thank you for using Boostnote!": "Thank you for using Boostnote!",
"Boostnote is used in about 200 different countries and regions by an awesome community of developers.": "Boostnote is used in about 200 different countries and regions by an awesome community of developers.",
"To continue supporting this growth, and to satisfy community expectations,": "To continue supporting this growth, and to satisfy community expectations,",
"we would like to invest more time and resources in this project.": "we would like to invest more time and resources in this project.",
"If you like this project and see its potential, you can help by supporting us on OpenCollective!": "If you like this project and see its potential, you can help by supporting us on OpenCollective!",
"Thanks,": "Thanks,",
"Boostnote maintainers": "Boostnote maintainers",
"Support via OpenCollective": "Support via OpenCollective",
"Language": "Language",
"English": "English",
"German": "German",
"French": "French",
"Show \"Saved to Clipboard\" notification when copying": "Show \"Saved to Clipboard\" notification when copying",
"All Notes": "All Notes",
"Starred": "Starred",
"Are you sure to ": "Are you sure to ",
" delete": " delete",
"this folder?": "this folder?",
"Confirm": "Confirm",
"Cancel": "Cancel",
"Markdown Note": "Markdown Note",
"This format is for creating text documents. Checklists, code blocks and Latex blocks are available.": "This format is for creating text documents. Checklists, code blocks and Latex blocks are available.",
"Snippet Note": "Snippet Note",
"This format is for creating code snippets. Multiple snippets can be grouped into a single note.": "This format is for creating code snippets. Multiple snippets can be grouped into a single note.",
"Tab to switch format": "Tab to switch format",
"Updated": "Updated",
"Created": "Created",
"Alphabetically": "Alphabetically",
"Default View": "Default View",
"Compressed View": "Compressed View",
"Search": "Search",
"Blog Type": "Blog Type",
"Blog Address": "Blog Address",
"Save": "Save",
"Auth": "Auth",
"Authentication Method": "Authentication Method",
"JWT": "JWT",
"USER": "USER",
"Token": "Token",
"Storage": "Storage",
"Hotkeys": "Hotkeys",
"Show/Hide Boostnote": "Show/Hide Boostnote",
"Restore": "Restore",
"Permanent Delete": "Permanent Delete",
"Confirm note deletion": "Confirm note deletion",
"This will permanently remove this note.": "This will permanently remove this note.",
"Successfully applied!": "Successfully applied!",
"Albanian": "Albanian",
"Chinese (zh-CN)": "Chinese (zh-CN)",
"Chinese (zh-TW)": "Chinese (zh-TW)",
"Danish": "Danish",
"Japanese": "Japanese",
"Korean": "Korean",
"Norwegian": "Norwegian",
"Polish": "Polish",
"Portuguese": "Portuguese",
"Spanish": "Spanish",
"You have to save!": "You have to save!",
"Russian": "Russian",
"Command(⌘)": "Command(⌘)",
"Editor Rulers": "Editor Rulers",
"Enable": "Enable",
"Disable": "Disable",
"Sanitization": "Sanitization",
"Only allow secure html tags (recommended)": "Only allow secure html tags (recommended)",
"Allow styles": "Allow styles",
"Allow dangerous html tags": "Allow dangerous html tags"
}

Some files were not shown because too many files have changed in this diff Show More