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
136 changed files with 1878 additions and 7840 deletions

View File

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

View File

@@ -23,7 +23,6 @@
"lineNumber": true "lineNumber": true
}, },
"sortBy": "UPDATED_AT", "sortBy": "UPDATED_AT",
"sortTagsBy": "ALPHABETICAL",
"ui": { "ui": {
"defaultNote": "ALWAYS_ASK", "defaultNote": "ALWAYS_ASK",
"disableDirectWrite": false, "disableDirectWrite": false,

View File

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

View File

@@ -3,9 +3,7 @@
"plugins": ["react"], "plugins": ["react"],
"rules": { "rules": {
"no-useless-escape": 0, "no-useless-escape": 0,
"prefer-const": ["warn", { "prefer-const": "warn",
"destructuring": "all"
}],
"no-unused-vars": "warn", "no-unused-vars": "warn",
"no-undef": "warn", "no-undef": "warn",
"no-lone-blocks": "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 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 && cd $(pwd) && snapcraft && snapcraft push *.snap --release edge"; fi
skip_cleanup: true 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. 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 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 it under the terms of the GNU General Public License as published by

View File

@@ -1,7 +0,0 @@
module.exports = {
require: jest.genMockFunction(),
match: jest.genMockFunction(),
app: jest.genMockFunction(),
remote: jest.genMockFunction(),
dialog: jest.genMockFunction()
}

View File

@@ -7,15 +7,10 @@ import path from 'path'
import copyImage from 'browser/main/lib/dataApi/copyImage' import copyImage from 'browser/main/lib/dataApi/copyImage'
import { findStorage } from 'browser/lib/findStorage' import { findStorage } from 'browser/lib/findStorage'
import fs from 'fs' 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' CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace'] const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
const buildCMRulers = (rulers, enableRulers) =>
enableRulers ? rulers.map(ruler => ({ column: ruler })) : []
function pass (name) { function pass (name) {
switch (name) { switch (name) {
@@ -36,13 +31,8 @@ export default class CodeEditor extends React.Component {
constructor (props) { constructor (props) {
super(props) super(props)
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {leading: false, trailing: true})
this.changeHandler = (e) => this.handleChange(e) this.changeHandler = (e) => this.handleChange(e)
this.focusHandler = () => {
ipcRenderer.send('editor:focused', true)
}
this.blurHandler = (editor, e) => { this.blurHandler = (editor, e) => {
ipcRenderer.send('editor:focused', false)
if (e == null) return null if (e == null) return null
let el = e.relatedTarget let el = e.relatedTarget
while (el != null) { while (el != null) {
@@ -57,47 +47,12 @@ export default class CodeEditor extends React.Component {
this.loadStyleHandler = (e) => { this.loadStyleHandler = (e) => {
this.editor.refresh() 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 () { componentDidMount () {
const { rulers, enableRulers } = this.props
this.value = this.props.value this.value = this.props.value
this.editor = CodeMirror(this.refs.root, { this.editor = CodeMirror(this.refs.root, {
rulers: buildCMRulers(rulers, enableRulers),
value: this.props.value, value: this.props.value,
lineNumbers: this.props.displayLineNumbers, lineNumbers: this.props.displayLineNumbers,
lineWrapping: true, lineWrapping: true,
@@ -109,8 +64,6 @@ export default class CodeEditor extends React.Component {
scrollPastEnd: this.props.scrollPastEnd, scrollPastEnd: this.props.scrollPastEnd,
inputStyle: 'textarea', inputStyle: 'textarea',
dragDrop: false, dragDrop: false,
foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
autoCloseBrackets: true, autoCloseBrackets: true,
extraKeys: { extraKeys: {
Tab: function (cm) { Tab: function (cm) {
@@ -139,7 +92,7 @@ export default class CodeEditor extends React.Component {
'Cmd-T': function (cm) { 'Cmd-T': function (cm) {
// Do nothing // Do nothing
}, },
Enter: 'boostNewLineAndIndentContinueMarkdownList', Enter: 'newlineAndIndentContinueMarkdownList',
'Ctrl-C': (cm) => { 'Ctrl-C': (cm) => {
if (cm.getOption('keyMap').substr(0, 3) === 'vim') { if (cm.getOption('keyMap').substr(0, 3) === 'vim') {
document.execCommand('copy') document.execCommand('copy')
@@ -151,14 +104,9 @@ export default class CodeEditor extends React.Component {
this.setMode(this.props.mode) this.setMode(this.props.mode)
this.editor.on('focus', this.focusHandler)
this.editor.on('blur', this.blurHandler) this.editor.on('blur', this.blurHandler)
this.editor.on('change', this.changeHandler) this.editor.on('change', this.changeHandler)
this.editor.on('paste', this.pasteHandler) 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') const editorTheme = document.getElementById('editorTheme')
editorTheme.addEventListener('load', this.loadStyleHandler) editorTheme.addEventListener('load', this.loadStyleHandler)
@@ -175,19 +123,15 @@ export default class CodeEditor extends React.Component {
} }
componentWillUnmount () { componentWillUnmount () {
this.editor.off('focus', this.focusHandler)
this.editor.off('blur', this.blurHandler) this.editor.off('blur', this.blurHandler)
this.editor.off('change', this.changeHandler) this.editor.off('change', this.changeHandler)
this.editor.off('paste', this.pasteHandler) this.editor.off('paste', this.pasteHandler)
eventEmitter.off('top:search', this.searchHandler)
this.editor.off('scroll', this.scrollHandler)
const editorTheme = document.getElementById('editorTheme') const editorTheme = document.getElementById('editorTheme')
editorTheme.removeEventListener('load', this.loadStyleHandler) editorTheme.removeEventListener('load', this.loadStyleHandler)
} }
componentDidUpdate (prevProps, prevState) { componentDidUpdate (prevProps, prevState) {
let needRefresh = false let needRefresh = false
const { rulers, enableRulers } = this.props
if (prevProps.mode !== this.props.mode) { if (prevProps.mode !== this.props.mode) {
this.setMode(this.props.mode) this.setMode(this.props.mode)
} }
@@ -205,10 +149,6 @@ export default class CodeEditor extends React.Component {
needRefresh = true needRefresh = true
} }
if (prevProps.enableRulers !== enableRulers || prevProps.rulers !== rulers) {
this.editor.setOption('rulers', buildCMRulers(rulers, enableRulers))
}
if (prevProps.indentSize !== this.props.indentSize) { if (prevProps.indentSize !== this.props.indentSize) {
this.editor.setOption('indentUnit', this.props.indentSize) this.editor.setOption('indentUnit', this.props.indentSize)
this.editor.setOption('tabSize', this.props.indentSize) this.editor.setOption('tabSize', this.props.indentSize)
@@ -277,16 +217,11 @@ export default class CodeEditor extends React.Component {
handleDropImage (e) { handleDropImage (e) {
e.preventDefault() e.preventDefault()
const ValidImageTypes = ['image/gif', 'image/jpeg', 'image/png'] const imagePath = e.dataTransfer.files[0].path
const filename = path.basename(imagePath)
const file = e.dataTransfer.files[0] copyImage(imagePath, this.props.storageKey).then((imagePath) => {
const filePath = file.path const imageMd = `![${filename}](${path.join('/:storage', imagePath)})`
const filename = path.basename(filePath)
const fileType = file['type']
copyImage(filePath, this.props.storageKey).then((imagePath) => {
var showPreview = ValidImageTypes.indexOf(fileType) > 0
const imageMd = `${showPreview ? '!' : ''}[${filename}](${path.join('/:storage', imagePath)})`
this.insertImageMd(imageMd) this.insertImageMd(imageMd)
}) })
} }
@@ -296,105 +231,27 @@ export default class CodeEditor extends React.Component {
} }
handlePaste (editor, e) { handlePaste (editor, e) {
const clipboardData = e.clipboardData const dataTransferItem = e.clipboardData.items[0]
const dataTransferItem = clipboardData.items[0] if (!dataTransferItem.type.match('image')) return
const pastedTxt = clipboardData.getData('text')
const isURL = (str) => { const blob = dataTransferItem.getAsFile()
const matcher = /^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/ const reader = new window.FileReader()
return matcher.test(str) 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 () { render () {
@@ -423,8 +280,6 @@ export default class CodeEditor extends React.Component {
CodeEditor.propTypes = { CodeEditor.propTypes = {
value: PropTypes.string, value: PropTypes.string,
enableRulers: PropTypes.bool,
rulers: PropTypes.arrayOf(Number),
mode: PropTypes.string, mode: PropTypes.string,
className: PropTypes.string, className: PropTypes.string,
onBlur: PropTypes.func, onBlur: PropTypes.func,

View File

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

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

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

View File

@@ -2,7 +2,6 @@ import React from 'react'
import CodeEditor from 'browser/components/CodeEditor' import CodeEditor from 'browser/components/CodeEditor'
import MarkdownPreview from 'browser/components/MarkdownPreview' import MarkdownPreview from 'browser/components/MarkdownPreview'
import { findStorage } from 'browser/lib/findStorage' import { findStorage } from 'browser/lib/findStorage'
import _ from 'lodash'
import styles from './MarkdownSplitEditor.styl' import styles from './MarkdownSplitEditor.styl'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
@@ -13,7 +12,6 @@ class MarkdownSplitEditor extends React.Component {
this.value = props.value this.value = props.value
this.focus = () => this.refs.code.focus() this.focus = () => this.refs.code.focus()
this.reload = () => this.refs.code.reload() this.reload = () => this.refs.code.reload()
this.userScroll = true
} }
handleOnChange () { handleOnChange () {
@@ -21,49 +19,6 @@ class MarkdownSplitEditor extends React.Component {
this.props.onChange() 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) { handleCheckboxClick (e) {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
@@ -110,13 +65,9 @@ class MarkdownSplitEditor extends React.Component {
displayLineNumbers={config.editor.displayLineNumbers} displayLineNumbers={config.editor.displayLineNumbers}
indentType={config.editor.indentType} indentType={config.editor.indentType}
indentSize={editorIndentSize} indentSize={editorIndentSize}
enableRulers={config.editor.enableRulers}
rulers={config.editor.rulers}
scrollPastEnd={config.editor.scrollPastEnd} scrollPastEnd={config.editor.scrollPastEnd}
fetchUrlTitle={config.editor.fetchUrlTitle}
storageKey={storageKey} storageKey={storageKey}
onChange={this.handleOnChange.bind(this)} onChange={this.handleOnChange.bind(this)}
onScroll={this.handleScroll.bind(this)}
/> />
<MarkdownPreview <MarkdownPreview
style={previewStyle} style={previewStyle}
@@ -129,13 +80,10 @@ class MarkdownSplitEditor extends React.Component {
codeBlockFontFamily={config.editor.fontFamily} codeBlockFontFamily={config.editor.fontFamily}
lineNumber={config.preview.lineNumber} lineNumber={config.preview.lineNumber}
scrollPastEnd={config.preview.scrollPastEnd} scrollPastEnd={config.preview.scrollPastEnd}
smartQuotes={config.preview.smartQuotes}
sanitize={config.preview.sanitize}
ref='preview' ref='preview'
tabInde='0' tabInde='0'
value={value} value={value}
onCheckboxClick={(e) => this.handleCheckboxClick(e)} onCheckboxClick={(e) => this.handleCheckboxClick(e)}
onScroll={this.handleScroll.bind(this)}
showCopyNotification={config.ui.showCopyNotification} showCopyNotification={config.ui.showCopyNotification}
storagePath={storage.path} storagePath={storage.path}
/> />

View File

@@ -8,7 +8,6 @@ import CSSModules from 'browser/lib/CSSModules'
import { getTodoStatus } from 'browser/lib/getTodoStatus' import { getTodoStatus } from 'browser/lib/getTodoStatus'
import styles from './NoteItem.styl' import styles from './NoteItem.styl'
import TodoProcess from './TodoProcess' import TodoProcess from './TodoProcess'
import i18n from 'browser/lib/i18n'
/** /**
* @description Tag element component. * @description Tag element component.
@@ -47,25 +46,14 @@ const TagElementList = (tags) => {
* @param {Function} handleDragStart * @param {Function} handleDragStart
* @param {string} dateDisplay * @param {string} dateDisplay
*/ */
const NoteItem = ({ const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleNoteContextMenu, handleDragStart, pathname }) => (
isActive,
note,
dateDisplay,
handleNoteClick,
handleNoteContextMenu,
handleDragStart,
pathname,
storageName,
folderName,
viewType
}) => (
<div styleName={isActive <div styleName={isActive
? 'item--active' ? 'item--active'
: 'item' : 'item'
} }
key={note.key} key={`${note.storage}-${note.key}`}
onClick={e => handleNoteClick(e, note.key)} onClick={e => handleNoteClick(e, `${note.storage}-${note.key}`)}
onContextMenu={e => handleNoteContextMenu(e, note.key)} onContextMenu={e => handleNoteContextMenu(e, `${note.storage}-${note.key}`)}
onDragStart={e => handleDragStart(e, note)} onDragStart={e => handleDragStart(e, note)}
draggable='true' draggable='true'
> >
@@ -77,36 +65,26 @@ const NoteItem = ({
<div styleName='item-title'> <div styleName='item-title'>
{note.title.trim().length > 0 {note.title.trim().length > 0
? note.title ? note.title
: <span styleName='item-title-empty'>{i18n.__('Empty note')}</span> : <span styleName='item-title-empty'>Empty</span>
} }
</div> </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'>
<div styleName='item-bottom-tagList'> <div styleName='item-bottom-tagList'>
{note.tags.length > 0 {note.tags.length > 0
? TagElementList(note.tags) ? TagElementList(note.tags)
: <span style={{ fontStyle: 'italic', opacity: 0.5 }} styleName='item-bottom-tagList-empty'>{i18n.__('No tags')}</span> : <span styleName='item-bottom-tagList-empty' />
}
</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)} />
: ''
} }
</div> </div>
</div> </div>
@@ -124,11 +102,7 @@ NoteItem.propTypes = {
title: PropTypes.string.isrequired, title: PropTypes.string.isrequired,
tags: PropTypes.array, tags: PropTypes.array,
isStarred: PropTypes.bool.isRequired, isStarred: PropTypes.bool.isRequired,
isTrashed: PropTypes.bool.isRequired, isTrashed: PropTypes.bool.isRequired
blog: {
blogLink: PropTypes.string,
blogId: PropTypes.number
}
}), }),
handleNoteClick: PropTypes.func.isRequired, handleNoteClick: PropTypes.func.isRequired,
handleNoteContextMenu: PropTypes.func.isRequired, handleNoteContextMenu: PropTypes.func.isRequired,

View File

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

View File

@@ -5,7 +5,6 @@ import PropTypes from 'prop-types'
import React from 'react' import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './NoteItemSimple.styl' import styles from './NoteItemSimple.styl'
import i18n from 'browser/lib/i18n'
/** /**
* @description Note item component when using simple display mode. * @description Note item component when using simple display mode.
@@ -15,23 +14,14 @@ import i18n from 'browser/lib/i18n'
* @param {Function} handleNoteContextMenu * @param {Function} handleNoteContextMenu
* @param {Function} handleDragStart * @param {Function} handleDragStart
*/ */
const NoteItemSimple = ({ const NoteItemSimple = ({ isActive, note, handleNoteClick, handleNoteContextMenu, handleDragStart, pathname }) => (
isActive,
isAllNotesView,
note,
handleNoteClick,
handleNoteContextMenu,
handleDragStart,
pathname,
storage
}) => (
<div styleName={isActive <div styleName={isActive
? 'item-simple--active' ? 'item-simple--active'
: 'item-simple' : 'item-simple'
} }
key={note.key} key={`${note.storage}-${note.key}`}
onClick={e => handleNoteClick(e, note.key)} onClick={e => handleNoteClick(e, `${note.storage}-${note.key}`)}
onContextMenu={e => handleNoteContextMenu(e, note.key)} onContextMenu={e => handleNoteContextMenu(e, `${note.storage}-${note.key}`)}
onDragStart={e => handleDragStart(e, note)} onDragStart={e => handleDragStart(e, note)}
draggable='true' draggable='true'
> >
@@ -40,19 +30,14 @@ 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-code' />
: <i styleName='item-simple-title-icon' className='fa fa-fw fa-file-text-o' /> : <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' /> ? <i styleName='item-pin' className='fa fa-thumb-tack' />
: '' : ''
} }
{note.title.trim().length > 0 {note.title.trim().length > 0
? note.title ? note.title
: <span styleName='item-simple-title-empty'>{i18n.__('Empty note')}</span> : <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>
</div> </div>
) )

View File

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

View File

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

View File

@@ -2,7 +2,6 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './SnippetTab.styl' import styles from './SnippetTab.styl'
import context from 'browser/lib/context' import context from 'browser/lib/context'
import i18n from 'browser/lib/i18n'
class SnippetTab extends React.Component { class SnippetTab extends React.Component {
constructor (props) { constructor (props) {
@@ -29,7 +28,7 @@ class SnippetTab extends React.Component {
handleContextMenu (e) { handleContextMenu (e) {
context.popup([ context.popup([
{ {
label: i18n.__('Rename'), label: 'Rename',
click: (e) => this.handleRenameClick(e) click: (e) => this.handleRenameClick(e)
} }
]) ])
@@ -115,7 +114,7 @@ class SnippetTab extends React.Component {
{snippet.name.trim().length > 0 {snippet.name.trim().length > 0
? snippet.name ? snippet.name
: <span styleName='button-unnamed'> : <span styleName='button-unnamed'>
{i18n.__('Unnamed')} Unnamed
</span> </span>
} }
</button> </button>

View File

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

View File

@@ -6,18 +6,6 @@ import React from 'react'
import styles from './StorageItem.styl' import styles from './StorageItem.styl'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import _ from 'lodash' 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 * @param {boolean} isActive
@@ -33,54 +21,34 @@ const FolderIcon = ({ className, color, isActive }) => {
* @return {React.Component} * @return {React.Component}
*/ */
const StorageItem = ({ const StorageItem = ({
styles, isActive, handleButtonClick, handleContextMenu, folderName,
isActive, folderColor, isFolded, noteCount, handleDrop, handleDragEnter, handleDragLeave
handleButtonClick, }) => (
handleContextMenu, <button styleName={isActive
folderName, ? 'folderList-item--active'
folderColor, : 'folderList-item'
isFolded, }
noteCount, onClick={handleButtonClick}
handleDrop, onContextMenu={handleContextMenu}
handleDragEnter, onDrop={handleDrop}
handleDragLeave onDragEnter={handleDragEnter}
}) => { onDragLeave={handleDragLeave}
return ( >
<button <span styleName={isFolded
styleName={isActive ? 'folderList-item--active' : 'folderList-item'} ? 'folderList-item-name--folded' : 'folderList-item-name'
onClick={handleButtonClick} }>
onContextMenu={handleContextMenu} <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}
onDrop={handleDrop} </span>
onDragEnter={handleDragEnter} {(!isFolded && _.isNumber(noteCount)) &&
onDragLeave={handleDragLeave} <span styleName='folderList-item-noteCount'>{noteCount}</span>
> }
{!isFolded && ( {isFolded &&
<DraggableIcon className={styles['folderList-item-reorder']} /> <span styleName='folderList-item-tooltip'>
)} {folderName}
<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}
</span> </span>
{!isFolded && }
_.isNumber(noteCount) && ( </button>
<span styleName='folderList-item-noteCount'>{noteCount}</span> )
)}
{isFolded && (
<span styleName='folderList-item-tooltip'>{folderName}</span>
)}
</button>
)
}
StorageItem.propTypes = { StorageItem.propTypes = {
isActive: PropTypes.bool.isRequired, isActive: PropTypes.bool.isRequired,

View File

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

View File

@@ -10,8 +10,8 @@ import CSSModules from 'browser/lib/CSSModules'
* @param {Array} storgaeList * @param {Array} storgaeList
*/ */
const StorageList = ({storageList, isFolded}) => ( const StorageList = ({storageList}) => (
<div styleName={isFolded ? 'storageList-folded' : 'storageList'}> <div styleName='storageList'>
{storageList.length > 0 ? storageList : ( {storageList.length > 0 ? storageList : (
<div styleName='storgaeList-empty'>No storage mount.</div> <div styleName='storgaeList-empty'>No storage mount.</div>
)} )}

View File

@@ -4,10 +4,6 @@
top 180px top 180px
overflow-y auto overflow-y auto
.storageList-folded
@extend .storageList
width 44px
.storageList-empty .storageList-empty
padding 0 10px padding 0 10px
margin-top 15px margin-top 15px

View File

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

View File

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

View File

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

View File

@@ -1,16 +0,0 @@
const path = require('path')
const { remote } = require('electron')
const { app } = remote
// load package for localization
const i18n = new (require('i18n-2'))({
// setup some locales - other locales default to the first locale
locales: ['en', 'sq', 'zh-CN', 'zh-TW', 'da', 'fr', 'de', 'hu', 'ja', 'ko', 'no', 'pl', 'pt', 'es-ES'],
extension: '.json',
directory: process.env.NODE_ENV === 'production'
? path.join(app.getAppPath(), './locales')
: path.resolve('./locales'),
devMode: false
})
export default i18n

View File

@@ -1,11 +1,7 @@
const crypto = require('crypto') const crypto = require('crypto')
const _ = require('lodash') const _ = require('lodash')
const uuidv4 = require('uuid/v4')
module.exports = function (uuid) { module.exports = function (length) {
if (typeof uuid === typeof true && uuid) { if (!_.isFinite(length)) length = 10
return uuidv4()
}
const length = 10
return crypto.randomBytes(length).toString('hex') 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 markdownit from 'markdown-it'
import sanitize from './markdown-it-sanitize-html'
import emoji from 'markdown-it-emoji' import emoji from 'markdown-it-emoji'
import math from '@rokt33r/markdown-it-math' import math from '@rokt33r/markdown-it-math'
import _ from 'lodash' import _ from 'lodash'
import ConfigManager from 'browser/main/lib/ConfigManager' import ConfigManager from 'browser/main/lib/ConfigManager'
import katex from 'katex'
import {lastFindInArray} from './utils'
function createGutter (str, firstLineNumber) { // FIXME We should not depend on global variable.
if (Number.isNaN(firstLineNumber)) firstLineNumber = 1 const katex = window.katex
const lastLineNumber = (str.match(/\n/g) || []).length + firstLineNumber - 1 const config = ConfigManager.get()
function createGutter (str) {
const lc = (str.match(/\n/g) || []).length
const lines = [] const lines = []
for (let i = firstLineNumber; i <= lastLineNumber; i++) { for (let i = 1; i <= lc; i++) {
lines.push('<span class="CodeMirror-linenumber">' + i + '</span>') lines.push('<span class="CodeMirror-linenumber">' + i + '</span>')
} }
return '<span class="lineNumber CodeMirror-gutters">' + lines.join('') + '</span>' return '<span class="lineNumber CodeMirror-gutters">' + lines.join('') + '</span>'
} }
class Markdown { var md = markdownit({
constructor (options = {}) { typographer: true,
const config = ConfigManager.get() linkify: true,
const defaultOptions = { html: true,
typographer: config.preview.smartQuotes, xhtmlOut: true,
linkify: true, breaks: true,
html: true, highlight: function (str, lang) {
xhtmlOut: true, if (lang === 'flowchart') {
breaks: true, return `<pre class="flowchart">${str}</pre>`
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'
} }
if (lang === 'sequence') {
const updatedOptions = Object.assign(defaultOptions, options) return `<pre class="sequence">${str}</pre>`
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']
})
} }
return '<pre class="code">' +
this.md.use(emoji, { createGutter(str) +
shortcuts: {} '<code class="' + lang + '">' +
}) str +
this.md.use(math, { '</code></pre>'
inlineOpen: config.preview.latexInlineOpen, }
inlineClose: config.preview.latexInlineClose, })
blockOpen: config.preview.latexBlockOpen, md.use(emoji, {
blockClose: config.preview.latexBlockClose, shortcuts: {}
inlineRenderer: function (str) { })
let output = '' md.use(math, {
try { inlineOpen: config.preview.latexInlineOpen,
output = katex.renderToString(str.trim()) inlineClose: config.preview.latexInlineClose,
} catch (err) { blockOpen: config.preview.latexBlockOpen,
output = `<span class="katex-error">${err.message}</span>` blockClose: config.preview.latexBlockClose,
} inlineRenderer: function (str) {
return output let output = ''
}, try {
blockRenderer: function (str) { output = katex.renderToString(str.trim())
let output = '' } catch (err) {
try { output = `<span class="katex-error">${err.message}</span>`
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
} }
// FIXME We should not depend on global variable. return output
window.md = this.md },
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) { content = state.getLines(startLine, nextLine, state.blkIndent, false).trim()
if (!_.isString(content)) content = ''
return this.md.render(content) 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) { token = state.push('inline', '', 0)
return this.md.normalizeLinkText(linkText) 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

@@ -4,28 +4,39 @@ export default function searchFromNotes (notes, search) {
if (search.trim().length === 0) return [] if (search.trim().length === 0) return []
const searchBlocks = search.split(' ').filter(block => { return block !== '' }) const searchBlocks = search.split(' ').filter(block => { return block !== '' })
let foundNotes = notes let foundNotes = findByWord(notes, searchBlocks[0])
searchBlocks.forEach((block) => { searchBlocks.forEach((block) => {
foundNotes = findByWordOrTag(foundNotes, block) foundNotes = findByWord(foundNotes, block)
if (block.match(/^#.+/)) {
foundNotes = foundNotes.concat(findByTag(notes, block))
}
}) })
return foundNotes return foundNotes
} }
function findByWordOrTag (notes, block) { function findByTag (notes, block) {
let tag = block const tag = block.match(/#(.+)/)[1]
if (tag.match(/^#.+/)) { const regExp = new RegExp(_.escapeRegExp(tag), 'i')
tag = tag.match(/#(.+)/)[1]
}
const tagRegExp = new RegExp(_.escapeRegExp(tag), 'i')
const wordRegExp = new RegExp(_.escapeRegExp(block), 'i')
return notes.filter((note) => { return notes.filter((note) => {
if (_.isArray(note.tags) && note.tags.some((_tag) => _tag.match(tagRegExp))) { if (!_.isArray(note.tags)) return false
return note.tags.some((_tag) => {
return _tag.match(regExp)
})
})
}
function findByWord (notes, block) {
const regExp = new RegExp(_.escapeRegExp(block), 'i')
return notes.filter((note) => {
if (_.isArray(note.tags) && note.tags.some((_tag) => {
return _tag.match(regExp)
})) {
return true return true
} }
if (note.type === 'SNIPPET_NOTE') { if (note.type === 'SNIPPET_NOTE') {
return note.description.match(wordRegExp) return note.description.match(regExp)
} else if (note.type === 'MARKDOWN_NOTE') { } else if (note.type === 'MARKDOWN_NOTE') {
return note.content.match(wordRegExp) return note.content.match(regExp)
} }
return false return false
}) })

View File

@@ -1,60 +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 function escapeHtmlCharacters (text) {
const matchHtmlRegExp = /["'&<>]/
const str = '' + text
const match = matchHtmlRegExp.exec(str)
if (!match) {
return str
}
let escape
let html = ''
let index = 0
let lastIndex = 0
for (index = match.index; index < str.length; index++) {
switch (str.charCodeAt(index)) {
case 34: // "
escape = '&quot;'
break
case 38: // &
escape = '&amp;'
break
case 39: // '
escape = '&#39;'
break
case 60: // <
escape = '&lt;'
break
case 62: // >
escape = '&gt;'
break
default:
continue
}
if (lastIndex !== index) {
html += str.substring(lastIndex, index)
}
lastIndex = index + 1
html += escape
}
return lastIndex !== index
? html + str.substring(lastIndex, index)
: html
}
export default {
lastFindInArray,
escapeHtmlCharacters
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,6 @@ import PropTypes from 'prop-types'
import React from 'react' import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './InfoPanel.styl' import styles from './InfoPanel.styl'
import i18n from 'browser/lib/i18n'
const InfoPanelTrashed = ({ const InfoPanelTrashed = ({
storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml 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 className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}>
<div> <div>
<p styleName='modification-date'>{updatedAt}</p> <p styleName='modification-date'>{updatedAt}</p>
<p styleName='modification-date-desc'>{i18n.__('MODIFICATION DATE')}</p> <p styleName='modification-date-desc'>MODIFICATION DATE</p>
</div> </div>
<hr /> <hr />
<div> <div>
<p styleName='infoPanel-default'>{storageName}</p> <p styleName='infoPanel-default'>{storageName}</p>
<p styleName='infoPanel-sub'>{i18n.__('STORAGE')}</p> <p styleName='infoPanel-sub'>STORAGE</p>
</div> </div>
<div> <div>
<p styleName='infoPanel-default'><text styleName='infoPanel-trash'>Trash</text>{folderName}</p> <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>
<div> <div>
<p styleName='infoPanel-default'>{createdAt}</p> <p styleName='infoPanel-default'>{createdAt}</p>
<p styleName='infoPanel-sub'>{i18n.__('CREATION DATE')}</p> <p styleName='infoPanel-sub'>CREATION DATE</p>
</div> </div>
<div id='export-wrap'> <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 ConfigManager from 'browser/main/lib/ConfigManager'
import TrashButton from './TrashButton' import TrashButton from './TrashButton'
import FullscreenButton from './FullscreenButton' import FullscreenButton from './FullscreenButton'
import RestoreButton from './RestoreButton'
import PermanentDeleteButton from './PermanentDeleteButton' import PermanentDeleteButton from './PermanentDeleteButton'
import InfoButton from './InfoButton' import InfoButton from './InfoButton'
import ToggleModeButton from './ToggleModeButton' import ToggleModeButton from './ToggleModeButton'
@@ -69,10 +68,13 @@ class MarkdownNoteDetail extends React.Component {
} }
componentWillUnmount () { componentWillUnmount () {
ee.off('topbar:togglelockbutton', this.toggleLockButton)
if (this.saveQueue != null) this.saveNow() if (this.saveQueue != null) this.saveNow()
} }
componentDidUnmount () {
ee.off('topbar:togglelockbutton', this.toggleLockButton)
}
handleUpdateTag () { handleUpdateTag () {
const { note } = this.state const { note } = this.state
if (this.refs.tags) note.tags = this.refs.tags.value if (this.refs.tags) note.tags = this.refs.tags.value
@@ -139,7 +141,7 @@ class MarkdownNoteDetail extends React.Component {
hashHistory.replace({ hashHistory.replace({
pathname: location.pathname, pathname: location.pathname,
query: { query: {
key: newNote.key key: newNote.storage + '-' + newNote.key
} }
}) })
this.setState({ this.setState({
@@ -196,9 +198,8 @@ class MarkdownNoteDetail extends React.Component {
noteKey: data.noteKey noteKey: data.noteKey
}) })
} }
ee.once('list:next', dispatchHandler) ee.once('list:moved', dispatchHandler)
}) })
.then(() => ee.emit('list:next'))
} }
} else { } else {
if (confirmDeletion()) { if (confirmDeletion()) {
@@ -322,7 +323,10 @@ class MarkdownNoteDetail extends React.Component {
const trashTopBar = <div styleName='info'> const trashTopBar = <div styleName='info'>
<div styleName='info-left'> <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>
<div styleName='info-right'> <div styleName='info-right'>
<PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} /> <PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} />
@@ -357,10 +361,12 @@ class MarkdownNoteDetail extends React.Component {
value={this.state.note.tags} value={this.state.note.tags}
onChange={this.handleUpdateTag.bind(this)} onChange={this.handleUpdateTag.bind(this)}
/> />
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
<TodoListPercentage percentageOfTodo={getTodoPercentageOfCompleted(note.content)} /> <TodoListPercentage percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
</div> </div>
<div styleName='info-right'> <div styleName='info-right'>
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
<StarButton <StarButton
onClick={(e) => this.handleStarButtonClick(e)} onClick={(e) => this.handleStarButtonClick(e)}
isActive={note.isStarred} isActive={note.isStarred}
@@ -393,7 +399,7 @@ class MarkdownNoteDetail extends React.Component {
<InfoPanel <InfoPanel
storageName={currentOption.storage.name} storageName={currentOption.storage.name}
folderName={currentOption.folder.name} folderName={currentOption.folder.name}
noteLink={`[${note.title}](:note:${location.query.key})`} noteLink={`[${note.title}](${location.query.key})`}
updatedAt={formatDate(note.updatedAt)} updatedAt={formatDate(note.updatedAt)}
createdAt={formatDate(note.createdAt)} createdAt={formatDate(note.createdAt)}
exportAsMd={this.exportAsMd} exportAsMd={this.exportAsMd}

View File

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

View File

@@ -2,7 +2,6 @@ import PropTypes from 'prop-types'
import React from 'react' import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './TrashButton.styl' import styles from './TrashButton.styl'
import i18n from 'browser/lib/i18n'
const PermanentDeleteButton = ({ const PermanentDeleteButton = ({
onClick onClick
@@ -11,7 +10,7 @@ const PermanentDeleteButton = ({
onClick={(e) => onClick(e)} onClick={(e) => onClick(e)}
> >
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' /> <img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
<span styleName='tooltip'>{i18n.__('Permanent Delete')}</span> <span styleName='tooltip'>Permanent Delete</span>
</button> </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 TagSelect from './TagSelect'
import FolderSelect from './FolderSelect' import FolderSelect from './FolderSelect'
import dataApi from 'browser/main/lib/dataApi' import dataApi from 'browser/main/lib/dataApi'
import {hashHistory} from 'react-router' import { hashHistory } from 'react-router'
import ee from 'browser/main/lib/eventEmitter' import ee from 'browser/main/lib/eventEmitter'
import CodeMirror from 'codemirror' import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir' import 'codemirror-mode-elixir'
@@ -17,16 +17,14 @@ import StatusBar from '../StatusBar'
import context from 'browser/lib/context' import context from 'browser/lib/context'
import ConfigManager from 'browser/main/lib/ConfigManager' import ConfigManager from 'browser/main/lib/ConfigManager'
import _ from 'lodash' import _ from 'lodash'
import {findNoteTitle} from 'browser/lib/findNoteTitle' import { findNoteTitle } from 'browser/lib/findNoteTitle'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import TrashButton from './TrashButton' import TrashButton from './TrashButton'
import RestoreButton from './RestoreButton'
import PermanentDeleteButton from './PermanentDeleteButton' import PermanentDeleteButton from './PermanentDeleteButton'
import InfoButton from './InfoButton' import InfoButton from './InfoButton'
import InfoPanel from './InfoPanel' import InfoPanel from './InfoPanel'
import InfoPanelTrashed from './InfoPanelTrashed' import InfoPanelTrashed from './InfoPanelTrashed'
import { formatDate } from 'browser/lib/date-formatter' import { formatDate } from 'browser/lib/date-formatter'
import i18n from 'browser/lib/i18n'
function pass (name) { function pass (name) {
switch (name) { switch (name) {
@@ -54,30 +52,12 @@ class SnippetNoteDetail extends React.Component {
this.state = { this.state = {
isMovingNote: false, isMovingNote: false,
snippetIndex: 0, snippetIndex: 0,
showArrows: false,
enableLeftArrow: false,
enableRightArrow: false,
note: Object.assign({ note: Object.assign({
description: '' description: ''
}, props.note, { }, props.note, {
snippets: props.note.snippets.map((snippet) => Object.assign({}, snippet)) 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) { componentWillReceiveProps (nextProps) {
@@ -97,7 +77,6 @@ class SnippetNoteDetail extends React.Component {
this.refs['code-' + index].reload() this.refs['code-' + index].reload()
}) })
if (this.refs.tags) this.refs.tags.reset() if (this.refs.tags) this.refs.tags.reset()
this.setState(this.getArrowsState())
}) })
} }
} }
@@ -167,7 +146,7 @@ class SnippetNoteDetail extends React.Component {
hashHistory.replace({ hashHistory.replace({
pathname: location.pathname, pathname: location.pathname,
query: { query: {
key: newNote.key key: newNote.storage + '-' + newNote.key
} }
}) })
this.setState({ this.setState({
@@ -212,9 +191,8 @@ class SnippetNoteDetail extends React.Component {
noteKey: data.noteKey noteKey: data.noteKey
}) })
} }
ee.once('list:next', dispatchHandler) ee.once('list:moved', dispatchHandler)
}) })
.then(() => ee.emit('list:next'))
} }
} else { } else {
if (confirmDeletion()) { if (confirmDeletion()) {
@@ -248,51 +226,6 @@ class SnippetNoteDetail extends React.Component {
ee.emit('editor:fullscreen') 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) { handleTabPlusButtonClick (e) {
this.addSnippet() this.addSnippet()
} }
@@ -329,9 +262,9 @@ class SnippetNoteDetail extends React.Component {
if (this.state.note.snippets[index].content.trim().length > 0) { if (this.state.note.snippets[index].content.trim().length > 0) {
const dialogIndex = dialog.showMessageBox(remote.getCurrentWindow(), { const dialogIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning', type: 'warning',
message: i18n.__('Delete a snippet'), message: 'Delete a snippet',
detail: i18n.__('This work cannot be undone.'), detail: 'This work cannot be undone.',
buttons: [i18n.__('Confirm'), i18n.__('Cancel')] buttons: ['Confirm', 'Cancel']
}) })
if (dialogIndex === 0) { if (dialogIndex === 0) {
this.deleteSnippetByIndex(index) this.deleteSnippetByIndex(index)
@@ -352,21 +285,6 @@ class SnippetNoteDetail extends React.Component {
this.setState({ note, snippetIndex }, () => { this.setState({ note, snippetIndex }, () => {
this.save() this.save()
this.refs['code-' + this.state.snippetIndex].reload() 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) { handleKeyDown (e) {
switch (e.keyCode) { switch (e.keyCode) {
// tab key
case 9: case 9:
if (e.ctrlKey && !e.shiftKey) { if (e.ctrlKey && !e.shiftKey) {
e.preventDefault() e.preventDefault()
@@ -436,7 +353,6 @@ class SnippetNoteDetail extends React.Component {
this.focusEditor() this.focusEditor()
} }
break break
// L key
case 76: case 76:
{ {
const isSuper = global.process.platform === 'darwin' const isSuper = global.process.platform === 'darwin'
@@ -448,7 +364,6 @@ class SnippetNoteDetail extends React.Component {
} }
} }
break break
// T key
case 84: case 84:
{ {
const isSuper = global.process.platform === 'darwin' const isSuper = global.process.platform === 'darwin'
@@ -540,51 +455,6 @@ class SnippetNoteDetail extends React.Component {
this.refs.description.focus() 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 () { addSnippet () {
const { note } = this.state const { note } = this.state
@@ -595,16 +465,10 @@ class SnippetNoteDetail extends React.Component {
}]) }])
const snippetIndex = note.snippets.length - 1 const snippetIndex = note.snippets.length - 1
this.setState(Object.assign({ this.setState({
note, note,
snippetIndex snippetIndex
}, this.getArrowsState()), () => { }, () => {
if (this.state.showArrows) {
const tabs = this.allTabs.querySelectorAll('div')
if (tabs) {
this.moveToTab(tabs[snippetIndex])
}
}
this.refs['tab-' + snippetIndex].startRenaming() this.refs['tab-' + snippetIndex].startRenaming()
}) })
} }
@@ -638,9 +502,9 @@ class SnippetNoteDetail extends React.Component {
showWarning () { showWarning () {
dialog.showMessageBox(remote.getCurrentWindow(), { dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning', type: 'warning',
message: i18n.__('Sorry!'), message: 'Sorry!',
detail: i18n.__('md/text import is available only a markdown note.'), detail: 'md/text import is available only a markdown note.',
buttons: [i18n.__('OK')] buttons: ['OK']
}) })
} }
@@ -703,7 +567,6 @@ class SnippetNoteDetail extends React.Component {
displayLineNumbers={config.editor.displayLineNumbers} displayLineNumbers={config.editor.displayLineNumbers}
keyMap={config.editor.keyMap} keyMap={config.editor.keyMap}
scrollPastEnd={config.editor.scrollPastEnd} scrollPastEnd={config.editor.scrollPastEnd}
fetchUrlTitle={config.editor.fetchUrlTitle}
onChange={(e) => this.handleCodeChange(index)(e)} onChange={(e) => this.handleCodeChange(index)(e)}
ref={'code-' + index} ref={'code-' + index}
/> />
@@ -724,7 +587,10 @@ class SnippetNoteDetail extends React.Component {
const trashTopBar = <div styleName='info'> const trashTopBar = <div styleName='info'>
<div styleName='info-left'> <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>
<div styleName='info-right'> <div styleName='info-right'>
<PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} /> <PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} />
@@ -766,10 +632,10 @@ class SnippetNoteDetail extends React.Component {
isActive={note.isStarred} isActive={note.isStarred}
/> />
<button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')} <button styleName='control-fullScreenButton' title='Fullscreen'
onMouseDown={(e) => this.handleFullScreenButton(e)}> onMouseDown={(e) => this.handleFullScreenButton(e)}>
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' /> <img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
<span styleName='tooltip'>{i18n.__('Fullscreen')}</span> <span styleName='tooltip'>Fullscreen</span>
</button> </button>
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} /> <TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
@@ -781,7 +647,7 @@ class SnippetNoteDetail extends React.Component {
<InfoPanel <InfoPanel
storageName={currentOption.storage.name} storageName={currentOption.storage.name}
folderName={currentOption.folder.name} folderName={currentOption.folder.name}
noteLink={`[${note.title}](:note:${location.query.key})`} noteLink={`[${note.title}](${location.query.key})`}
updatedAt={formatDate(note.updatedAt)} updatedAt={formatDate(note.updatedAt)}
createdAt={formatDate(note.createdAt)} createdAt={formatDate(note.createdAt)}
exportAsMd={this.showWarning} exportAsMd={this.showWarning}
@@ -807,32 +673,16 @@ class SnippetNoteDetail extends React.Component {
fontSize: parseInt(config.preview.fontSize, 10) fontSize: parseInt(config.preview.fontSize, 10)
}} }}
ref='description' ref='description'
placeholder={i18n.__('Description...')} placeholder='Description...'
value={this.state.note.description} value={this.state.note.description}
onChange={(e) => this.handleChange(e)} onChange={(e) => this.handleChange(e)}
/> />
</div> </div>
<div styleName='tabList'> <div styleName='tabList'>
<button styleName='tabButton' <div styleName='list'>
hidden={!this.state.showArrows} {tabList}
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> </div>
<button styleName='tabButton' <button styleName='plusButton'
hidden={!this.state.showArrows}
disabled={!this.state.enableRightArrow}
onClick={(e) => this.handleTabMoveRightButtonClick(e)}
>
<i className='fa fa-chevron-right' />
</button>
<button styleName='tabButton'
onClick={(e) => this.handleTabPlusButtonClick(e)} onClick={(e) => this.handleTabPlusButtonClick(e)}
> >
<i className='fa fa-plus' /> <i className='fa fa-plus' />
@@ -846,7 +696,7 @@ class SnippetNoteDetail extends React.Component {
onClick={(e) => this.handleModeButtonClick(e, this.state.snippetIndex)} onClick={(e) => this.handleModeButtonClick(e, this.state.snippetIndex)}
> >
{this.state.note.snippets[this.state.snippetIndex].mode == null {this.state.note.snippets[this.state.snippetIndex].mode == null
? i18n.__('Select Syntax...') ? 'Select Syntax...'
: this.state.note.snippets[this.state.snippetIndex].mode : this.state.note.snippets[this.state.snippetIndex].mode
}&nbsp; }&nbsp;
<i className='fa fa-caret-down' /> <i className='fa fa-caret-down' />

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,8 +5,8 @@
width 52px width 52px
display flex display flex
align-items center align-items center
position: relative position absolute
top 2px right 165px
.active .active
background-color #1EC38B background-color #1EC38B
width 33px width 33px
@@ -55,4 +55,4 @@ body[data-theme="solarized-dark"]
background-color #002B36 background-color #002B36
.active .active
background-color #1EC38B 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 React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './TrashButton.styl' import styles from './TrashButton.styl'
import i18n from 'browser/lib/i18n'
const TrashButton = ({ const TrashButton = ({
onClick onClick
@@ -11,7 +10,7 @@ const TrashButton = ({
onClick={(e) => onClick(e)} onClick={(e) => onClick(e)}
> >
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' /> <img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
<span styleName='tooltip'>{i18n.__('Trash')}</span> <span styleName='tooltip'>Trash</span>
</button> </button>
) )

View File

@@ -7,7 +7,6 @@ import MarkdownNoteDetail from './MarkdownNoteDetail'
import SnippetNoteDetail from './SnippetNoteDetail' import SnippetNoteDetail from './SnippetNoteDetail'
import ee from 'browser/main/lib/eventEmitter' import ee from 'browser/main/lib/eventEmitter'
import StatusBar from '../StatusBar' import StatusBar from '../StatusBar'
import i18n from 'browser/lib/i18n'
const OSX = global.process.platform === 'darwin' const OSX = global.process.platform === 'darwin'
@@ -41,9 +40,9 @@ class Detail extends React.Component {
const alertConfig = { const alertConfig = {
type: 'warning', type: 'warning',
message: i18n.__('Confirm note deletion'), message: 'Confirm note deletion',
detail: i18n.__('This will permanently remove this note.'), detail: 'This will permanently remove this note.',
buttons: [i18n.__('Confirm'), i18n.__('Cancel')] buttons: ['Confirm', 'Cancel']
} }
const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), alertConfig) const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), alertConfig)
@@ -57,8 +56,11 @@ class Detail extends React.Component {
const { location, data, config } = this.props const { location, data, config } = this.props
let note = null let note = null
if (location.query.key != null) { if (location.query.key != null) {
const noteKey = location.query.key const splitted = location.query.key.split('-')
note = data.noteMap.get(noteKey) const storageKey = splitted.shift()
const noteKey = splitted.shift()
note = data.noteMap.get(storageKey + '-' + noteKey)
} }
if (note == null) { if (note == null) {
@@ -68,7 +70,7 @@ class Detail extends React.Component {
tabIndex='0' tabIndex='0'
> >
<div styleName='empty'> <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> </div>
<StatusBar <StatusBar
{..._.pick(this.props, ['config', 'location', 'dispatch'])} {..._.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 eventEmitter from 'browser/main/lib/eventEmitter'
import { hashHistory } from 'react-router' import { hashHistory } from 'react-router'
import store from 'browser/main/store' import store from 'browser/main/store'
import i18n from 'browser/lib/i18n'
const path = require('path') const path = require('path')
const electron = require('electron') const electron = require('electron')
const { remote } = electron const { remote } = electron
@@ -149,37 +148,6 @@ class Main extends React.Component {
} else { } else {
document.body.setAttribute('data-theme', 'default') document.body.setAttribute('data-theme', 'default')
} }
if (config.ui.language === 'sq') {
i18n.setLocale('sq')
} else if (config.ui.language === 'zh-CN') {
i18n.setLocale('zh-CN')
} else if (config.ui.language === 'zh-TW') {
i18n.setLocale('zh-TW')
} else if (config.ui.language === 'da') {
i18n.setLocale('da')
} else if (config.ui.language === 'fr') {
i18n.setLocale('fr')
} else if (config.ui.language === 'de') {
i18n.setLocale('de')
} else if (config.ui.language === 'hu') {
i18n.setLocale('hu')
} else if (config.ui.language === 'ja') {
i18n.setLocale('ja')
} else if (config.ui.language === 'ko') {
i18n.setLocale('ko')
} else if (config.ui.language === 'no') {
i18n.setLocale('no')
} else if (config.ui.language === 'pl') {
i18n.setLocale('pl')
} else if (config.ui.language === 'pt') {
i18n.setLocale('pt')
} else if (config.ui.language === 'ru') {
i18n.setLocale('ru')
} else if (config.ui.language === 'es-ES') {
i18n.setLocale('es-ES')
} else {
i18n.setLocale('en')
}
// Reload all data // Reload all data
dataApi.init() dataApi.init()

View File

@@ -6,7 +6,6 @@ import _ from 'lodash'
import modal from 'browser/main/lib/modal' import modal from 'browser/main/lib/modal'
import NewNoteModal from 'browser/main/modals/NewNoteModal' import NewNoteModal from 'browser/main/modals/NewNoteModal'
import eventEmitter from 'browser/main/lib/eventEmitter' import eventEmitter from 'browser/main/lib/eventEmitter'
import i18n from 'browser/lib/i18n'
const { remote } = require('electron') const { remote } = require('electron')
const { dialog } = remote 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] 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 { return {
storage, storage,
@@ -87,7 +86,7 @@ class NewNoteButton extends React.Component {
onClick={(e) => this.handleNewNoteButtonClick(e)}> onClick={(e) => this.handleNewNoteButtonClick(e)}>
<img styleName='iconTag' src='../resources/icon/icon-newnote.svg' /> <img styleName='iconTag' src='../resources/icon/icon-newnote.svg' />
<span styleName='control-newNoteButton-tooltip'> <span styleName='control-newNoteButton-tooltip'>
{i18n.__('Make a note')} {OSX ? '⌘' : i18n.__('Ctrl')} + N Make a note {OSX ? '⌘' : 'Ctrl'} + N
</span> </span>
</button> </button>
</div> </div>

View File

@@ -1,4 +1,3 @@
/* global electron */
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import React from 'react' import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
@@ -14,14 +13,10 @@ import searchFromNotes from 'browser/lib/search'
import fs from 'fs' import fs from 'fs'
import path from 'path' import path from 'path'
import { hashHistory } from 'react-router' import { hashHistory } from 'react-router'
import copy from 'copy-to-clipboard'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import Markdown from '../../lib/markdown'
import i18n from 'browser/lib/i18n'
const { remote } = require('electron') const { remote } = require('electron')
const { Menu, MenuItem, dialog } = remote const { Menu, MenuItem, dialog } = remote
const WP_POST_PATH = '/wp/v2/posts'
function sortByCreatedAt (a, b) { function sortByCreatedAt (a, b) {
return new Date(b.createdAt) - new Date(a.createdAt) return new Date(b.createdAt) - new Date(a.createdAt)
@@ -36,7 +31,7 @@ function sortByUpdatedAt (a, b) {
} }
function findNoteByKey (notes, noteKey) { function findNoteByKey (notes, noteKey) {
return notes.find((note) => note.key === noteKey) return notes.find((note) => `${note.storage}-${note.key}` === noteKey)
} }
function findNotesByKeys (notes, noteKeys) { function findNotesByKeys (notes, noteKeys) {
@@ -44,7 +39,7 @@ function findNotesByKeys (notes, noteKeys) {
} }
function getNoteKey (note) { function getNoteKey (note) {
return note.key return `${note.storage}-${note.key}`
} }
class NoteList extends React.Component { class NoteList extends React.Component {
@@ -71,11 +66,6 @@ class NoteList extends React.Component {
this.deleteNote = this.deleteNote.bind(this) this.deleteNote = this.deleteNote.bind(this)
this.focusNote = this.focusNote.bind(this) this.focusNote = this.focusNote.bind(this)
this.pinToTop = this.pinToTop.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) // TODO: not Selected noteKeys but SelectedNote(for reusing)
this.state = { this.state = {
@@ -119,27 +109,14 @@ class NoteList extends React.Component {
componentDidUpdate (prevProps) { componentDidUpdate (prevProps) {
const { location } = this.props 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 const { router } = this.context
if (!location.pathname.match(/\/searched/)) this.contextNotes = this.getContextNotes() if (!location.pathname.match(/\/searched/)) this.contextNotes = this.getContextNotes()
// A visible note is an active note
if (!selectedNoteKeys.includes(noteKey)) {
if (selectedNoteKeys.length === 1) selectedNoteKeys.pop()
selectedNoteKeys.push(noteKey)
ee.emit('list:moved')
}
router.replace({ router.replace({
pathname: location.pathname, pathname: location.pathname,
query: { query: {
key: noteKey key: this.notes[0].storage + '-' + this.notes[0].key
} }
}) })
return return
@@ -263,38 +240,27 @@ class NoteList extends React.Component {
handleNoteListKeyDown (e) { handleNoteListKeyDown (e) {
if (e.metaKey || e.ctrlKey) return true if (e.metaKey || e.ctrlKey) return true
// A key
if (e.keyCode === 65 && !e.shiftKey) { if (e.keyCode === 65 && !e.shiftKey) {
e.preventDefault() e.preventDefault()
ee.emit('top:new-note') ee.emit('top:new-note')
} }
// D key
if (e.keyCode === 68) { if (e.keyCode === 68) {
e.preventDefault() e.preventDefault()
this.deleteNote() this.deleteNote()
} }
// E key
if (e.keyCode === 69) { if (e.keyCode === 69) {
e.preventDefault() e.preventDefault()
ee.emit('detail:focus') ee.emit('detail:focus')
} }
// F or S key if (e.keyCode === 38) {
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) {
e.preventDefault() e.preventDefault()
this.selectPriorNote() this.selectPriorNote()
} }
// DOWN or J key if (e.keyCode === 40) {
if (e.keyCode === 40 || e.keyCode === 74) {
e.preventDefault() e.preventDefault()
this.selectNextNote() this.selectNextNote()
} }
@@ -326,10 +292,8 @@ class NoteList extends React.Component {
} }
if (location.pathname.match(/\/searched/)) { if (location.pathname.match(/\/searched/)) {
const searchInputText = params.searchword const searchInputText = document.getElementsByClassName('searchInput')[0].value
const allNotes = data.noteMap.map((note) => note) if (searchInputText === '') {
this.contextNotes = allNotes
if (searchInputText === undefined || searchInputText === '') {
return this.sortByPin(this.contextNotes) return this.sortByPin(this.contextNotes)
} }
return searchFromNotes(this.contextNotes, searchInputText) return searchFromNotes(this.contextNotes, searchInputText)
@@ -447,9 +411,9 @@ class NoteList extends React.Component {
if (this.notes[targetIndex].type === 'SNIPPET_NOTE') { if (this.notes[targetIndex].type === 'SNIPPET_NOTE') {
dialog.showMessageBox(remote.getCurrentWindow(), { dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning', type: 'warning',
message: i18n.__('Sorry!'), message: 'Sorry!',
detail: i18n.__('md/text import is available only a markdown note.'), detail: 'md/text import is available only a markdown note.',
buttons: [i18n.__('OK'), i18n.__('Cancel')] buttons: ['OK', 'Cancel']
}) })
} }
} }
@@ -473,110 +437,50 @@ class NoteList extends React.Component {
this.handleNoteClick(e, uniqueKey) this.handleNoteClick(e, uniqueKey)
} }
const pinLabel = note.isPinned ? i18n.__('Remove pin') : i18n.__('Pin to Top') const pinLabel = note.isPinned ? 'Remove pin' : 'Pin to Top'
const deleteLabel = i18n.__('Delete Note') const deleteLabel = 'Delete Note'
const cloneNote = i18n.__('Clone Note') const cloneNote = '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 menu = new Menu() const menu = new Menu()
if (!location.pathname.match(/\/home|\/starred|\/trash/)) {
if (location.pathname.match(/\/trash/)) {
menu.append(new MenuItem({ menu.append(new MenuItem({
label: restoreNote, label: pinLabel,
click: this.restoreNote click: this.pinToTop
})) }))
menu.append(new MenuItem({
label: deleteLabel,
click: this.deleteNote
}))
} else {
if (!location.pathname.match(/\/starred/)) {
menu.append(new MenuItem({
label: pinLabel,
click: this.pinToTop
}))
}
menu.append(new MenuItem({
label: deleteLabel,
click: this.deleteNote
}))
menu.append(new MenuItem({
label: cloneNote,
click: this.cloneNote.bind(this)
}))
menu.append(new MenuItem({
label: copyNoteLink,
click: this.copyNoteLink(note)
}))
if (note.type === 'MARKDOWN_NOTE') {
if (note.blog && note.blog.blogLink && note.blog.blogId) {
menu.append(new MenuItem({
label: updateLabel,
click: this.publishMarkdown.bind(this)
}))
menu.append(new MenuItem({
label: openBlogLabel,
click: () => this.openBlog.bind(this)(note)
}))
} else {
menu.append(new MenuItem({
label: publishLabel,
click: this.publishMarkdown.bind(this)
}))
}
}
} }
menu.append(new MenuItem({
label: deleteLabel,
click: this.deleteNote
}))
menu.append(new MenuItem({
label: cloneNote,
click: this.cloneNote.bind(this)
}))
menu.popup() menu.popup()
} }
updateSelectedNotes (updateFunc, cleanSelection = true) { pinToTop () {
const { selectedNoteKeys } = this.state const { selectedNoteKeys } = this.state
const { dispatch } = this.props const { dispatch } = this.props
const notes = this.notes.map((note) => Object.assign({}, note)) const notes = this.notes.map((note) => Object.assign({}, note))
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys) 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( Promise.all(
selectedNotes.map((note) => { selectedNotes.map((note) => {
note = updateFunc(note) note.isPinned = !note.isPinned
return dataApi return dataApi
.updateNote(note.storage, note.key, note) .updateNote(note.storage, note.key, note)
}) })
) )
.then((updatedNotes) => { .then((updatedNotes) => {
updatedNotes.forEach((note) => { updatedNotes.forEach((note) => {
dispatch({ dispatch({
type: 'UPDATE_NOTE', type: 'UPDATE_NOTE',
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 () { deleteNote () {
@@ -590,15 +494,17 @@ class NoteList extends React.Component {
const noteExp = selectedNotes.length > 1 ? 'notes' : 'note' const noteExp = selectedNotes.length > 1 ? 'notes' : 'note'
const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), { const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning', type: 'warning',
message: i18n.__('Confirm note deletion'), message: 'Confirm note deletion',
detail: `This will permanently remove ${selectedNotes.length} ${noteExp}.`, detail: `This will permanently remove ${selectedNotes.length} ${noteExp}.`,
buttons: [i18n.__('Confirm'), i18n.__('Cancel')] buttons: ['Confirm', 'Cancel']
}) })
if (dialogueButtonIndex === 1) return if (dialogueButtonIndex === 1) return
Promise.all( Promise.all(
selectedNotes.map((note) => { selectedNoteKeys.map((uniqueKey) => {
const storageKey = uniqueKey.split('-')[0]
const noteKey = uniqueKey.split('-')[1]
return dataApi return dataApi
.deleteNote(note.storage, note.key) .deleteNote(storageKey, noteKey)
}) })
) )
.then((data) => { .then((data) => {
@@ -655,138 +561,27 @@ class NoteList extends React.Component {
.createNote(storage.key, { .createNote(storage.key, {
type: firstNote.type, type: firstNote.type,
folder: folder.key, folder: folder.key,
title: firstNote.title + ' ' + i18n.__('copy'), title: firstNote.title + ' copy',
content: firstNote.content content: firstNote.content
}) })
.then((note) => { .then((note) => {
const uniqueKey = note.storage + '-' + note.key
dispatch({ dispatch({
type: 'UPDATE_NOTE', type: 'UPDATE_NOTE',
note: note note: note
}) })
this.setState({ this.setState({
selectedNoteKeys: [note.key] selectedNoteKeys: [uniqueKey]
}) })
hashHistory.push({ hashHistory.push({
pathname: location.pathname, 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 () { importFromFile () {
const options = { const options = {
filters: [ filters: [
@@ -879,28 +674,10 @@ class NoteList extends React.Component {
dialog.showMessageBox(remote.getCurrentWindow(), { dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning', type: 'warning',
message: message, 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 () { render () {
const { location, config } = this.props const { location, config } = this.props
let { notes } = this.props let { notes } = this.props
@@ -910,7 +687,7 @@ class NoteList extends React.Component {
: config.sortBy === 'ALPHABETICAL' : config.sortBy === 'ALPHABETICAL'
? sortByAlphabetical ? sortByAlphabetical
: sortByUpdatedAt : sortByUpdatedAt
const sortedNotes = location.pathname.match(/\/starred|\/trash/) const sortedNotes = location.pathname.match(/\/home|\/starred|\/trash/)
? this.getNotes().sort(sortFunc) ? this.getNotes().sort(sortFunc)
: this.sortByPin(this.getNotes().sort(sortFunc)) : this.sortByPin(this.getNotes().sort(sortFunc))
this.notes = notes = sortedNotes.filter((note) => { this.notes = notes = sortedNotes.filter((note) => {
@@ -937,8 +714,6 @@ class NoteList extends React.Component {
} }
}) })
const viewType = this.getViewType()
const noteList = notes const noteList = notes
.map(note => { .map(note => {
if (note == null) { if (note == null) {
@@ -964,9 +739,6 @@ class NoteList extends React.Component {
handleNoteClick={this.handleNoteClick.bind(this)} handleNoteClick={this.handleNoteClick.bind(this)}
handleDragStart={this.handleDragStart.bind(this)} handleDragStart={this.handleDragStart.bind(this)}
pathname={location.pathname} pathname={location.pathname}
folderName={this.getNoteFolder(note).name}
storageName={this.getNoteStorage(note).name}
viewType={viewType}
/> />
) )
} }
@@ -980,9 +752,6 @@ class NoteList extends React.Component {
handleNoteClick={this.handleNoteClick.bind(this)} handleNoteClick={this.handleNoteClick.bind(this)}
handleDragStart={this.handleDragStart.bind(this)} handleDragStart={this.handleDragStart.bind(this)}
pathname={location.pathname} pathname={location.pathname}
folderName={this.getNoteFolder(note).name}
storageName={this.getNoteStorage(note).name}
viewType={viewType}
/> />
) )
}) })
@@ -997,17 +766,16 @@ class NoteList extends React.Component {
<div styleName='control-sortBy'> <div styleName='control-sortBy'>
<i className='fa fa-angle-down' /> <i className='fa fa-angle-down' />
<select styleName='control-sortBy-select' <select styleName='control-sortBy-select'
title={i18n.__('Select filter mode')}
value={config.sortBy} value={config.sortBy}
onChange={(e) => this.handleSortByChange(e)} onChange={(e) => this.handleSortByChange(e)}
> >
<option title='Sort by update time' value='UPDATED_AT'>{i18n.__('Updated')}</option> <option value='UPDATED_AT'>Updated</option>
<option title='Sort by create time' value='CREATED_AT'>{i18n.__('Created')}</option> <option value='CREATED_AT'>Created</option>
<option title='Sort alphabetically' value='ALPHABETICAL'>{i18n.__('Alphabetically')}</option> <option value='ALPHABETICAL'>Alphabetically</option>
</select> </select>
</div> </div>
<div styleName='control-button-area'> <div styleName='control-button-area'>
<button title={i18n.__('Default View')} styleName={config.listStyle === 'DEFAULT' <button styleName={config.listStyle === 'DEFAULT'
? 'control-button--active' ? 'control-button--active'
: 'control-button' : 'control-button'
} }
@@ -1015,7 +783,7 @@ class NoteList extends React.Component {
> >
<img styleName='iconTag' src='../resources/icon/icon-column.svg' /> <img styleName='iconTag' src='../resources/icon/icon-column.svg' />
</button> </button>
<button title={i18n.__('Compressed View')} styleName={config.listStyle === 'SMALL' <button styleName={config.listStyle === 'SMALL'
? 'control-button--active' ? 'control-button--active'
: 'control-button' : 'control-button'
} }

View File

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

View File

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

View File

@@ -30,33 +30,11 @@
display flex display flex
flex-direction column flex-direction column
.tag-control .tag-title
display flex padding-left 15px
height 30px padding-bottom 13px
line-height 25px p
overflow hidden color $ui-button-default-color
.tag-control-title
padding-left 15px
padding-bottom 13px
flex 1
p
color $ui-button-default-color
.tag-control-sortTagsBy
user-select none
font-size 12px
color $ui-inactive-text-color
margin-left 12px
margin-right 12px
.tag-control-sortTagsBy-select
appearance: none;
margin-left 5px
color $ui-inactive-text-color
padding 0
border none
background-color transparent
outline none
cursor pointer
font-size 12px
.tagList .tagList
overflow-y auto overflow-y auto

View File

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

View File

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

View File

@@ -1,9 +1,6 @@
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import React from 'react' import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
const { remote } = require('electron')
const { Menu } = remote
import dataApi from 'browser/main/lib/dataApi'
import styles from './SideNav.styl' import styles from './SideNav.styl'
import { openModal } from 'browser/main/lib/modal' import { openModal } from 'browser/main/lib/modal'
import PreferencesModal from '../modals/PreferencesModal' import PreferencesModal from '../modals/PreferencesModal'
@@ -17,8 +14,6 @@ import EventEmitter from 'browser/main/lib/eventEmitter'
import PreferenceButton from './PreferenceButton' import PreferenceButton from './PreferenceButton'
import ListButton from './ListButton' import ListButton from './ListButton'
import TagButton from './TagButton' import TagButton from './TagButton'
import {SortableContainer} from 'react-sortable-hoc'
import i18n from 'browser/lib/i18n'
class SideNav extends React.Component { class SideNav extends React.Component {
// TODO: should not use electron stuff v0.7 // TODO: should not use electron stuff v0.7
@@ -70,19 +65,8 @@ class SideNav extends React.Component {
router.push('/alltags') 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) { SideNavComponent (isFolded, storageList) {
const { location, data, config } = this.props const { location, data } = this.props
const isHomeActive = !!location.pathname.match(/^\/home$/) const isHomeActive = !!location.pathname.match(/^\/home$/)
const isStarredActive = !!location.pathname.match(/^\/starred$/) const isStarredActive = !!location.pathname.match(/^\/starred$/)
@@ -102,36 +86,20 @@ class SideNav extends React.Component {
isTrashedActive={isTrashedActive} isTrashedActive={isTrashedActive}
handleStarredButtonClick={(e) => this.handleStarredButtonClick(e)} handleStarredButtonClick={(e) => this.handleStarredButtonClick(e)}
handleTrashedButtonClick={(e) => this.handleTrashedButtonClick(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} counterStarredNote={data.starredSet._set.size}
counterDelNote={data.trashedSet._set.size} counterDelNote={data.trashedSet._set.size}
handleFilterButtonContextMenu={this.handleFilterButtonContextMenu.bind(this)}
/> />
<StorageList storageList={storageList} isFolded={isFolded} /> <StorageList storageList={storageList} />
<NavToggleButton isFolded={isFolded} handleToggleButtonClick={this.handleToggleButtonClick.bind(this)} /> <NavToggleButton isFolded={isFolded} handleToggleButtonClick={this.handleToggleButtonClick.bind(this)} />
</div> </div>
) )
} else { } else {
component = ( component = (
<div styleName='tabBody'> <div styleName='tabBody'>
<div styleName='tag-control'> <div styleName='tag-title'>
<div styleName='tag-control-title'> <p>Tags</p>
<p>{i18n.__('Tags')}</p>
</div>
<div styleName='tag-control-sortTagsBy'>
<i className='fa fa-angle-down' />
<select styleName='tag-control-sortTagsBy-select'
title={i18n.__('Select filter mode')}
value={config.sortTagsBy}
onChange={(e) => this.handleSortTagsByChange(e)}
>
<option title='Sort alphabetically'
value='ALPHABETICAL'>{i18n.__('Alphabetically')}</option>
<option title='Sort by update time'
value='COUNTER'>{i18n.__('Counter')}</option>
</select>
</div>
</div> </div>
<div styleName='tagList'> <div styleName='tagList'>
{this.tagListComponent(data)} {this.tagListComponent(data)}
@@ -144,26 +112,19 @@ class SideNav extends React.Component {
} }
tagListComponent () { tagListComponent () {
const { data, location, config } = this.props const { data, location } = this.props
let tagList = _.sortBy(data.tagNoteMap.map( const tagList = data.tagNoteMap.map((tag, key) => {
(tag, name) => ({name, size: tag.size})), return key
['name'] })
)
if (config.sortTagsBy === 'COUNTER') {
tagList = _.sortBy(tagList, item => (0 - item.size))
}
return ( return (
tagList.map(tag => { tagList.map(tag => (
return ( <TagListItem
<TagListItem name={tag}
name={tag.name} handleClickTagListItem={this.handleClickTagListItem.bind(this)}
handleClickTagListItem={this.handleClickTagListItem.bind(this)} isActive={this.getTagActive(location.pathname, tag)}
isActive={this.getTagActive(location.pathname, tag.name)} key={tag}
key={tag.name} />
count={tag.size} ))
/>
)
})
) )
} }
@@ -178,62 +139,19 @@ class SideNav extends React.Component {
router.push(`/tags/${name}`) router.push(`/tags/${name}`)
} }
handleSortTagsByChange (e) {
const { dispatch } = this.props
const config = {
sortTagsBy: e.target.value
}
ConfigManager.set(config)
dispatch({
type: 'SET_CONFIG',
config
})
}
emptyTrash (entries) {
const { dispatch } = this.props
const deletionPromises = entries.map((note) => {
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 () { render () {
const { data, location, config, dispatch } = this.props const { data, location, config, dispatch } = this.props
const isFolded = config.isSideNavFolded const isFolded = config.isSideNavFolded
const storageList = data.storageMap.map((storage, key) => { const storageList = data.storageMap.map((storage, key) => {
const SortableStorageItem = SortableContainer(StorageItem) return <StorageItem
return <SortableStorageItem
key={storage.key} key={storage.key}
storage={storage} storage={storage}
data={data} data={data}
location={location} location={location}
isFolded={isFolded} isFolded={isFolded}
dispatch={dispatch} dispatch={dispatch}
onSortEnd={this.onSortEnd.bind(this)(storage)}
useDragHandle
/> />
}) })
const style = {} const style = {}

View File

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

View File

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

View File

@@ -40,32 +40,6 @@ $control-height = 34px
padding-bottom 2px padding-bottom 2px
background-color $ui-noteList-backgroundColor 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 .control-search-optionList
position fixed position fixed
z-index 200 z-index 200
@@ -233,4 +207,4 @@ body[data-theme="solarized-dark"]
background-color $ui-solarized-dark-noteList-backgroundColor background-color $ui-solarized-dark-noteList-backgroundColor
input input
background-color $ui-solarized-dark-noteList-backgroundColor 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 _ from 'lodash'
import ee from 'browser/main/lib/eventEmitter' import ee from 'browser/main/lib/eventEmitter'
import NewNoteButton from 'browser/main/NewNoteButton' import NewNoteButton from 'browser/main/NewNoteButton'
import i18n from 'browser/lib/i18n'
class TopBar extends React.Component { class TopBar extends React.Component {
constructor (props) { constructor (props) {
@@ -23,37 +22,14 @@ class TopBar extends React.Component {
this.focusSearchHandler = () => { this.focusSearchHandler = () => {
this.handleOnSearchFocus() this.handleOnSearchFocus()
} }
this.codeInitHandler = this.handleCodeInit.bind(this)
} }
componentDidMount () { componentDidMount () {
const { params } = this.props
const searchWord = params.searchword
if (searchWord !== undefined) {
this.setState({
search: searchWord,
isSearching: true
})
}
ee.on('top:focus-search', this.focusSearchHandler) ee.on('top:focus-search', this.focusSearchHandler)
ee.on('code:init', this.codeInitHandler)
} }
componentWillUnmount () { componentWillUnmount () {
ee.off('top:focus-search', this.focusSearchHandler) 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) { handleKeyDown (e) {
@@ -63,23 +39,6 @@ class TopBar extends React.Component {
isIME: false 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 // When the key is an alphabet, del, enter or ctr
if (e.keyCode <= 90 || e.keyCode >= 186 && e.keyCode <= 222) { if (e.keyCode <= 90 || e.keyCode >= 186 && e.keyCode <= 222) {
this.setState({ this.setState({
@@ -105,26 +64,23 @@ class TopBar extends React.Component {
this.setState({ this.setState({
isConfirmTranslation: true isConfirmTranslation: true
}) })
const keyword = this.refs.searchInput.value router.push('/searched')
router.push(`/searched/${encodeURIComponent(keyword)}`)
this.setState({ this.setState({
search: keyword search: this.refs.searchInput.value
}) })
} }
} }
handleSearchChange (e) { handleSearchChange (e) {
const { router } = this.context const { router } = this.context
const keyword = this.refs.searchInput.value
if (this.state.isAlphabet || this.state.isConfirmTranslation) { if (this.state.isAlphabet || this.state.isConfirmTranslation) {
router.push(`/searched/${encodeURIComponent(keyword)}`) router.push('/searched')
} else { } else {
e.preventDefault() e.preventDefault()
} }
this.setState({ this.setState({
search: keyword search: this.refs.searchInput.value
}) })
ee.emit('top:search', keyword)
} }
handleSearchFocus (e) { handleSearchFocus (e) {
@@ -152,19 +108,13 @@ class TopBar extends React.Component {
} }
handleOnSearchFocus () { handleOnSearchFocus () {
const el = this.refs.search.childNodes[0]
if (this.state.isSearching) { if (this.state.isSearching) {
el.blur() this.refs.search.childNodes[0].blur()
} else { } else {
el.focus() this.refs.search.childNodes[0].focus()
el.setSelectionRange(0, el.value.length)
} }
} }
handleCodeInit () {
ee.emit('top:search', this.refs.searchInput.value)
}
render () { render () {
const { config, style, location } = this.props const { config, style, location } = this.props
return ( return (
@@ -186,19 +136,19 @@ class TopBar extends React.Component {
onChange={(e) => this.handleSearchChange(e)} onChange={(e) => this.handleSearchChange(e)}
onKeyDown={(e) => this.handleKeyDown(e)} onKeyDown={(e) => this.handleKeyDown(e)}
onKeyUp={(e) => this.handleKeyUp(e)} onKeyUp={(e) => this.handleKeyUp(e)}
placeholder={i18n.__('Search')} placeholder='Search'
type='text' type='text'
className='searchInput' 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> </div>
{this.state.search > 0 &&
<button styleName='left-search-clearButton'
onClick={(e) => this.handleSearchClearButton(e)}
>
<i className='fa fa-times' />
</button>
}
</div> </div>
</div> </div>
{location.pathname === '/trashed' ? '' {location.pathname === '/trashed' ? ''

View File

@@ -108,21 +108,6 @@ body[data-theme="dark"]
background #B1D7FE background #B1D7FE
::selection ::selection
background #B1D7FE background #B1D7FE
.CodeMirror-foldmarker
font-family: arial
.CodeMirror-foldgutter
width: .7em
.CodeMirror-foldgutter-open,
.CodeMirror-foldgutter-folded
cursor: pointer
.CodeMirror-foldgutter-open:after
content: "\25BE"
.CodeMirror-foldgutter-folded:after
content: "\25B8"
.sortableItemHelper .sortableItemHelper
z-index modalZIndex + 5 z-index modalZIndex + 5

View File

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

View File

@@ -1,6 +1,5 @@
import _ from 'lodash' import _ from 'lodash'
import RcParser from 'browser/lib/RcParser' import RcParser from 'browser/lib/RcParser'
import i18n from 'browser/lib/i18n'
const OSX = global.process.platform === 'darwin' const OSX = global.process.platform === 'darwin'
const win = global.process.platform === 'win32' const win = global.process.platform === 'win32'
@@ -16,14 +15,12 @@ export const DEFAULT_CONFIG = {
listWidth: 280, listWidth: 280,
navWidth: 200, navWidth: 200,
sortBy: 'UPDATED_AT', // 'CREATED_AT', 'UPDATED_AT', 'APLHABETICAL' sortBy: 'UPDATED_AT', // 'CREATED_AT', 'UPDATED_AT', 'APLHABETICAL'
sortTagsBy: 'ALPHABETICAL', // 'ALPHABETICAL', 'COUNTER'
listStyle: 'DEFAULT', // 'DEFAULT', 'SMALL' listStyle: 'DEFAULT', // 'DEFAULT', 'SMALL'
amaEnabled: true, amaEnabled: true,
hotkey: { hotkey: {
toggleMain: OSX ? 'Cmd + Alt + L' : 'Super + Alt + E' toggleMain: OSX ? 'Cmd + Alt + L' : 'Super + Alt + E'
}, },
ui: { ui: {
language: 'en',
theme: 'default', theme: 'default',
showCopyNotification: true, showCopyNotification: true,
disableDirectWrite: false, disableDirectWrite: false,
@@ -36,13 +33,10 @@ export const DEFAULT_CONFIG = {
fontFamily: win ? 'Segoe UI' : 'Monaco, Consolas', fontFamily: win ? 'Segoe UI' : 'Monaco, Consolas',
indentType: 'space', indentType: 'space',
indentSize: '2', indentSize: '2',
enableRulers: false,
rulers: [80, 120],
displayLineNumbers: true, displayLineNumbers: true,
switchPreview: 'BLUR', // Available value: RIGHTCLICK, BLUR switchPreview: 'BLUR', // Available value: RIGHTCLICK, BLUR
scrollPastEnd: false, scrollPastEnd: false,
type: 'SPLIT', type: 'SPLIT'
fetchUrlTitle: true
}, },
preview: { preview: {
fontSize: '14', fontSize: '14',
@@ -53,17 +47,7 @@ export const DEFAULT_CONFIG = {
latexInlineClose: '$', latexInlineClose: '$',
latexBlockOpen: '$$', latexBlockOpen: '$$',
latexBlockClose: '$$', latexBlockClose: '$$',
scrollPastEnd: false, 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: ''
} }
} }
@@ -139,8 +123,6 @@ function set (updates) {
document.body.setAttribute('data-theme', 'default') document.body.setAttribute('data-theme', 'default')
} }
i18n.setLocale(newConfig.ui.language)
let editorTheme = document.getElementById('editorTheme') let editorTheme = document.getElementById('editorTheme')
if (editorTheme == null) { if (editorTheme == null) {
editorTheme = document.createElement('link') editorTheme = document.createElement('link')
@@ -168,7 +150,6 @@ function set (updates) {
function assignConfigValues (originalConfig, rcConfig) { function assignConfigValues (originalConfig, rcConfig) {
const config = Object.assign({}, DEFAULT_CONFIG, originalConfig, rcConfig) const config = Object.assign({}, DEFAULT_CONFIG, originalConfig, rcConfig)
config.hotkey = Object.assign({}, DEFAULT_CONFIG.hotkey, originalConfig.hotkey, rcConfig.hotkey) 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.ui = Object.assign({}, DEFAULT_CONFIG.ui, originalConfig.ui, rcConfig.ui)
config.editor = Object.assign({}, DEFAULT_CONFIG.editor, originalConfig.editor, rcConfig.editor) config.editor = Object.assign({}, DEFAULT_CONFIG.editor, originalConfig.editor, rcConfig.editor)
config.preview = Object.assign({}, DEFAULT_CONFIG.preview, originalConfig.preview, rcConfig.preview) 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') 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} filePath
* @param {String} storageKey * @param {String} storageKey
* @param {Boolean} rename create new filename or leave the old one * @return {String} an image path
* @return {Promise<any>} an image path
*/ */
function copyImage (filePath, storageKey, rename = true) { function copyImage (filePath, storageKey) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
const targetStorage = findStorage(storageKey) const targetStorage = findStorage(storageKey)
const inputImage = fs.createReadStream(filePath) const inputImage = fs.createReadStream(filePath)
const imageExt = path.extname(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 basename = `${imageName}${imageExt}`
const imageDir = path.join(targetStorage.path, 'images') const imageDir = path.join(targetStorage.path, 'images')
if (!fs.existsSync(imageDir)) fs.mkdirSync(imageDir) if (!fs.existsSync(imageDir)) fs.mkdirSync(imageDir)

View File

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

View File

@@ -1,7 +1,6 @@
import { findStorage } from 'browser/lib/findStorage' import { findStorage } from 'browser/lib/findStorage'
import resolveStorageData from './resolveStorageData' import resolveStorageData from './resolveStorageData'
import resolveStorageNotes from './resolveStorageNotes' import resolveStorageNotes from './resolveStorageNotes'
import filenamify from 'filenamify'
import * as path from 'path' import * as path from 'path'
import * as fs from 'fs' import * as fs from 'fs'
@@ -46,7 +45,7 @@ function exportFolder (storageKey, folderKey, fileType, exportDir) {
notes notes
.filter(note => note.folder === folderKey && note.isTrashed === false && note.type === 'MARKDOWN_NOTE') .filter(note => note.folder === folderKey && note.isTrashed === false && note.type === 'MARKDOWN_NOTE')
.forEach(snippet => { .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) 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 resolveStorageData = require('./resolveStorageData')
const _ = require('lodash') const _ = require('lodash')
const path = require('path') const path = require('path')
const fs = require('fs')
const CSON = require('@rokt33r/season') const CSON = require('@rokt33r/season')
const keygen = require('browser/lib/keygen') const keygen = require('browser/lib/keygen')
const sander = require('sander') const sander = require('sander')
const { findStorage } = require('browser/lib/findStorage') const { findStorage } = require('browser/lib/findStorage')
const copyImage = require('./copyImage')
function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) { function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
let oldStorage, newStorage let oldStorage, newStorage
@@ -39,12 +37,12 @@ function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
return resolveStorageData(newStorage) return resolveStorageData(newStorage)
.then(function findNewNoteKey (_newStorage) { .then(function findNewNoteKey (_newStorage) {
newStorage = _newStorage newStorage = _newStorage
newNoteKey = keygen(true) newNoteKey = keygen()
let isUnique = false let isUnique = false
while (!isUnique) { while (!isUnique) {
try { try {
sander.statSync(path.join(newStorage.path, 'notes', newNoteKey + '.cson')) sander.statSync(path.join(newStorage.path, 'notes', newNoteKey + '.cson'))
newNoteKey = keygen(true) newNoteKey = keygen()
} catch (err) { } catch (err) {
if (err.code === 'ENOENT') { if (err.code === 'ENOENT') {
isUnique = true isUnique = true
@@ -67,27 +65,6 @@ function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
return noteData 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) { .then(function writeAndReturn (noteData) {
CSON.writeFileSync(path.join(newStorage.path, 'notes', noteData.key + '.cson'), _.omit(noteData, ['key', 'storage'])) CSON.writeFileSync(path.join(newStorage.path, 'notes', noteData.key + '.cson'), _.omit(noteData, ['key', 'storage']))
return noteData return noteData

View File

@@ -27,12 +27,9 @@ function resolveStorageNotes (storage) {
data.storage = storage.key data.storage = storage.key
return data return data
} catch (err) { } catch (err) {
console.error(`error on note path: ${notePath}, error: ${err}`) console.error(notePath)
} }
}) })
.filter(function filterOnlyNoteObject (noteObj) {
return typeof noteObj === 'object'
})
return Promise.resolve(notes) return Promise.resolve(notes)
} }

View File

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

View File

@@ -1,4 +1,5 @@
import ConfigManager from './ConfigManager' import ConfigManager from './ConfigManager'
import store from 'browser/main/store'
const nodeIpc = require('node-ipc') const nodeIpc = require('node-ipc')
const { remote, ipcRenderer } = require('electron') const { remote, ipcRenderer } = require('electron')
@@ -17,7 +18,7 @@ nodeIpc.connectTo(
console.log(err) console.log(err)
}) })
nodeIpc.of.node.on('connect', function () { nodeIpc.of.node.on('connect', function () {
console.log('Connected successfully') console.log('Conncted successfully')
ipcRenderer.send('config-renew', {config: ConfigManager.get()}) ipcRenderer.send('config-renew', {config: ConfigManager.get()})
}) })
nodeIpc.of.node.on('disconnect', function () { 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 consts from 'browser/lib/consts'
import ModalEscButton from 'browser/components/ModalEscButton' import ModalEscButton from 'browser/components/ModalEscButton'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import i18n from 'browser/lib/i18n'
class CreateFolderModal extends React.Component { class CreateFolderModal extends React.Component {
constructor (props) { constructor (props) {
@@ -80,12 +79,12 @@ class CreateFolderModal extends React.Component {
onKeyDown={(e) => this.handleKeyDown(e)} onKeyDown={(e) => this.handleKeyDown(e)}
> >
<div styleName='header'> <div styleName='header'>
<div styleName='title'>{i18n.__('Create new folder')}</div> <div styleName='title'>Create new folder</div>
</div> </div>
<ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} /> <ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} />
<div styleName='control'> <div styleName='control'>
<div styleName='control-folder'> <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' <input styleName='control-folder-input'
ref='name' ref='name'
value={this.state.name} value={this.state.name}
@@ -96,7 +95,7 @@ class CreateFolderModal extends React.Component {
<button styleName='control-confirmButton' <button styleName='control-confirmButton'
onClick={(e) => this.handleConfirmButtonClick(e)} onClick={(e) => this.handleConfirmButtonClick(e)}
> >
{i18n.__('Create')} Create
</button> </button>
</div> </div>
</div> </div>

View File

@@ -6,7 +6,6 @@ import { hashHistory } from 'react-router'
import ee from 'browser/main/lib/eventEmitter' import ee from 'browser/main/lib/eventEmitter'
import ModalEscButton from 'browser/components/ModalEscButton' import ModalEscButton from 'browser/components/ModalEscButton'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import i18n from 'browser/lib/i18n'
class NewNoteModal extends React.Component { class NewNoteModal extends React.Component {
constructor (props) { constructor (props) {
@@ -36,16 +35,14 @@ class NewNoteModal extends React.Component {
content: '' content: ''
}) })
.then((note) => { .then((note) => {
const noteHash = note.key
dispatch({ dispatch({
type: 'UPDATE_NOTE', type: 'UPDATE_NOTE',
note: note note: note
}) })
hashHistory.push({ hashHistory.push({
pathname: location.pathname, pathname: location.pathname,
query: {key: noteHash} query: {key: note.storage + '-' + note.key}
}) })
ee.emit('list:jump', noteHash)
ee.emit('detail:focus') ee.emit('detail:focus')
this.props.close() this.props.close()
}) })
@@ -76,16 +73,14 @@ class NewNoteModal extends React.Component {
}] }]
}) })
.then((note) => { .then((note) => {
const noteHash = note.key
dispatch({ dispatch({
type: 'UPDATE_NOTE', type: 'UPDATE_NOTE',
note: note note: note
}) })
hashHistory.push({ hashHistory.push({
pathname: location.pathname, pathname: location.pathname,
query: {key: noteHash} query: {key: note.storage + '-' + note.key}
}) })
ee.emit('list:jump', noteHash)
ee.emit('detail:focus') ee.emit('detail:focus')
this.props.close() this.props.close()
}) })
@@ -111,7 +106,7 @@ class NewNoteModal extends React.Component {
onKeyDown={(e) => this.handleKeyDown(e)} onKeyDown={(e) => this.handleKeyDown(e)}
> >
<div styleName='header'> <div styleName='header'>
<div styleName='title'>{i18n.__('Make a note')}</div> <div styleName='title'>Make a note</div>
</div> </div>
<ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} /> <ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} />
<div styleName='control'> <div styleName='control'>
@@ -123,8 +118,8 @@ class NewNoteModal extends React.Component {
<i styleName='control-button-icon' <i styleName='control-button-icon'
className='fa fa-file-text-o' className='fa fa-file-text-o'
/><br /> /><br />
<span styleName='control-button-label'>{i18n.__('Markdown Note')}</span><br /> <span styleName='control-button-label'>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-description'>This format is for creating text documents. Checklists, code blocks and Latex blocks are available.</span>
</button> </button>
<button styleName='control-button' <button styleName='control-button'
@@ -135,13 +130,13 @@ class NewNoteModal extends React.Component {
<i styleName='control-button-icon' <i styleName='control-button-icon'
className='fa fa-code' className='fa fa-code'
/><br /> /><br />
<span styleName='control-button-label'>{i18n.__('Snippet Note')}</span><br /> <span styleName='control-button-label'>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-description'>This format is for creating code snippets. Multiple snippets can be grouped into a single note.
</span> </span>
</button> </button>
</div> </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> </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 React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './Crowdfunding.styl' import styles from './Crowdfunding.styl'
import i18n from 'browser/lib/i18n'
const electron = require('electron') const electron = require('electron')
const { shell } = electron const { shell } = electron
@@ -22,22 +21,22 @@ class Crowdfunding extends React.Component {
render () { render () {
return ( return (
<div styleName='root'> <div styleName='root'>
<div styleName='header'>{i18n.__('Crowdfunding')}</div> <div styleName='header'>Crowdfunding</div>
<p>{i18n.__('Dear everyone,')}</p> <p>Dear everyone,</p>
<br /> <br />
<p>{i18n.__('Thank you for using Boostnote!')}</p> <p>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>Boostnote is used in about 200 different countries and regions by an awesome community of developers.</p>
<br /> <br />
<p>{i18n.__('To continue supporting this growth, and to satisfy community expectations,')}</p> <p>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>we would like to invest more time and resources in this project.</p>
<br /> <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 /> <br />
<p>{i18n.__('Thanks,')}</p> <p>Thanks,</p>
<p>{i18n.__('Boostnote maintainers')}</p> <p>Boostnote maintainers</p>
<br /> <br />
<button styleName='cf-link'> <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> </button>
</div> </div>
) )

View File

@@ -7,7 +7,6 @@ import dataApi from 'browser/main/lib/dataApi'
import store from 'browser/main/store' import store from 'browser/main/store'
import { SketchPicker } from 'react-color' import { SketchPicker } from 'react-color'
import { SortableElement, SortableHandle } from 'react-sortable-hoc' import { SortableElement, SortableHandle } from 'react-sortable-hoc'
import i18n from 'browser/lib/i18n'
class FolderItem extends React.Component { class FolderItem extends React.Component {
constructor (props) { constructor (props) {
@@ -151,12 +150,12 @@ class FolderItem extends React.Component {
<button styleName='folderItem-right-confirmButton' <button styleName='folderItem-right-confirmButton'
onClick={(e) => this.handleConfirmButtonClick(e)} onClick={(e) => this.handleConfirmButtonClick(e)}
> >
{i18n.__('Confirm')} Confirm
</button> </button>
<button styleName='folderItem-right-button' <button styleName='folderItem-right-button'
onClick={(e) => this.handleCancelButtonClick(e)} onClick={(e) => this.handleCancelButtonClick(e)}
> >
{i18n.__('Cancel')} Cancel
</button> </button>
</div> </div>
</div> </div>
@@ -180,18 +179,18 @@ class FolderItem extends React.Component {
return ( return (
<div styleName='folderItem'> <div styleName='folderItem'>
<div styleName='folderItem-left'> <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>
<div styleName='folderItem-right'> <div styleName='folderItem-right'>
<button styleName='folderItem-right-dangerButton' <button styleName='folderItem-right-dangerButton'
onClick={(e) => this.handleDeleteConfirmButtonClick(e)} onClick={(e) => this.handleDeleteConfirmButtonClick(e)}
> >
{i18n.__('Confirm')} Confirm
</button> </button>
<button styleName='folderItem-right-button' <button styleName='folderItem-right-button'
onClick={(e) => this.handleCancelButtonClick(e)} onClick={(e) => this.handleCancelButtonClick(e)}
> >
{i18n.__('Cancel')} Cancel
</button> </button>
</div> </div>
</div> </div>
@@ -232,12 +231,12 @@ class FolderItem extends React.Component {
<button styleName='folderItem-right-button' <button styleName='folderItem-right-button'
onClick={(e) => this.handleEditButtonClick(e)} onClick={(e) => this.handleEditButtonClick(e)}
> >
{i18n.__('Edit')} Edit
</button> </button>
<button styleName='folderItem-right-button' <button styleName='folderItem-right-button'
onClick={(e) => this.handleDeleteButtonClick(e)} onClick={(e) => this.handleDeleteButtonClick(e)}
> >
{i18n.__('Delete')} Delete
</button> </button>
</div> </div>
</div> </div>

View File

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

View File

@@ -5,7 +5,6 @@ import styles from './ConfigTab.styl'
import ConfigManager from 'browser/main/lib/ConfigManager' import ConfigManager from 'browser/main/lib/ConfigManager'
import store from 'browser/main/store' import store from 'browser/main/store'
import _ from 'lodash' import _ from 'lodash'
import i18n from 'browser/lib/i18n'
const electron = require('electron') const electron = require('electron')
const ipc = electron.ipcRenderer const ipc = electron.ipcRenderer
@@ -24,13 +23,13 @@ class HotkeyTab extends React.Component {
this.handleSettingDone = () => { this.handleSettingDone = () => {
this.setState({keymapAlert: { this.setState({keymapAlert: {
type: 'success', type: 'success',
message: i18n.__('Successfully applied!') message: 'Successfully applied!'
}}) }})
} }
this.handleSettingError = (err) => { this.handleSettingError = (err) => {
this.setState({keymapAlert: { this.setState({keymapAlert: {
type: 'error', 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 this.oldHotkey = this.state.config.hotkey
@@ -78,7 +77,7 @@ class HotkeyTab extends React.Component {
this.props.haveToSave({ this.props.haveToSave({
tab: 'Hotkey', tab: 'Hotkey',
type: 'warning', type: 'warning',
message: i18n.__('You have to save!') message: 'You have to save!'
}) })
} }
} }
@@ -103,9 +102,9 @@ class HotkeyTab extends React.Component {
return ( return (
<div styleName='root'> <div styleName='root'>
<div styleName='group'> <div styleName='group'>
<div styleName='group-header'>{i18n.__('Hotkeys')}</div> <div styleName='group-header'>Hotkeys</div>
<div styleName='group-section'> <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'> <div styleName='group-section-control'>
<input styleName='group-section-control-input' <input styleName='group-section-control-input'
onChange={(e) => this.handleHotkeyChange(e)} onChange={(e) => this.handleHotkeyChange(e)}
@@ -120,18 +119,18 @@ class HotkeyTab extends React.Component {
onClick={(e) => this.handleHintToggleButtonClick(e)} onClick={(e) => this.handleHintToggleButtonClick(e)}
> >
{this.state.isHotkeyHintOpen {this.state.isHotkeyHintOpen
? i18n.__('Hide Help') ? 'Hide Help'
: i18n.__('Help') : 'Help'
} }
</button> </button>
<button styleName='group-control-rightButton' <button styleName='group-control-rightButton'
onClick={(e) => this.handleSaveButtonClick(e)}>{i18n.__('Save')} onClick={(e) => this.handleSaveButtonClick(e)}>Save
</button> </button>
{keymapAlertElement} {keymapAlertElement}
</div> </div>
{this.state.isHotkeyHintOpen && {this.state.isHotkeyHintOpen &&
<div styleName='group-hint'> <div styleName='group-hint'>
<p>{i18n.__('Available Keys')}</p> <p>Available Keys</p>
<ul> <ul>
<li><code>0</code> to <code>9</code></li> <li><code>0</code> to <code>9</code></li>
<li><code>A</code> to <code>Z</code></li> <li><code>A</code> to <code>Z</code></li>

View File

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

View File

@@ -6,7 +6,6 @@ import consts from 'browser/lib/consts'
import dataApi from 'browser/main/lib/dataApi' import dataApi from 'browser/main/lib/dataApi'
import store from 'browser/main/store' import store from 'browser/main/store'
import FolderList from './FolderList' import FolderList from './FolderList'
import i18n from 'browser/lib/i18n'
const { shell, remote } = require('electron') const { shell, remote } = require('electron')
const { dialog } = remote const { dialog } = remote
@@ -23,7 +22,7 @@ class StorageItem extends React.Component {
handleNewFolderButtonClick (e) { handleNewFolderButtonClick (e) {
const { storage } = this.props const { storage } = this.props
const input = { const input = {
name: i18n.__('New Folder'), name: 'Untitled',
color: consts.FOLDER_COLORS[Math.floor(Math.random() * 7) % 7] color: consts.FOLDER_COLORS[Math.floor(Math.random() * 7) % 7]
} }
@@ -47,9 +46,9 @@ class StorageItem extends React.Component {
handleUnlinkButtonClick (e) { handleUnlinkButtonClick (e) {
const index = dialog.showMessageBox(remote.getCurrentWindow(), { const index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning', type: 'warning',
message: i18n.__('Unlink Storage'), message: '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.'), detail: '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')] buttons: ['Unlink', 'Cancel']
}) })
if (index === 0) { if (index === 0) {
@@ -128,7 +127,7 @@ class StorageItem extends React.Component {
<i className='fa fa-plus' /> <i className='fa fa-plus' />
<span styleName='header-control-button-tooltip' <span styleName='header-control-button-tooltip'
style={{left: -20}} style={{left: -20}}
>{i18n.__('Add Folder')}</span> >Add Folder</span>
</button> </button>
<button styleName='header-control-button' <button styleName='header-control-button'
onClick={(e) => this.handleExternalButtonClick(e)} onClick={(e) => this.handleExternalButtonClick(e)}
@@ -136,7 +135,7 @@ class StorageItem extends React.Component {
<i className='fa fa-external-link' /> <i className='fa fa-external-link' />
<span styleName='header-control-button-tooltip' <span styleName='header-control-button-tooltip'
style={{left: -50}} style={{left: -50}}
>{i18n.__('Open Storage folder')}</span> >Open Storage folder</span>
</button> </button>
<button styleName='header-control-button' <button styleName='header-control-button'
onClick={(e) => this.handleUnlinkButtonClick(e)} onClick={(e) => this.handleUnlinkButtonClick(e)}
@@ -144,7 +143,7 @@ class StorageItem extends React.Component {
<i className='fa fa-unlink' /> <i className='fa fa-unlink' />
<span styleName='header-control-button-tooltip' <span styleName='header-control-button-tooltip'
style={{left: -10}} style={{left: -10}}
>{i18n.__('Unlink')}</span> >Unlink</span>
</button> </button>
</div> </div>
</div> </div>

View File

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

View File

@@ -9,7 +9,6 @@ import ReactCodeMirror from 'react-codemirror'
import CodeMirror from 'codemirror' import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir' import 'codemirror-mode-elixir'
import _ from 'lodash' import _ from 'lodash'
import i18n from 'browser/lib/i18n'
const OSX = global.process.platform === 'darwin' const OSX = global.process.platform === 'darwin'
@@ -30,13 +29,13 @@ class UiTab extends React.Component {
this.handleSettingDone = () => { this.handleSettingDone = () => {
this.setState({UiAlert: { this.setState({UiAlert: {
type: 'success', type: 'success',
message: i18n.__('Successfully applied!') message: 'Successfully applied!'
}}) }})
} }
this.handleSettingError = (err) => { this.handleSettingError = (err) => {
this.setState({UiAlert: { this.setState({UiAlert: {
type: 'error', 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) ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
@@ -62,7 +61,6 @@ class UiTab extends React.Component {
const newConfig = { const newConfig = {
ui: { ui: {
theme: this.refs.uiTheme.value, theme: this.refs.uiTheme.value,
language: this.refs.uiLanguage.value,
showCopyNotification: this.refs.showCopyNotification.checked, showCopyNotification: this.refs.showCopyNotification.checked,
confirmDeletion: this.refs.confirmDeletion.checked, confirmDeletion: this.refs.confirmDeletion.checked,
disableDirectWrite: this.refs.uiD2w != null disableDirectWrite: this.refs.uiD2w != null
@@ -75,13 +73,10 @@ class UiTab extends React.Component {
fontFamily: this.refs.editorFontFamily.value, fontFamily: this.refs.editorFontFamily.value,
indentType: this.refs.editorIndentType.value, indentType: this.refs.editorIndentType.value,
indentSize: this.refs.editorIndentSize.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, displayLineNumbers: this.refs.editorDisplayLineNumbers.checked,
switchPreview: this.refs.editorSwitchPreview.value, switchPreview: this.refs.editorSwitchPreview.value,
keyMap: this.refs.editorKeyMap.value, keyMap: this.refs.editorKeyMap.value,
scrollPastEnd: this.refs.scrollPastEnd.checked, scrollPastEnd: this.refs.scrollPastEnd.checked
fetchUrlTitle: this.refs.editorFetchUrlTitle.checked
}, },
preview: { preview: {
fontSize: this.refs.previewFontSize.value, fontSize: this.refs.previewFontSize.value,
@@ -92,9 +87,7 @@ class UiTab extends React.Component {
latexInlineClose: this.refs.previewLatexInlineClose.value, latexInlineClose: this.refs.previewLatexInlineClose.value,
latexBlockOpen: this.refs.previewLatexBlockOpen.value, latexBlockOpen: this.refs.previewLatexBlockOpen.value,
latexBlockClose: this.refs.previewLatexBlockClose.value, latexBlockClose: this.refs.previewLatexBlockClose.value,
scrollPastEnd: this.refs.previewScrollPastEnd.checked, scrollPastEnd: this.refs.previewScrollPastEnd.checked
smartQuotes: this.refs.previewSmartQuotes.checked,
sanitize: this.refs.previewSanitize.value
} }
} }
@@ -112,7 +105,7 @@ class UiTab extends React.Component {
this.props.haveToSave({ this.props.haveToSave({
tab: 'UI', tab: 'UI',
type: 'warning', 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 themes = consts.THEMES
const { config, codemirrorTheme } = this.state 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 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 ( return (
<div styleName='root'> <div styleName='root'>
<div styleName='group'> <div styleName='group'>
<div styleName='group-header'>{i18n.__('Interface')}</div> <div styleName='group-header'>Interface</div>
<div styleName='group-section'> <div styleName='group-section'>
{i18n.__('Interface Theme')} Interface Theme
<div styleName='group-section-control'> <div styleName='group-section-control'>
<select value={config.ui.theme} <select value={config.ui.theme}
onChange={(e) => this.handleUIChange(e)} onChange={(e) => this.handleUIChange(e)}
ref='uiTheme' ref='uiTheme'
> >
<option value='default'>{i18n.__('Default')}</option> <option value='default'>Default</option>
<option value='white'>{i18n.__('White')}</option> <option value='white'>White</option>
<option value='solarized-dark'>{i18n.__('Solarized Dark')}</option> <option value='solarized-dark'>Solarized Dark</option>
<option value='dark'>{i18n.__('Dark')}</option> <option value='dark'>Dark</option>
</select> </select>
</div> </div>
</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'> <div styleName='group-checkBoxSection'>
<label> <label>
<input onChange={(e) => this.handleUIChange(e)} <input onChange={(e) => this.handleUIChange(e)}
@@ -208,7 +173,7 @@ class UiTab extends React.Component {
ref='showCopyNotification' ref='showCopyNotification'
type='checkbox' type='checkbox'
/>&nbsp; />&nbsp;
{i18n.__('Show "Saved to Clipboard" notification when copying')} Show &quot;Saved to Clipboard&quot; notification when copying
</label> </label>
</div> </div>
<div styleName='group-checkBoxSection'> <div styleName='group-checkBoxSection'>
@@ -218,7 +183,7 @@ class UiTab extends React.Component {
ref='confirmDeletion' ref='confirmDeletion'
type='checkbox' type='checkbox'
/>&nbsp; />&nbsp;
{i18n.__('Show a confirmation dialog when deleting notes')} Show a confirmation dialog when deleting notes
</label> </label>
</div> </div>
{ {
@@ -240,7 +205,7 @@ class UiTab extends React.Component {
<div styleName='group-section'> <div styleName='group-section'>
<div styleName='group-section-label'> <div styleName='group-section-label'>
{i18n.__('Editor Theme')} Editor Theme
</div> </div>
<div styleName='group-section-control'> <div styleName='group-section-control'>
<select value={config.editor.theme} <select value={config.editor.theme}
@@ -260,7 +225,7 @@ class UiTab extends React.Component {
</div> </div>
<div styleName='group-section'> <div styleName='group-section'>
<div styleName='group-section-label'> <div styleName='group-section-label'>
{i18n.__('Editor Font Size')} Editor Font Size
</div> </div>
<div styleName='group-section-control'> <div styleName='group-section-control'>
<input styleName='group-section-control-input' <input styleName='group-section-control-input'
@@ -273,7 +238,7 @@ class UiTab extends React.Component {
</div> </div>
<div styleName='group-section'> <div styleName='group-section'>
<div styleName='group-section-label'> <div styleName='group-section-label'>
{i18n.__('Editor Font Family')} Editor Font Family
</div> </div>
<div styleName='group-section-control'> <div styleName='group-section-control'>
<input styleName='group-section-control-input' <input styleName='group-section-control-input'
@@ -286,7 +251,7 @@ class UiTab extends React.Component {
</div> </div>
<div styleName='group-section'> <div styleName='group-section'>
<div styleName='group-section-label'> <div styleName='group-section-label'>
{i18n.__('Editor Indent Style')} Editor Indent Style
</div> </div>
<div styleName='group-section-control'> <div styleName='group-section-control'>
<select value={config.editor.indentSize} <select value={config.editor.indentSize}
@@ -302,70 +267,41 @@ class UiTab extends React.Component {
ref='editorIndentType' ref='editorIndentType'
onChange={(e) => this.handleUIChange(e)} onChange={(e) => this.handleUIChange(e)}
> >
<option value='space'>{i18n.__('Spaces')}</option> <option value='space'>Spaces</option>
<option value='tab'>{i18n.__('Tabs')}</option> <option value='tab'>Tabs</option>
</select> </select>
</div> </div>
</div> </div>
<div styleName='group-section'> <div styleName='group-section'>
<div styleName='group-section-label'> <div styleName='group-section-label'>
{i18n.__('Editor Rulers')} Switch to Preview
</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')}
</div> </div>
<div styleName='group-section-control'> <div styleName='group-section-control'>
<select value={config.editor.switchPreview} <select value={config.editor.switchPreview}
ref='editorSwitchPreview' ref='editorSwitchPreview'
onChange={(e) => this.handleUIChange(e)} onChange={(e) => this.handleUIChange(e)}
> >
<option value='BLUR'>{i18n.__('When Editor Blurred')}</option> <option value='BLUR'>When Editor Blurred</option>
<option value='DBL_CLICK'>{i18n.__('When Editor Blurred, Edit On Double Click')}</option> <option value='RIGHTCLICK'>On Right Click</option>
<option value='RIGHTCLICK'>{i18n.__('On Right Click')}</option>
</select> </select>
</div> </div>
</div> </div>
<div styleName='group-section'> <div styleName='group-section'>
<div styleName='group-section-label'> <div styleName='group-section-label'>
{i18n.__('Editor Keymap')} Editor Keymap
</div> </div>
<div styleName='group-section-control'> <div styleName='group-section-control'>
<select value={config.editor.keyMap} <select value={config.editor.keyMap}
ref='editorKeyMap' ref='editorKeyMap'
onChange={(e) => this.handleUIChange(e)} onChange={(e) => this.handleUIChange(e)}
> >
<option value='sublime'>{i18n.__('default')}</option> <option value='sublime'>default</option>
<option value='vim'>{i18n.__('vim')}</option> <option value='vim'>vim</option>
<option value='emacs'>{i18n.__('emacs')}</option> <option value='emacs'>emacs</option>
</select> </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>
</div> </div>
@@ -376,7 +312,7 @@ class UiTab extends React.Component {
ref='editorDisplayLineNumbers' ref='editorDisplayLineNumbers'
type='checkbox' type='checkbox'
/>&nbsp; />&nbsp;
{i18n.__('Show line numbers in the editor')} Show line numbers in the editor
</label> </label>
</div> </div>
@@ -387,25 +323,14 @@ class UiTab extends React.Component {
ref='scrollPastEnd' ref='scrollPastEnd'
type='checkbox' type='checkbox'
/>&nbsp; />&nbsp;
{i18n.__('Allow editor to scroll past the last line')} Allow editor to scroll past the last line
</label> </label>
</div> </div>
<div styleName='group-checkBoxSection'> <div styleName='group-header2'>Preview</div>
<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-section'> <div styleName='group-section'>
<div styleName='group-section-label'> <div styleName='group-section-label'>
{i18n.__('Preview Font Size')} Preview Font Size
</div> </div>
<div styleName='group-section-control'> <div styleName='group-section-control'>
<input styleName='group-section-control-input' <input styleName='group-section-control-input'
@@ -418,7 +343,7 @@ class UiTab extends React.Component {
</div> </div>
<div styleName='group-section'> <div styleName='group-section'>
<div styleName='group-section-label'> <div styleName='group-section-label'>
{i18n.__('Preview Font Family')} Preview Font Family
</div> </div>
<div styleName='group-section-control'> <div styleName='group-section-control'>
<input styleName='group-section-control-input' <input styleName='group-section-control-input'
@@ -430,7 +355,7 @@ class UiTab extends React.Component {
</div> </div>
</div> </div>
<div styleName='group-section'> <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'> <div styleName='group-section-control'>
<select value={config.preview.codeBlockTheme} <select value={config.preview.codeBlockTheme}
ref='previewCodeBlockTheme' ref='previewCodeBlockTheme'
@@ -451,7 +376,7 @@ class UiTab extends React.Component {
ref='previewScrollPastEnd' ref='previewScrollPastEnd'
type='checkbox' type='checkbox'
/>&nbsp; />&nbsp;
{i18n.__('Allow preview to scroll past the last line')} Allow preview to scroll past the last line
</label> </label>
</div> </div>
<div styleName='group-checkBoxSection'> <div styleName='group-checkBoxSection'>
@@ -461,39 +386,12 @@ class UiTab extends React.Component {
ref='previewLineNumber' ref='previewLineNumber'
type='checkbox' type='checkbox'
/>&nbsp; />&nbsp;
{i18n.__('Show line numbers for preview code blocks')} Show line numbers for preview code blocks
</label> </label>
</div> </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'>
<div styleName='group-section-label'> <div styleName='group-section-label'>
{i18n.__('LaTeX Inline Open Delimiter')} LaTeX Inline Open Delimiter
</div> </div>
<div styleName='group-section-control'> <div styleName='group-section-control'>
<input styleName='group-section-control-input' <input styleName='group-section-control-input'
@@ -506,7 +404,7 @@ class UiTab extends React.Component {
</div> </div>
<div styleName='group-section'> <div styleName='group-section'>
<div styleName='group-section-label'> <div styleName='group-section-label'>
{i18n.__('LaTeX Inline Close Delimiter')} LaTeX Inline Close Delimiter
</div> </div>
<div styleName='group-section-control'> <div styleName='group-section-control'>
<input styleName='group-section-control-input' <input styleName='group-section-control-input'
@@ -519,7 +417,7 @@ class UiTab extends React.Component {
</div> </div>
<div styleName='group-section'> <div styleName='group-section'>
<div styleName='group-section-label'> <div styleName='group-section-label'>
{i18n.__('LaTeX Block Open Delimiter')} LaTeX Block Open Delimiter
</div> </div>
<div styleName='group-section-control'> <div styleName='group-section-control'>
<input styleName='group-section-control-input' <input styleName='group-section-control-input'
@@ -532,7 +430,7 @@ class UiTab extends React.Component {
</div> </div>
<div styleName='group-section'> <div styleName='group-section'>
<div styleName='group-section-label'> <div styleName='group-section-label'>
{i18n.__('LaTeX Block Close Delimiter')} LaTeX Block Close Delimiter
</div> </div>
<div styleName='group-section-control'> <div styleName='group-section-control'>
<input styleName='group-section-control-input' <input styleName='group-section-control-input'
@@ -546,7 +444,7 @@ class UiTab extends React.Component {
<div styleName='group-control'> <div styleName='group-control'>
<button styleName='group-control-rightButton' <button styleName='group-control-rightButton'
onClick={(e) => this.handleSaveUIClick(e)}>{i18n.__('Save')} onClick={(e) => this.handleSaveUIClick(e)}>Save
</button> </button>
{UiAlertElement} {UiAlertElement}
</div> </div>

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ $danger-color = #c9302c
$danger-lighten-color = lighten(#c9302c, 5%) $danger-lighten-color = lighten(#c9302c, 5%)
// Layouts // Layouts
$statusBar-height = 22px $statusBar-height = 0px
$sideNav-width = 200px $sideNav-width = 200px
$sideNav--folded-width = 44px $sideNav--folded-width = 44px
$topBar-height = 60px $topBar-height = 60px
@@ -347,4 +347,4 @@ modalSolarizedDark()
width 100% width 100%
background-color $ui-solarized-dark-backgroundColor background-color $ui-solarized-dark-backgroundColor
overflow hidden 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 ### 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. 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. 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
Если вы делаете pull request, значит вы согласны передать авторские права на изменения кода в BoostIO. Если вы делаете pull request, значит вы согласны передать авторские права на изменения кода в Maisin&Co.
Это не означает, что Boostnote станет платным приложением. Если мы захотим заработать немного денег, мы найдем другой способ. Например, использование облачного хранилища, интеграцией мобильных приложений или другими специальными функциями. Это не означает, что Boostnote станет платным приложением. Если мы захотим заработать немного денег, мы найдем другой способ. Например, использование облачного хранилища, интеграцией мобильных приложений или другими специальными функциями.
Так как лицензия GPL v3 слишком строгая, чтобы быть совместимой с любой другой лицензией, мы думаем, что нужно заменить лицензию на более свободную (например, BSD, MIT). Так как лицензия 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의 저작권에 관하여
당신이 pull request를 요청하면, 코드 변경에 대한 저작권을 BoostIO에 양도한다는 것에 동의한다는 의미입니다. 당신이 pull request를 요청하면, 코드 변경에 대한 저작권을 Maisin&Co에 양도한다는 것에 동의한다는 의미입니다.
이것은 Boostnote가 유료화가 되는 것을 의미하는 건 아닙니다. 만약 우리가 자금이 필요하다면, 우리는 클라우드 연동, 모바일 앱 통합 혹은 특수한 기능 같은 것을 사용해 수입 창출을 시도할 것입니다. 이것은 Boostnote가 유료화가 되는 것을 의미하는 건 아닙니다. 만약 우리가 자금이 필요하다면, 우리는 클라우드 연동, 모바일 앱 통합 혹은 특수한 기능 같은 것을 사용해 수입 창출을 시도할 것입니다.
GPL v3 라이센스는 다른 라이센스와 혼합해 사용하기엔 너무 엄격하므로, 우리는 BSD, MIT 라이센스와 같은 더 자유로운 라이센스로 교체하는 것을 생각하고 있습니다. GPL v3 라이센스는 다른 라이센스와 혼합해 사용하기엔 너무 엄격하므로, 우리는 BSD, MIT 라이센스와 같은 더 자유로운 라이센스로 교체하는 것을 생각하고 있습니다.
@@ -63,7 +63,7 @@ GPL v3 라이센스는 다른 라이센스와 혼합해 사용하기엔 너무
### Pull requestの著作権について ### Pull requestの著作権について
Pull requestをすることはその変化分のコードの著作権をBoostIOに譲渡することに同意することになります。 Pull requestをすることはその変化分のコードの著作権をMaisin&Co.に譲渡することに同意することになります。
アプリケーションのLicenseをいつでも変える選択肢を残したいと思うからです。 アプリケーションのLicenseをいつでも変える選択肢を残したいと思うからです。
これはいずれかBoostnoteが有料の商用アプリになる可能性がある話ではありません。 これはいずれかBoostnoteが有料の商用アプリになる可能性がある話ではありません。
@@ -83,7 +83,7 @@ Pull requestをすることはその変化分のコードの著作権をBoostIO
感谢您对我们的支持。 感谢您对我们的支持。
### 关于您提供的Pull Request的著作权版权问题 ### 关于您提供的Pull Request的著作权版权问题
如果您提供了一个Pull Request这表示您将您所修改的代码的著作权移交给BoostIO 如果您提供了一个Pull Request这表示您将您所修改的代码的著作权移交给Maisin&Co
这并不表示Boostnote会成为一个需要付费的软件。如果我们想获得收益我们会尝试一些其他的方法比如说云存储、绑定手机软件等。 这并不表示Boostnote会成为一个需要付费的软件。如果我们想获得收益我们会尝试一些其他的方法比如说云存储、绑定手机软件等。
因为GPLv3过于严格不能和其他的一些协议兼容所以我们有可能在将来会把BoostNote的协议改为一些较为宽松的协议比如说BSD、MIT。 因为GPLv3过于严格不能和其他的一些协议兼容所以我们有可能在将来会把BoostNote的协议改为一些较为宽松的协议比如说BSD、MIT。

View File

@@ -1,5 +1,5 @@
# Build # 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 ## Umgebungen
* npm: 4.x * npm: 4.x

View File

@@ -1,31 +1,25 @@
# How to debug Boostnote (Electron app) # 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 is eine Electron app, somit basiert sie auf Chromium; Entwickler können die `Developer Tools` verwenden, wie Google Chrome.
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:
Du kannst die `Developer Tools` so einschalten:
![how_to_toggle_devTools](https://cloud.githubusercontent.com/assets/11307908/24343585/162187e2-127c-11e7-9c01-23578db03ecf.png) ![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) ![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 ## Debugging
Zum Beispiel kannst du mit dem `debugger` Haltepunkte im Code setzen wie hier veranschaulicht:
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.
![debugger](https://cloud.githubusercontent.com/assets/11307908/24343879/9459efea-127d-11e7-9943-f60bf7f66d4a.png) ![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 ## Referenz
* [Official document of Google Chrome about debugging](https://developer.chrome.com/devtools) * [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) # 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. 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 # 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 ## Environnements
* npm: 4.x * npm: 4.x

View File

@@ -1,5 +1,5 @@
# Comment débugger Boostnote (Application Electron) # 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. 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. 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 ## 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 = { var initConfig = {
pkg: grunt.file.readJSON('package.json'), pkg: grunt.file.readJSON('package.json'),
'create-windows-installer': { 'create-windows-installer': {
x64: { ia32: {
appDirectory: path.join(__dirname, 'dist', 'Boostnote-win32-x64'), appDirectory: path.join(__dirname, 'dist', 'Boostnote-win32-x64'),
outputDirectory: path.join(__dirname, 'dist'), outputDirectory: path.join(__dirname, 'dist'),
authors: 'MAISIN&CO., Inc.', authors: 'MAISIN&CO., Inc.',
@@ -109,7 +109,7 @@ module.exports = function (grunt) {
var done = this.async() var done = this.async()
var opts = { var opts = {
name: 'Boostnote', name: 'Boostnote',
arch: 'x64', arch: 'ia32',
dir: __dirname, dir: __dirname,
version: grunt.config.get('pkg.config.electron-version'), version: grunt.config.get('pkg.config.electron-version'),
'app-version': grunt.config.get('pkg.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 () { app.on('ready', function () {
mainWindow = require('./main-window') mainWindow = require('./main-window')

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