mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-14 10:16:26 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d98f0cb03 | ||
|
|
3503233631 | ||
|
|
c39393c453 | ||
|
|
564cc80ef7 | ||
|
|
77f7144fbf |
2
.babelrc
2
.babelrc
@@ -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" } ]
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
compiled/
|
compiled/
|
||||||
dist/
|
dist/
|
||||||
extra_scripts/
|
|
||||||
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -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
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
require: jest.genMockFunction(),
|
|
||||||
match: jest.genMockFunction(),
|
|
||||||
app: jest.genMockFunction(),
|
|
||||||
remote: jest.genMockFunction(),
|
|
||||||
dialog: jest.genMockFunction()
|
|
||||||
}
|
|
||||||
@@ -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 = `})`
|
||||||
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 = `})`
|
||||||
|
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 = `})`
|
|
||||||
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,
|
||||||
|
|||||||
@@ -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
163
browser/components/MarkdownPreview.js
Executable file → Normal 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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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 = '"'
|
|
||||||
break
|
|
||||||
case 38: // &
|
|
||||||
escape = '&'
|
|
||||||
break
|
|
||||||
case 39: // '
|
|
||||||
escape = '''
|
|
||||||
break
|
|
||||||
case 60: // <
|
|
||||||
escape = '<'
|
|
||||||
break
|
|
||||||
case 62: // >
|
|
||||||
escape = '>'
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastIndex !== index) {
|
|
||||||
html += str.substring(lastIndex, index)
|
|
||||||
}
|
|
||||||
|
|
||||||
lastIndex = index + 1
|
|
||||||
html += escape
|
|
||||||
}
|
|
||||||
|
|
||||||
return lastIndex !== index
|
|
||||||
? html + str.substring(lastIndex, index)
|
|
||||||
: html
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
lastFindInArray,
|
|
||||||
escapeHtmlCharacters
|
|
||||||
}
|
|
||||||
@@ -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)}
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
22
browser/main/Detail/MarkdownNoteDetail.js
Executable file → Normal 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}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
|
||||||
@@ -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()
|
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
<i className='fa fa-caret-down' />
|
<i className='fa fa-caret-down' />
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)}
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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'])}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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 = {}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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' ? ''
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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'>
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -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 ``
|
|
||||||
})
|
|
||||||
|
|
||||||
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
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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':
|
||||||
|
|||||||
@@ -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 () {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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)
|
|
||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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'
|
||||||
/>
|
/>
|
||||||
{i18n.__('Show "Saved to Clipboard" notification when copying')}
|
Show "Saved to Clipboard" 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'
|
||||||
/>
|
/>
|
||||||
{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'
|
||||||
/>
|
/>
|
||||||
{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'
|
||||||
/>
|
/>
|
||||||
{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'
|
|
||||||
/>
|
|
||||||
{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'
|
||||||
/>
|
/>
|
||||||
{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'
|
||||||
/>
|
/>
|
||||||
{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'
|
|
||||||
/>
|
|
||||||
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>
|
||||||
|
|||||||
@@ -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'>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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。
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||

|

|
||||||
|
|
||||||
Die Anzeige der `Developer Tools` sieht in etwa so aus:
|
Die `Developer Tools` schauen dann ungefähr so aus:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
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.
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -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.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
> ### 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.
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
@@ -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'),
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user