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

Merge branch 'master' into feature-editor-line-lines

This commit is contained in:
David Pavlík
2017-12-23 23:06:26 +01:00
committed by GitHub
222 changed files with 6346 additions and 2127 deletions

View File

@@ -12,5 +12,10 @@
"react/no-find-dom-node": "warn", "react/no-find-dom-node": "warn",
"react/no-render-return-value": "warn", "react/no-render-return-value": "warn",
"react/no-deprecated": "warn" "react/no-deprecated": "warn"
},
"globals": {
"FileReader": true,
"localStorage": true,
"fetch": true
} }
} }

2
.gitignore vendored
View File

@@ -8,3 +8,5 @@ node_modules/*
/compiled /compiled
/secret /secret
*.log *.log
.vscode
.idea

Binary file not shown.

View File

@@ -1,6 +1,20 @@
language: node_js language: node_js
node_js: node_js:
- 'stable' - stable
- 'lts/*' - lts/*
script:
script: npm run lint && npm run test - npm run lint && npm run test
- 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" ]]; then npm install -g grunt npm@5.2 && grunt pre-build; fi'
after_success:
- openssl aes-256-cbc -K $encrypted_440d7f9a3c38_key -iv $encrypted_440d7f9a3c38_iv
-in .snapcraft/travis_snapcraft.cfg -out .snapcraft/snapcraft.cfg -d
sudo: required
services:
- docker
deploy:
'on':
branch: master
provider: script
script: if [ ${TRAVIS_NODE_VERSION} = "stable" ];then docker run -v $(pwd):$(pwd) -t snapcore/snapcraft sh -c "apt update -qq
&& cd $(pwd) && snapcraft && snapcraft push *.snap --release edge"; fi
skip_cleanup: true

View File

@@ -1,9 +1,72 @@
Become a [backer](https://salt.bountysource.com/teams/boostnote) and support Boostnote! <h1 align="center">Sponsors &amp; Backers</h1>
You can support Boostnote from $ 5 a month!
# Backers Boostnote is an open source project. It's an independent project with its ongoing development made possible entirely thanks to the support by these awesome backers. If you'd like to join them, please consider:
[Kazu Yokomizo](https://twitter.com/kazup_bot)
kolchan11 - [Become a backer or sponsor on Open Collective.](https://opencollective.com/boostnoteio)
RonWalker22 ---
## Backers via OpenCollective
### [Gold Sponsors / $1,000 per month](https://opencollective.com/boostnoteio/order/2259)
- Get your logo on our Readme.md on GitHub and the frontpage of https://boostnote.io/.
### [Silver Sponsors / $250 per month](https://opencollective.com/boostnoteio/order/2257)
- Get your logo on our Readme.md on GitHub and the frontpage of https://boostnote.io/.
### [Bronze Sponsors / $50 per month](https://opencollective.com/boostnoteio/order/2258)
- Get your name and Url (or E-mail) on Readme.md on GitHub.
### [Backers3 / $10 per month](https://opencollective.com/boostnoteio/order/2176)
- [Ralph03](https://opencollective.com/ralph03)
- [Nikolas Dan](https://opencollective.com/nikolas-dan)
### [Backers2 / $5 per month](https://opencollective.com/boostnoteio/order/2175)
- [Yeojong Kim](https://twitter.com/yeojoy)
- [Scotia Draven](https://opencollective.com/scotia-draven)
- [A. J. Vargas](https://opencollective.com/aj-vargas)
### [Backers1](https://opencollective.com/boostnoteio/order/2563) and One-time sponsors
- Ryosuke Tamura - $30
- tatoosh11 - $10
- Alexander Borovkov - $10
- spoonhoop - $5
- Drew Williams - $2
- Andy Shaw - $2
- mysafesky -$2
---
## Backers via Bountysource
https://salt.bountysource.com/teams/boostnote
- Kuzz - $65
- Intense Raiden - $45
- ravy22 - $25
- trentpolack - $20
- hikariru - $10
- kolchan11 - $10
- RonWalker22 - $10
- hocchuc - $5
- Adam - $5
- Steve - $5
- evmin - $5

View File

@@ -1 +1,10 @@
Please paste some **screenshots** with the **developer tool** open if you report a bug. <!--
Please paste some **screenshots** with the **developer tool** open (console tab) when you report a bug.
If your issue is regarding boostnote mobile, move to https://github.com/BoostIO/boostnote-mobile.
-->
<!--
Love Boostnote? Please consider supporting us via OpenCollective:
👉 https://opencollective.com/boostnoteio
-->

View File

@@ -1,9 +1,11 @@
import React, { PropTypes } from 'react' import PropTypes from 'prop-types'
import React from 'react'
import _ from 'lodash' import _ from 'lodash'
import CodeMirror from 'codemirror' import CodeMirror from 'codemirror'
import path from 'path' 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'
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js' CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
@@ -57,6 +59,7 @@ export default class CodeEditor extends React.Component {
tabSize: this.props.indentSize, tabSize: this.props.indentSize,
indentWithTabs: this.props.indentType !== 'space', indentWithTabs: this.props.indentType !== 'space',
keyMap: this.props.keyMap, keyMap: this.props.keyMap,
scrollPastEnd: this.props.scrollPastEnd,
inputStyle: 'textarea', inputStyle: 'textarea',
dragDrop: false, dragDrop: false,
extraKeys: { extraKeys: {
@@ -66,7 +69,7 @@ export default class CodeEditor extends React.Component {
if (cm.somethingSelected()) cm.indentSelection('add') if (cm.somethingSelected()) cm.indentSelection('add')
else { else {
const tabs = cm.getOption('indentWithTabs') const tabs = cm.getOption('indentWithTabs')
if (line.trimLeft() === '- ' || line.trimLeft() === '* ' || line.trimLeft() === '+ ') { if (line.trimLeft().match(/^(-|\*|\+) (\[( |x)\] )?$/)) {
cm.execCommand('goLineStart') cm.execCommand('goLineStart')
if (tabs) { if (tabs) {
cm.execCommand('insertTab') cm.execCommand('insertTab')
@@ -102,15 +105,25 @@ export default class CodeEditor extends React.Component {
this.editor.on('change', this.changeHandler) this.editor.on('change', this.changeHandler)
this.editor.on('paste', this.pasteHandler) this.editor.on('paste', this.pasteHandler)
let editorTheme = document.getElementById('editorTheme') const editorTheme = document.getElementById('editorTheme')
editorTheme.addEventListener('load', this.loadStyleHandler) editorTheme.addEventListener('load', this.loadStyleHandler)
CodeMirror.Vim.defineEx('quit', 'q', this.quitEditor)
CodeMirror.Vim.defineEx('q!', 'q!', this.quitEditor)
CodeMirror.Vim.defineEx('wq', 'wq', this.quitEditor)
CodeMirror.Vim.defineEx('qw', 'qw', this.quitEditor)
CodeMirror.Vim.map('ZZ', ':q', 'normal')
}
quitEditor () {
document.querySelector('textarea').blur()
} }
componentWillUnmount () { componentWillUnmount () {
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)
let editorTheme = document.getElementById('editorTheme') const editorTheme = document.getElementById('editorTheme')
editorTheme.removeEventListener('load', this.loadStyleHandler) editorTheme.removeEventListener('load', this.loadStyleHandler)
} }
@@ -145,6 +158,10 @@ export default class CodeEditor extends React.Component {
this.editor.setOption('lineNumbers', this.props.displayLineNumbers) this.editor.setOption('lineNumbers', this.props.displayLineNumbers)
} }
if (prevProps.scrollPastEnd !== this.props.scrollPastEnd) {
this.editor.setOption('scrollPastEnd', this.props.scrollPastEnd)
}
if (needRefresh) { if (needRefresh) {
this.editor.refresh() this.editor.refresh()
} }
@@ -190,7 +207,7 @@ export default class CodeEditor extends React.Component {
} }
setValue (value) { setValue (value) {
let cursor = this.editor.getCursor() const cursor = this.editor.getCursor()
this.editor.setValue(value) this.editor.setValue(value)
this.editor.setCursor(cursor) this.editor.setCursor(cursor)
} }
@@ -207,9 +224,7 @@ export default class CodeEditor extends React.Component {
} }
insertImageMd (imageMd) { insertImageMd (imageMd) {
const textarea = this.editor.getInputField() this.editor.replaceSelection(imageMd)
const cm = this.editor
cm.replaceSelection(`${textarea.value.substr(0, textarea.selectionStart)}${imageMd}${textarea.value.substr(textarea.selectionEnd)}`)
} }
handlePaste (editor, e) { handlePaste (editor, e) {
@@ -217,7 +232,7 @@ export default class CodeEditor extends React.Component {
if (!dataTransferItem.type.match('image')) return if (!dataTransferItem.type.match('image')) return
const blob = dataTransferItem.getAsFile() const blob = dataTransferItem.getAsFile()
let reader = new FileReader() const reader = new FileReader()
let base64data let base64data
reader.readAsDataURL(blob) reader.readAsDataURL(blob)
@@ -227,16 +242,18 @@ export default class CodeEditor extends React.Component {
const binaryData = new Buffer(base64data, 'base64').toString('binary') const binaryData = new Buffer(base64data, 'base64').toString('binary')
const imageName = Math.random().toString(36).slice(-16) const imageName = Math.random().toString(36).slice(-16)
const storagePath = findStorage(this.props.storageKey).path const storagePath = findStorage(this.props.storageKey).path
const imagePath = path.join(`${storagePath}`, 'images', `${imageName}.png`) const imageDir = path.join(storagePath, 'images')
if (!fs.existsSync(imageDir)) fs.mkdirSync(imageDir)
require('fs').writeFile(imagePath, binaryData, 'binary') const imagePath = path.join(imageDir, `${imageName}.png`)
fs.writeFile(imagePath, binaryData, 'binary')
const imageMd = `![${imageName}](${path.join('/:storage', `${imageName}.png`)})` const imageMd = `![${imageName}](${path.join('/:storage', `${imageName}.png`)})`
this.insertImageMd(imageMd) this.insertImageMd(imageMd)
} }
} }
render () { render () {
let { className, fontFamily, fontSize } = this.props const { className, fontSize } = this.props
let fontFamily = this.props.className
fontFamily = _.isString(fontFamily) && fontFamily.length > 0 fontFamily = _.isString(fontFamily) && fontFamily.length > 0
? [fontFamily].concat(defaultEditorFontFamily) ? [fontFamily].concat(defaultEditorFontFamily)
: defaultEditorFontFamily : defaultEditorFontFamily

View File

@@ -1,11 +1,11 @@
import React, { PropTypes } from 'react' import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './MarkdownEditor.styl' 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'
const _ = require('lodash')
class MarkdownEditor extends React.Component { class MarkdownEditor extends React.Component {
constructor (props) { constructor (props) {
@@ -70,9 +70,9 @@ class MarkdownEditor extends React.Component {
} }
handleContextMenu (e) { handleContextMenu (e) {
let { config } = this.props const { config } = this.props
if (config.editor.switchPreview === 'RIGHTCLICK') { if (config.editor.switchPreview === 'RIGHTCLICK') {
let newStatus = this.state.status === 'PREVIEW' const newStatus = this.state.status === 'PREVIEW'
? 'CODE' ? 'CODE'
: 'PREVIEW' : 'PREVIEW'
this.setState({ this.setState({
@@ -91,9 +91,9 @@ class MarkdownEditor extends React.Component {
handleBlur (e) { handleBlur (e) {
if (this.state.isLocked) return if (this.state.isLocked) return
this.setState({ keyPressed: new Set() }) this.setState({ keyPressed: new Set() })
let { config } = this.props const { config } = this.props
if (config.editor.switchPreview === 'BLUR') { if (config.editor.switchPreview === 'BLUR') {
let cursorPosition = this.refs.code.editor.getCursor() const cursorPosition = this.refs.code.editor.getCursor()
this.setState({ this.setState({
status: 'PREVIEW' status: 'PREVIEW'
}, () => { }, () => {
@@ -109,7 +109,7 @@ class MarkdownEditor extends React.Component {
} }
handlePreviewMouseUp (e) { handlePreviewMouseUp (e) {
let { config } = this.props const { config } = this.props
if (config.editor.switchPreview === 'BLUR' && new Date() - this.previewMouseDownedAt < 200) { if (config.editor.switchPreview === 'BLUR' && new Date() - this.previewMouseDownedAt < 200) {
this.setState({ this.setState({
status: 'CODE' status: 'CODE'
@@ -123,15 +123,15 @@ class MarkdownEditor extends React.Component {
handleCheckboxClick (e) { handleCheckboxClick (e) {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
let idMatch = /checkbox-([0-9]+)/ const idMatch = /checkbox-([0-9]+)/
let checkedMatch = /\[x\]/i const checkedMatch = /\[x\]/i
let uncheckedMatch = /\[ \]/ const uncheckedMatch = /\[ \]/
if (idMatch.test(e.target.getAttribute('id'))) { if (idMatch.test(e.target.getAttribute('id'))) {
let lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1 const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
let lines = this.refs.code.value const lines = this.refs.code.value
.split('\n') .split('\n')
let targetLine = lines[lineIndex] const targetLine = lines[lineIndex]
if (targetLine.match(checkedMatch)) { if (targetLine.match(checkedMatch)) {
lines[lineIndex] = targetLine.replace(checkedMatch, '[ ]') lines[lineIndex] = targetLine.replace(checkedMatch, '[ ]')
@@ -163,12 +163,12 @@ class MarkdownEditor extends React.Component {
} }
handleKeyDown (e) { handleKeyDown (e) {
let { config } = this.props const { config } = this.props
if (this.state.status !== 'CODE') return false if (this.state.status !== 'CODE') return false
const keyPressed = this.state.keyPressed const keyPressed = this.state.keyPressed
keyPressed.add(e.keyCode) keyPressed.add(e.keyCode)
this.setState({ keyPressed }) this.setState({ keyPressed })
let isNoteHandlerKey = (el) => { return keyPressed.has(el) } const isNoteHandlerKey = (el) => { return keyPressed.has(el) }
// These conditions are for ctrl-e and ctrl-w // These conditions are for ctrl-e and ctrl-w
if (keyPressed.size === this.escapeFromEditor.length && if (keyPressed.size === this.escapeFromEditor.length &&
!this.state.isLocked && this.state.status === 'CODE' && !this.state.isLocked && this.state.status === 'CODE' &&
@@ -207,14 +207,14 @@ class MarkdownEditor extends React.Component {
} }
render () { render () {
let { className, value, config, storageKey } = this.props const { className, value, config, storageKey } = this.props
let editorFontSize = parseInt(config.editor.fontSize, 10) let editorFontSize = parseInt(config.editor.fontSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14 if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
let editorIndentSize = parseInt(config.editor.indentSize, 10) let editorIndentSize = parseInt(config.editor.indentSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4 if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
let previewStyle = {} const previewStyle = {}
if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none' if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none'
const storage = findStorage(storageKey) const storage = findStorage(storageKey)
@@ -243,6 +243,7 @@ class MarkdownEditor extends React.Component {
indentType={config.editor.indentType} indentType={config.editor.indentType}
indentSize={editorIndentSize} indentSize={editorIndentSize}
displayLineNumbers={config.editor.displayLineNumbers} displayLineNumbers={config.editor.displayLineNumbers}
scrollPastEnd={config.editor.scrollPastEnd}
storageKey={storageKey} storageKey={storageKey}
onChange={(e) => this.handleChange(e)} onChange={(e) => this.handleChange(e)}
onBlur={(e) => this.handleBlur(e)} onBlur={(e) => this.handleBlur(e)}
@@ -260,6 +261,7 @@ class MarkdownEditor extends React.Component {
codeBlockFontFamily={config.editor.fontFamily} codeBlockFontFamily={config.editor.fontFamily}
lineNumber={config.preview.lineNumber} lineNumber={config.preview.lineNumber}
indentSize={editorIndentSize} indentSize={editorIndentSize}
scrollPastEnd={config.editor.scrollPastEnd}
ref='preview' ref='preview'
onContextMenu={(e) => this.handleContextMenu(e)} onContextMenu={(e) => this.handleContextMenu(e)}
tabIndex='0' tabIndex='0'
@@ -267,6 +269,7 @@ class MarkdownEditor extends React.Component {
onMouseUp={(e) => this.handlePreviewMouseUp(e)} onMouseUp={(e) => this.handlePreviewMouseUp(e)}
onMouseDown={(e) => this.handlePreviewMouseDown(e)} onMouseDown={(e) => this.handlePreviewMouseDown(e)}
onCheckboxClick={(e) => this.handleCheckboxClick(e)} onCheckboxClick={(e) => this.handleCheckboxClick(e)}
showCopyNotification={config.ui.showCopyNotification}
storagePath={storage.path} storagePath={storage.path}
/> />
</div> </div>

View File

@@ -1,4 +1,5 @@
import React, { PropTypes } from 'react' import PropTypes from 'prop-types'
import React from 'react'
import markdown from 'browser/lib/markdown' import markdown from 'browser/lib/markdown'
import _ from 'lodash' import _ from 'lodash'
import CodeMirror from 'codemirror' import CodeMirror from 'codemirror'
@@ -10,6 +11,7 @@ import eventEmitter from 'browser/main/lib/eventEmitter'
import fs from 'fs' 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'
const { remote } = require('electron') const { remote } = require('electron')
const { app } = remote const { app } = remote
@@ -32,19 +34,27 @@ function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber) {
font-weight: normal; font-weight: normal;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
} }
@font-face {
font-family: 'Lato';
src: url('${appPath}/resources/fonts/Lato-Black.woff2') format('woff2'), /* Modern Browsers */
url('${appPath}/resources/fonts/Lato-Black.woff') format('woff'), /* Modern Browsers */
url('${appPath}/resources/fonts/Lato-Black.ttf') format('truetype');
font-style: normal;
font-weight: 700;
text-rendering: optimizeLegibility;
}
${markdownStyle} ${markdownStyle}
body { body {
font-family: ${fontFamily.join(', ')}; font-family: '${fontFamily.join("','")}';
font-size: ${fontSize}px; font-size: ${fontSize}px;
} }
code { code {
font-family: ${codeBlockFontFamily.join(', ')}; font-family: '${codeBlockFontFamily.join("','")}';
background-color: rgba(0,0,0,0.04); background-color: rgba(0,0,0,0.04);
color: #CC305F;
} }
.lineNumber { .lineNumber {
${lineNumber && 'display: block !important;'} ${lineNumber && 'display: block !important;'}
font-family: ${codeBlockFontFamily.join(', ')}; font-family: '${codeBlockFontFamily.join("','")}';
} }
.clipboardButton { .clipboardButton {
@@ -92,7 +102,7 @@ const OSX = global.process.platform === 'darwin'
const defaultFontFamily = ['helvetica', 'arial', 'sans-serif'] const defaultFontFamily = ['helvetica', 'arial', 'sans-serif']
if (!OSX) { if (!OSX) {
defaultFontFamily.unshift('\'Microsoft YaHei\'') defaultFontFamily.unshift('Microsoft YaHei')
defaultFontFamily.unshift('meiryo') defaultFontFamily.unshift('meiryo')
} }
const defaultCodeBlockFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace'] const defaultCodeBlockFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
@@ -117,10 +127,10 @@ export default class MarkdownPreview extends React.Component {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
let anchor = e.target.closest('a') const anchor = e.target.closest('a')
let href = anchor.getAttribute('href') const href = anchor.getAttribute('href')
if (_.isString(href) && href.match(/^#/)) { if (_.isString(href) && href.match(/^#/)) {
let targetElement = this.refs.root.contentWindow.document.getElementById(href.substring(1, href.length)) const targetElement = this.refs.root.contentWindow.document.getElementById(href.substring(1, href.length))
if (targetElement != null) { if (targetElement != null) {
this.getWindow().scrollTo(0, targetElement.offsetTop) this.getWindow().scrollTo(0, targetElement.offsetTop)
} }
@@ -134,10 +144,12 @@ export default class MarkdownPreview extends React.Component {
} }
handleContextMenu (e) { handleContextMenu (e) {
if (!this.props.onContextMenu) return
this.props.onContextMenu(e) this.props.onContextMenu(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':
@@ -149,6 +161,7 @@ export default class MarkdownPreview extends React.Component {
} }
handleMouseUp (e) { handleMouseUp (e) {
if (!this.props.onMouseUp) return
if (e.target != null && e.target.tagName === 'A') { if (e.target != null && e.target.tagName === 'A') {
return null return null
} }
@@ -184,6 +197,16 @@ export default class MarkdownPreview extends React.Component {
}) })
} }
fixDecodedURI (node) {
if (node && node.children.length === 1 && typeof node.children[0] === 'string') {
const { innerText, href } = node
node.innerText = mdurl.decode(href) === innerText
? href
: innerText
}
}
componentDidMount () { componentDidMount () {
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)
@@ -224,6 +247,7 @@ export default class MarkdownPreview extends React.Component {
prevProps.codeBlockFontFamily !== this.props.codeBlockFontFamily || prevProps.codeBlockFontFamily !== this.props.codeBlockFontFamily ||
prevProps.codeBlockTheme !== this.props.codeBlockTheme || prevProps.codeBlockTheme !== this.props.codeBlockTheme ||
prevProps.lineNumber !== this.props.lineNumber || prevProps.lineNumber !== this.props.lineNumber ||
prevProps.showCopyNotification !== this.props.showCopyNotification ||
prevProps.theme !== this.props.theme) { prevProps.theme !== this.props.theme) {
this.applyStyle() this.applyStyle()
this.rewriteIframe() this.rewriteIframe()
@@ -231,12 +255,13 @@ export default class MarkdownPreview extends React.Component {
} }
applyStyle () { applyStyle () {
let { fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme } = this.props const { fontSize, lineNumber, codeBlockTheme } = this.props
let { fontFamily, codeBlockFontFamily } = this.props
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0 fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
? [fontFamily].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].concat(defaultCodeBlockFontFamily) ? codeBlockFontFamily.split(',').map(fontName => fontName.trim()).concat(defaultCodeBlockFontFamily)
: defaultCodeBlockFontFamily : defaultCodeBlockFontFamily
this.setCodeTheme(codeBlockTheme) this.setCodeTheme(codeBlockTheme)
@@ -247,7 +272,9 @@ export default class MarkdownPreview extends React.Component {
theme = consts.THEMES.some((_theme) => _theme === theme) && theme !== 'default' theme = consts.THEMES.some((_theme) => _theme === theme) && theme !== 'default'
? theme ? theme
: 'elegant' : 'elegant'
this.getWindow().document.getElementById('codeTheme').href = `${appPath}/node_modules/codemirror/theme/${theme}.css` this.getWindow().document.getElementById('codeTheme').href = theme.startsWith('solarized')
? `${appPath}/node_modules/codemirror/theme/solarized.css`
: `${appPath}/node_modules/codemirror/theme/${theme}.css`
} }
rewriteIframe () { rewriteIframe () {
@@ -262,7 +289,8 @@ export default class MarkdownPreview extends React.Component {
el.removeEventListener('click', this.linkClickHandler) el.removeEventListener('click', this.linkClickHandler)
}) })
let { value, theme, indentSize, codeBlockTheme, storagePath } = this.props const { theme, indentSize, showCopyNotification, storagePath } = this.props
let { value, codeBlockTheme } = this.props
this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme) this.refs.root.contentWindow.document.body.setAttribute('data-theme', theme)
@@ -279,6 +307,7 @@ export default class MarkdownPreview extends React.Component {
}) })
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => { _.forEach(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
this.fixDecodedURI(el)
el.addEventListener('click', this.anchorClickHandler) el.addEventListener('click', this.anchorClickHandler)
}) })
@@ -304,25 +333,32 @@ export default class MarkdownPreview extends React.Component {
let syntax = CodeMirror.findModeByName(el.className) let syntax = CodeMirror.findModeByName(el.className)
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text') if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
CodeMirror.requireMode(syntax.mode, () => { CodeMirror.requireMode(syntax.mode, () => {
let content = htmlTextHelper.decodeEntities(el.innerHTML) const content = htmlTextHelper.decodeEntities(el.innerHTML)
const copyIcon = document.createElement('i') const copyIcon = document.createElement('i')
copyIcon.innerHTML = '<button class="clipboardButton"><svg width="13" height="13" viewBox="0 0 1792 1792" ><path d="M768 1664h896v-640h-416q-40 0-68-28t-28-68v-416h-384v1152zm256-1440v-64q0-13-9.5-22.5t-22.5-9.5h-704q-13 0-22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h704q13 0 22.5-9.5t9.5-22.5zm256 672h299l-299-299v299zm512 128v672q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-160h-544q-40 0-68-28t-28-68v-1344q0-40 28-68t68-28h1088q40 0 68 28t28 68v328q21 13 36 28l408 408q28 28 48 76t20 88z"/></svg></button>' copyIcon.innerHTML = '<button class="clipboardButton"><svg width="13" height="13" viewBox="0 0 1792 1792" ><path d="M768 1664h896v-640h-416q-40 0-68-28t-28-68v-416h-384v1152zm256-1440v-64q0-13-9.5-22.5t-22.5-9.5h-704q-13 0-22.5 9.5t-9.5 22.5v64q0 13 9.5 22.5t22.5 9.5h704q13 0 22.5-9.5t9.5-22.5zm256 672h299l-299-299v299zm512 128v672q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-160h-544q-40 0-68-28t-28-68v-1344q0-40 28-68t68-28h1088q40 0 68 28t28 68v328q21 13 36 28l408 408q28 28 48 76t20 88z"/></svg></button>'
copyIcon.onclick = (e) => { copyIcon.onclick = (e) => {
copy(content) copy(content)
if (showCopyNotification) {
this.notify('Saved to Clipboard!', { this.notify('Saved to Clipboard!', {
body: 'Paste it wherever you want!', body: 'Paste it wherever you want!',
silent: true silent: true
}) })
} }
}
el.parentNode.appendChild(copyIcon) el.parentNode.appendChild(copyIcon)
el.innerHTML = '' el.innerHTML = ''
if (codeBlockTheme.indexOf('solarized') === 0) {
const [refThema, color] = codeBlockTheme.split(' ')
el.parentNode.className += ` cm-s-${refThema} cm-s-${color} CodeMirror`
} else {
el.parentNode.className += ` cm-s-${codeBlockTheme} CodeMirror` el.parentNode.className += ` cm-s-${codeBlockTheme} CodeMirror`
}
CodeMirror.runMode(content, syntax.mime, el, { CodeMirror.runMode(content, syntax.mime, el, {
tabSize: indentSize tabSize: indentSize
}) })
}) })
}) })
let opts = {} const opts = {}
// if (this.props.theme === 'dark') { // if (this.props.theme === 'dark') {
// opts['font-color'] = '#DDD' // opts['font-color'] = '#DDD'
// opts['line-color'] = '#DDD' // opts['line-color'] = '#DDD'
@@ -332,7 +368,7 @@ export default class MarkdownPreview extends React.Component {
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.flowchart'), (el) => { _.forEach(this.refs.root.contentWindow.document.querySelectorAll('.flowchart'), (el) => {
Raphael.setWindow(this.getWindow()) Raphael.setWindow(this.getWindow())
try { try {
let diagram = flowchart.parse(htmlTextHelper.decodeEntities(el.innerHTML)) const diagram = flowchart.parse(htmlTextHelper.decodeEntities(el.innerHTML))
el.innerHTML = '' el.innerHTML = ''
diagram.drawSVG(el, opts) diagram.drawSVG(el, opts)
_.forEach(el.querySelectorAll('a'), (el) => { _.forEach(el.querySelectorAll('a'), (el) => {
@@ -348,7 +384,7 @@ export default class MarkdownPreview extends React.Component {
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.sequence'), (el) => { _.forEach(this.refs.root.contentWindow.document.querySelectorAll('.sequence'), (el) => {
Raphael.setWindow(this.getWindow()) Raphael.setWindow(this.getWindow())
try { try {
let diagram = SequenceDiagram.parse(htmlTextHelper.decodeEntities(el.innerHTML)) const diagram = SequenceDiagram.parse(htmlTextHelper.decodeEntities(el.innerHTML))
el.innerHTML = '' el.innerHTML = ''
diagram.drawSVG(el, {theme: 'simple'}) diagram.drawSVG(el, {theme: 'simple'})
_.forEach(el.querySelectorAll('a'), (el) => { _.forEach(el.querySelectorAll('a'), (el) => {
@@ -371,11 +407,11 @@ export default class MarkdownPreview extends React.Component {
} }
scrollTo (targetRow) { scrollTo (targetRow) {
let blocks = this.getWindow().document.querySelectorAll('body>[data-line]') const blocks = this.getWindow().document.querySelectorAll('body>[data-line]')
for (let index = 0; index < blocks.length; index++) { for (let index = 0; index < blocks.length; index++) {
let block = blocks[index] let block = blocks[index]
let row = parseInt(block.getAttribute('data-line')) const row = parseInt(block.getAttribute('data-line'))
if (row > targetRow || index === blocks.length - 1) { if (row > targetRow || index === blocks.length - 1) {
block = blocks[index - 1] block = blocks[index - 1]
block != null && this.getWindow().scrollTo(0, block.offsetTop) block != null && this.getWindow().scrollTo(0, block.offsetTop)
@@ -405,7 +441,7 @@ export default class MarkdownPreview extends React.Component {
} }
render () { render () {
let { className, style, tabIndex } = this.props const { className, style, tabIndex } = this.props
return ( return (
<iframe className={className != null <iframe className={className != null
? 'MarkdownPreview ' + className ? 'MarkdownPreview ' + className
@@ -426,5 +462,6 @@ MarkdownPreview.propTypes = {
onMouseDown: PropTypes.func, onMouseDown: PropTypes.func,
className: PropTypes.string, className: PropTypes.string,
value: PropTypes.string, value: PropTypes.string,
showCopyNotification: PropTypes.bool,
storagePath: PropTypes.string storagePath: PropTypes.string
} }

View File

@@ -0,0 +1,87 @@
import React from 'react'
import CodeEditor from 'browser/components/CodeEditor'
import MarkdownPreview from 'browser/components/MarkdownPreview'
import { findStorage } from 'browser/lib/findStorage'
import styles from './MarkdownSplitEditor.styl'
import CSSModules from 'browser/lib/CSSModules'
class MarkdownSplitEditor extends React.Component {
constructor (props) {
super(props)
this.value = props.value
this.focus = () => this.refs.code.focus()
this.reload = () => this.refs.code.reload()
}
handleOnChange () {
this.value = this.refs.code.value
this.props.onChange()
}
handleCheckboxClick (e) {
e.preventDefault()
e.stopPropagation()
const idMatch = /checkbox-([0-9]+)/
const checkedMatch = /\[x\]/i
const uncheckedMatch = /\[ \]/
if (idMatch.test(e.target.getAttribute('id'))) {
const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
const lines = this.refs.code.value
.split('\n')
const targetLine = lines[lineIndex]
if (targetLine.match(checkedMatch)) {
lines[lineIndex] = targetLine.replace(checkedMatch, '[ ]')
}
if (targetLine.match(uncheckedMatch)) {
lines[lineIndex] = targetLine.replace(uncheckedMatch, '[x]')
}
this.refs.code.setValue(lines.join('\n'))
}
}
render () {
const { config, value, storageKey } = this.props
const storage = findStorage(storageKey)
const previewStyle = {}
if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none'
return (
<div styleName='root'>
<CodeEditor
styleName='codeEditor'
ref='code'
mode='GitHub Flavored Markdown'
value={value}
theme={config.editor.theme}
keyMap={config.editor.keyMap}
fontFamily={config.editor.fontFamily}
indentType={config.editor.indentType}
scrollPastEnd={config.editor.scrollPastEnd}
storageKey={storageKey}
onChange={this.handleOnChange.bind(this)}
/>
<MarkdownPreview
style={previewStyle}
styleName='preview'
theme={config.ui.theme}
keyMap={config.editor.keyMap}
fontSize={config.preview.fontSize}
fontFamily={config.preview.fontFamily}
codeBlockTheme={config.preview.codeBlockTheme}
codeBlockFontFamily={config.editor.fontFamily}
lineNumber={config.preview.lineNumber}
ref='preview'
tabInde='0'
value={value}
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
showCopyNotification={config.ui.showCopyNotification}
storagePath={storage.path}
/>
</div>
)
}
}
export default CSSModules(MarkdownSplitEditor, styles)

View File

@@ -0,0 +1,9 @@
.root
width 100%
height 100%
font-size 30px
display flex
.codeEditor
width 50%
.preview
width 50%

View File

@@ -1,4 +1,5 @@
import React, {PropTypes} from 'react' import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './ModalEscButton.styl' import styles from './ModalEscButton.styl'
@@ -6,7 +7,7 @@ const ModalEscButton = ({
handleEscButtonClick handleEscButtonClick
}) => ( }) => (
<button styleName='escButton' onClick={handleEscButtonClick}> <button styleName='escButton' onClick={handleEscButtonClick}>
<div styleName='esc-mark'>x</div> <div styleName='esc-mark'>×</div>
<div styleName='esc-text'>esc</div> <div styleName='esc-text'>esc</div>
</button> </button>
) )

View File

@@ -11,4 +11,6 @@
height top-bar-height height top-bar-height
.esc-mark .esc-mark
font-size 15px font-size 28px
margin-top -5px
margin-bottom -7px

View File

@@ -0,0 +1,30 @@
/**
* @fileoverview Micro component for toggle SideNav
*/
import PropTypes from 'prop-types'
import React from 'react'
import styles from './NavToggleButton.styl'
import CSSModules from 'browser/lib/CSSModules'
/**
* @param {boolean} isFolded
* @param {Function} handleToggleButtonClick
*/
const NavToggleButton = ({isFolded, handleToggleButtonClick}) => (
<button styleName='navToggle'
onClick={(e) => handleToggleButtonClick(e)}
>
{isFolded
? <i className='fa fa-angle-double-right' />
: <i className='fa fa-angle-double-left' />
}
</button>
)
NavToggleButton.propTypes = {
isFolded: PropTypes.bool.isRequired,
handleToggleButtonClick: PropTypes.func.isRequired
}
export default CSSModules(NavToggleButton, styles)

View File

@@ -0,0 +1,26 @@
.navToggle
navButtonColor()
display block
position absolute
left 5px
bottom 5px
border-radius 16.5px
height 34px
width 34px
line-height 32px
padding 0
&:hover
border: 1px solid #1EC38B;
background-color: alpha(#1EC38B, 30%)
border-radius: 50%;
body[data-theme="white"]
navWhiteButtonColor()
body[data-theme="dark"]
.navToggle
&:hover
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
transition 0.15s
color $ui-dark-text-color

View File

@@ -1,7 +1,8 @@
/** /**
* @fileoverview Note item component. * @fileoverview Note item component.
*/ */
import React, { PropTypes } from 'react' import PropTypes from 'prop-types'
import React from 'react'
import { isArray } from 'lodash' import { isArray } from 'lodash'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import { getTodoStatus } from 'browser/lib/getTodoStatus' import { getTodoStatus } from 'browser/lib/getTodoStatus'
@@ -41,16 +42,18 @@ const TagElementList = (tags) => {
* @param {boolean} isActive * @param {boolean} isActive
* @param {Object} note * @param {Object} note
* @param {Function} handleNoteClick * @param {Function} handleNoteClick
* @param {Function} handleNoteContextMenu
* @param {Function} handleDragStart * @param {Function} handleDragStart
* @param {string} dateDisplay * @param {string} dateDisplay
*/ */
const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleDragStart }) => ( const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleNoteContextMenu, handleDragStart, pathname }) => (
<div styleName={isActive <div styleName={isActive
? 'item--active' ? 'item--active'
: 'item' : 'item'
} }
key={`${note.storage}-${note.key}`} key={`${note.storage}-${note.key}`}
onClick={e => handleNoteClick(e, `${note.storage}-${note.key}`)} onClick={e => handleNoteClick(e, `${note.storage}-${note.key}`)}
onContextMenu={e => handleNoteContextMenu(e, `${note.storage}-${note.key}`)}
onDragStart={e => handleDragStart(e, note)} onDragStart={e => handleDragStart(e, note)}
draggable='true' draggable='true'
> >
@@ -68,7 +71,10 @@ const NoteItem = ({ isActive, note, dateDisplay, handleNoteClick, handleDragStar
<div styleName='item-bottom-time'>{dateDisplay}</div> <div styleName='item-bottom-time'>{dateDisplay}</div>
{note.isStarred {note.isStarred
? <i styleName='item-star' className='fa fa-star' /> : '' ? <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' {note.type === 'MARKDOWN_NOTE'
? <TodoProcess todoStatus={getTodoStatus(note.content)} /> ? <TodoProcess todoStatus={getTodoStatus(note.content)} />
@@ -99,6 +105,7 @@ NoteItem.propTypes = {
isTrashed: PropTypes.bool.isRequired isTrashed: PropTypes.bool.isRequired
}), }),
handleNoteClick: PropTypes.func.isRequired, handleNoteClick: PropTypes.func.isRequired,
handleNoteContextMenu: PropTypes.func.isRequired,
handleDragStart: PropTypes.func.isRequired, handleDragStart: PropTypes.func.isRequired,
handleDragEnd: PropTypes.func.isRequired handleDragEnd: PropTypes.func.isRequired
} }

View File

@@ -11,9 +11,9 @@ $control-height = 30px
user-select none user-select none
cursor pointer cursor pointer
background-color $ui-noteList-backgroundColor background-color $ui-noteList-backgroundColor
transition background-color 0.2s transition 0.2s
&:hover &:hover
background-color alpha($ui-button--active-backgroundColor, 40%) background-color alpha($ui-button--active-backgroundColor, 20%)
.item-title .item-title
.item-title-icon .item-title-icon
.item-bottom-time .item-bottom-time
@@ -25,7 +25,7 @@ $control-height = 30px
.item-star .item-star
color $ui-favorite-star-button-color color $ui-favorite-star-button-color
&:active &:active
background-color $ui-button--active-backgroundColor background-color alpha($ui-button--active-backgroundColor, 40%)
color $ui-text-color color $ui-text-color
.item-title .item-title
.item-title-icon .item-title-icon
@@ -43,7 +43,7 @@ $control-height = 30px
.item--active .item--active
@extend .item @extend .item
background-color $ui-button--active-backgroundColor background-color alpha($ui-button--active-backgroundColor, 60%)
color $ui-text-color color $ui-text-color
.item-title .item-title
.item-title-empty .item-title-empty
@@ -59,20 +59,30 @@ $control-height = 30px
.item-star .item-star
color $ui-favorite-star-button-color color $ui-favorite-star-button-color
&:hover &:hover
background-color $ui-button--active-backgroundColor background-color alpha($ui-button--active-backgroundColor, 40%)
color #e74c3c
.menu-button-label
color $ui-text-color
&:active, &:active:hover
background-color alpha($ui-button--active-backgroundColor, 40%)
color #e74c3c
.menu-button-label
color $ui-text-color
.item-title-icon .item-title-icon
position relative position relative
font-size 12px font-size 12px
color $ui-inactive-text-color color $ui-inactive-text-color
top 2px
.item-title .item-title
font-size 14px font-size 15px
font-weight 700
position relative position relative
top -12px top -12px
left 20px left 20px
padding-right 15px padding 0px 15px 0px 0px
padding-bottom 4px margin-bottom 4px
overflow ellipsis overflow ellipsis
color $ui-inactive-text-color color $ui-inactive-text-color
@@ -83,7 +93,7 @@ $control-height = 30px
.item-bottom .item-bottom
position relative position relative
bottom 0px bottom 0px
margin-top 2px margin-top 10px
font-size 12px font-size 12px
line-height 20px line-height 20px
overflow ellipsis overflow ellipsis
@@ -92,40 +102,63 @@ $control-height = 30px
.item-bottom-tagList .item-bottom-tagList
flex 1 flex 1
overflow ellipsis overflow ellipsis
line-height 20px line-height 25px
padding-top 7px
padding-left 2px padding-left 2px
margin-right 27px margin-right 40px
.item-bottom-tagList-item .item-bottom-tagList-item
font-size 11px font-size 11px
margin-right 8px margin-right 8px
padding 0 padding 0
height 20px
box-sizing border-box box-sizing border-box
border-radius 2px border-radius 2px
padding 1px 2px padding 4px
vertical-align middle vertical-align middle
background-color white background-color white
color $ui-inactive-text-color color $ui-inactive-text-color
.item-bottom-time .item-bottom-time
color $ui-inactive-text-color color $ui-inactive-text-color
font-size 11px font-size 13px
padding-left 2px padding-left 2px
padding-bottom 2px padding-bottom 2px
.item-star .item-star
position absolute position absolute
right -20px right -6px
bottom 2px bottom 23px
width 34px width 16px
height 34px 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
border-radius 17px border-radius 17px
.item-pin
position absolute
right 0px
bottom 2px
width 34px
height 34px
color #E54D42
font-size 14px
padding 0
border-radius 17px
body[data-theme="white"]
.item
background-color $ui-white-noteList-backgroundColor
&:hover
background-color alpha($ui-button--active-backgroundColor, 60%)
&:active
background-color $ui-button--active-backgroundColor
.item--active
@extend .item
background-color $ui-button--active-backgroundColor
&:hover
background-color alpha($ui-button--active-backgroundColor, 60%)
body[data-theme="dark"] body[data-theme="dark"]
.root .root
border-color $ui-dark-borderColor border-color $ui-dark-borderColor
@@ -137,6 +170,7 @@ body[data-theme="dark"]
&:hover &:hover
transition 0.15s transition 0.15s
background-color alpha($ui-dark-button--active-backgroundColor, 20%) background-color alpha($ui-dark-button--active-backgroundColor, 20%)
color $ui-dark-text-color
.item-title .item-title
.item-title-icon .item-title-icon
.item-bottom-time .item-bottom-time
@@ -144,11 +178,12 @@ body[data-theme="dark"]
color $ui-dark-text-color color $ui-dark-text-color
.item-bottom-tagList-item .item-bottom-tagList-item
transition 0.15s transition 0.15s
background-color alpha($ui-dark-button--active-backgroundColor, 40%) background-color alpha(#fff, 20%)
color $ui-dark-text-color color $ui-dark-text-color
&:active &:active
transition 0.15s transition 0.15s
background-color $ui-dark-button--active-backgroundColor background-color $ui-dark-button--active-backgroundColor
color $ui-dark-text-color
.item-title .item-title
.item-title-icon .item-title-icon
.item-bottom-time .item-bottom-time
@@ -174,6 +209,85 @@ body[data-theme="dark"]
.item-bottom-tagList-item .item-bottom-tagList-item
background-color alpha(white, 10%) background-color alpha(white, 10%)
color $ui-dark-text-color color $ui-dark-text-color
&:hover
background-color alpha($ui-dark-button--active-backgroundColor, 60%)
color #c0392b
.item-bottom-tagList-item
background-color alpha(#fff, 20%)
.item-title
color $ui-inactive-text-color
.item-title-icon
color $ui-inactive-text-color
.item-title-empty
color $ui-inactive-text-color
.item-bottom-tagList-item
background-color alpha($ui-dark-button--active-backgroundColor, 40%)
color $ui-inactive-text-color
.item-bottom-tagList-empty
color $ui-inactive-text-color
vertical-align middle
body[data-theme="solarized-dark"]
.root
border-color $ui-solarized-dark-borderColor
background-color $ui-solarized-dark-noteList-backgroundColor
.item
border-color $ui-solarized-dark-borderColor
background-color $ui-solarized-dark-noteList-backgroundColor
&:hover
transition 0.15s
// background-color alpha($ui-solarized-dark-noteList-backgroundColor, 20%)
color $ui-solarized-dark-text-color
.item-title
.item-title-icon
.item-bottom-time
transition 0.15s
color $ui-solarized-dark-text-color
.item-bottom-tagList-item
transition 0.15s
background-color alpha($ui-solarized-dark-noteList-backgroundColor, 20%)
color $ui-solarized-dark-text-color
&:active
transition 0.15s
background-color $ui-solarized-dark-noteList-backgroundColor
color $ui-solarized-dark-text-color
.item-title
.item-title-icon
.item-bottom-time
transition 0.15s
color $ui-solarized-dark-text-color
.item-bottom-tagList-item
transition 0.15s
background-color alpha($ui-solarized-dark-noteList-backgroundColor, 10%)
color $ui-solarized-dark-text-color
.item-wrapper
border-color alpha($ui-solarized-dark-button--active-backgroundColor, 60%)
.item--active
border-color $ui-solarized-dark-borderColor
background-color $ui-solarized-dark-button-backgroundColor
.item-wrapper
border-color transparent
.item-title
.item-title-icon
.item-bottom-time
color $ui-solarized-dark-text-color
.item-bottom-tagList-item
background-color alpha(white, 10%)
color $ui-solarized-dark-text-color
&:hover
// background-color alpha($ui-solarized-dark-button--active-backgroundColor, 60%)
color #c0392b
.item-bottom-tagList-item
background-color alpha(#fff, 20%)
.item-title .item-title
color $ui-inactive-text-color color $ui-inactive-text-color

View File

@@ -1,7 +1,8 @@
/** /**
* @fileoverview Note item component with simple display mode. * @fileoverview Note item component with simple display mode.
*/ */
import React, { PropTypes } from 'react' import PropTypes from 'prop-types'
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'
@@ -10,15 +11,17 @@ import styles from './NoteItemSimple.styl'
* @param {boolean} isActive * @param {boolean} isActive
* @param {Object} note * @param {Object} note
* @param {Function} handleNoteClick * @param {Function} handleNoteClick
* @param {Function} handleNoteContextMenu
* @param {Function} handleDragStart * @param {Function} handleDragStart
*/ */
const NoteItemSimple = ({ isActive, note, handleNoteClick, handleDragStart }) => ( const NoteItemSimple = ({ isActive, note, handleNoteClick, handleNoteContextMenu, handleDragStart, pathname }) => (
<div styleName={isActive <div styleName={isActive
? 'item-simple--active' ? 'item-simple--active'
: 'item-simple' : 'item-simple'
} }
key={`${note.storage}-${note.key}`} key={`${note.storage}-${note.key}`}
onClick={e => handleNoteClick(e, `${note.storage}-${note.key}`)} onClick={e => handleNoteClick(e, `${note.storage}-${note.key}`)}
onContextMenu={e => handleNoteContextMenu(e, `${note.storage}-${note.key}`)}
onDragStart={e => handleDragStart(e, note)} onDragStart={e => handleDragStart(e, note)}
draggable='true' draggable='true'
> >
@@ -27,6 +30,10 @@ const NoteItemSimple = ({ isActive, note, handleNoteClick, handleDragStart }) =>
? <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(/\/home|\/starred|\/trash/)
? <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'>Empty</span> : <span styleName='item-simple-title-empty'>Empty</span>
@@ -44,6 +51,7 @@ NoteItemSimple.propTypes = {
title: PropTypes.string.isrequired title: PropTypes.string.isrequired
}), }),
handleNoteClick: PropTypes.func.isRequired, handleNoteClick: PropTypes.func.isRequired,
handleNoteContextMenu: PropTypes.func.isRequired,
handleDragStart: PropTypes.func.isRequired handleDragStart: PropTypes.func.isRequired
} }

View File

@@ -11,15 +11,15 @@ $control-height = 30px
user-select none user-select none
cursor pointer cursor pointer
background-color $ui-noteList-backgroundColor background-color $ui-noteList-backgroundColor
transition background-color 0.15s transition 0.2s
&:hover &:hover
background-color alpha($ui-button--active-backgroundColor, 40%) background-color alpha($ui-button--active-backgroundColor, 20%)
.item-simple-title .item-simple-title
.item-simple-title-empty .item-simple-title-empty
.item-simple-title-icon .item-simple-title-icon
color $ui-text-color color $ui-text-color
&:active &:active
background-color $ui-button--active-backgroundColor background-color alpha($ui-button--active-backgroundColor, 40%)
color $ui-text-color color $ui-text-color
.item-simple-title .item-simple-title
.item-simple-title-empty .item-simple-title-empty
@@ -28,7 +28,7 @@ $control-height = 30px
.item-simple--active .item-simple--active
@extend .item-simple @extend .item-simple
background-color $ui-button--active-backgroundColor background-color alpha($ui-button--active-backgroundColor, 60%)
color $ui-text-color color $ui-text-color
.item-simple-title .item-simple-title
.item-simple-title-empty .item-simple-title-empty
@@ -37,11 +37,20 @@ $control-height = 30px
.item-simple-title-icon .item-simple-title-icon
color $ui-text-color color $ui-text-color
&:hover &:hover
background-color $ui-button--active-backgroundColor background-color alpha($ui-button--active-backgroundColor, 40%)
color #e74c3c
.menu-button-label
color $ui-text-color
&:active, &:active:hover
background-color alpha($ui-button--active-backgroundColor, 40%)
color #e74c3c
.menu-button-label
color $ui-text-color
.item-simple-title .item-simple-title
font-size 13px font-size 13px
height 40px height 40px
padding-right 20px
box-sizing border-box box-sizing border-box
line-height 24px line-height 24px
padding-top 8px padding-top 8px
@@ -59,6 +68,29 @@ $control-height = 30px
font-weight normal font-weight normal
color $ui-inactive-text-color color $ui-inactive-text-color
.item-pin
position absolute
right 0px
top 12px
color #E54D42
font-size 14px
padding 0
border-radius 17px
body[data-theme="white"]
.item-simple
background-color $ui-white-noteList-backgroundColor
&:hover
background-color alpha($ui-button--active-backgroundColor, 60%)
&:active
background-color $ui-button--active-backgroundColor
.item-simple--active
@extend .item-simple
background-color $ui-button--active-backgroundColor
&:hover
background-color alpha($ui-button--active-backgroundColor, 60%)
body[data-theme="dark"] body[data-theme="dark"]
.root .root
border-color $ui-dark-borderColor border-color $ui-dark-borderColor
@@ -67,33 +99,50 @@ body[data-theme="dark"]
.item-simple .item-simple
border-color $ui-dark-borderColor border-color $ui-dark-borderColor
background-color $ui-dark-noteList-backgroundColor background-color $ui-dark-noteList-backgroundColor
&:active &:hover
background-color $ui-dark-button--active-backgroundColor transition 0.15s
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
color $ui-dark-text-color
.item-simple-title .item-simple-title
.item-simple-title-icon .item-simple-title-icon
.item-simple-bottom-time .item-simple-bottom-time
.item-simple-bottom-tagList-item
transition 0.15s transition 0.15s
color $ui-dark-text-color color $ui-dark-text-color
&:hover .item-simple-bottom-tagList-item
background-color alpha($ui-dark-button--active-backgroundColor, 20%) transition 0.15s
background-color alpha(#fff, 20%)
color $ui-dark-text-color
&:active
transition 0.15s
background-color $ui-dark-button--active-backgroundColor
color $ui-dark-text-color
.item-simple-title .item-simple-title
.item-simple-title-icon .item-simple-title-icon
.item-simple-bottom-time .item-simple-bottom-time
transition 0.15s
color $ui-dark-text-color
.item-simple-bottom-tagList-item .item-simple-bottom-tagList-item
transition 0.15s transition 0.15s
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
background-color $ui-dark-button--active-backgroundColor background-color $ui-dark-button--active-backgroundColor
.item-simple-wrapper
border-color transparent
.item-simple-title .item-simple-title
.item-simple-title-icon .item-simple-title-icon
.item-simple-bottom-time .item-simple-bottom-time
color $ui-dark-text-color color $ui-dark-text-color
.item-simple-bottom-tagList-item .item-simple-bottom-tagList-item
background-color transparent background-color alpha(white, 10%)
color $ui-dark-text-color color $ui-dark-text-color
&:hover
background-color alpha($ui-dark-button--active-backgroundColor, 60%)
color #c0392b
.item-simple-bottom-tagList-item
background-color alpha(#fff, 20%)
.item-simple-title .item-simple-title
color $ui-inactive-text-color color $ui-inactive-text-color
@@ -104,3 +153,57 @@ body[data-theme="dark"]
.item-simple-title-empty .item-simple-title-empty
color $ui-dark-inactive-text-color color $ui-dark-inactive-text-color
body[data-theme="solarized-dark"]
.root
border-color $ui-solarized-dark-borderColor
background-color $ui-solarized-dark-noteList-backgroundColor
.item-simple
border-color $ui-solarized-dark-borderColor
background-color $ui-solarized-dark-noteList-backgroundColor
&:hover
transition 0.15s
// background-color alpha($ui-dark-button--active-backgroundColor, 20%)
color $ui-solarized-dark-text-color
.item-simple-title
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
color $ui-solarized-dark-text-color
.item-simple-bottom-tagList-item
transition 0.15s
background-color alpha(#fff, 20%)
color $ui-solarized-dark-text-color
&:active
transition 0.15s
background-color $ui-solarized-dark-button--active-backgroundColor
color $ui-solarized-dark-text-color
.item-simple-title
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
color $ui-solarized-dark-text-color
.item-simple-bottom-tagList-item
transition 0.15s
background-color alpha(white, 10%)
color $ui-solarized-dark-text-color
.item-simple--active
border-color $ui-solarized-dark-borderColor
background-color $ui-solarized-dark-button--active-backgroundColor
.item-simple-wrapper
border-color transparent
.item-simple-title
.item-simple-title-icon
.item-simple-bottom-time
color $ui-solarized-dark-text-color
.item-simple-bottom-tagList-item
background-color alpha(white, 10%)
color $ui-solarized-dark-text-color
&:hover
// background-color alpha($ui-dark-button--active-backgroundColor, 60%)
color #c0392b
.item-simple-bottom-tagList-item
background-color alpha(#fff, 20%)

View File

@@ -0,0 +1,55 @@
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './RealtimeNotification.styl'
const electron = require('electron')
const { shell } = electron
class RealtimeNotification extends React.Component {
constructor (props) {
super(props)
this.state = {
notifications: []
}
}
componentDidMount () {
this.fetchNotifications()
}
fetchNotifications () {
const notificationsUrl = 'https://raw.githubusercontent.com/BoostIO/notification/master/notification.json'
fetch(notificationsUrl)
.then(response => {
return response.json()
})
.then(json => {
this.setState({notifications: json.notifications})
})
}
handleLinkClick (e) {
shell.openExternal(e.currentTarget.href)
e.preventDefault()
}
render () {
const { notifications } = this.state
const link = notifications.length > 0
? <a styleName='notification-link' href={notifications[0].linkUrl}
onClick={(e) => this.handleLinkClick(e)}
>
Info: {notifications[0].text}
</a>
: ''
return (
<div styleName='notification-area' style={this.props.style}>{link}</div>
)
}
}
RealtimeNotification.propTypes = {}
export default CSSModules(RealtimeNotification, styles)

View File

@@ -0,0 +1,43 @@
.notification-area
z-index 1000
font-size 12px
position: relative
top: 12px
background-color none
.notification-link
position absolute
text-decoration none
color #282A36
font-size 14px
border 1px solid #6FA8E6
background-color alpha(#6FA8E6, 0.2)
padding 5px 12px
border-radius 2px
transition 0.2s
&:hover
color #1378BD
body[data-theme="dark"]
.notification-area
background-color none
.notification-link
color #fff
border 1px solid alpha(#5CB85C, 0.6)
background-color alpha(#5CB85C, 0.2)
transition 0.2s
&:hover
color #5CB85C
body[data-theme="solarized-dark"]
.notification-area
background-color none
.notification-link
color $ui-solarized-dark-text-color
border none
background-color $ui-solarized-dark-button-backgroundColor
&:hover
color #5CB85C

View File

@@ -1,7 +1,8 @@
/** /**
* @fileoverview Filter for all notes. * @fileoverview Filter for all notes.
*/ */
import React, { PropTypes } from 'react' import PropTypes from 'prop-types'
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'
@@ -15,27 +16,53 @@ import styles from './SideNavFilter.styl'
*/ */
const SideNavFilter = ({ const SideNavFilter = ({
isFolded, isHomeActive, handleAllNotesButtonClick, isFolded, isHomeActive, handleAllNotesButtonClick,
isStarredActive, handleStarredButtonClick, isTrashedActive, handleTrashedButtonClick isStarredActive, handleStarredButtonClick, isTrashedActive, handleTrashedButtonClick, counterDelNote,
counterTotalNote, counterStarredNote
}) => ( }) => (
<div styleName={isFolded ? 'menu--folded' : 'menu'}> <div styleName={isFolded ? 'menu--folded' : 'menu'}>
<button styleName={isHomeActive ? 'menu-button--active' : 'menu-button'} <button styleName={isHomeActive ? 'menu-button--active' : 'menu-button'}
onClick={handleAllNotesButtonClick} onClick={handleAllNotesButtonClick}
> >
<i className='fa fa-archive fa-fw' /> <div styleName='iconWrap'>
<img src={isHomeActive
? '../resources/icon/icon-all-active.svg'
: '../resources/icon/icon-all.svg'
}
/>
</div>
<span styleName='menu-button-label'>All Notes</span> <span styleName='menu-button-label'>All Notes</span>
<span styleName='counters'>{counterTotalNote}</span>
</button> </button>
<button styleName={isStarredActive ? 'menu-button-star--active' : 'menu-button'} <button styleName={isStarredActive ? 'menu-button-star--active' : 'menu-button'}
onClick={handleStarredButtonClick} onClick={handleStarredButtonClick}
> >
<i className='fa fa-star fa-fw' /> <div styleName='iconWrap'>
<img src={isStarredActive
? '../resources/icon/icon-star-active.svg'
: '../resources/icon/icon-star-sidenav.svg'
}
/>
</div>
<span styleName='menu-button-label'>Starred</span> <span styleName='menu-button-label'>Starred</span>
<span styleName='counters'>{counterStarredNote}</span>
</button> </button>
<button styleName={isTrashedActive ? 'menu-button--active' : 'menu-button'}
<button styleName={isTrashedActive ? 'menu-button-trash--active' : 'menu-button'}
onClick={handleTrashedButtonClick} onClick={handleTrashedButtonClick}
> >
<i className='fa fa-trash fa-fw' /> <div styleName='iconWrap'>
<img src={isTrashedActive
? '../resources/icon/icon-trash-active.svg'
: '../resources/icon/icon-trash-sidenav.svg'
}
/>
</div>
<span styleName='menu-button-label'>Trash</span> <span styleName='menu-button-label'>Trash</span>
<span styleName='counters'>{counterDelNote}</span>
</button> </button>
</div> </div>
) )

View File

@@ -3,57 +3,68 @@
.menu-button .menu-button
navButtonColor() navButtonColor()
height 32px height 36px
padding 0 15px padding 0 15px 0 20px
font-size 12px font-size 14px
width 100% width 100%
text-align left text-align left
overflow ellipsis overflow ellipsis
display flex
align-items center
&:hover, &:active, &:active:hover
color #1EC38B
background-color alpha($ui-button-default--active-backgroundColor, 20%)
.iconWrap
width 20px
text-align center
.counters
float right
color $ui-inactive-text-color
.menu-button--active .menu-button--active
@extend .menu-button @extend .menu-button
color #e74c3c SideNavFilter()
background-color $ui-button--active-backgroundColor color #1EC38B
.menu-button-label background-color alpha($ui-button-default--active-backgroundColor, 20%)
color $ui-text-color .menu-button-label, .counters
color #1EC38B
&:hover &:hover
background-color $ui-button--active-backgroundColor color #1EC38B
color #e74c3c
.menu-button-label
color $ui-text-color
&:active, &:active:hover
background-color $ui-button--active-backgroundColor
color #e74c3c
.menu-button-label
color $ui-text-color
.menu-button-star--active .menu-button-star--active
@extend .menu-button @extend .menu-button
color #F9BF3B SideNavFilter()
background-color $ui-button--active-backgroundColor color #1EC38B
.menu-button-label background-color alpha($ui-button-default--active-backgroundColor, 20%)
color $ui-text-color .menu-button-label, .counters
&:hover color #1EC38B
background-color $ui-button--active-backgroundColor
color #F9BF3B .menu-button-trash--active
.menu-button-label @extend .menu-button
color $ui-text-color SideNavFilter()
&:active, &:active:hover color #1EC38B
background-color $ui-button--active-backgroundColor background-color alpha($ui-button-default--active-backgroundColor, 20%)
color #F9BF3B .menu-button-label, .counters
.menu-button-label color #1EC38B
color $ui-text-color
.menu-button-label .menu-button-label
margin-left 5px margin-left 10px
flex 1
.menu--folded .menu--folded
@extend .menu @extend .menu
.menu-button, .menu-button--active .menu-button, .menu-button--active, .menu-button-star--active, .menu-button-trash--active
text-align center text-align center
padding 0 12px
&:hover .menu-button-label &:hover .menu-button-label
transition opacity 0.15s transition opacity 0.15s
opacity 1 opacity 1
color $ui-tooltip-text-color
background-color $ui-tooltip-backgroundColor
.menu-button-label .menu-button-label
position fixed position fixed
display inline-block display inline-block
@@ -63,15 +74,73 @@
margin-top -8px margin-top -8px
margin-left 0 margin-left 0
overflow ellipsis overflow ellipsis
background-color $ui-tooltip-backgroundColor
z-index 10 z-index 10
color white
line-height 32px line-height 32px
border-top-right-radius 2px border-top-right-radius 2px
border-bottom-right-radius 2px border-bottom-right-radius 2px
pointer-events none pointer-events none
opacity 0 opacity 0
font-size 12px font-size 13px
.counters
display none
body[data-theme="white"]
.menu-button
navWhiteButtonColor()
.counters
color $ui-inactive-text-color
.menu-button--active
@extend .menu-button
color #e74c3c
background-color $ui-button--active-backgroundColor
.menu-button-label
color $ui-text-color
&:hover
background-color alpha($ui-button--active-backgroundColor, 50%)
color #e74c3c
.menu-button-label
color $ui-text-color
&:active, &:active:hover
background-color alpha($ui-button--active-backgroundColor, 50%)
color #e74c3c
.menu-button-label
color $ui-text-color
.menu-button-star--active
@extend .menu-button
color #F9BF3B
background-color $ui-button--active-backgroundColor
.menu-button-label
color $ui-text-color
&:hover
background-color alpha($ui-button--active-backgroundColor, 50%)
color #F9BF3B
.menu-button-label
color $ui-text-color
&:active, &:active:hover
background-color alpha($ui-button--active-backgroundColor, 50%)
color #F9BF3B
.menu-button-label
color $ui-text-color
.menu-button-trash--active
@extend .menu-button
color #5D9E36
background-color $ui-button--active-backgroundColor
.menu-button-label
color $ui-text-color
&:hover
background-color alpha($ui-button--active-backgroundColor, 50%)
color #5D9E36
.menu-button-label
color $ui-text-color
&:active, &:active:hover
background-color alpha($ui-button--active-backgroundColor, 50%)
color #5D9E36
.menu-button-label
color $ui-text-color
body[data-theme="dark"] body[data-theme="dark"]
.menu-button .menu-button
@@ -88,7 +157,7 @@ body[data-theme="dark"]
.menu-button-label .menu-button-label
color $ui-dark-text-color color $ui-dark-text-color
&:hover &:hover
background-color $ui-dark-button--active-backgroundColor background-color alpha($ui-dark-button--active-backgroundColor, 50%)
color #c0392b color #c0392b
.menu-button-label .menu-button-label
color $ui-dark-text-color color $ui-dark-text-color
@@ -99,7 +168,61 @@ body[data-theme="dark"]
.menu-button-label .menu-button-label
color $ui-dark-text-color color $ui-dark-text-color
&:hover &:hover
background-color $ui-dark-button--active-backgroundColor background-color alpha($ui-dark-button--active-backgroundColor, 50%)
color $ui-favorite-star-button-color color $ui-favorite-star-button-color
.menu-button-label .menu-button-label
color $ui-dark-text-color color $ui-dark-text-color
.menu-button-trash--active
color #5D9E36
background-color $ui-dark-button--active-backgroundColor
.menu-button-label
color $ui-dark-text-color
&:hover
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
color #5D9E36
.menu-button-label
color $ui-dark-text-color
body[data-theme="solarized-dark"]
.menu-button
&:active
background-color $ui-solarized-dark-noteList-backgroundColor
color $ui-solarized-dark-text-color
&:hover
background-color $ui-solarized-dark-button-backgroundColor
color $ui-solarized-dark-text-color
.menu-button--active
color $ui-solarized-dark-text-color
background-color $ui-solarized-dark-button-backgroundColor
.menu-button-label
color $ui-solarized-dark-text-color
&:hover
background-color $ui-solarized-dark-button-backgroundColor
color $ui-solarized-dark-text-color
.menu-button-label
color $ui-solarized-dark-text-color
.menu-button-star--active
color $ui-solarized-dark-text-color
background-color $ui-solarized-dark-button-backgroundColor
.menu-button-label
color $ui-solarized-dark-text-color
&:hover
background-color $ui-solarized-dark-button-backgroundColor
color $ui-solarized-dark-text-color
.menu-button-label
color $ui-solarized-dark-text-color
.menu-button-trash--active
color $ui-solarized-dark-text-color
background-color $ui-solarized-dark-button-backgroundColor
.menu-button-label
color $ui-solarized-dark-text-color
&:hover
background-color $ui-solarized-dark-button-backgroundColor
color $ui-solarized-dark-text-color
.menu-button-label
color $ui-solarized-dark-text-color

View File

@@ -85,8 +85,17 @@ class SnippetTab extends React.Component {
}) })
} }
handleDragStart (e) {
e.dataTransfer.dropEffect = 'move'
this.props.onDragStart(e)
}
handleDrop (e) {
this.props.onDrop(e)
}
render () { render () {
let { isActive, snippet, isDeletable } = this.props const { isActive, snippet, isDeletable } = this.props
return ( return (
<div styleName={isActive <div styleName={isActive
? 'root--active' ? 'root--active'
@@ -98,6 +107,9 @@ class SnippetTab extends React.Component {
onClick={(e) => this.handleClick(e)} onClick={(e) => this.handleClick(e)}
onDoubleClick={(e) => this.handleRenameClick(e)} onDoubleClick={(e) => this.handleRenameClick(e)}
onContextMenu={(e) => this.handleContextMenu(e)} onContextMenu={(e) => this.handleContextMenu(e)}
onDragStart={(e) => this.handleDragStart(e)}
onDrop={(e) => this.handleDrop(e)}
draggable='true'
> >
{snippet.name.trim().length > 0 {snippet.name.trim().length > 0
? snippet.name ? snippet.name
@@ -127,6 +139,7 @@ class SnippetTab extends React.Component {
} }
SnippetTab.propTypes = { SnippetTab.propTypes = {
} }
export default CSSModules(SnippetTab, styles) export default CSSModules(SnippetTab, styles)

View File

@@ -38,7 +38,7 @@
text-align center text-align center
border none border none
padding 0 padding 0
color transparent color $ui-inactive-text-color
background-color transparent background-color transparent
border-radius 2px border-radius 2px
@@ -89,3 +89,50 @@ body[data-theme="dark"]
.input .input
background-color $ui-dark-button--hover-backgroundColor background-color $ui-dark-button--hover-backgroundColor
color $ui-dark-text-color color $ui-dark-text-color
.deleteButton
color alpha($ui-dark-text-color, 30%)
body[data-theme="solarized-dark"]
.root
color $ui-solarized-dark-text-color
border-color $ui-dark-borderColor
&:hover
background-color $ui-solarized-dark-noteDetail-backgroundColor
.deleteButton
color $ui-solarized-dark-text-color
&:hover
background-color darken($ui-solarized-dark-noteDetail-backgroundColor, 15%)
&:active
color $ui-solarized-dark-text-color
background-color $ui-dark-button--active-backgroundColor
.root--active
color $ui-solarized-dark-text-color
border-color $ui-solarized-dark-borderColor
&:hover
background-color $ui-solarized-dark-noteDetail-backgroundColor
.deleteButton
color $ui-solarized-dark-text-color
&:hover
background-color darken($ui-solarized-dark-noteDetail-backgroundColor, 15%)
&:active
color $ui-solarized-dark-text-color
background-color $ui-dark-button--active-backgroundColor
.button
border none
color $ui-solarized-dark-text-color
background-color transparent
transition color background-color 0.15s
border-left 4px solid transparent
&:hover
color $ui-solarized-dark-text-color
background-color $ui-solarized-dark-noteDetail-backgroundColor
.input
background-color $ui-solarized-dark-noteDetail-backgroundColor
color $ui-solarized-dark-text-color
.deleteButton
color alpha($ui-solarized-dark-text-color, 30%)

View File

@@ -1,10 +1,11 @@
/** /**
* @fileoverview Micro component for showing storage. * @fileoverview Micro component for showing storage.
*/ */
import React, { PropTypes } from 'react' import PropTypes from 'prop-types'
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 { isNumber } from 'lodash' import _ from 'lodash'
/** /**
* @param {boolean} isActive * @param {boolean} isActive
@@ -35,12 +36,10 @@ const StorageItem = ({
> >
<span styleName={isFolded <span styleName={isFolded
? 'folderList-item-name--folded' : 'folderList-item-name' ? 'folderList-item-name--folded' : 'folderList-item-name'
} }>
style={{borderColor: folderColor}} <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}
>
{isFolded ? folderName.substring(0, 1) : folderName}
</span> </span>
{(!isFolded && isNumber(noteCount)) && {(!isFolded && _.isNumber(noteCount)) &&
<span styleName='folderList-item-noteCount'>{noteCount}</span> <span styleName='folderList-item-noteCount'>{noteCount}</span>
} }
{isFolded && {isFolded &&

View File

@@ -5,37 +5,36 @@
.folderList-item .folderList-item
display flex display flex
width 100% width 100%
height 26px height 34px
background-color transparent background-color transparent
color $ui-inactive-text-color color $ui-inactive-text-color
padding 0 padding 0
margin-bottom 5px
text-align left text-align left
border none border none
overflow ellipsis overflow ellipsis
font-size 12px font-size 14px
&:first-child &:first-child
margin-top 0 margin-top 0
&:hover &:hover
color $ui-text-color color #1EC38B;
background-color alpha($ui-button--active-backgroundColor, 20%) background-color alpha($ui-button-default--active-backgroundColor, 20%)
transition background-color 0.15s transition background-color 0.15s
&:active &:active
color $ui-text-color color $$ui-button-default-color
background-color $ui-button--active-backgroundColor background-color alpha($ui-button-default--active-backgroundColor, 20%)
.folderList-item--active .folderList-item--active
@extend .folderList-item @extend .folderList-item
color $ui-text-color color #1EC38B
background-color $ui-button--active-backgroundColor background-color alpha($ui-button-default--active-backgroundColor, 20%)
&:hover &:hover
color $ui-text-color color #1EC38B;
background-color $ui-button--active-backgroundColor background-color alpha($ui-button-default--active-backgroundColor, 50%)
.folderList-item-name .folderList-item-name
display block display block
flex 1 flex 1
padding 0 25px padding 0 12px
height 26px height 26px
line-height 26px line-height 26px
border-width 0 0 0 2px border-width 0 0 0 2px
@@ -48,7 +47,7 @@
float right float right
line-height 26px line-height 26px
padding-right 15px padding-right 15px
font-size 12px font-size 13px
.folderList-item-tooltip .folderList-item-tooltip
tooltip() tooltip()
@@ -69,8 +68,28 @@
.folderList-item-name--folded .folderList-item-name--folded
@extend .folderList-item-name @extend .folderList-item-name
padding-left 12px padding-left 7px
text
font-size 9px
body[data-theme="white"]
.folderList-item
color $ui-inactive-text-color
&:hover
color $ui-text-color
background-color alpha($ui-button--active-backgroundColor, 20%)
transition background-color 0.15s
&:active
color $ui-text-color
background-color $ui-button--active-backgroundColor
.folderList-item--active
@extend .folderList-item
color $ui-text-color
background-color $ui-button--active-backgroundColor
&:hover
color $ui-text-color
background-color alpha($ui-button--active-backgroundColor, 50%)
body[data-theme="dark"] body[data-theme="dark"]
.folderList-item .folderList-item
@@ -86,7 +105,26 @@ body[data-theme="dark"]
color $ui-dark-text-color color $ui-dark-text-color
background-color $ui-dark-button--active-backgroundColor background-color $ui-dark-button--active-backgroundColor
&:active &:active
background-color $ui-dark-button--active-backgroundColor background-color alpha($ui-dark-button--active-backgroundColor, 50%)
&:hover &:hover
color $ui-dark-text-color color $ui-dark-text-color
background-color $ui-dark-button--active-backgroundColor background-color alpha($ui-dark-button--active-backgroundColor, 50%)
body[data-theme="solarized-dark"]
.folderList-item
&:hover
background-color $ui-solarized-dark-button-backgroundColor
color $ui-solarized-dark-text-color
&:active
color $ui-solarized-dark-text-color
background-color $ui-solarized-dark-button-backgroundColor
.folderList-item--active
@extend .folderList-item
color $ui-solarized-dark-text-color
background-color $ui-solarized-dark-button-backgroundColor
&:active
background-color $ui-solarized-dark-button-backgroundColor
&:hover
color $ui-solarized-dark-text-color
background-color $ui-solarized-dark-button-backgroundColor

View File

@@ -0,0 +1,24 @@
/**
* @fileoverview Micro component for showing StorageList
*/
import PropTypes from 'prop-types'
import React from 'react'
import styles from './StorageList.styl'
import CSSModules from 'browser/lib/CSSModules'
/**
* @param {Array} storgaeList
*/
const StorageList = ({storageList}) => (
<div styleName='storageList'>
{storageList.length > 0 ? storageList : (
<div styleName='storgaeList-empty'>No storage mount.</div>
)}
</div>
)
StorageList.propTypes = {
storgaeList: PropTypes.arrayOf(PropTypes.element).isRequired
}
export default CSSModules(StorageList, styles)

View File

@@ -0,0 +1,20 @@
.storageList
absolute left right
bottom 37px
top 180px
overflow-y auto
.storageList-empty
padding 0 10px
margin-top 15px
line-height 24px
color $ui-inactive-text-color
body[data-theme="dark"]
.storageList-empty
color $ui-dark-inactive-text-color
.root-folded
.storageList-empty
white-space nowrap
transform rotate(90deg)

View File

@@ -0,0 +1,28 @@
/**
* @fileoverview Micro component for showing TagList.
*/
import PropTypes from 'prop-types'
import React from 'react'
import styles from './TagListItem.styl'
import CSSModules from 'browser/lib/CSSModules'
/**
* @param {string} name
* @param {Function} handleClickTagListItem
* @param {bool} isActive
*/
const TagListItem = ({name, handleClickTagListItem, isActive}) => (
<button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}>
<span styleName='tagList-item-name'>
{`# ${name}`}
</span>
</button>
)
TagListItem.propTypes = {
name: PropTypes.string.isRequired,
handleClickTagListItem: PropTypes.func.isRequired
}
export default CSSModules(TagListItem, styles)

View File

@@ -0,0 +1,84 @@
.tagList-item
display flex
width 100%
height 26px
background-color transparent
color $ui-inactive-text-color
padding 0
margin-bottom 5px
text-align left
border none
overflow ellipsis
font-size 13px
&:first-child
margin-top 0
&:hover
color $ui-button-default-color
background-color alpha($ui-button-default--active-backgroundColor, 20%)
transition background-color 0.15s
&:active, &:active:hover
color $ui-button-default-color
background-color $ui-button-default--active-backgroundColor
.tagList-item-active
background-color $ui-button-default--active-backgroundColor
display flex
width 100%
height 26px
padding 0
margin-bottom 5px
text-align left
border none
overflow ellipsis
font-size 13px
color $ui-button-default-color
&:hover
background-color alpha($ui-button-default--active-backgroundColor, 60%)
transition 0.2s
.tagList-item-name
display block
flex 1
padding 0 15px
height 26px
line-height 26px
border-width 0 0 0 2px
border-style solid
border-color transparent
overflow hidden
text-overflow ellipsis
body[data-theme="white"]
.tagList-item
color $ui-inactive-text-color
&:hover
color $ui-text-color
background-color alpha($ui-button--active-backgroundColor, 20%)
&:active
color $ui-text-color
background-color $ui-button--active-backgroundColor
.tagList-item-active
background-color $ui-button--active-backgroundColor
color $ui-text-color
&:hover
background-color alpha($ui-button--active-backgroundColor, 60%)
body[data-theme="dark"]
.tagList-item
color $ui-dark-inactive-text-color
&:hover
color $ui-dark-text-color
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
&:active
color $ui-dark-text-color
background-color $ui-dark-button--active-backgroundColor
.tagList-item-active
background-color $ui-dark-button--active-backgroundColor
color $ui-dark-text-color
&:active
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
&:hover
color $ui-dark-text-color
background-color alpha($ui-dark-button--active-backgroundColor, 50%)

View File

@@ -2,7 +2,8 @@
* @fileoverview Percentage of todo achievement. * @fileoverview Percentage of todo achievement.
*/ */
import React, { PropTypes } from 'react' import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './TodoListPercentage.styl' import styles from './TodoListPercentage.styl'
@@ -15,9 +16,11 @@ const TodoListPercentage = ({
}) => ( }) => (
<div styleName='percentageBar' style={{display: isNaN(percentageOfTodo) ? 'none' : ''}}> <div styleName='percentageBar' style={{display: isNaN(percentageOfTodo) ? 'none' : ''}}>
<div styleName='progressBar' style={{width: `${percentageOfTodo}%`}}> <div styleName='progressBar' style={{width: `${percentageOfTodo}%`}}>
<div styleName='progressBarInner'>
<p styleName='percentageText'>{percentageOfTodo}%</p> <p styleName='percentageText'>{percentageOfTodo}%</p>
</div> </div>
</div> </div>
</div>
) )
TodoListPercentage.propTypes = { TodoListPercentage.propTypes = {

View File

@@ -1,31 +1,51 @@
.percentageBar .percentageBar
position absolute position absolute
top 58px top 50px
right: 0px right 0px
left 0px left 0px
background-color #DADFE1 background-color #DADFE1
width 100% width 100%
height: 15px height: 17px
font-size: 12px font-size: 12px
z-index 100 z-index 100
border-radius 2px border-radius 2px
.progressBar .progressBar
background-color: #6C7A89 background-color: #1EC38B
height 15px height 17px
border-radius 2px border-radius 2px
transition 0.3s transition 0.4s cubic-bezier(0.4, 0.4, 0, 1)
.progressBarInner
padding 0 10px
min-width 1px
height 100%
display -webkit-box
display box
justify-content center
align-items center
.percentageText .percentageText
color #f4f4f4 color #f4f4f4
padding: 2px 43% font-weight 600
body[data-theme="dark"] body[data-theme="dark"]
.percentageBar .percentageBar
background-color #363A3D background-color #444444
.progressBar .progressBar
background-color: alpha(#939395, 50%) background-color: #1EC38B
.percentageText .percentageText
color $ui-dark-text-color color $ui-dark-text-color
body[data-theme="solarized-dark"]
.percentageBar
background-color #002b36
.progressBar
background-color: #2aa198
.percentageText
color #fdf6e3

View File

@@ -2,7 +2,8 @@
* @fileoverview Percentage of todo achievement. * @fileoverview Percentage of todo achievement.
*/ */
import React, { PropTypes } from 'react' import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './TodoProcess.styl' import styles from './TodoProcess.styl'

View File

@@ -77,6 +77,9 @@ body
li li
label.taskListItem label.taskListItem
margin-left -2em margin-left -2em
&.checked
text-decoration line-through
opacity 0.5
div.math-rendered div.math-rendered
text-align center text-align center
.math-failed .math-failed
@@ -117,8 +120,9 @@ hr
margin 15px 0 margin 15px 0
h1, h2, h3, h4, h5, h6 h1, h2, h3, h4, h5, h6
font-weight bold font-weight bold
word-wrap break-word
h1 h1
font-size 2.25em font-size 2.55em
padding-bottom 0.3em padding-bottom 0.3em
line-height 1.2em line-height 1.2em
border-bottom solid 1px borderColor border-bottom solid 1px borderColor
@@ -154,6 +158,7 @@ p
line-height 1.6em line-height 1.6em
margin 0 0 1em margin 0 0 1em
white-space pre-line white-space pre-line
word-wrap break-word
img img
max-width 100% max-width 100%
strong, b strong, b
@@ -193,6 +198,7 @@ ol
&>li>ul, &>li>ol &>li>ul, &>li>ol
margin 0 margin 0
code code
color #CC305F
padding 0.2em 0.4em padding 0.2em 0.4em
background-color #f7f7f7 background-color #f7f7f7
border-radius 3px border-radius 3px
@@ -268,6 +274,16 @@ table
border-color borderColor border-color borderColor
&:last-child &:last-child
border-right solid 1px borderColor border-right solid 1px borderColor
kbd
background-color #fafbfc
border solid 1px borderColor
border-bottom-color btnColor
border-radius 3px
box-shadow inset 0 -1px 0 #959da5
display inline-block
font-size .8em
line-height 1
padding 3px 5px
themeDarkBackground = darken(#21252B, 10%) themeDarkBackground = darken(#21252B, 10%)
themeDarkText = #f9f9f9 themeDarkText = #f9f9f9
@@ -316,3 +332,12 @@ body[data-theme="dark"]
border-color themeDarkTableBorder border-color themeDarkTableBorder
&:last-child &:last-child
border-right solid 1px themeDarkTableBorder border-right solid 1px themeDarkTableBorder
kbd
background-color themeDarkBorder
color themeDarkText
body[data-theme="solarized-dark"]
color $ui-solarized-dark-text-color
border-color themeDarkBorder
background-color $ui-solarized-dark-noteDetail-backgroundColor

View File

@@ -64,7 +64,7 @@ $list-width = 250px
.result-nav-storageList .result-nav-storageList
absolute bottom left right absolute bottom left right
top 110px + 32px + 10px + 10px top 110px + 32px + 10px + 10px + 20px
overflow-y auto overflow-y auto
.result-list .result-list
@@ -116,3 +116,41 @@ body[data-theme="dark"]
absolute top bottom right absolute top bottom right
left $nav-width + $list-width left $nav-width + $list-width
background-color $ui-dark-noteDetail-backgroundColor background-color $ui-dark-noteDetail-backgroundColor
body[data-theme="solarized-dark"]
.root
background-color $ui-solarized-dark-backgroundColor
.search
border-color $ui-solarized-dark-borderColor
.search-input
color $ui-dark-text-color
.result
background-color $ui-solarized-dark-backgroundColor
.result-nav
background-color $ui-solarized-dark-backgroundColor
label
color $ui-dark-text-color
.result-nav-menu
navDarkButtonColor()
.result-nav-menu--active
background-color $ui-solarized-dark-button-backgroundColor
color $ui-dark-button--active-color
&:hover
background-color $ui-dark-button--active-backgroundColor
.result-list
border-color $ui-solarized-dark-borderColor
box-shadow none
top 0
.result-detail
absolute top bottom right
left $nav-width + $list-width
background-color $ui-solarized-dark-backgroundColor

View File

@@ -56,7 +56,7 @@ class NoteDetail extends React.Component {
} }
selectPriorSnippet () { selectPriorSnippet () {
let { note } = this.props const { note } = this.props
if (note.type === 'SNIPPET_NOTE' && note.snippets.length > 1) { if (note.type === 'SNIPPET_NOTE' && note.snippets.length > 1) {
this.setState({ this.setState({
snippetIndex: (this.state.snippetIndex + note.snippets.length - 1) % note.snippets.length snippetIndex: (this.state.snippetIndex + note.snippets.length - 1) % note.snippets.length
@@ -65,7 +65,7 @@ class NoteDetail extends React.Component {
} }
selectNextSnippet () { selectNextSnippet () {
let { note } = this.props const { note } = this.props
if (note.type === 'SNIPPET_NOTE' && note.snippets.length > 1) { if (note.type === 'SNIPPET_NOTE' && note.snippets.length > 1) {
this.setState({ this.setState({
snippetIndex: (this.state.snippetIndex + 1) % note.snippets.length snippetIndex: (this.state.snippetIndex + 1) % note.snippets.length
@@ -74,7 +74,7 @@ class NoteDetail extends React.Component {
} }
saveToClipboard () { saveToClipboard () {
let { note } = this.props const { note } = this.props
if (note.type === 'MARKDOWN_NOTE') { if (note.type === 'MARKDOWN_NOTE') {
clipboard.writeText(note.content) clipboard.writeText(note.content)
@@ -95,7 +95,7 @@ class NoteDetail extends React.Component {
} }
render () { render () {
let { note, config } = this.props const { note, config } = this.props
if (note == null) { if (note == null) {
return ( return (
<div styleName='root' /> <div styleName='root' />
@@ -110,8 +110,8 @@ class NoteDetail extends React.Component {
const storage = findStorage(note.storage) const storage = findStorage(note.storage)
if (note.type === 'SNIPPET_NOTE') { if (note.type === 'SNIPPET_NOTE') {
let tabList = note.snippets.map((snippet, index) => { const tabList = note.snippets.map((snippet, index) => {
let isActive = this.state.snippetIndex === index const isActive = this.state.snippetIndex === index
return <div styleName={isActive return <div styleName={isActive
? 'tabList-item--active' ? 'tabList-item--active'
: 'tabList-item' : 'tabList-item'
@@ -131,8 +131,8 @@ class NoteDetail extends React.Component {
</div> </div>
}) })
let viewList = note.snippets.map((snippet, index) => { const viewList = note.snippets.map((snippet, index) => {
let isActive = this.state.snippetIndex === index const isActive = this.state.snippetIndex === index
let syntax = CodeMirror.findModeByName(pass(snippet.mode)) let syntax = CodeMirror.findModeByName(pass(snippet.mode))
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text') if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
@@ -157,6 +157,7 @@ class NoteDetail extends React.Component {
indentType={config.editor.indentType} indentType={config.editor.indentType}
indentSize={editorIndentSize} indentSize={editorIndentSize}
keyMap={config.editor.keyMap} keyMap={config.editor.keyMap}
scrollPastEnd={config.editor.scrollPastEnd}
readOnly readOnly
ref={'code-' + index} ref={'code-' + index}
/> />
@@ -196,6 +197,7 @@ class NoteDetail extends React.Component {
lineNumber={config.preview.lineNumber} lineNumber={config.preview.lineNumber}
indentSize={editorIndentSize} indentSize={editorIndentSize}
value={note.content} value={note.content}
showCopyNotification={config.ui.showCopyNotification}
storagePath={storage.path} storagePath={storage.path}
/> />
) )

View File

@@ -2,8 +2,8 @@
.root .root
absolute top bottom left right absolute top bottom left right
left $note-detail-left-margin bottom 30px
right $note-detail-right-margin margin 0 25px
height 100% height 100%
width 365px width 365px
background-color $ui-noteDetail-backgroundColor background-color $ui-noteDetail-backgroundColor
@@ -95,3 +95,35 @@ body[data-theme="dark"]
&:hover &:hover
color white color white
background-color $ui-dark-button--hover-backgroundColor background-color $ui-dark-button--hover-backgroundColor
body[data-theme="solarized-dark"]
.root
background-color $ui-solarized-dark-backgroundColor
.description
border-color $ui-dark-borderColor
background-color $ui-solarized-dark-backgroundColor
.description-textarea
background-color $ui-solarized-dark-backgroundColor
color white
.tabList
background-color $ui-solarized-dark-backgroundColor
.tabList-item
border-color $ui-dark-borderColor
&:hover
background-color $ui-dark-button--hover-backgroundColor
.tabList-item-button
border none
color $ui-dark-text-color
background-color transparent
transition color background-color 0.15s
border-left 4px solid transparent
&:hover
color white
background-color $ui-dark-button--hover-backgroundColor

View File

@@ -18,18 +18,18 @@ class NoteList extends React.Component {
} }
componentDidUpdate () { componentDidUpdate () {
let { index } = this.props const { index } = this.props
if (index > -1) { if (index > -1) {
let list = this.refs.root const list = this.refs.root
let item = list.childNodes[index] const item = list.childNodes[index]
if (item == null) return null if (item == null) return null
let overflowBelow = item.offsetTop + item.clientHeight - list.clientHeight - list.scrollTop > 0 const overflowBelow = item.offsetTop + item.clientHeight - list.clientHeight - list.scrollTop > 0
if (overflowBelow) { if (overflowBelow) {
list.scrollTop = item.offsetTop + item.clientHeight - list.clientHeight list.scrollTop = item.offsetTop + item.clientHeight - list.clientHeight
} }
let overflowAbove = list.scrollTop > item.offsetTop const overflowAbove = list.scrollTop > item.offsetTop
if (overflowAbove) { if (overflowAbove) {
list.scrollTop = item.offsetTop list.scrollTop = item.offsetTop
} }
@@ -44,7 +44,7 @@ class NoteList extends React.Component {
} }
handleScroll (e) { handleScroll (e) {
let { notes } = this.props const { notes } = this.props
if (e.target.offsetHeight + e.target.scrollTop > e.target.scrollHeight - 100 && notes.length > this.state.range * 10 + 10) { if (e.target.offsetHeight + e.target.scrollTop > e.target.scrollHeight - 100 && notes.length > this.state.range * 10 + 10) {
this.setState({ this.setState({
@@ -54,9 +54,9 @@ class NoteList extends React.Component {
} }
render () { render () {
let { notes, index } = this.props const { notes, index } = this.props
let notesList = notes const notesList = notes
.slice(0, 10 + 10 * this.state.range) .slice(0, 10 + 10 * this.state.range)
.map((note, _index) => { .map((note, _index) => {
const isActive = (index === _index) const isActive = (index === _index)

View File

@@ -19,18 +19,18 @@ class StorageSection extends React.Component {
} }
handleHeaderClick (e) { handleHeaderClick (e) {
let { storage } = this.props const { storage } = this.props
this.props.handleStorageButtonClick(e, storage.key) this.props.handleStorageButtonClick(e, storage.key)
} }
handleFolderClick (e, folder) { handleFolderClick (e, folder) {
let { storage } = this.props const { storage } = this.props
this.props.handleFolderButtonClick(e, storage.key, folder.key) this.props.handleFolderButtonClick(e, storage.key, folder.key)
} }
render () { render () {
let { storage, filter } = this.props const { storage, filter } = this.props
let folderList = storage.folders const folderList = storage.folders
.map(folder => ( .map(folder => (
<StorageItem <StorageItem
key={folder.key} key={folder.key}

View File

@@ -1,8 +1,8 @@
import React, { PropTypes } from 'react' import PropTypes from 'prop-types'
import React from 'react'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import { connect, Provider } from 'react-redux' import { connect, Provider } from 'react-redux'
import _ from 'lodash' import _ from 'lodash'
import ipc from './ipcClient'
import store from './store' import store from './store'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './FinderMain.styl' import styles from './FinderMain.styl'
@@ -13,13 +13,14 @@ import SideNavFilter from 'browser/components/SideNavFilter'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig' import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
require('!!style!css!stylus?sourceMap!../main/global.styl') require('!!style!css!stylus?sourceMap!../main/global.styl')
require('../lib/customMeta') require('../lib/customMeta')
require('./ipcClient.js')
const electron = require('electron') const electron = require('electron')
const { remote } = electron const { remote } = electron
const { Menu } = remote const { Menu } = remote
function hideFinder () { function hideFinder () {
let finderWindow = remote.getCurrentWindow() const finderWindow = remote.getCurrentWindow()
if (global.process.platform === 'win32') { if (global.process.platform === 'win32') {
finderWindow.blur() finderWindow.blur()
finderWindow.hide() finderWindow.hide()
@@ -136,7 +137,7 @@ class FinderMain extends React.Component {
} }
handleOnlySnippetCheckboxChange (e) { handleOnlySnippetCheckboxChange (e) {
let { filter } = this.state const { filter } = this.state
filter.includeSnippet = e.target.checked filter.includeSnippet = e.target.checked
this.setState({ this.setState({
filter: filter, filter: filter,
@@ -147,7 +148,7 @@ class FinderMain extends React.Component {
} }
handleOnlyMarkdownCheckboxChange (e) { handleOnlyMarkdownCheckboxChange (e) {
let { filter } = this.state const { filter } = this.state
filter.includeMarkdown = e.target.checked filter.includeMarkdown = e.target.checked
this.refs.list.resetScroll() this.refs.list.resetScroll()
this.setState({ this.setState({
@@ -159,7 +160,7 @@ class FinderMain extends React.Component {
} }
handleAllNotesButtonClick (e) { handleAllNotesButtonClick (e) {
let { filter } = this.state const { filter } = this.state
filter.type = 'ALL' filter.type = 'ALL'
this.refs.list.resetScroll() this.refs.list.resetScroll()
this.setState({ this.setState({
@@ -171,7 +172,7 @@ class FinderMain extends React.Component {
} }
handleStarredButtonClick (e) { handleStarredButtonClick (e) {
let { filter } = this.state const { filter } = this.state
filter.type = 'STARRED' filter.type = 'STARRED'
this.refs.list.resetScroll() this.refs.list.resetScroll()
this.setState({ this.setState({
@@ -183,7 +184,7 @@ class FinderMain extends React.Component {
} }
handleStorageButtonClick (e, storage) { handleStorageButtonClick (e, storage) {
let { filter } = this.state const { filter } = this.state
filter.type = 'STORAGE' filter.type = 'STORAGE'
filter.storage = storage filter.storage = storage
this.refs.list.resetScroll() this.refs.list.resetScroll()
@@ -196,7 +197,7 @@ class FinderMain extends React.Component {
} }
handleFolderButtonClick (e, storage, folder) { handleFolderButtonClick (e, storage, folder) {
let { filter } = this.state const { filter } = this.state
filter.type = 'FOLDER' filter.type = 'FOLDER'
filter.storage = storage filter.storage = storage
filter.folder = folder filter.folder = folder
@@ -218,12 +219,12 @@ class FinderMain extends React.Component {
} }
render () { render () {
let { data, config } = this.props const { data, config } = this.props
let { filter, search } = this.state const { filter, search } = this.state
let storageList = [] const storageList = []
for (let key in data.storageMap) { for (const key in data.storageMap) {
let storage = data.storageMap[key] const storage = data.storageMap[key]
let item = ( const item = (
<StorageSection <StorageSection
filter={filter} filter={filter}
storage={storage} storage={storage}
@@ -252,7 +253,7 @@ class FinderMain extends React.Component {
notes.push(data.noteMap[id]) notes.push(data.noteMap[id])
}) })
} else { } else {
for (let key in data.noteMap) { for (const key in data.noteMap) {
notes.push(data.noteMap[key]) notes.push(data.noteMap[key])
} }
} }
@@ -264,13 +265,13 @@ class FinderMain extends React.Component {
} }
if (search.trim().length > 0) { if (search.trim().length > 0) {
let needle = new RegExp(_.escapeRegExp(search.trim()), 'i') const needle = new RegExp(_.escapeRegExp(search.trim()), 'i')
notes = notes.filter((note) => note.title.match(needle)) notes = notes.filter((note) => note.title.match(needle))
} }
notes = notes notes = notes
.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt)) .sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt))
let activeNote = notes[this.state.index] const activeNote = notes[this.state.index]
this.noteCount = notes.length this.noteCount = notes.length
return ( return (

View File

@@ -10,7 +10,7 @@ nodeIpc.config.retry = 1500
nodeIpc.config.silent = true nodeIpc.config.silent = true
function killFinder () { function killFinder () {
let finderWindow = remote.getCurrentWindow() const finderWindow = remote.getCurrentWindow()
finderWindow.removeAllListeners() finderWindow.removeAllListeners()
if (global.process.platform === 'darwin') { if (global.process.platform === 'darwin') {
// Only OSX has another app process. // Only OSX has another app process.
@@ -21,7 +21,7 @@ function killFinder () {
} }
function toggleFinder () { function toggleFinder () {
let finderWindow = remote.getCurrentWindow() const finderWindow = remote.getCurrentWindow()
if (global.process.platform === 'darwin') { if (global.process.platform === 'darwin') {
if (finderWindow.isVisible()) { if (finderWindow.isVisible()) {
finderWindow.hide() finderWindow.hide()
@@ -84,6 +84,10 @@ nodeIpc.connectTo(
const { config } = payload const { config } = payload
if (config.ui.theme === 'dark') { if (config.ui.theme === 'dark') {
document.body.setAttribute('data-theme', 'dark') document.body.setAttribute('data-theme', 'dark')
} else if (config.ui.theme === 'white') {
document.body.setAttribute('data-theme', 'white')
} else if (config.ui.theme === 'solarized-dark') {
document.body.setAttribute('data-theme', 'solarized-dark')
} else { } else {
document.body.setAttribute('data-theme', 'default') document.body.setAttribute('data-theme', 'default')
} }

View File

@@ -2,7 +2,7 @@ import { combineReducers, createStore } from 'redux'
import { routerReducer } from 'react-router-redux' import { routerReducer } from 'react-router-redux'
import { DEFAULT_CONFIG } from 'browser/main/lib/ConfigManager' import { DEFAULT_CONFIG } from 'browser/main/lib/ConfigManager'
let defaultData = { const defaultData = {
storageMap: {}, storageMap: {},
noteMap: {}, noteMap: {},
starredSet: [], starredSet: [],
@@ -40,12 +40,12 @@ function config (state = DEFAULT_CONFIG, action) {
return state return state
} }
let reducer = combineReducers({ const reducer = combineReducers({
data, data,
config, config,
routing: routerReducer routing: routerReducer
}) })
let store = createStore(reducer) const store = createStore(reducer)
export default store export default store

View File

@@ -38,15 +38,15 @@ class MutableMap {
} }
map (cb) { map (cb) {
let result = [] const result = []
for (let [key, value] of this._map) { for (const [key, value] of this._map) {
result.push(cb(value, key)) result.push(cb(value, key))
} }
return result return result
} }
toJS () { toJS () {
let result = {} const result = {}
for (let [key, value] of this._map) { for (let [key, value] of this._map) {
if (value instanceof MutableSet || value instanceof MutableMap) { if (value instanceof MutableSet || value instanceof MutableMap) {
value = value.toJS() value = value.toJS()
@@ -85,7 +85,7 @@ class MutableSet {
} }
map (cb) { map (cb) {
let result = [] const result = []
this._set.forEach(function (value, key) { this._set.forEach(function (value, key) {
result.push(cb(value, key)) result.push(cb(value, key))
}) })

View File

@@ -10,6 +10,7 @@ const themes = fs.readdirSync(themePath)
.map((themePath) => { .map((themePath) => {
return themePath.substring(0, themePath.lastIndexOf('.')) return themePath.substring(0, themePath.lastIndexOf('.'))
}) })
themes.splice(themes.indexOf('solarized'), 1, 'solarized dark', 'solarized light')
const consts = { const consts = {
FOLDER_COLORS: [ FOLDER_COLORS: [

View File

@@ -1,11 +1,11 @@
export function findNoteTitle (value) { export function findNoteTitle (value) {
let splitted = value.split('\n') const splitted = value.split('\n')
let title = null let title = null
let isInsideCodeBlock = false let isInsideCodeBlock = false
splitted.some((line, index) => { splitted.some((line, index) => {
let trimmedLine = line.trim() const trimmedLine = line.trim()
let trimmedNextLine = splitted[index + 1] === undefined ? '' : splitted[index + 1].trim() const trimmedNextLine = splitted[index + 1] === undefined ? '' : splitted[index + 1].trim()
if (trimmedLine.match('```')) { if (trimmedLine.match('```')) {
isInsideCodeBlock = !isInsideCodeBlock isInsideCodeBlock = !isInsideCodeBlock
} }

View File

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

View File

@@ -2,12 +2,15 @@ import markdownit from 'markdown-it'
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'
// FIXME We should not depend on global variable.
const katex = window.katex const katex = window.katex
const config = ConfigManager.get()
function createGutter (str) { function createGutter (str) {
let lc = (str.match(/\n/g) || []).length const lc = (str.match(/\n/g) || []).length
let lines = [] const lines = []
for (let i = 1; i <= lc; i++) { for (let i = 1; i <= lc; i++) {
lines.push('<span class="CodeMirror-linenumber">' + i + '</span>') lines.push('<span class="CodeMirror-linenumber">' + i + '</span>')
} }
@@ -38,6 +41,10 @@ md.use(emoji, {
shortcuts: {} shortcuts: {}
}) })
md.use(math, { md.use(math, {
inlineOpen: config.preview.latexInlineOpen,
inlineClose: config.preview.latexInlineClose,
blockOpen: config.preview.latexBlockOpen,
blockClose: config.preview.latexBlockClose,
inlineRenderer: function (str) { inlineRenderer: function (str) {
let output = '' let output = ''
try { try {
@@ -68,12 +75,15 @@ md.use(require('markdown-it-named-headers'), {
.replace(/\-+$/, '') .replace(/\-+$/, '')
} }
}) })
md.use(require('markdown-it-kbd'))
md.use(require('markdown-it-plantuml'))
// Override task item // Override task item
md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) { md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
let content, terminate, i, l, token let content, terminate, i, l, token
let nextLine = startLine + 1 let nextLine = startLine + 1
let terminatorRules = state.md.block.ruler.getRules('paragraph') const terminatorRules = state.md.block.ruler.getRules('paragraph')
let endLine = state.lineMax const endLine = state.lineMax
// jump line-by-line until empty one or EOF // jump line-by-line until empty one or EOF
for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) { for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {
@@ -103,9 +113,9 @@ md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
token.map = [ startLine, state.line ] token.map = [ startLine, state.line ]
if (state.parentType === 'list') { if (state.parentType === 'list') {
let match = content.match(/^\[( |x)\] ?(.+)/i) const match = content.match(/^\[( |x)\] ?(.+)/i)
if (match) { if (match) {
content = `<label class='taskListItem' for='checkbox-${startLine + 1}'><input type='checkbox'${match[1] !== ' ' ? ' checked' : ''} id='checkbox-${startLine + 1}'/> ${content.substring(4, content.length)}</label>` 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>`
} }
} }
@@ -120,7 +130,7 @@ md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
}) })
// Add line number attribute for scrolling // Add line number attribute for scrolling
let originalRender = md.renderer.render const originalRender = md.renderer.render
md.renderer.render = function render (tokens, options, env) { md.renderer.render = function render (tokens, options, env) {
tokens.forEach((token) => { tokens.forEach((token) => {
switch (token.type) { switch (token.type) {
@@ -131,40 +141,12 @@ md.renderer.render = function render (tokens, options, env) {
token.attrPush(['data-line', token.map[0]]) token.attrPush(['data-line', token.map[0]])
} }
}) })
let result = originalRender.call(md.renderer, tokens, options, env) const result = originalRender.call(md.renderer, tokens, options, env)
return result return result
} }
// FIXME We should not depend on global variable.
window.md = md window.md = md
function strip (input) {
var output = input
try {
output = output
.replace(/^([\s\t]*)([\*\-\+]|\d\.)\s+/gm, '$1')
.replace(/\n={2,}/g, '\n')
.replace(/~~/g, '')
.replace(/`{3}.*\n/g, '')
.replace(/<(.*?)>/g, '$1')
.replace(/^[=\-]{2,}\s*$/g, '')
.replace(/\[\^.+?\](: .*?$)?/g, '')
.replace(/\s{0,2}\[.*?\]: .*?$/g, '')
.replace(/!\[.*?\][\[\(].*?[\]\)]/g, '')
.replace(/\[(.*?)\][\[\(].*?[\]\)]/g, '$1')
.replace(/>/g, '')
.replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g, '')
.replace(/^#{1,6}\s*([^#]*)\s*(#{1,6})?/gm, '$1')
.replace(/([\*_]{1,3})(\S.*?\S)\1/g, '$2')
.replace(/(`{3,})(.*?)\1/gm, '$2')
.replace(/^-{3,}\s*$/g, '')
.replace(/`(.+?)`/g, '$1')
.replace(/\n{2,}/g, '\n\n')
} catch (e) {
console.error(e)
return input
}
return output
}
function normalizeLinkText (linkText) { function normalizeLinkText (linkText) {
return md.normalizeLinkText(linkText) return md.normalizeLinkText(linkText)
} }
@@ -175,7 +157,7 @@ const markdown = {
const renderedContent = md.render(content) const renderedContent = md.render(content)
return renderedContent return renderedContent
}, },
strip,
normalizeLinkText normalizeLinkText
} }
export default markdown export default markdown

View File

@@ -0,0 +1,39 @@
/**
* @fileoverview Text trimmer for markdown note.
*/
/**
* @param {string} input
* @return {string}
*/
export function strip (input) {
let output = input
try {
output = output
.replace(/^([\s\t]*)([\*\-\+]|\d+\.)\s+/gm, '$1')
.replace(/\n={2,}/g, '\n')
.replace(/~~/g, '')
.replace(/`{3}.*\n/g, '')
.replace(/<(.*?)>/g, '$1')
.replace(/^[=\-]{2,}\s*$/g, '')
.replace(/\[\^.+?\](: .*?$)?/g, '')
.replace(/\s{0,2}\[.*?\]: .*?$/g, '')
.replace(/!\[.*?\][\[\(].*?[\]\)]/g, '')
.replace(/\[(.*?)\][\[\(].*?[\]\)]/g, '$1')
.replace(/>/g, '')
.replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g, '')
.replace(/^#{1,6}\s*([^#]*)\s*(#{1,6})?/gm, '$1')
.replace(/(`{3,})(.*?)\1/gm, '$2')
.replace(/^-{3,}\s*$/g, '')
.replace(/`(.+?)`/g, '$1')
.replace(/\n{2,}/g, '\n\n')
} catch (e) {
console.error(e)
return input
}
return output
}
export default {
strip
}

View File

@@ -2,20 +2,21 @@ import _ from 'lodash'
export default function searchFromNotes (notes, search) { export default function searchFromNotes (notes, search) {
if (search.trim().length === 0) return [] if (search.trim().length === 0) return []
let searchBlocks = search.split(' ') const searchBlocks = search.split(' ').filter(block => { return block !== '' })
let foundNotes = findByWord(notes, searchBlocks[0])
searchBlocks.forEach((block) => { searchBlocks.forEach((block) => {
foundNotes = findByWord(foundNotes, block)
if (block.match(/^#.+/)) { if (block.match(/^#.+/)) {
notes = findByTag(notes, block) foundNotes = foundNotes.concat(findByTag(notes, block))
} else {
notes = findByWord(notes, block)
} }
}) })
return notes return foundNotes
} }
function findByTag (notes, block) { function findByTag (notes, block) {
const tag = block.match(/#(.+)/)[1] const tag = block.match(/#(.+)/)[1]
let regExp = new RegExp(_.escapeRegExp(tag), 'i') const regExp = new RegExp(_.escapeRegExp(tag), 'i')
return notes.filter((note) => { return notes.filter((note) => {
if (!_.isArray(note.tags)) return false if (!_.isArray(note.tags)) return false
return note.tags.some((_tag) => { return note.tags.some((_tag) => {
@@ -25,7 +26,7 @@ function findByTag (notes, block) {
} }
function findByWord (notes, block) { function findByWord (notes, block) {
let regExp = new RegExp(_.escapeRegExp(block), 'i') const regExp = new RegExp(_.escapeRegExp(block), 'i')
return notes.filter((note) => { return notes.filter((note) => {
if (_.isArray(note.tags) && note.tags.some((_tag) => { if (_.isArray(note.tags) && note.tags.some((_tag) => {
return _tag.match(regExp) return _tag.match(regExp)

View File

@@ -1,5 +1,8 @@
.root .root
absolute top bottom right absolute top bottom right
display flex
align-items center
justify-content center
.empty .empty
height 320px height 320px
@@ -8,8 +11,9 @@
.empty-message .empty-message
width 100% width 100%
font-size 42px font-size 36px
line-height 72px font-weight 600
line-height 56px
text-align center text-align center
color $ui-inactive-text-color color $ui-inactive-text-color
@@ -18,3 +22,9 @@ body[data-theme="dark"]
background-color $ui-dark-backgroundColor background-color $ui-dark-backgroundColor
.empty-message .empty-message
color $ui-dark-inactive-text-color color $ui-dark-inactive-text-color
body[data-theme="solarized-dark"]
.root
background-color $ui-solarized-dark-noteDetail-backgroundColor
.empty-message
color $ui-solarized-dark-text-color

View File

@@ -3,7 +3,9 @@
*/ */
// Margin on the left side and the right side for NoteDetail component. // Margin on the left side and the right side for NoteDetail component.
$note-detail-left-margin = 25px $note-detail-left-margin = 100px
$note-detail-right-margin = 25px $note-detail-right-margin = 120px
$snippet-note-detail-left-margin = 60px
$snippet-note-detail-right-margin = 80px
$note-detail-box-shadow = 2px 0 15px -8px #b1b1b1 inset $note-detail-box-shadow = 2px 0 15px -8px #b1b1b1 inset

View File

@@ -1,4 +1,5 @@
import React, { PropTypes } from 'react' import PropTypes from 'prop-types'
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'
@@ -73,8 +74,8 @@ class FolderSelect extends React.Component {
case 9: case 9:
if (e.shiftKey) { if (e.shiftKey) {
e.preventDefault() e.preventDefault()
let tabbable = document.querySelectorAll('a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])') const tabbable = document.querySelectorAll('a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])')
let previousEl = tabbable[Array.prototype.indexOf.call(tabbable, this.refs.root) - 1] const previousEl = tabbable[Array.prototype.indexOf.call(tabbable, this.refs.root) - 1]
if (previousEl != null) previousEl.focus() if (previousEl != null) previousEl.focus()
} }
} }
@@ -89,9 +90,9 @@ class FolderSelect extends React.Component {
} }
handleSearchInputChange (e) { handleSearchInputChange (e) {
let { folders } = this.props const { folders } = this.props
let search = this.refs.search.value const search = this.refs.search.value
let optionIndex = search.length > 0 const optionIndex = search.length > 0
? _.findIndex(folders, (folder) => { ? _.findIndex(folders, (folder) => {
return folder.name.match(new RegExp('^' + _.escapeRegExp(search), 'i')) return folder.name.match(new RegExp('^' + _.escapeRegExp(search), 'i'))
}) })
@@ -129,7 +130,7 @@ class FolderSelect extends React.Component {
nextOption () { nextOption () {
let { optionIndex } = this.state let { optionIndex } = this.state
let { folders } = this.props const { folders } = this.props
optionIndex++ optionIndex++
if (optionIndex >= folders.length) optionIndex = 0 if (optionIndex >= folders.length) optionIndex = 0
@@ -140,7 +141,7 @@ class FolderSelect extends React.Component {
} }
previousOption () { previousOption () {
let { folders } = this.props const { folders } = this.props
let { optionIndex } = this.state let { optionIndex } = this.state
optionIndex-- optionIndex--
@@ -152,10 +153,10 @@ class FolderSelect extends React.Component {
} }
selectOption () { selectOption () {
let { folders } = this.props const { folders } = this.props
let optionIndex = this.state.optionIndex const optionIndex = this.state.optionIndex
let folder = folders[optionIndex] const folder = folders[optionIndex]
if (folder != null) { if (folder != null) {
this.setState({ this.setState({
status: 'FOCUS' status: 'FOCUS'
@@ -184,10 +185,10 @@ class FolderSelect extends React.Component {
} }
render () { render () {
let { className, data, value } = this.props const { className, data, value } = this.props
let splitted = value.split('-') const splitted = value.split('-')
let storageKey = splitted.shift() const storageKey = splitted.shift()
let folderKey = splitted.shift() const folderKey = splitted.shift()
let options = [] let options = []
data.storageMap.forEach((storage, index) => { data.storageMap.forEach((storage, index) => {
storage.folders.forEach((folder) => { storage.folders.forEach((folder) => {
@@ -198,14 +199,14 @@ class FolderSelect extends React.Component {
}) })
}) })
let currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0] const currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
if (this.state.search.trim().length > 0) { if (this.state.search.trim().length > 0) {
let filter = new RegExp('^' + _.escapeRegExp(this.state.search), 'i') const filter = new RegExp('^' + _.escapeRegExp(this.state.search), 'i')
options = options.filter((option) => filter.test(option.folder.name)) options = options.filter((option) => filter.test(option.folder.name))
} }
let optionList = options const optionList = options
.map((option, index) => { .map((option, index) => {
return ( return (
<div styleName={index === this.state.optionIndex <div styleName={index === this.state.optionIndex
@@ -259,12 +260,11 @@ class FolderSelect extends React.Component {
{optionList} {optionList}
</div> </div>
</div> </div>
: <div styleName='idle'> : <div styleName='idle' style={{color: currentOption.folder.color}}>
<div styleName='idle-label'> <div styleName='idle-label'>
<span styleName='idle-label-name' <i className='fa fa-folder' />
style={{color: currentOption.folder.color}} <span styleName='idle-label-name'>
> {currentOption.folder.name}
{currentOption.folder.name} /
</span> </span>
</div> </div>
</div> </div>

View File

@@ -1,21 +1,22 @@
.root .root
position relative position relative
border solid 1px transparent border solid 1px transparent
line-height 26px
vertical-align middle vertical-align middle
border-radius 2px border-radius 2px
transition 0.15s transition 0.15s
user-select none user-select none
margin-right 10px
&:hover &:hover
background-color $ui-button--hover-backgroundColor background-color $ui-button--hover-backgroundColor
.root--search, .root--focus .root--search, .root--focus
@extend .root @extend .root
background-color $ui-noteDetail-backgroundColor = #F4F4F4 background-color $ui-noteDetail-backgroundColor = #fff
border-color $ui-input--focus-borderColor border-color $ui-input--focus-borderColor
width 100px width 154px
height 30px
&:hover &:hover
border-color $ui-input--focus-borderColor border-color $ui-input--focus-borderColor = #fff
.idle .idle
position relative position relative
@@ -24,13 +25,16 @@
.idle-label .idle-label
right 20px right 20px
overflow ellipsis overflow ellipsis
display flex
align-items center
.idle-label-name .idle-label-name
font-size 14px font-size 13px
padding 2px font-weight 600
margin-left 4px
.idle-label-name-surfix .idle-label-name-surfix
font-size 10px font-size 15px
color $ui-inactive-text-color color $ui-inactive-text-color
margin-left 5px margin-left 5px
.idle-caret .idle-caret
@@ -39,35 +43,37 @@
width 20px width 20px
line-height 34px line-height 34px
.search
absolute top left right bottom
line-height 34px
.search-input .search-input
vertical-align middle vertical-align middle
position relative position relative
top -2px top 0
font-size 14px
outline none outline none
border none border none
height 20px width 100%
line-height 20px
background-color transparent background-color transparent
padding 0 10px padding 0 10px
.search-optionList .search-optionList
position fixed position absolute
top 30px
max-height 450px max-height 450px
min-width 150px
overflow auto overflow auto
z-index 200 z-index 200
border $ui-border border $ui-border
background-color white background-color white
border-radius 2px border-radius 2px
padding 10px 6px
.search-optionList-item .search-optionList-item
width 140px
height 34px height 34px
width 250px display flex
align-items center
box-sizing border-box box-sizing border-box
padding 0 5px padding 0
margin-bottom 10px
overflow ellipsis overflow ellipsis
cursor pointer cursor pointer
&:hover &:hover
@@ -81,13 +87,17 @@
background-color $ui-button--active-backgroundColor background-color $ui-button--active-backgroundColor
color $ui-button--active-color color $ui-button--active-color
.search-optionList-item-name .search-optionList-item-name
border-left solid 2px transparent border-left solid 3px transparent
padding 2px 5px padding 6px
.search-optionList-item-name-surfix .search-optionList-item-name-surfix
font-size 10px font-size 10px
color $ui-inactive-text-color color $ui-inactive-text-color
margin-left 5px margin-left 5px
body[data-theme="dark"] body[data-theme="dark"]
.root .root
color $ui-dark-text-color color $ui-dark-text-color

View File

@@ -0,0 +1,19 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './FullscreenButton.styl'
const FullscreenButton = ({
onClick
}) => (
<button styleName='control-fullScreenButton' title='Fullscreen' onMouseDown={(e) => onClick(e)}>
<img styleName='iconInfo' src='../resources/icon/icon-full.svg' />
<span styleName='tooltip'>Fullscreen</span>
</button>
)
FullscreenButton.propTypes = {
onClick: PropTypes.func.isRequired
}
export default CSSModules(FullscreenButton, styles)

View File

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

View File

@@ -1,4 +1,5 @@
import React, { PropTypes } from 'react' import PropTypes from 'prop-types'
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'
@@ -6,9 +7,10 @@ const InfoButton = ({
onClick onClick
}) => ( }) => (
<button styleName='control-infoButton' <button styleName='control-infoButton'
onClick={onClick} onClick={(e) => onClick(e)}
> >
<i className='fa fa-info infoButton' styleName='info-button' /> <img className='infoButton' src='../resources/icon/icon-info.svg' />
<span styleName='tooltip'>Info</span>
</button> </button>
) )

View File

@@ -1,12 +1,17 @@
.control-infoButton .control-infoButton
float right top 10px
topBarButtonLight() topBarButtonRight()
&:hover .tooltip
opacity 1
.control-infoPanel .tooltip
position fixed tooltip()
position absolute
pointer-events none pointer-events none
top 50px top 26px
right 0
z-index 200 z-index 200
padding 5px
line-height normal line-height normal
border-radius 2px border-radius 2px
opacity 0 opacity 0
@@ -14,7 +19,6 @@
.infoButton .infoButton
padding 0px padding 0px
margin 15px 0
body[data-theme="dark"] body[data-theme="dark"]
.control-infoButton .control-infoButton

View File

@@ -1,73 +1,60 @@
import React, { PropTypes } from 'react' import PropTypes from 'prop-types'
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'
const InfoPanel = ({ const InfoPanel = ({
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, wordCount, letterCount, type storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, wordCount, letterCount, type, print
}) => ( }) => (
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}> <div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
<div styleName='group-section'> <div>
<div styleName='group-section-label'> <p styleName='modification-date'>{updatedAt}</p>
Storage <p styleName='modification-date-desc'>MODIFICATION DATE</p>
</div>
<div styleName='group-section-control'>
{storageName}
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
Folder
</div>
<div styleName='group-section-control'>
{folderName}
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
Created
</div>
<div styleName='group-section-control'>
{createdAt}
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
Updated
</div>
<div styleName='group-section-control'>
{updatedAt}
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
Note Link
</div>
<div styleName='group-section-control'>
<input value={noteLink} onClick={(e) => { e.target.select() }} />
</div>
</div> </div>
<hr />
{type === 'SNIPPET_NOTE' {type === 'SNIPPET_NOTE'
? '' ? ''
: <div> : <div styleName='count-wrap'>
<div styleName='group-section'> <div styleName='count-number'>
<div styleName='group-section-label'> <p styleName='infoPanel-defaul-count'>{wordCount}</p>
Words <p styleName='infoPanel-sub-count'>Words</p>
</div>
<div styleName='group-section-control'>
{wordCount}
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
Letters
</div>
<div styleName='group-section-control'>
{letterCount}
</div> </div>
<div styleName='count-number'>
<p styleName='infoPanel-defaul-count'>{letterCount}</p>
<p styleName='infoPanel-sub-count'>Letters</p>
</div> </div>
</div> </div>
} }
{type === 'SNIPPET_NOTE'
? ''
: <hr />
}
<div>
<p styleName='infoPanel-default'>{storageName}</p>
<p styleName='infoPanel-sub'>STORAGE</p>
</div>
<div>
<p styleName='infoPanel-default'>{folderName}</p>
<p styleName='infoPanel-sub'>FOLDER</p>
</div>
<div>
<p styleName='infoPanel-default'>{createdAt}</p>
<p styleName='infoPanel-sub'>CREATION DATE</p>
</div>
<div>
<input styleName='infoPanel-noteLink' value={noteLink} onClick={(e) => { e.target.select() }} />
<p styleName='infoPanel-sub'>NOTE LINK</p>
</div>
<hr />
<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 fa-fw' /> <i className='fa fa-file-code-o fa-fw' />
@@ -79,9 +66,9 @@ const InfoPanel = ({
<p>.txt</p> <p>.txt</p>
</button> </button>
<button styleName='export--unable'> <button styleName='export--enable' onClick={(e) => print(e)}>
<i className='fa fa-file-pdf-o fa-fw' /> <i className='fa fa-print fa-fw' />
<p>.pdf</p> <p>Print</p>
</button> </button>
</div> </div>
</div> </div>
@@ -97,7 +84,8 @@ InfoPanel.propTypes = {
exportAsTxt: PropTypes.func.isRequired, exportAsTxt: PropTypes.func.isRequired,
wordCount: PropTypes.number, wordCount: PropTypes.number,
letterCount: PropTypes.number, letterCount: PropTypes.number,
type: PropTypes.string.isRequired type: PropTypes.string.isRequired,
print: PropTypes.func.isRequired
} }
export default CSSModules(InfoPanel, styles) export default CSSModules(InfoPanel, styles)

View File

@@ -10,52 +10,81 @@
.control-infoButton-panel .control-infoButton-panel
z-index 200 z-index 200
margin-top 45px margin-top 0px
margin-left -210px right 0
position absolute position absolute
padding 20px 20px 0 20px padding 20px 25px 0 25px
width 340px width 300px
height 350px
overflow auto
background-color $ui-noteList-backgroundColor background-color $ui-noteList-backgroundColor
border 1px solid $border-color box-shadow 2px 12px 15px 2px rgba(0, 0, 0, 0.1), 2px 1px 50px 2px rgba(0, 0, 0, 0.1)
border-radius 2px
.modification-date
font-size 18px
line-height 30px
color $ui-text-color
.modification-date-desc
font-size 18px
color $ui-inactive-text-color
.control-infoButton-panel-trash .control-infoButton-panel-trash
z-index 200 z-index 200
margin-top 45px margin-top 0px
margin-left -230px right 0px
position absolute position absolute
padding 20px 20px 0 20px padding 20px 25px 0 25px
width 320px width 300px
background-color $ui-noteList-backgroundColor background-color $ui-noteList-backgroundColor
border 1px solid $border-color box-shadow 2px 12px 15px 2px rgba(0, 0, 0, 0.1), 2px 1px 50px 2px rgba(0, 0, 0, 0.1)
border-radius 2px
.group-section .count-wrap
display flex display flex
position relative
width 100%
.count-number
position relative
display block
width 50%
overflow hidden
margin 0
padding 0
.infoPanel-defaul-count
font-size 16px
line-height 30px line-height 30px
font-size 13px
.group-section-label
width 70px
height 20px
text-align left
margin-right 50px
font-size 13px
color $ui-inactive-text-color
.group-section-control
flex 1
font-size 13px
color $ui-text-color color $ui-text-color
.group-section-control input .infoPanel-sub-count
width 160px font-size 16px
height 25px color $ui-inactive-text-color
padding-bottom 8px
.group-section-control text .infoPanel-default
font-size 14px
line-height 30px
color $ui-text-color
.infoPanel-sub
font-size 14px
color $ui-inactive-text-color
padding-bottom 8px
.infoPanel-noteLink
padding-right 5px
width 200px
height 25px
margin-bottom 6px
.infoPanel-trash
color #EA4447 color #EA4447
font-weight 600 font-weight 600
font-size 14px font-size 14px
width 70px width 70px
height 25px
background-color rgba(226,33,113,0.1) background-color rgba(226,33,113,0.1)
border none border none
outline none outline none
@@ -96,20 +125,30 @@
body[data-theme="dark"] body[data-theme="dark"]
.control-infoButton-panel .control-infoButton-panel
background-color $ui-dark-noteList-backgroundColor background-color $ui-dark-noteList-backgroundColor
border 1px solid $ui-dark-borderColor
.control-infoButton-panel-trash .control-infoButton-panel-trash
background-color $ui-dark-noteList-backgroundColor background-color $ui-dark-noteList-backgroundColor
border 1px solid $ui-dark-borderColor
.group-section-label .modification-date
color $ui-inactive-text-color
.group-section-control
color $ui-dark-text-color color $ui-dark-text-color
.group-section-control input .modification-date-desc
background-color alpha($ui-dark-borderColor, 80%) color $ui-inactive-text-color
.infoPanel-defaul-count
color $ui-dark-text-color
.infoPanel-sub-count
color $ui-inactive-text-color
.infoPanel-default
color $ui-dark-text-color
.infoPanel-sub
color $ui-inactive-text-color
.infoPanel-noteLink
background-color alpha($ui-dark-borderColor, 60%)
color $ui-dark-text-color color $ui-dark-text-color
[id=export-wrap] [id=export-wrap]

View File

@@ -1,4 +1,5 @@
import React, { PropTypes } from 'react' import PropTypes from 'prop-types'
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'
@@ -6,37 +7,26 @@ const InfoPanelTrashed = ({
storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt
}) => ( }) => (
<div className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}> <div className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}>
<div styleName='group-section'> <div>
<div styleName='group-section-label'> <p styleName='modification-date'>{updatedAt}</p>
Storage <p styleName='modification-date-desc'>MODIFICATION DATE</p>
</div> </div>
<div styleName='group-section-control'>
{storageName} <hr />
<div>
<p styleName='infoPanel-default'>{storageName}</p>
<p styleName='infoPanel-sub'>STORAGE</p>
</div> </div>
<div>
<p styleName='infoPanel-default'><text styleName='infoPanel-trash'>Trash</text>{folderName}</p>
<p styleName='infoPanel-sub'>FOLDER</p>
</div> </div>
<div styleName='group-section'>
<div styleName='group-section-label'> <div>
Folder <p styleName='infoPanel-default'>{createdAt}</p>
</div> <p styleName='infoPanel-sub'>CREATION DATE</p>
<div styleName='group-section-control'>
<text>Trash</text>{folderName}
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
Created
</div>
<div styleName='group-section-control'>
{createdAt}
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
Updated
</div>
<div styleName='group-section-control'>
{updatedAt}
</div>
</div> </div>
<div id='export-wrap'> <div id='export-wrap'>

View File

@@ -1,7 +1,9 @@
import React, { PropTypes } from 'react' import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './MarkdownNoteDetail.styl' import styles from './MarkdownNoteDetail.styl'
import MarkdownEditor from 'browser/components/MarkdownEditor' import MarkdownEditor from 'browser/components/MarkdownEditor'
import MarkdownSplitEditor from 'browser/components/MarkdownSplitEditor'
import TodoListPercentage from 'browser/components/TodoListPercentage' import TodoListPercentage from 'browser/components/TodoListPercentage'
import StarButton from './StarButton' import StarButton from './StarButton'
import TagSelect from './TagSelect' import TagSelect from './TagSelect'
@@ -9,21 +11,26 @@ 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 markdown from 'browser/lib/markdown' import markdown from 'browser/lib/markdownTextHelper'
import StatusBar from '../StatusBar' import StatusBar from '../StatusBar'
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 ConfigManager from 'browser/main/lib/ConfigManager'
import TrashButton from './TrashButton' import TrashButton from './TrashButton'
import FullscreenButton from './FullscreenButton'
import PermanentDeleteButton from './PermanentDeleteButton'
import InfoButton from './InfoButton' import InfoButton from './InfoButton'
import ToggleModeButton from './ToggleModeButton'
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 { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus' import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus'
import striptags from 'striptags'
const electron = require('electron') const electron = require('electron')
const { remote } = electron const { remote } = electron
const { Menu, MenuItem, dialog } = remote const { dialog } = remote
class MarkdownNoteDetail extends React.Component { class MarkdownNoteDetail extends React.Component {
constructor (props) { constructor (props) {
@@ -36,7 +43,8 @@ class MarkdownNoteDetail extends React.Component {
content: '' content: ''
}, props.note), }, props.note),
isLockButtonShown: false, isLockButtonShown: false,
isLocked: false isLocked: false,
editorType: props.config.editor.type
} }
this.dispatchTimer = null this.dispatchTimer = null
@@ -72,11 +80,11 @@ class MarkdownNoteDetail extends React.Component {
} }
handleChange (e) { handleChange (e) {
let { note } = this.state const { note } = this.state
note.content = this.refs.content.value note.content = this.refs.content.value
if (this.refs.tags) note.tags = this.refs.tags.value if (this.refs.tags) note.tags = this.refs.tags.value
note.title = markdown.strip(findNoteTitle(note.content)) note.title = markdown.strip(striptags(findNoteTitle(note.content)))
note.updatedAt = new Date() note.updatedAt = new Date()
this.setState({ this.setState({
@@ -94,7 +102,7 @@ class MarkdownNoteDetail extends React.Component {
} }
saveNow () { saveNow () {
let { note, dispatch } = this.props const { note, dispatch } = this.props
clearTimeout(this.saveQueue) clearTimeout(this.saveQueue)
this.saveQueue = null this.saveQueue = null
@@ -110,11 +118,11 @@ class MarkdownNoteDetail extends React.Component {
} }
handleFolderChange (e) { handleFolderChange (e) {
let { note } = this.state const { note } = this.state
let value = this.refs.folder.value const value = this.refs.folder.value
let splitted = value.split('-') const splitted = value.split('-')
let newStorageKey = splitted.shift() const newStorageKey = splitted.shift()
let newFolderKey = splitted.shift() const newFolderKey = splitted.shift()
dataApi dataApi
.moveNote(note.storage, note.key, newStorageKey, newFolderKey) .moveNote(note.storage, note.key, newStorageKey, newFolderKey)
@@ -123,7 +131,7 @@ class MarkdownNoteDetail extends React.Component {
isMovingNote: true, isMovingNote: true,
note: Object.assign({}, newNote) note: Object.assign({}, newNote)
}, () => { }, () => {
let { dispatch, location } = this.props const { dispatch, location } = this.props
dispatch({ dispatch({
type: 'MOVE_NOTE', type: 'MOVE_NOTE',
originNote: note, originNote: note,
@@ -143,7 +151,7 @@ class MarkdownNoteDetail extends React.Component {
} }
handleStarButtonClick (e) { handleStarButtonClick (e) {
let { note } = this.state const { note } = this.state
if (!note.isStarred) AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_STAR') if (!note.isStarred) AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_STAR')
note.isStarred = !note.isStarred note.isStarred = !note.isStarred
@@ -168,22 +176,22 @@ class MarkdownNoteDetail extends React.Component {
} }
handleTrashButtonClick (e) { handleTrashButtonClick (e) {
let { note } = this.state const { note } = this.state
const { isTrashed } = note const { isTrashed } = note
if (isTrashed) { if (isTrashed) {
let dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), { const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning', type: 'warning',
message: 'Confirm note deletion', message: 'Confirm note deletion',
detail: 'This will permanently remove this note.', detail: 'This will permanently remove this note.',
buttons: ['Confirm', 'Cancel'] buttons: ['Confirm', 'Cancel']
}) })
if (dialogueButtonIndex === 1) return if (dialogueButtonIndex === 1) return
let { note, dispatch } = this.props const { note, dispatch } = this.props
dataApi dataApi
.deleteNote(note.storage, note.key) .deleteNote(note.storage, note.key)
.then((data) => { .then((data) => {
let dispatchHandler = () => { const dispatchHandler = () => {
dispatch({ dispatch({
type: 'DELETE_NOTE', type: 'DELETE_NOTE',
storageKey: data.storageKey, storageKey: data.storageKey,
@@ -205,7 +213,7 @@ class MarkdownNoteDetail extends React.Component {
} }
handleUndoButtonClick (e) { handleUndoButtonClick (e) {
let { note } = this.state const { note } = this.state
note.isTrashed = false note.isTrashed = false
@@ -230,7 +238,7 @@ class MarkdownNoteDetail extends React.Component {
} }
getToggleLockButton () { getToggleLockButton () {
return this.state.isLocked ? 'fa-lock' : 'fa-unlock' return this.state.isLocked ? '../resources/icon/icon-previewoff-on.svg' : '../resources/icon/icon-previewoff-off.svg'
} }
handleDeleteKeyDown (e) { handleDeleteKeyDown (e) {
@@ -255,13 +263,50 @@ class MarkdownNoteDetail extends React.Component {
if (infoPanel.style) infoPanel.style.display = infoPanel.style.display === 'none' ? 'inline' : 'none' if (infoPanel.style) infoPanel.style.display = infoPanel.style.display === 'none' ? 'inline' : 'none'
} }
render () { print (e) {
let { data, config, location } = this.props ee.emit('print')
let { note } = this.state }
let storageKey = note.storage
let folderKey = note.folder
let options = [] handleSwitchMode (type) {
this.setState({ editorType: type }, () => {
const newConfig = Object.assign({}, this.props.config)
newConfig.editor.type = type
ConfigManager.set(newConfig)
})
}
renderEditor () {
const { config, ignorePreviewPointerEvents } = this.props
const { note } = this.state
if (this.state.editorType === 'EDITOR_PREVIEW') {
return <MarkdownEditor
ref='content'
styleName='body-noteEditor'
config={config}
value={note.content}
storageKey={note.storage}
onChange={(e) => this.handleChange(e)}
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
/>
} else {
return <MarkdownSplitEditor
ref='content'
config={config}
value={note.content}
storageKey={note.storage}
onChange={(e) => this.handleChange(e)}
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
/>
}
}
render () {
const { data, location } = this.props
const { note, editorType } = this.state
const storageKey = note.storage
const folderKey = note.folder
const options = []
data.storageMap.forEach((storage, index) => { data.storageMap.forEach((storage, index) => {
storage.folders.forEach((folder) => { storage.folders.forEach((folder) => {
options.push({ options.push({
@@ -270,7 +315,7 @@ class MarkdownNoteDetail extends React.Component {
}) })
}) })
}) })
let currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0] const currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
const trashTopBar = <div styleName='info'> const trashTopBar = <div styleName='info'>
<div styleName='info-left'> <div styleName='info-left'>
@@ -280,7 +325,7 @@ class MarkdownNoteDetail extends React.Component {
/> />
</div> </div>
<div styleName='info-right'> <div styleName='info-right'>
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} /> <PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} />
<InfoButton <InfoButton
onClick={(e) => this.handleInfoButtonClick(e)} onClick={(e) => this.handleInfoButtonClick(e)}
/> />
@@ -297,10 +342,6 @@ class MarkdownNoteDetail extends React.Component {
const detailTopBar = <div styleName='info'> const detailTopBar = <div styleName='info'>
<div styleName='info-left'> <div styleName='info-left'>
<StarButton styleName='info-left-button'
onClick={(e) => this.handleStarButtonClick(e)}
isActive={note.isStarred}
/>
<div styleName='info-left-top'> <div styleName='info-left-top'>
<FolderSelect styleName='info-left-top-folderSelect' <FolderSelect styleName='info-left-top-folderSelect'
value={this.state.note.storage + '-' + this.state.note.folder} value={this.state.note.storage + '-' + this.state.note.folder}
@@ -315,36 +356,40 @@ class MarkdownNoteDetail extends React.Component {
value={this.state.note.tags} value={this.state.note.tags}
onChange={(e) => this.handleChange(e)} onChange={(e) => this.handleChange(e)}
/> />
<TodoListPercentage
percentageOfTodo={getTodoPercentageOfCompleted(note.content)} <ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
/>
<TodoListPercentage percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
</div> </div>
<div styleName='info-right'> <div styleName='info-right'>
<InfoButton
onClick={(e) => this.handleInfoButtonClick(e)}
/>
<StarButton
onClick={(e) => this.handleStarButtonClick(e)}
isActive={note.isStarred}
/>
{(() => { {(() => {
const faClassName = `fa ${this.getToggleLockButton()}` const imgSrc = `${this.getToggleLockButton()}`
const lockButtonComponent = const lockButtonComponent =
<button styleName='control-lockButton' <button styleName='control-lockButton'
onFocus={(e) => this.handleFocus(e)} onFocus={(e) => this.handleFocus(e)}
onMouseDown={(e) => this.handleLockButtonMouseDown(e)} onMouseDown={(e) => this.handleLockButtonMouseDown(e)}
> >
<i className={faClassName} styleName='lock-button' /> <img styleName='iconInfo' src={imgSrc} />
<span styleName='control-lockButton-tooltip'>
{this.state.isLocked ? 'Unlock' : 'Lock'}
</span>
</button> </button>
return ( return (
this.state.isLockButtonShown ? lockButtonComponent : '' this.state.isLockButtonShown ? lockButtonComponent : ''
) )
})()} })()}
<FullscreenButton onClick={(e) => this.handleFullScreenButton(e)} />
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} /> <TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
<button styleName='control-fullScreenButton'
onMouseDown={(e) => this.handleFullScreenButton(e)}
>
<i className='fa fa-expand' styleName='fullScreen-button' />
</button>
<InfoButton
onClick={(e) => this.handleInfoButtonClick(e)}
/>
<InfoPanel <InfoPanel
storageName={currentOption.storage.name} storageName={currentOption.storage.name}
folderName={currentOption.folder.name} folderName={currentOption.folder.name}
@@ -354,8 +399,9 @@ class MarkdownNoteDetail extends React.Component {
exportAsMd={this.exportAsMd} exportAsMd={this.exportAsMd}
exportAsTxt={this.exportAsTxt} exportAsTxt={this.exportAsTxt}
wordCount={note.content.split(' ').length} wordCount={note.content.split(' ').length}
letterCount={note.content.length} letterCount={note.content.replace(/\r?\n/g, '').length}
type={note.type} type={note.type}
print={this.print}
/> />
</div> </div>
</div> </div>
@@ -369,15 +415,7 @@ class MarkdownNoteDetail extends React.Component {
{location.pathname === '/trashed' ? trashTopBar : detailTopBar} {location.pathname === '/trashed' ? trashTopBar : detailTopBar}
<div styleName='body'> <div styleName='body'>
<MarkdownEditor {this.renderEditor()}
ref='content'
styleName='body-noteEditor'
config={config}
value={this.state.note.content}
storageKey={this.state.note.storage}
onChange={(e) => this.handleChange(e)}
ignorePreviewPointerEvents={this.props.ignorePreviewPointerEvents}
/>
</div> </div>
<StatusBar <StatusBar

View File

@@ -3,50 +3,42 @@
.root .root
absolute top right bottom absolute top right bottom
border-width 0 0 1px border-left 1px solid alpha(#DEDEDE, 60%)
border-style solid
border-color $ui-borderColor
background-color $ui-noteDetail-backgroundColor background-color $ui-noteDetail-backgroundColor
box-shadow $note-detail-box-shadow box-shadow none
padding 20px 40px
.lock-button .lock-button
padding-bottom 3px padding-bottom 3px
.control-lockButton .control-lockButton
topBarButtonLight() top 150px
topBarButtonRight()
.control-lockButton-tooltip .trashed-infopanel
tooltip() top 40px
position fixed position relative
pointer-events none
top 50px
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s
.control-fullScreenButton
float right
padding 0 0 2px 0
topBarButtonLight()
.body .body
absolute left right absolute left right
left $note-detail-left-margin left 0
right $note-detail-right-margin right 0
top $info-height + $info-margin-under-border top $info-height + $info-margin-under-border
bottom $statusBar-height bottom $statusBar-height
margin 0 45px
.body-noteEditor .body-noteEditor
absolute top bottom left right absolute top bottom left right
body[data-theme="white"]
.root
box-shadow $note-detail-box-shadow
border none
body[data-theme="dark"] body[data-theme="dark"]
.root .root
border-color $ui-dark-borderColor
background-color $ui-dark-noteDetail-backgroundColor background-color $ui-dark-noteDetail-backgroundColor
box-shadow none box-shadow none
border-left 1px solid $ui-dark-borderColor
.control-lockButton .control-lockButton
topBarButtonDark() topBarButtonDark()
@@ -56,3 +48,9 @@ body[data-theme="dark"]
.control-fullScreenButton .control-fullScreenButton
topBarButtonDark() topBarButtonDark()
body[data-theme="solarized-dark"]
.root
border-left 1px solid $ui-solarized-dark-borderColor
background-color $ui-solarized-dark-noteDetail-backgroundColor

View File

@@ -1,33 +1,31 @@
@import('DetailVars') @import('DetailVars')
$info-height = 60px $info-height = 50px
$info-margin-under-border = 27px $info-margin-under-border = 30px
.info .info
absolute top left right absolute top left right
left $note-detail-left-margin left 0
right $note-detail-right-margin right 0
height $info-height height $info-height
border-bottom $ui-border border-bottom 1px solid #eee
background-color $ui-noteDetail-backgroundColor background-color $ui-noteDetail-backgroundColor
width 100%
display flex
align-items center
.info-left .info-left
float left padding 0 10px
padding 0 5px width 100%
margin 0px 2px display flex
.info-left-top align-items center
display inline-block
height $info-height
line-height $info-height
.info-left-top-folderSelect .info-left-top-folderSelect
padding 0 3px
height 34px
line-height 26px
display flex display flex
align-items center align-items center
justify-content center justify-content center
border-radius 3px
.info-left-button .info-left-button
width 34px width 34px
height 34px height 34px
@@ -48,18 +46,18 @@ $info-margin-under-border = 27px
.info-right .info-right
position absolute position absolute
right 0 right 40px
top 0 top 60px
background $ui-noteDetail-backgroundColor
bottom 1px bottom 1px
padding-left 30px padding-left 30px
z-index 101
.undo-button .undo-button
width 34px width 34px
height 34px height 34px
border-radius 17px border-radius 17px
font-size 14px font-size 14px
margin 15px 7px margin 5px 0px
border none border none
color $ui-button-color color $ui-button-color
display flex display flex
@@ -72,6 +70,7 @@ $info-margin-under-border = 27px
border-color $ui-button--active-backgroundColor border-color $ui-button--active-backgroundColor
&:hover &:hover
background-color alpha($ui-button--hover-backgroundColor, 60%) background-color alpha($ui-button--hover-backgroundColor, 60%)
transition 0.2s
.control-lockButton-tooltip .control-lockButton-tooltip
opacity 1 opacity 1
@@ -97,3 +96,9 @@ body[data-theme="dark"]
.undo-button .undo-button
topBarButtonDark() topBarButtonDark()
body[data-theme="solarized-dark"]
.info
border-color $ui-solarized-dark-borderColor
background-color $ui-solarized-dark-noteDetail-backgroundColor

View File

@@ -0,0 +1,21 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './TrashButton.styl'
const PermanentDeleteButton = ({
onClick
}) => (
<button styleName='control-trashButton--in-trash'
onClick={(e) => onClick(e)}
>
<img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
<span styleName='tooltip'>Permanent Delete</span>
</button>
)
PermanentDeleteButton.propTypes = {
onClick: PropTypes.func.isRequired
}
export default CSSModules(PermanentDeleteButton, styles)

View File

@@ -1,4 +1,5 @@
import React, { PropTypes } from 'react' import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './SnippetNoteDetail.styl' import styles from './SnippetNoteDetail.styl'
import CodeEditor from 'browser/components/CodeEditor' import CodeEditor from 'browser/components/CodeEditor'
@@ -18,6 +19,7 @@ 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 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'
@@ -60,7 +62,7 @@ class SnippetNoteDetail extends React.Component {
componentWillReceiveProps (nextProps) { componentWillReceiveProps (nextProps) {
if (nextProps.note.key !== this.props.note.key && !this.isMovingNote) { if (nextProps.note.key !== this.props.note.key && !this.isMovingNote) {
if (this.saveQueue != null) this.saveNow() if (this.saveQueue != null) this.saveNow()
let nextNote = Object.assign({ const nextNote = Object.assign({
description: '' description: ''
}, nextProps.note, { }, nextProps.note, {
snippets: nextProps.note.snippets.map((snippet) => Object.assign({}, snippet)) snippets: nextProps.note.snippets.map((snippet) => Object.assign({}, snippet))
@@ -69,7 +71,7 @@ class SnippetNoteDetail extends React.Component {
snippetIndex: 0, snippetIndex: 0,
note: nextNote note: nextNote
}, () => { }, () => {
let { snippets } = this.state.note const { snippets } = this.state.note
snippets.forEach((snippet, index) => { snippets.forEach((snippet, index) => {
this.refs['code-' + index].reload() this.refs['code-' + index].reload()
}) })
@@ -83,7 +85,7 @@ class SnippetNoteDetail extends React.Component {
} }
handleChange (e) { handleChange (e) {
let { 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
note.description = this.refs.description.value note.description = this.refs.description.value
@@ -105,7 +107,7 @@ class SnippetNoteDetail extends React.Component {
} }
saveNow () { saveNow () {
let { note, dispatch } = this.props const { note, dispatch } = this.props
clearTimeout(this.saveQueue) clearTimeout(this.saveQueue)
this.saveQueue = null this.saveQueue = null
@@ -121,11 +123,11 @@ class SnippetNoteDetail extends React.Component {
} }
handleFolderChange (e) { handleFolderChange (e) {
let { note } = this.state const { note } = this.state
let value = this.refs.folder.value const value = this.refs.folder.value
let splitted = value.split('-') const splitted = value.split('-')
let newStorageKey = splitted.shift() const newStorageKey = splitted.shift()
let newFolderKey = splitted.shift() const newFolderKey = splitted.shift()
dataApi dataApi
.moveNote(note.storage, note.key, newStorageKey, newFolderKey) .moveNote(note.storage, note.key, newStorageKey, newFolderKey)
@@ -134,7 +136,7 @@ class SnippetNoteDetail extends React.Component {
isMovingNote: true, isMovingNote: true,
note: Object.assign({}, newNote) note: Object.assign({}, newNote)
}, () => { }, () => {
let { dispatch, location } = this.props const { dispatch, location } = this.props
dispatch({ dispatch({
type: 'MOVE_NOTE', type: 'MOVE_NOTE',
originNote: note, originNote: note,
@@ -154,7 +156,7 @@ class SnippetNoteDetail extends React.Component {
} }
handleStarButtonClick (e) { handleStarButtonClick (e) {
let { note } = this.state const { note } = this.state
if (!note.isStarred) AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_STAR') if (!note.isStarred) AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_STAR')
note.isStarred = !note.isStarred note.isStarred = !note.isStarred
@@ -171,22 +173,22 @@ class SnippetNoteDetail extends React.Component {
} }
handleTrashButtonClick (e) { handleTrashButtonClick (e) {
let { note } = this.state const { note } = this.state
const { isTrashed } = note const { isTrashed } = note
if (isTrashed) { if (isTrashed) {
let dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), { const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning', type: 'warning',
message: 'Confirm note deletion', message: 'Confirm note deletion',
detail: 'This will permanently remove this note.', detail: 'This will permanently remove this note.',
buttons: ['Confirm', 'Cancel'] buttons: ['Confirm', 'Cancel']
}) })
if (dialogueButtonIndex === 1) return if (dialogueButtonIndex === 1) return
let { note, dispatch } = this.props const { note, dispatch } = this.props
dataApi dataApi
.deleteNote(note.storage, note.key) .deleteNote(note.storage, note.key)
.then((data) => { .then((data) => {
let dispatchHandler = () => { const dispatchHandler = () => {
dispatch({ dispatch({
type: 'DELETE_NOTE', type: 'DELETE_NOTE',
storageKey: data.storageKey, storageKey: data.storageKey,
@@ -208,7 +210,7 @@ class SnippetNoteDetail extends React.Component {
} }
handleUndoButtonClick (e) { handleUndoButtonClick (e) {
let { note } = this.state const { note } = this.state
note.isTrashed = false note.isTrashed = false
@@ -234,10 +236,31 @@ class SnippetNoteDetail extends React.Component {
}) })
} }
handleTabDragStart (e, index) {
e.dataTransfer.setData('text/plain', index)
}
handleTabDrop (e, index) {
const oldIndex = parseInt(e.dataTransfer.getData('text'))
const snippets = this.state.note.snippets.slice()
const draggedSnippet = snippets[oldIndex]
snippets[oldIndex] = snippets[index]
snippets[index] = draggedSnippet
const snippetIndex = index
const note = Object.assign({}, this.state.note, {snippets})
this.setState({ note, snippetIndex }, () => {
this.save()
this.refs['code-' + index].reload()
this.refs['code-' + oldIndex].reload()
})
}
handleTabDeleteButtonClick (e, index) { handleTabDeleteButtonClick (e, index) {
if (this.state.note.snippets.length > 1) { if (this.state.note.snippets.length > 1) {
if (this.state.note.snippets[index].content.trim().length > 0) { if (this.state.note.snippets[index].content.trim().length > 0) {
let dialogIndex = dialog.showMessageBox(remote.getCurrentWindow(), { const dialogIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning', type: 'warning',
message: 'Delete a snippet', message: 'Delete a snippet',
detail: 'This work cannot be undone.', detail: 'This work cannot be undone.',
@@ -266,11 +289,16 @@ class SnippetNoteDetail extends React.Component {
} }
renameSnippetByIndex (index, name) { renameSnippetByIndex (index, name) {
let snippets = this.state.note.snippets.slice() const snippets = this.state.note.snippets.slice()
snippets[index].name = name snippets[index].name = name
let syntax = CodeMirror.findModeByFileName(name.trim()) const syntax = CodeMirror.findModeByFileName(name.trim())
let mode = syntax != null ? syntax.name : null const mode = syntax != null ? syntax.name : null
if (mode != null) snippets[index].mode = mode if (mode != null) {
snippets[index].mode = mode
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('SNIPPET_LANG', {
name: mode
})
}
this.setState({note: Object.assign(this.state.note, {snippets: snippets})}) this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
this.setState({ this.setState({
@@ -282,7 +310,7 @@ class SnippetNoteDetail extends React.Component {
handleModeOptionClick (index, name) { handleModeOptionClick (index, name) {
return (e) => { return (e) => {
let snippets = this.state.note.snippets.slice() const snippets = this.state.note.snippets.slice()
snippets[index].mode = name snippets[index].mode = name
this.setState({note: Object.assign(this.state.note, {snippets: snippets})}) this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
@@ -291,12 +319,16 @@ class SnippetNoteDetail extends React.Component {
}, () => { }, () => {
this.save() this.save()
}) })
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('SELECT_LANG', {
name
})
} }
} }
handleCodeChange (index) { handleCodeChange (index) {
return (e) => { return (e) => {
let snippets = this.state.note.snippets.slice() const snippets = this.state.note.snippets.slice()
snippets[index].content = this.refs['code-' + index].value snippets[index].content = this.refs['code-' + index].value
this.setState({note: Object.assign(this.state.note, {snippets: snippets})}) this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
this.setState({ this.setState({
@@ -323,7 +355,7 @@ class SnippetNoteDetail extends React.Component {
break break
case 76: case 76:
{ {
let isSuper = global.process.platform === 'darwin' const isSuper = global.process.platform === 'darwin'
? e.metaKey ? e.metaKey
: e.ctrlKey : e.ctrlKey
if (isSuper) { if (isSuper) {
@@ -334,7 +366,7 @@ class SnippetNoteDetail extends React.Component {
break break
case 84: case 84:
{ {
let isSuper = global.process.platform === 'darwin' const isSuper = global.process.platform === 'darwin'
? e.metaKey ? e.metaKey
: e.ctrlKey : e.ctrlKey
if (isSuper) { if (isSuper) {
@@ -347,7 +379,7 @@ class SnippetNoteDetail extends React.Component {
} }
handleModeButtonClick (e, index) { handleModeButtonClick (e, index) {
let menu = new Menu() const menu = new Menu()
CodeMirror.modeInfo.forEach((mode) => { CodeMirror.modeInfo.forEach((mode) => {
menu.append(new MenuItem({ menu.append(new MenuItem({
label: mode.name, label: mode.name,
@@ -388,8 +420,8 @@ class SnippetNoteDetail extends React.Component {
} }
handleIndentSizeItemClick (e, indentSize) { handleIndentSizeItemClick (e, indentSize) {
let { config, dispatch } = this.props const { config, dispatch } = this.props
let editor = Object.assign({}, config.editor, { const editor = Object.assign({}, config.editor, {
indentSize indentSize
}) })
ConfigManager.set({ ConfigManager.set({
@@ -404,8 +436,8 @@ class SnippetNoteDetail extends React.Component {
} }
handleIndentTypeItemClick (e, indentType) { handleIndentTypeItemClick (e, indentType) {
let { config, dispatch } = this.props const { config, dispatch } = this.props
let editor = Object.assign({}, config.editor, { const editor = Object.assign({}, config.editor, {
indentType indentType
}) })
ConfigManager.set({ ConfigManager.set({
@@ -424,14 +456,14 @@ class SnippetNoteDetail extends React.Component {
} }
addSnippet () { addSnippet () {
let { note } = this.state const { note } = this.state
note.snippets = note.snippets.concat([{ note.snippets = note.snippets.concat([{
name: '', name: '',
mode: 'Plain Text', mode: 'Plain Text',
content: '' content: ''
}]) }])
let snippetIndex = note.snippets.length - 1 const snippetIndex = note.snippets.length - 1
this.setState({ this.setState({
note, note,
@@ -477,19 +509,19 @@ class SnippetNoteDetail extends React.Component {
} }
render () { render () {
let { data, config, location } = this.props const { data, config, location } = this.props
let { note } = this.state const { note } = this.state
let storageKey = note.storage const storageKey = note.storage
let folderKey = note.folder const folderKey = note.folder
let editorFontSize = parseInt(config.editor.fontSize, 10) let editorFontSize = parseInt(config.editor.fontSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14 if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
let editorIndentSize = parseInt(config.editor.indentSize, 10) let editorIndentSize = parseInt(config.editor.indentSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4 if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
let tabList = note.snippets.map((snippet, index) => { const tabList = note.snippets.map((snippet, index) => {
let isActive = this.state.snippetIndex === index const isActive = this.state.snippetIndex === index
return <SnippetTab return <SnippetTab
key={index} key={index}
@@ -500,11 +532,13 @@ class SnippetNoteDetail extends React.Component {
onDelete={(e) => this.handleTabDeleteButtonClick(e, index)} onDelete={(e) => this.handleTabDeleteButtonClick(e, index)}
onRename={(name) => this.renameSnippetByIndex(index, name)} onRename={(name) => this.renameSnippetByIndex(index, name)}
isDeletable={note.snippets.length > 1} isDeletable={note.snippets.length > 1}
onDragStart={(e) => this.handleTabDragStart(e, index)}
onDrop={(e) => this.handleTabDrop(e, index)}
/> />
}) })
let viewList = note.snippets.map((snippet, index) => { const viewList = note.snippets.map((snippet, index) => {
let isActive = this.state.snippetIndex === index const isActive = this.state.snippetIndex === index
let syntax = CodeMirror.findModeByName(pass(snippet.mode)) let syntax = CodeMirror.findModeByName(pass(snippet.mode))
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text') if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
@@ -532,6 +566,7 @@ class SnippetNoteDetail extends React.Component {
indentSize={editorIndentSize} indentSize={editorIndentSize}
displayLineNumbers={config.editor.displayLineNumbers} displayLineNumbers={config.editor.displayLineNumbers}
keyMap={config.editor.keyMap} keyMap={config.editor.keyMap}
scrollPastEnd={config.editor.scrollPastEnd}
onChange={(e) => this.handleCodeChange(index)(e)} onChange={(e) => this.handleCodeChange(index)(e)}
ref={'code-' + index} ref={'code-' + index}
/> />
@@ -539,7 +574,7 @@ class SnippetNoteDetail extends React.Component {
</div> </div>
}) })
let options = [] const options = []
data.storageMap.forEach((storage, index) => { data.storageMap.forEach((storage, index) => {
storage.folders.forEach((folder) => { storage.folders.forEach((folder) => {
options.push({ options.push({
@@ -548,7 +583,7 @@ class SnippetNoteDetail extends React.Component {
}) })
}) })
}) })
let currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0] const currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
const trashTopBar = <div styleName='info'> const trashTopBar = <div styleName='info'>
<div styleName='info-left'> <div styleName='info-left'>
@@ -558,7 +593,7 @@ class SnippetNoteDetail extends React.Component {
/> />
</div> </div>
<div styleName='info-right'> <div styleName='info-right'>
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} /> <PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} />
<InfoButton <InfoButton
onClick={(e) => this.handleInfoButtonClick(e)} onClick={(e) => this.handleInfoButtonClick(e)}
/> />
@@ -575,10 +610,6 @@ class SnippetNoteDetail extends React.Component {
const detailTopBar = <div styleName='info'> const detailTopBar = <div styleName='info'>
<div styleName='info-left'> <div styleName='info-left'>
<StarButton styleName='info-left-button'
onClick={(e) => this.handleStarButtonClick(e)}
isActive={note.isStarred}
/>
<div styleName='info-left-top'> <div styleName='info-left-top'>
<FolderSelect styleName='info-left-top-folderSelect' <FolderSelect styleName='info-left-top-folderSelect'
value={this.state.note.storage + '-' + this.state.note.folder} value={this.state.note.storage + '-' + this.state.note.folder}
@@ -595,15 +626,21 @@ class SnippetNoteDetail extends React.Component {
/> />
</div> </div>
<div styleName='info-right'> <div styleName='info-right'>
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
<button styleName='control-fullScreenButton'
onMouseDown={(e) => this.handleFullScreenButton(e)}
>
<i className='fa fa-expand' styleName='fullScreen-button' />
</button>
<InfoButton <InfoButton
onClick={(e) => this.handleInfoButtonClick(e)} onClick={(e) => this.handleInfoButtonClick(e)}
/> />
<StarButton
onClick={(e) => this.handleStarButtonClick(e)}
isActive={note.isStarred}
/>
<button styleName='control-fullScreenButton' title='Fullscreen'
onMouseDown={(e) => this.handleFullScreenButton(e)}>
<img styleName='iconInfo' src='../resources/icon/icon-sidebar.svg' />
</button>
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
<InfoPanel <InfoPanel
storageName={currentOption.storage.name} storageName={currentOption.storage.name}
folderName={currentOption.folder.name} folderName={currentOption.folder.name}

View File

@@ -3,23 +3,21 @@
.root .root
absolute top bottom right absolute top bottom right
border-width 0 0 1px border-left 1px solid alpha(#DEDEDE, 60%)
border-style solid
border-color $ui-borderColor
background-color $ui-noteDetail-backgroundColor background-color $ui-noteDetail-backgroundColor
box-shadow $note-detail-box-shadow box-shadow none
.body .body
absolute left right absolute left right
left $note-detail-left-margin left $snippet-note-detail-left-margin
right $note-detail-right-margin right $snippet-note-detail-right-margin
top $info-height + $info-margin-under-border top $info-height + $info-margin-under-border
bottom $statusBar-height bottom $statusBar-height
background-color $ui-noteDetail-backgroundColor background-color $ui-noteDetail-backgroundColor
.body .description .body .description
absolute top left right absolute top left right
height 80px height 50px
.body .description textarea .body .description textarea
outline none outline none
@@ -27,14 +25,14 @@
height 100% height 100%
width 100% width 100%
resize none resize none
border none border 1px solid $ui-borderColor
padding 10px padding 2px 5px
line-height 1.6 line-height 1.6
background-color $ui-noteDetail-backgroundColor background-color $ui-noteDetail-backgroundColor
.tabList .tabList
absolute left right absolute left right
top 80px top 55px
height 30px height 30px
display flex display flex
background-color $ui-noteDetail-backgroundColor background-color $ui-noteDetail-backgroundColor
@@ -45,35 +43,42 @@
overflow hidden overflow hidden
.tabList .plusButton .tabList .plusButton
navButtonColor() navWhiteButtonColor()
width 30px width 30px
.tabView .tabView
absolute left right bottom absolute left right bottom
top 130px top 100px
.tabView-content .tabView-content
absolute top left right bottom absolute top left right bottom
.override .override
absolute bottom left absolute bottom left
bottom 1px
left 60px left 60px
height 23px z-index 101
z-index 1
button button
navButtonColor() navButtonColor()
height 24px padding 0 6px
&:hover
color $ui-active-color
&:active .update-icon &:active .update-icon
color white color $ui-active-color
.control-fullScreenButton .control-fullScreenButton
float right top 80px
padding 0 0 2px 0 margin-bottom 10px
topBarButtonLight() topBarButtonRight()
body[data-theme="white"]
.root
box-shadow $note-detail-box-shadow
border none
body[data-theme="dark"] body[data-theme="dark"]
.root .root
border-color $ui-dark-borderColor border-left 1px solid $ui-dark-borderColor
background-color $ui-dark-noteDetail-backgroundColor background-color $ui-dark-noteDetail-backgroundColor
box-shadow none box-shadow none
@@ -83,6 +88,7 @@ body[data-theme="dark"]
.body .description textarea .body .description textarea
background-color $ui-dark-noteDetail-backgroundColor background-color $ui-dark-noteDetail-backgroundColor
color $ui-dark-text-color color $ui-dark-text-color
border 1px solid $ui-dark-borderColor
.tabList .tabList
background-color $ui-button--active-backgroundColor background-color $ui-button--active-backgroundColor
@@ -103,3 +109,20 @@ body[data-theme="dark"]
.control-fullScreenButton .control-fullScreenButton
topBarButtonDark() topBarButtonDark()
body[data-theme="solarized-dark"]
.root
border-left 1px solid $ui-solarized-dark-borderColor
background-color $ui-solarized-dark-noteDetail-backgroundColor
.body
background-color $ui-solarized-dark-noteDetail-backgroundColor
.body .description textarea
background-color $ui-solarized-dark-noteDetail-backgroundColor
color $ui-solarized-dark-text-color
border 1px solid $ui-solarized-dark-borderColor
.tabList
background-color $ui-solarized-dark-noteDetail-backgroundColor
color $ui-solarized-dark-text-color

View File

@@ -1,4 +1,5 @@
import React, { PropTypes } from 'react' import PropTypes from 'prop-types'
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'
@@ -31,7 +32,7 @@ class StarButton extends React.Component {
} }
render () { render () {
let { className } = this.props const { className } = this.props
return ( return (
<button className={_.isString(className) <button className={_.isString(className)
@@ -45,14 +46,14 @@ class StarButton extends React.Component {
onMouseDown={(e) => this.handleMouseDown(e)} onMouseDown={(e) => this.handleMouseDown(e)}
onMouseUp={(e) => this.handleMouseUp(e)} onMouseUp={(e) => this.handleMouseUp(e)}
onMouseLeave={(e) => this.handleMouseLeave(e)} onMouseLeave={(e) => this.handleMouseLeave(e)}
onClick={this.props.onClick} onClick={this.props.onClick}>
> <img styleName='icon'
<i styleName='icon' src={this.state.isActive || this.props.isActive
className={this.state.isActive || this.props.isActive ? '../resources/icon/icon-starred.svg'
? 'fa fa-star' : '../resources/icon/icon-star.svg'
: 'fa fa-star-o'
} }
/> />
<span styleName='tooltip'>Star</span>
</button> </button>
) )
} }

View File

@@ -1,47 +1,40 @@
.root .root
left 7px top 45px
top 0 topBarButtonRight()
padding 0
color alpha($ui-favorite-star-button-color, 60%)
&:hover &:hover
transition 0.15s transition 0.2s
background-color alpha($ui-button--active-backgroundColor, 40%) color alpha($ui-favorite-star-button-color, 0.6)
color $ui-favorite-star-button-color &:hover .tooltip
&:active opacity 1
transition 0.15s
background-color alpha($ui-button--active-backgroundColor, 40%) .tooltip
color $ui-favorite-star-button-color tooltip()
position absolute
pointer-events none
top 26px
right 0
width 100%
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s
.root--active .root--active
@extend .root @extend .root
transition 0.15s
color $ui-favorite-star-button-color color $ui-favorite-star-button-color
&:hover &:hover
transition 0.15s transition 0.2s
color $ui-favorite-star-button-color color alpha($ui-favorite-star-button-color, 0.6)
background-color alpha($ui-button--active-backgroundColor, 40%)
&:active
transition 0.15s
color $ui-favorite-star-button-color
background-color alpha($ui-button--active-backgroundColor, 40%)
.icon .icon
transition transform 0.15s transition transform 0.15s
body[data-theme="dark"] body[data-theme="dark"]
.root .root
topBarButtonDark()
&:hover &:hover
transition 0.15s transition 0.2s
background-color alpha($ui-dark-button--active-backgroundColor, 20%) color alpha($ui-favorite-star-button-color, 0.6)
color $ui-favorite-star-button-color
&:active
transition 0.15s
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
color $ui-favorite-star-button-color
.root--active
@extend .root
color $ui-favorite-star-button-color
&:hover
transition 0.15s
color $ui-favorite-star-button-color
background-color alpha($ui-dark-button--active-backgroundColor, 20%)

View File

@@ -1,4 +1,5 @@
import React, { PropTypes } from 'react' import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './TagSelect.styl' import styles from './TagSelect.styl'
import _ from 'lodash' import _ from 'lodash'
@@ -37,6 +38,10 @@ class TagSelect extends React.Component {
} }
} }
handleNewTagBlur (e) {
this.submitTag()
}
removeLastTag () { removeLastTag () {
let { value } = this.props let { value } = this.props
@@ -59,7 +64,7 @@ class TagSelect extends React.Component {
submitTag () { submitTag () {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_TAG') AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_TAG')
let { value } = this.props let { value } = this.props
let newTag = this.refs.newTag.value.trim().replace(/ +/g, '_') const newTag = this.refs.newTag.value.trim().replace(/ +/g, '_')
if (newTag.length <= 0) { if (newTag.length <= 0) {
this.setState({ this.setState({
@@ -101,9 +106,9 @@ class TagSelect extends React.Component {
} }
render () { render () {
let { value, className } = this.props const { value, className } = this.props
let tagList = _.isArray(value) const tagList = _.isArray(value)
? value.map((tag) => { ? value.map((tag) => {
return ( return (
<span styleName='tag' <span styleName='tag'
@@ -113,7 +118,7 @@ class TagSelect extends React.Component {
<button styleName='tag-removeButton' <button styleName='tag-removeButton'
onClick={(e) => this.handleTagRemoveButtonClick(tag)(e)} onClick={(e) => this.handleTagRemoveButtonClick(tag)(e)}
> >
<i className='fa fa-times fa-fw tag-removeButton-icon' /> <img className='tag-removeButton-icon' src='../resources/icon/icon-x.svg' width='8px' />
</button> </button>
</span> </span>
) )
@@ -134,6 +139,7 @@ class TagSelect extends React.Component {
placeholder='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)}
/> />
</div> </div>
) )

View File

@@ -1,61 +1,53 @@
.root .root
display inline-block display flex
align-items center
user-select none user-select none
height 23px
vertical-align middle vertical-align middle
width 300px width 100%
overflow-x scroll overflow-x scroll
white-space nowrap white-space nowrap
margin-right 10px
.root::-webkit-scrollbar .root::-webkit-scrollbar
display none display none
.tag .tag
display inline-block display flex
margin 1px 3px align-items center
padding 0 margin 0px 2px
height 20px padding 2px 4px
background-color alpha($ui-tag-backgroundColor, 10%) background-color alpha($ui-tag-backgroundColor, 3%)
border-radius 3px border-radius 4px
overflow hidden position relative
clearfix() clearfix()
.tag-removeButton .tag-removeButton
float right
height 20px
width 18px
margin 0 margin 0
padding 0 padding 0
border-style solid border-style solid
border-width 0 border-width 0
border-radius 20px border-radius 20px
line-height 18px
background-color transparent background-color transparent
color $ui-button-color color $ui-button-color
position absolute
right 6px
.tag-removeButton-icon .tag-removeButton-icon
width 5px width 5px
padding-right 4px padding-right 4px
.tag-label .tag-label
font-size 11px font-size 13px
font-weight 600 color: $ui-text-color
color: alpha($ui-text-color, 80%) padding 4px 16px 4px 8px
float left
height 20px
line-height 20px
padding 0 6px
.newTag .newTag
display inline-block box-sizing border-box
margin 2px 0 15px 2px
vertical-align middle
height 18px
box-sizing borde-box
border none border none
background-color transparent background-color transparent
outline none outline none
padding 0 4px padding 0 4px
font-size 13px
body[data-theme="dark"] body[data-theme="dark"]
.tag .tag
@@ -73,3 +65,19 @@ body[data-theme="dark"]
border-color none border-color none
background-color transparent background-color transparent
color $ui-dark-text-color color $ui-dark-text-color
body[data-theme="solarized-dark"]
.tag
background-color $ui-solarized-dark-tag-backgroundColor
.tag-removeButton
border-color $ui-button--focus-borderColor
background-color transparent
.tag-label
color $ui-solarized-dark-text-color
.newTag
border-color none
background-color transparent
color $ui-solarized-dark-text-color

View File

@@ -0,0 +1,25 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './ToggleModeButton.styl'
const ToggleModeButton = ({
onClick, editorType
}) => (
<div styleName='control-toggleModeButton'>
<div styleName={editorType === 'SPLIT' ? 'active' : 'non-active'} onClick={() => onClick('SPLIT')}>
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-split-on.svg' : '../resources/icon/icon-mode-split-on-active.svg'} />
</div>
<div styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : 'non-active'} onClick={() => onClick('EDITOR_PREVIEW')}>
<img styleName='item-star' src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-markdown-off-active.svg' : '../resources/icon/icon-mode-markdown-off.svg'} />
</div>
<span styleName='tooltip'>Toggle Mode</span>
</div>
)
ToggleModeButton.propTypes = {
onClick: PropTypes.func.isRequired,
editorType: PropTypes.string.Required
}
export default CSSModules(ToggleModeButton, styles)

View File

@@ -0,0 +1,61 @@
.control-toggleModeButton
border 1px solid #eee
height 34px
display flex
align-items center
div
width 40px
height 100%
background-color #f9f9f9
display flex
align-items center
justify-content center
cursor pointer
&:first-child
border-right 1px solid #eee
.active
background-color #fff
box-shadow 2px 0px 7px #eee
z-index 1
&:hover .tooltip
opacity 1
.tooltip
tooltip()
position absolute
pointer-events none
top 47px
right 11px
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s
body[data-theme="dark"]
.control-fullScreenButton
topBarButtonDark()
.control-toggleModeButton
border 1px solid #444444
div
background-color $ui-dark-noteDetail-backgroundColor
&:first-child
border-right 1px solid #444444
.active
background-color #3A404C
box-shadow 2px 0px 7px #444444
body[data-theme="solarized-dark"]
.control-toggleModeButton
border 1px solid #586E75
div
background-color $ui-solarized-dark-noteDetail-backgroundColor
&:first-child
border-right 1px solid #586E75
.active
background-color #002B36
box-shadow 2px 0px 7px #222222

View File

@@ -1,4 +1,5 @@
import React, { PropTypes } from 'react' import PropTypes from 'prop-types'
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'
@@ -8,7 +9,8 @@ const TrashButton = ({
<button styleName='control-trashButton' <button styleName='control-trashButton'
onClick={(e) => onClick(e)} onClick={(e) => onClick(e)}
> >
<i className='fa fa-trash trashButton' styleName='info-button' /> <img styleName='iconInfo' src='../resources/icon/icon-trash.svg' />
<span styleName='tooltip'>Trash</span>
</button> </button>
) )

View File

@@ -1,10 +1,28 @@
.control-trashButton .control-trashButton
float right top 115px
topBarButtonLight() topBarButtonRight()
&:hover .tooltip
opacity 1
.tooltip
tooltip()
position absolute
pointer-events none
top 26px
right 0
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s
.control-trashButton--in-trash
top 60px
topBarButtonRight()
.trashButton .trashButton
padding 0px padding 0px
margin 15px 0
body[data-theme="dark"] body[data-theme="dark"]
.control-trashButton .control-trashButton

View File

@@ -1,4 +1,5 @@
import React, { PropTypes } from 'react' import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './Detail.styl' import styles from './Detail.styl'
import _ from 'lodash' import _ from 'lodash'
@@ -32,12 +33,12 @@ class Detail extends React.Component {
} }
render () { render () {
let { 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) {
let splitted = location.query.key.split('-') const splitted = location.query.key.split('-')
let storageKey = splitted.shift() const storageKey = splitted.shift()
let noteKey = splitted.shift() const noteKey = splitted.shift()
note = data.noteMap.get(storageKey + '-' + noteKey) note = data.noteMap.get(storageKey + '-' + noteKey)
} }

View File

@@ -1,4 +1,5 @@
import React, { PropTypes } from 'react' import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './Main.styl' import styles from './Main.styl'
import { connect } from 'react-redux' import { connect } from 'react-redux'
@@ -11,15 +12,11 @@ import _ from 'lodash'
import ConfigManager from 'browser/main/lib/ConfigManager' import ConfigManager from 'browser/main/lib/ConfigManager'
import modal from 'browser/main/lib/modal' import modal from 'browser/main/lib/modal'
import InitModal from 'browser/main/modals/InitModal' import InitModal from 'browser/main/modals/InitModal'
import mixpanel from 'browser/main/lib/mixpanel'
import mobileAnalytics from 'browser/main/lib/AwsMobileAnalyticsConfig' import mobileAnalytics from 'browser/main/lib/AwsMobileAnalyticsConfig'
import eventEmitter from 'browser/main/lib/eventEmitter' import eventEmitter from 'browser/main/lib/eventEmitter'
function focused () {
mixpanel.track('MAIN_FOCUSED')
}
class Main extends React.Component { class Main extends React.Component {
constructor (props) { constructor (props) {
super(props) super(props)
@@ -27,7 +24,7 @@ class Main extends React.Component {
mobileAnalytics.initAwsMobileAnalytics() mobileAnalytics.initAwsMobileAnalytics()
} }
let { config } = props const { config } = props
this.state = { this.state = {
isRightSliderFocused: false, isRightSliderFocused: false,
@@ -43,7 +40,7 @@ class Main extends React.Component {
} }
getChildContext () { getChildContext () {
let { status, config } = this.props const { status, config } = this.props
return { return {
status, status,
@@ -52,10 +49,14 @@ class Main extends React.Component {
} }
componentDidMount () { componentDidMount () {
let { dispatch, config } = this.props const { dispatch, config } = this.props
if (config.ui.theme === 'dark') { if (config.ui.theme === 'dark') {
document.body.setAttribute('data-theme', 'dark') document.body.setAttribute('data-theme', 'dark')
} else if (config.ui.theme === 'white') {
document.body.setAttribute('data-theme', 'white')
} else if (config.ui.theme === 'solarized-dark') {
document.body.setAttribute('data-theme', 'solarized-dark')
} else { } else {
document.body.setAttribute('data-theme', 'default') document.body.setAttribute('data-theme', 'default')
} }
@@ -75,11 +76,9 @@ class Main extends React.Component {
}) })
eventEmitter.on('editor:fullscreen', this.toggleFullScreen) eventEmitter.on('editor:fullscreen', this.toggleFullScreen)
window.addEventListener('focus', focused)
} }
componentWillUnmount () { componentWillUnmount () {
window.removeEventListener('focus', focused)
eventEmitter.off('editor:fullscreen', this.toggleFullScreen) eventEmitter.off('editor:fullscreen', this.toggleFullScreen)
} }
@@ -103,8 +102,8 @@ class Main extends React.Component {
this.setState({ this.setState({
isRightSliderFocused: false isRightSliderFocused: false
}, () => { }, () => {
let { dispatch } = this.props const { dispatch } = this.props
let newListWidth = this.state.listWidth const newListWidth = this.state.listWidth
// TODO: ConfigManager should dispatch itself. // TODO: ConfigManager should dispatch itself.
ConfigManager.set({listWidth: newListWidth}) ConfigManager.set({listWidth: newListWidth})
dispatch({ dispatch({
@@ -119,8 +118,8 @@ class Main extends React.Component {
this.setState({ this.setState({
isLeftSliderFocused: false isLeftSliderFocused: false
}, () => { }, () => {
let { dispatch } = this.props const { dispatch } = this.props
let navWidth = this.state.navWidth const navWidth = this.state.navWidth
// TODO: ConfigManager should dispatch itself. // TODO: ConfigManager should dispatch itself.
ConfigManager.set({ navWidth }) ConfigManager.set({ navWidth })
dispatch({ dispatch({
@@ -133,7 +132,7 @@ class Main extends React.Component {
handleMouseMove (e) { handleMouseMove (e) {
if (this.state.isRightSliderFocused) { if (this.state.isRightSliderFocused) {
let offset = this.refs.body.getBoundingClientRect().left const offset = this.refs.body.getBoundingClientRect().left
let newListWidth = e.pageX - offset let newListWidth = e.pageX - offset
if (newListWidth < 10) { if (newListWidth < 10) {
newListWidth = 10 newListWidth = 10
@@ -186,7 +185,10 @@ class Main extends React.Component {
} }
render () { render () {
let { config } = this.props const { config } = this.props
// the width of the navigation bar when it is folded/collapsed
const foldedNavigationWidth = 44
return ( return (
<div <div
@@ -216,7 +218,7 @@ class Main extends React.Component {
<div styleName={config.isSideNavFolded ? 'body--expanded' : 'body'} <div styleName={config.isSideNavFolded ? 'body--expanded' : 'body'}
id='main-body' id='main-body'
ref='body' ref='body'
style={{left: config.isSideNavFolded ? 44 : this.state.navWidth}} style={{left: config.isSideNavFolded ? foldedNavigationWidth : this.state.navWidth}}
> >
<TopBar style={{width: this.state.listWidth}} <TopBar style={{width: this.state.listWidth}}
{..._.pick(this.props, [ {..._.pick(this.props, [

View File

@@ -4,7 +4,6 @@
height $topBar-height - 1 height $topBar-height - 1
margin-left: auto; margin-left: auto;
width: 64px; width: 64px;
margin-right: -15px;
.root--expanded .root--expanded
@extend .root @extend .root
@@ -14,10 +13,8 @@ $control-height = 34px
.control .control
position absolute position absolute
top 13px top 13px
left 8px right 7px
right 8px
height $control-height height $control-height
overflow hidden
display flex display flex
.control-newNoteButton .control-newNoteButton
@@ -35,10 +32,11 @@ $control-height = 34px
.control-newNoteButton-tooltip .control-newNoteButton-tooltip
tooltip() tooltip()
position fixed position absolute
pointer-events none pointer-events none
top 50px top 26px
left 433px right -43px
width 124px
z-index 200 z-index 200
padding 5px padding 5px
line-height normal line-height normal
@@ -46,6 +44,13 @@ $control-height = 34px
opacity 0 opacity 0
transition 0.1s transition 0.1s
body[data-theme="white"]
.root, .root--expanded
background-color $ui-white-noteList-backgroundColor
.control-newNoteButton
background-color $ui-white-noteList-backgroundColor
body[data-theme="dark"] body[data-theme="dark"]
.root, .root--expanded .root, .root--expanded
background-color $ui-dark-noteList-backgroundColor background-color $ui-dark-noteList-backgroundColor
@@ -66,3 +71,7 @@ body[data-theme="dark"]
.control-newNoteButton-tooltip .control-newNoteButton-tooltip
darkTooltip() darkTooltip()
body[data-theme="solarized-dark"]
.root, .root--expanded
background-color $ui-solarized-dark-noteList-backgroundColor

View File

@@ -1,12 +1,11 @@
import React, { PropTypes } from 'react' import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './NewNoteButton.styl' import styles from './NewNoteButton.styl'
import _ from 'lodash' 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 { hashHistory } from 'react-router'
import eventEmitter from 'browser/main/lib/eventEmitter' import eventEmitter from 'browser/main/lib/eventEmitter'
import dataApi from 'browser/main/lib/dataApi'
const { remote } = require('electron') const { remote } = require('electron')
const { dialog } = remote const { dialog } = remote
@@ -34,7 +33,7 @@ class NewNoteButton extends React.Component {
} }
handleNewNoteButtonClick (e) { handleNewNoteButtonClick (e) {
const { config, location, dispatch } = this.props const { location, dispatch } = this.props
const { storage, folder } = this.resolveTargetFolder() const { storage, folder } = this.resolveTargetFolder()
modal.open(NewNoteModal, { modal.open(NewNoteModal, {
@@ -51,7 +50,7 @@ class NewNoteButton extends React.Component {
// Find first storage // Find first storage
if (storage == null) { if (storage == null) {
for (let kv of data.storageMap) { for (const kv of data.storageMap) {
storage = kv[1] storage = kv[1]
break break
} }
@@ -85,7 +84,7 @@ class NewNoteButton extends React.Component {
<div styleName='control'> <div styleName='control'>
<button styleName='control-newNoteButton' <button styleName='control-newNoteButton'
onClick={(e) => this.handleNewNoteButtonClick(e)}> onClick={(e) => this.handleNewNoteButtonClick(e)}>
<i className='fa fa-pencil-square-o' /> <img styleName='iconTag' src='../resources/icon/icon-newnote.svg' />
<span styleName='control-newNoteButton-tooltip'> <span styleName='control-newNoteButton-tooltip'>
Make a Note {OSX ? '⌘' : '^'} + n Make a Note {OSX ? '⌘' : '^'} + n
</span> </span>

View File

@@ -21,14 +21,14 @@ $control-height = 30px
.control-sortBy-select .control-sortBy-select
appearance: none; appearance: none;
margin-left 3px margin-left 5px
color $ui-inactive-text-color color $ui-inactive-text-color
padding 0 padding 0
border none border none
background-color transparent background-color transparent
outline none outline none
cursor pointer cursor pointer
font-size 11px font-size 12px
&:hover &:hover
transition 0.2s transition 0.2s
color $ui-text-color color $ui-text-color
@@ -59,6 +59,13 @@ $control-height = 30px
top $control-height top $control-height
overflow auto overflow auto
body[data-theme="white"]
.root
background-color $ui-white-noteList-backgroundColor
.control
background-color $ui-white-noteList-backgroundColor
body[data-theme="dark"] body[data-theme="dark"]
.root .root
border-color $ui-dark-borderColor border-color $ui-dark-borderColor
@@ -82,3 +89,28 @@ body[data-theme="dark"]
color $ui-dark-text-color color $ui-dark-text-color
&:active &:active
color $ui-dark-text-color color $ui-dark-text-color
body[data-theme="solarized-dark"]
.root
border-color $ui-solarized-dark-borderColor
background-color $ui-solarized-dark-noteList-backgroundColor
.control
background-color $ui-solarized-dark-noteList-backgroundColor
border-color $ui-solarized-dark-borderColor
.control-sortBy-select
&:hover
transition 0.2s
color $ui-solarized-dark-text-color
.control-button
color $ui-solarized-dark-inactive-text-color
&:hover
color $ui-solarized-dark-text-color
.control-button--active
color $ui-solarized-dark-text-color
&:active
color $ui-solarized-dark-text-color

View File

@@ -1,4 +1,5 @@
import React, { PropTypes } from 'react' import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './NoteList.styl' import styles from './NoteList.styl'
import moment from 'moment' import moment from 'moment'
@@ -11,8 +12,10 @@ import NoteItemSimple from 'browser/components/NoteItemSimple'
import searchFromNotes from 'browser/lib/search' import searchFromNotes from 'browser/lib/search'
import fs from 'fs' import fs from 'fs'
import { hashHistory } from 'react-router' import { hashHistory } from 'react-router'
import markdown from 'browser/lib/markdown' import markdown from 'browser/lib/markdownTextHelper'
import { findNoteTitle } from 'browser/lib/findNoteTitle' import { findNoteTitle } from 'browser/lib/findNoteTitle'
import store from 'browser/main/store'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
const { remote } = require('electron') const { remote } = require('electron')
const { Menu, MenuItem, dialog } = remote const { Menu, MenuItem, dialog } = remote
@@ -29,6 +32,18 @@ function sortByUpdatedAt (a, b) {
return new Date(b.updatedAt) - new Date(a.updatedAt) return new Date(b.updatedAt) - new Date(a.updatedAt)
} }
function findNoteByKey (notes, noteKey) {
return notes.find((note) => `${note.storage}-${note.key}` === noteKey)
}
function findNotesByKeys (notes, noteKeys) {
return notes.filter((note) => noteKeys.includes(getNoteKey(note)))
}
function getNoteKey (note) {
return `${note.storage}-${note.key}`
}
class NoteList extends React.Component { class NoteList extends React.Component {
constructor (props) { constructor (props) {
super(props) super(props)
@@ -48,9 +63,19 @@ class NoteList extends React.Component {
} }
this.importFromFileHandler = this.importFromFile.bind(this) this.importFromFileHandler = this.importFromFile.bind(this)
this.jumpNoteByHash = this.jumpNoteByHashHandler.bind(this) this.jumpNoteByHash = this.jumpNoteByHashHandler.bind(this)
this.handleNoteListKeyUp = this.handleNoteListKeyUp.bind(this)
this.getNoteKeyFromTargetIndex = this.getNoteKeyFromTargetIndex.bind(this)
this.deleteNote = this.deleteNote.bind(this)
this.focusNote = this.focusNote.bind(this)
this.pinToTop = this.pinToTop.bind(this)
// TODO: not Selected noteKeys but SelectedNote(for reusing)
this.state = { this.state = {
shiftKeyDown: false,
selectedNoteKeys: []
} }
this.contextNotes = []
} }
componentDidMount () { componentDidMount () {
@@ -85,10 +110,11 @@ class NoteList extends React.Component {
} }
componentDidUpdate (prevProps) { componentDidUpdate (prevProps) {
let { location } = this.props const { location } = this.props
if (this.notes.length > 0 && location.query.key == null) { if (this.notes.length > 0 && location.query.key == null) {
let { router } = this.context const { router } = this.context
if (!location.pathname.match(/\/searched/)) this.contextNotes = this.getContextNotes()
router.replace({ router.replace({
pathname: location.pathname, pathname: location.pathname,
query: { query: {
@@ -100,20 +126,18 @@ class NoteList extends React.Component {
// Auto scroll // Auto scroll
if (_.isString(location.query.key) && prevProps.location.query.key === location.query.key) { if (_.isString(location.query.key) && prevProps.location.query.key === location.query.key) {
let targetIndex = _.findIndex(this.notes, (note) => { const targetIndex = this.getTargetIndex()
return note != null && note.storage + '-' + note.key === location.query.key
})
if (targetIndex > -1) { if (targetIndex > -1) {
let list = this.refs.list const list = this.refs.list
let item = list.childNodes[targetIndex] const item = list.childNodes[targetIndex]
if (item == null) return false if (item == null) return false
let overflowBelow = item.offsetTop + item.clientHeight - list.clientHeight - list.scrollTop > 0 const overflowBelow = item.offsetTop + item.clientHeight - list.clientHeight - list.scrollTop > 0
if (overflowBelow) { if (overflowBelow) {
list.scrollTop = item.offsetTop + item.clientHeight - list.clientHeight list.scrollTop = item.offsetTop + item.clientHeight - list.clientHeight
} }
let overflowAbove = list.scrollTop > item.offsetTop const overflowAbove = list.scrollTop > item.offsetTop
if (overflowAbove) { if (overflowAbove) {
list.scrollTop = item.offsetTop list.scrollTop = item.offsetTop
} }
@@ -121,29 +145,54 @@ class NoteList extends React.Component {
} }
} }
focusNote (selectedNoteKeys, noteKey) {
const { router } = this.context
const { location } = this.props
this.setState({
selectedNoteKeys
})
router.push({
pathname: location.pathname,
query: {
key: noteKey
}
})
}
getNoteKeyFromTargetIndex (targetIndex) {
const note = Object.assign({}, this.notes[targetIndex])
const noteKey = getNoteKey(note)
return noteKey
}
selectPriorNote () { selectPriorNote () {
if (this.notes == null || this.notes.length === 0) { if (this.notes == null || this.notes.length === 0) {
return return
} }
let { router } = this.context let { router } = this.context
let { location } = this.props let { location } = this.props
let { selectedNoteKeys, shiftKeyDown } = this.state
let targetIndex = _.findIndex(this.notes, (note) => { let targetIndex = this.getTargetIndex()
return note.storage + '-' + note.key === location.query.key
})
if (targetIndex === 0) { if (targetIndex === 0) {
return return
} }
targetIndex-- targetIndex--
if (targetIndex < 0) targetIndex = 0
router.push({ if (!shiftKeyDown) { selectedNoteKeys = [] }
pathname: location.pathname, const priorNoteKey = this.getNoteKeyFromTargetIndex(targetIndex)
query: { if (selectedNoteKeys.includes(priorNoteKey)) {
key: this.notes[targetIndex].storage + '-' + this.notes[targetIndex].key selectedNoteKeys.pop()
} else {
selectedNoteKeys.push(priorNoteKey)
} }
})
this.focusNote(selectedNoteKeys, priorNoteKey)
ee.emit('list:moved')
} }
selectNextNote () { selectNextNote () {
@@ -152,25 +201,31 @@ class NoteList extends React.Component {
} }
let { router } = this.context let { router } = this.context
let { location } = this.props let { location } = this.props
let { selectedNoteKeys, shiftKeyDown } = this.state
let targetIndex = _.findIndex(this.notes, (note) => { let targetIndex = this.getTargetIndex()
return note.storage + '-' + note.key === location.query.key const isTargetLastNote = targetIndex === this.notes.length - 1
})
if (targetIndex === this.notes.length - 1) { if (isTargetLastNote && shiftKeyDown) {
return
} else if (isTargetLastNote) {
targetIndex = 0 targetIndex = 0
} else { } else {
targetIndex++ targetIndex++
if (targetIndex < 0) targetIndex = 0 if (targetIndex < 0) targetIndex = 0
else if (targetIndex > this.notes.length - 1) targetIndex === this.notes.length - 1 else if (targetIndex > this.notes.length - 1) targetIndex = this.notes.length - 1
} }
router.push({ if (!shiftKeyDown) { selectedNoteKeys = [] }
pathname: location.pathname, const nextNoteKey = this.getNoteKeyFromTargetIndex(targetIndex)
query: { if (selectedNoteKeys.includes(nextNoteKey)) {
key: this.notes[targetIndex].storage + '-' + this.notes[targetIndex].key selectedNoteKeys.pop()
} else {
selectedNoteKeys.push(nextNoteKey)
} }
})
this.focusNote(selectedNoteKeys, nextNoteKey)
ee.emit('list:moved') ee.emit('list:moved')
} }
@@ -183,23 +238,21 @@ class NoteList extends React.Component {
const { router } = this.context const { router } = this.context
const { location } = this.props const { location } = this.props
let targetIndex = _.findIndex(this.notes, (note) => { let targetIndex = this.getTargetIndex()
return note.storage + '-' + note.key === noteHash
})
if (targetIndex < 0) targetIndex = 0 if (targetIndex < 0) targetIndex = 0
router.push({ const selectedNoteKeys = []
pathname: location.pathname, const nextNoteKey = this.getNoteKeyFromTargetIndex(targetIndex)
query: { selectedNoteKeys.push(nextNoteKey)
key: this.notes[targetIndex].storage + '-' + this.notes[targetIndex].key
} this.focusNote(selectedNoteKeys, nextNoteKey)
})
ee.emit('list:moved') ee.emit('list:moved')
} }
handleNoteListKeyDown (e) { handleNoteListKeyDown (e) {
const { shiftKeyDown } = this.state
if (e.metaKey || e.ctrlKey) return true if (e.metaKey || e.ctrlKey) return true
if (e.keyCode === 65 && !e.shiftKey) { if (e.keyCode === 65 && !e.shiftKey) {
@@ -209,7 +262,7 @@ class NoteList extends React.Component {
if (e.keyCode === 68) { if (e.keyCode === 68) {
e.preventDefault() e.preventDefault()
ee.emit('detail:delete') this.deleteNote()
} }
if (e.keyCode === 69) { if (e.keyCode === 69) {
@@ -226,60 +279,110 @@ class NoteList extends React.Component {
e.preventDefault() e.preventDefault()
this.selectNextNote() this.selectNextNote()
} }
if (e.shiftKey) {
this.setState({ shiftKeyDown: true })
}
}
handleNoteListKeyUp (e) {
if (!e.shiftKey) {
this.setState({ shiftKeyDown: false })
}
} }
getNotes () { getNotes () {
let { data, params, location } = this.props const { data, params, location } = this.props
let { router } = this.context
if (location.pathname.match(/\/home/)) { if (location.pathname.match(/\/home/) || location.pathname.match(/\alltags/)) {
return data.noteMap.map((note) => note) const allNotes = data.noteMap.map((note) => note)
this.contextNotes = allNotes
return allNotes
} }
if (location.pathname.match(/\/starred/)) { if (location.pathname.match(/\/starred/)) {
return data.starredSet.toJS() const starredNotes = data.starredSet.toJS().map((uniqueKey) => data.noteMap.get(uniqueKey))
.map((uniqueKey) => data.noteMap.get(uniqueKey)) this.contextNotes = starredNotes
return starredNotes
} }
if (location.pathname.match(/\/searched/)) { if (location.pathname.match(/\/searched/)) {
const searchInputText = document.getElementsByClassName('searchInput')[0].value const searchInputText = document.getElementsByClassName('searchInput')[0].value
if (searchInputText === '') { if (searchInputText === '') {
router.push('/home') return this.sortByPin(this.contextNotes)
} }
return searchFromNotes(this.notes, searchInputText) return searchFromNotes(this.contextNotes, searchInputText)
} }
if (location.pathname.match(/\/trashed/)) { if (location.pathname.match(/\/trashed/)) {
return data.trashedSet.toJS() const trashedNotes = data.trashedSet.toJS().map((uniqueKey) => data.noteMap.get(uniqueKey))
.map((uniqueKey) => data.noteMap.get(uniqueKey)) this.contextNotes = trashedNotes
return trashedNotes
} }
let storageKey = params.storageKey if (location.pathname.match(/\/tags/)) {
let folderKey = params.folderKey return data.noteMap.map(note => {
let storage = data.storageMap.get(storageKey) return note
if (storage == null) return [] }).filter(note => {
return note.tags.includes(params.tagname)
let folder = _.find(storage.folders, {key: folderKey}) })
if (folder == null) {
let storageNoteSet = data.storageNoteMap
.get(storage.key)
if (storageNoteSet == null) storageNoteSet = []
return storageNoteSet
.map((uniqueKey) => data.noteMap.get(uniqueKey))
} }
let folderNoteKeyList = data.folderNoteMap return this.getContextNotes()
.get(storage.key + '-' + folder.key) }
return folderNoteKeyList != null // get notes in the current folder
? folderNoteKeyList getContextNotes () {
.map((uniqueKey) => data.noteMap.get(uniqueKey)) const { data, params } = this.props
: [] const storageKey = params.storageKey
const folderKey = params.folderKey
const storage = data.storageMap.get(storageKey)
if (storage === undefined) return []
const folder = _.find(storage.folders, {key: folderKey})
if (folder === undefined) {
const storageNoteSet = data.storageNoteMap.get(storage.key) || []
return storageNoteSet.map((uniqueKey) => data.noteMap.get(uniqueKey))
}
const folderNoteKeyList = data.folderNoteMap.get(`${storage.key}-${folder.key}`) || []
return folderNoteKeyList.map((uniqueKey) => data.noteMap.get(uniqueKey))
}
sortByPin (unorderedNotes) {
const pinnedNotes = []
const unpinnedNotes = []
unorderedNotes.forEach((note) => {
if (note.isPinned) {
pinnedNotes.push(note)
} else {
unpinnedNotes.push(note)
}
})
return pinnedNotes.concat(unpinnedNotes)
} }
handleNoteClick (e, uniqueKey) { handleNoteClick (e, uniqueKey) {
let { router } = this.context let { router } = this.context
let { location } = this.props let { location } = this.props
let { shiftKeyDown, selectedNoteKeys } = this.state
if (shiftKeyDown && selectedNoteKeys.includes(uniqueKey)) {
const newSelectedNoteKeys = selectedNoteKeys.filter((noteKey) => noteKey !== uniqueKey)
this.setState({
selectedNoteKeys: newSelectedNoteKeys
})
return
}
if (!shiftKeyDown) {
selectedNoteKeys = []
}
selectedNoteKeys.push(uniqueKey)
this.setState({
selectedNoteKeys
})
router.push({ router.push({
pathname: location.pathname, pathname: location.pathname,
@@ -290,9 +393,9 @@ class NoteList extends React.Component {
} }
handleSortByChange (e) { handleSortByChange (e) {
let { dispatch } = this.props const { dispatch } = this.props
let config = { const config = {
sortBy: e.target.value sortBy: e.target.value
} }
@@ -304,9 +407,9 @@ class NoteList extends React.Component {
} }
handleListStyleButtonClick (e, style) { handleListStyleButtonClick (e, style) {
let { dispatch } = this.props const { dispatch } = this.props
let config = { const config = {
listStyle: style listStyle: style
} }
@@ -318,10 +421,7 @@ class NoteList extends React.Component {
} }
alertIfSnippet () { alertIfSnippet () {
let { location } = this.props const targetIndex = this.getTargetIndex()
const targetIndex = _.findIndex(this.notes, (note) => {
return `${note.storage}-${note.key}` === location.query.key
})
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',
@@ -333,13 +433,129 @@ class NoteList extends React.Component {
} }
handleDragStart (e, note) { handleDragStart (e, note) {
const noteData = JSON.stringify(note) const { selectedNoteKeys } = this.state
const notes = this.notes.map((note) => Object.assign({}, note))
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
const noteData = JSON.stringify(selectedNotes)
e.dataTransfer.setData('note', noteData) e.dataTransfer.setData('note', noteData)
this.setState({ selectedNoteKeys: [] })
}
handleNoteContextMenu (e, uniqueKey) {
const { location } = this.props
const { selectedNoteKeys } = this.state
const note = findNoteByKey(this.notes, uniqueKey)
const noteKey = getNoteKey(note)
if (selectedNoteKeys.length === 0 || !selectedNoteKeys.includes(noteKey)) {
this.handleNoteClick(e, uniqueKey)
}
const pinLabel = note.isPinned ? 'Remove pin' : 'Pin to Top'
const deleteLabel = 'Delete Note'
const menu = new Menu()
if (!location.pathname.match(/\/home|\/starred|\/trash/)) {
menu.append(new MenuItem({
label: pinLabel,
click: this.pinToTop
}))
}
menu.append(new MenuItem({
label: deleteLabel,
click: this.deleteNote
}))
menu.popup()
}
pinToTop () {
const { selectedNoteKeys } = this.state
const { dispatch } = this.props
const notes = this.notes.map((note) => Object.assign({}, note))
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
Promise.all(
selectedNotes.map((note) => {
note.isPinned = !note.isPinned
return dataApi
.updateNote(note.storage, note.key, note)
})
)
.then((updatedNotes) => {
updatedNotes.forEach((note) => {
dispatch({
type: 'UPDATE_NOTE',
note
})
})
})
this.setState({ selectedNoteKeys: [] })
}
deleteNote () {
const { dispatch } = this.props
const { selectedNoteKeys } = this.state
const notes = this.notes.map((note) => Object.assign({}, note))
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
const firstNote = selectedNotes[0]
if (firstNote.isTrashed) {
const noteExp = selectedNotes.length > 1 ? 'notes' : 'note'
const dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: 'Confirm note deletion',
detail: `This will permanently remove ${selectedNotes.length} ${noteExp}.`,
buttons: ['Confirm', 'Cancel']
})
if (dialogueButtonIndex === 1) return
Promise.all(
selectedNoteKeys.map((uniqueKey) => {
const storageKey = uniqueKey.split('-')[0]
const noteKey = uniqueKey.split('-')[1]
return dataApi
.deleteNote(storageKey, noteKey)
})
)
.then((data) => {
data.forEach((item) => {
dispatch({
type: 'DELETE_NOTE',
storageKey: item.storageKey,
noteKey: item.noteKey
})
})
})
.catch((err) => {
console.error('Cannot Delete note: ' + err)
})
console.log('Notes were all deleted')
} else {
Promise.all(
selectedNotes.map((note) => {
note.isTrashed = true
return dataApi
.updateNote(note.storage, note.key, note)
})
)
.then((newNotes) => {
newNotes.forEach((newNote) => {
dispatch({
type: 'UPDATE_NOTE',
note: newNote
})
})
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('EDIT_NOTE')
console.log('Notes went to trash')
})
.catch((err) => {
console.error('Notes could not go to trash: ' + err)
})
}
this.setState({ selectedNoteKeys: [] })
} }
importFromFile () { importFromFile () {
const { dispatch, location } = this.props
const options = { const options = {
filters: [ filters: [
{ name: 'Documents', extensions: ['md', 'txt'] } { name: 'Documents', extensions: ['md', 'txt'] }
@@ -347,26 +563,41 @@ class NoteList extends React.Component {
properties: ['openFile', 'multiSelections'] properties: ['openFile', 'multiSelections']
} }
const targetIndex = _.findIndex(this.notes, (note) => {
return note !== null && `${note.storage}-${note.key}` === location.query.key
})
const storageKey = this.notes[targetIndex].storage
const folderKey = this.notes[targetIndex].folder
dialog.showOpenDialog(remote.getCurrentWindow(), options, (filepaths) => { dialog.showOpenDialog(remote.getCurrentWindow(), options, (filepaths) => {
this.addNotesFromFiles(filepaths)
})
}
handleDrop (e) {
e.preventDefault()
const { location } = this.props
const filepaths = Array.from(e.dataTransfer.files).map(file => { return file.path })
if (!location.pathname.match(/\/trashed/)) this.addNotesFromFiles(filepaths)
}
// Add notes to the current folder
addNotesFromFiles (filepaths) {
const { dispatch, location } = this.props
const { storage, folder } = this.resolveTargetFolder()
if (filepaths === undefined) return if (filepaths === undefined) return
filepaths.forEach((filepath) => { filepaths.forEach((filepath) => {
fs.readFile(filepath, (err, data) => { fs.readFile(filepath, (err, data) => {
if (err) throw Error('File reading error: ', err) if (err) throw Error('File reading error: ', err)
fs.stat(filepath, (err, {mtime, birthtime}) => {
if (err) throw Error('File stat reading error: ', err)
const content = data.toString() const content = data.toString()
const newNote = { const newNote = {
content: content, content: content,
folder: folderKey, folder: folder.key,
title: markdown.strip(findNoteTitle(content)), title: markdown.strip(findNoteTitle(content)),
type: 'MARKDOWN_NOTE' type: 'MARKDOWN_NOTE',
createdAt: birthtime,
updatedAt: mtime
} }
dataApi.createNote(storageKey, newNote) dataApi.createNote(storage.key, newNote)
.then((note) => { .then((note) => {
dispatch({ dispatch({
type: 'UPDATE_NOTE', type: 'UPDATE_NOTE',
@@ -374,7 +605,7 @@ class NoteList extends React.Component {
}) })
hashHistory.push({ hashHistory.push({
pathname: location.pathname, pathname: location.pathname,
query: {key: `${note.storage}-${note.key}`} query: {key: getNoteKey(note)}
}) })
}) })
}) })
@@ -382,32 +613,92 @@ class NoteList extends React.Component {
}) })
} }
getTargetIndex () {
const { location } = this.props
const targetIndex = _.findIndex(this.notes, (note) => {
return getNoteKey(note) === location.query.key
})
return targetIndex
}
resolveTargetFolder () {
const { data, params } = this.props
let storage = data.storageMap.get(params.storageKey)
// Find first storage
if (storage == null) {
for (const kv of data.storageMap) {
storage = kv[1]
break
}
}
if (storage == null) this.showMessageBox('No storage for importing note(s)')
const folder = _.find(storage.folders, {key: params.folderKey}) || storage.folders[0]
if (folder == null) this.showMessageBox('No folder for importing note(s)')
return {
storage,
folder
}
}
showMessageBox (message) {
dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: message,
buttons: ['OK']
})
}
render () { render () {
let { location, notes, config, dispatch } = this.props let { location, notes, config, dispatch } = this.props
let { selectedNoteKeys } = this.state
let sortFunc = config.sortBy === 'CREATED_AT' let sortFunc = config.sortBy === 'CREATED_AT'
? sortByCreatedAt ? sortByCreatedAt
: config.sortBy === 'ALPHABETICAL' : config.sortBy === 'ALPHABETICAL'
? sortByAlphabetical ? sortByAlphabetical
: sortByUpdatedAt : sortByUpdatedAt
this.notes = notes = this.getNotes() const sortedNotes = location.pathname.match(/\/home|\/starred|\/trash/)
.sort(sortFunc) ? this.getNotes().sort(sortFunc)
.filter((note) => { : this.sortByPin(this.getNotes().sort(sortFunc))
this.notes = notes = sortedNotes.filter((note) => {
// this is for the trash box // this is for the trash box
if (note.isTrashed !== true || location.pathname === '/trashed') return true if (note.isTrashed !== true || location.pathname === '/trashed') return true
}) })
let noteList = notes moment.locale('en', {
relativeTime: {
future: 'in %s',
past: '%s ago',
s: '%ds',
ss: '%ss',
m: '1m',
mm: '%dm',
h: 'an hour',
hh: '%dh',
d: '1d',
dd: '%dd',
M: '1M',
MM: '%dM',
y: '1Y',
yy: '%dY'
}
})
const noteList = notes
.map(note => { .map(note => {
if (note == null) { if (note == null) {
return null return null
} }
const isDefault = config.listStyle === 'DEFAULT' const isDefault = config.listStyle === 'DEFAULT'
const isActive = location.query.key === note.storage + '-' + note.key const uniqueKey = getNoteKey(note)
const isActive = selectedNoteKeys.includes(uniqueKey)
const dateDisplay = moment( const dateDisplay = moment(
config.sortBy === 'CREATED_AT' config.sortBy === 'CREATED_AT'
? note.createdAt : note.updatedAt ? note.createdAt : note.updatedAt
).fromNow() ).fromNow('D')
const key = `${note.storage}-${note.key}` const key = `${note.storage}-${note.key}`
if (isDefault) { if (isDefault) {
@@ -416,9 +707,11 @@ class NoteList extends React.Component {
isActive={isActive} isActive={isActive}
note={note} note={note}
dateDisplay={dateDisplay} dateDisplay={dateDisplay}
key={key} key={uniqueKey}
handleNoteContextMenu={this.handleNoteContextMenu.bind(this)}
handleNoteClick={this.handleNoteClick.bind(this)} handleNoteClick={this.handleNoteClick.bind(this)}
handleDragStart={this.handleDragStart.bind(this)} handleDragStart={this.handleDragStart.bind(this)}
pathname={location.pathname}
/> />
) )
} }
@@ -427,9 +720,11 @@ class NoteList extends React.Component {
<NoteItemSimple <NoteItemSimple
isActive={isActive} isActive={isActive}
note={note} note={note}
key={key} key={uniqueKey}
handleNoteContextMenu={this.handleNoteContextMenu.bind(this)}
handleNoteClick={this.handleNoteClick.bind(this)} handleNoteClick={this.handleNoteClick.bind(this)}
handleDragStart={this.handleDragStart.bind(this)} handleDragStart={this.handleDragStart.bind(this)}
pathname={location.pathname}
/> />
) )
}) })
@@ -438,16 +733,17 @@ class NoteList extends React.Component {
<div className='NoteList' <div className='NoteList'
styleName='root' styleName='root'
style={this.props.style} style={this.props.style}
onDrop={(e) => this.handleDrop(e)}
> >
<div styleName='control'> <div styleName='control'>
<div styleName='control-sortBy'> <div styleName='control-sortBy'>
<i className='fa fa-bolt' /> <i className='fa fa-angle-down' />
<select styleName='control-sortBy-select' <select styleName='control-sortBy-select'
value={config.sortBy} value={config.sortBy}
onChange={(e) => this.handleSortByChange(e)} onChange={(e) => this.handleSortByChange(e)}
> >
<option value='UPDATED_AT'>Last Updated</option> <option value='UPDATED_AT'>Updated</option>
<option value='CREATED_AT'>Creation Time</option> <option value='CREATED_AT'>Created</option>
<option value='ALPHABETICAL'>Alphabetically</option> <option value='ALPHABETICAL'>Alphabetically</option>
</select> </select>
</div> </div>
@@ -458,7 +754,7 @@ class NoteList extends React.Component {
} }
onClick={(e) => this.handleListStyleButtonClick(e, 'DEFAULT')} onClick={(e) => this.handleListStyleButtonClick(e, 'DEFAULT')}
> >
<i className='fa fa-th-large' /> <img styleName='iconTag' src='../resources/icon/icon-column.svg' />
</button> </button>
<button styleName={config.listStyle === 'SMALL' <button styleName={config.listStyle === 'SMALL'
? 'control-button--active' ? 'control-button--active'
@@ -466,7 +762,7 @@ class NoteList extends React.Component {
} }
onClick={(e) => this.handleListStyleButtonClick(e, 'SMALL')} onClick={(e) => this.handleListStyleButtonClick(e, 'SMALL')}
> >
<i className='fa fa-list-ul' /> <img styleName='iconTag' src='../resources/icon/icon-column-list.svg' />
</button> </button>
</div> </div>
</div> </div>
@@ -474,6 +770,7 @@ class NoteList extends React.Component {
ref='list' ref='list'
tabIndex='-1' tabIndex='-1'
onKeyDown={(e) => this.handleNoteListKeyDown(e)} onKeyDown={(e) => this.handleNoteListKeyDown(e)}
onKeyUp={this.handleNoteListKeyUp}
> >
{noteList} {noteList}
</div> </div>

View File

@@ -0,0 +1,24 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './SwitchButton.styl'
const ListButton = ({
onClick, isTagActive
}) => (
<button styleName={isTagActive ? 'non-active-button' : 'active-button'} onClick={onClick}>
<img src={isTagActive
? '../resources/icon/icon-list.svg'
: '../resources/icon/icon-list-active.svg'
}
/>
<span styleName='tooltip'>Notes</span>
</button>
)
ListButton.propTypes = {
onClick: PropTypes.func.isRequired,
isTagActive: PropTypes.bool.isRequired
}
export default CSSModules(ListButton, styles)

View File

@@ -0,0 +1,19 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './PreferenceButton.styl'
const PreferenceButton = ({
onClick
}) => (
<button styleName='top-menu-preference' onClick={(e) => onClick(e)}>
<img styleName='iconTag' src='../resources/icon/icon-setting.svg' />
<span styleName='tooltip'>Preferences</span>
</button>
)
PreferenceButton.propTypes = {
onClick: PropTypes.func.isRequired
}
export default CSSModules(PreferenceButton, styles)

View File

@@ -0,0 +1,51 @@
.top-menu-preference
navButtonColor()
position absolute
top 22px
right 10px
width 2em
background-color transparent
&:hover
color $ui-button-default--active-backgroundColor
background-color transparent
.tooltip
opacity 1
&:active, &:active:hover
color $ui-button-default--active-backgroundColor
body[data-theme="white"]
.top-menu-preference
navWhiteButtonColor()
background-color transparent
&:hover
color #0B99F1
background-color transparent
&:active, &:active:hover
color #0B99F1
background-color transparent
body[data-theme="dark"]
.top-menu-preference
navDarkButtonColor()
background-color transparent
&:active
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
background-color transparent
&:hover
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
background-color transparent
.tooltip
tooltip()
position absolute
pointer-events none
top 26px
left -20px
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s

View File

@@ -1,62 +1,57 @@
.root .root
absolute top left bottom absolute top left bottom
width $sideNav-width width $sideNav-width
background-color #f9f9f9 background-color #2E3235
user-select none user-select none
color $ui-text-color color $ui-text-color
height: 100vh
display: flex
flex-direction column
.top .top
height $topBar-height padding-bottom 15px
.switch-buttons
background-color transparent
border 0
margin 24px auto 4px 14px
display flex
text-align center
.top-menu
navButtonColor()
height $topBar-height
padding 0 15px
font-size 12px
width 100%
text-align left
&:hover
color $ui-text-color
&:active, &:active:hover
color $ui-text-color
background-color alpha($ui-button--active-backgroundColor, 20%)
.top-menu-label .top-menu-label
margin-left 5px margin-left 5px
overflow ellipsis overflow ellipsis
opacity 0
.storageList .tabBody
absolute left right flex 1
bottom 37px display flex
top 160px flex-direction column
.tag-title
padding-left 15px
padding-bottom 13px
p
color $ui-button-default-color
.tagList
overflow-y auto overflow-y auto
flex: 1
.storageList-empty
padding 0 10px
margin-top 15px
line-height 24px
color $ui-inactive-text-color
.navToggle
navButtonColor()
display block
position absolute
right 5px
bottom 5px
border-radius 16.5px
height 34px
width 34px
line-height 32px
padding 0
.root--folded .root--folded
@extend .root height 100vh
width 44px width $sideNav--folded-width
.storageList-empty background-color #2E3235
white-space nowrap .switch-buttons
transform rotate(90deg) display none
.top
height 60px
.top-menu .top-menu
width 44px - 1 position static
width $sideNav--folded-width
height 60px
text-align center text-align center
&:hover .top-menu-label &:hover .top-menu-label
transition opacity 0.15s transition opacity 0.15s
@@ -65,66 +60,38 @@
position fixed position fixed
display inline-block display inline-block
height 30px height 30px
left 32px left $sideNav--folded-width
padding 0 10px padding 0 10px
margin-top -8px margin-top -8px
opacity 0 opacity 0
margin-left 0 margin-left 0
overflow hidden overflow hidden
background-color $ui-tooltip-backgroundColor
z-index 10 z-index 10
color white color white
line-height 30px line-height 30px
border-top-right-radius 2px border-top-right-radius 2px
border-bottom-right-radius 2px border-bottom-right-radius 2px
pointer-events none pointer-events none
font-size 12px font-size 13px
.menu-button, .menu-button--active .top-menu-preference
text-align center position absolute
&:hover .menu-button-label left 7px
transition opacity 0.15s
opacity 1
.menu-button-label body[data-theme="white"]
position fixed .root, .root--folded
display inline-block background-color #f9f9f9
height 32px color $ui-text-color
left 44px
padding 0 10px
margin-top -8px
margin-left 0
overflow ellipsis
background-color $ui-tooltip-backgroundColor
z-index 10
color white
line-height 32px
border-top-right-radius 2px
border-bottom-right-radius 2px
pointer-events none
opacity 0
font-size 12px
body[data-theme="dark"] body[data-theme="dark"]
.root, .root--folded .root, .root--folded
border-color $ui-dark-borderColor border-right 1px solid $ui-dark-borderColor
background-color $ui-dark-backgroundColor background-color $ui-dark-backgroundColor
color $ui-dark-text-color color $ui-dark-text-color
.top .top
border-color $ui-dark-borderColor border-color $ui-dark-borderColor
.top-menu body[data-theme="solarized-dark"]
navDarkButtonColor() .root, .root--folded
&:active background-color $ui-solarized-dark-backgroundColor
background-color alpha($ui-dark-button--active-backgroundColor, 20%) border-right 1px solid $ui-solarized-dark-borderColor
&:hover
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
.storageList-empty
color $ui-dark-inactive-text-color
.navToggle
&:hover
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
transition 0.15s
color $ui-dark-text-color

View File

@@ -1,4 +1,5 @@
import React, { PropTypes } from 'react' import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './StorageItem.styl' import styles from './StorageItem.styl'
import { hashHistory } from 'react-router' import { hashHistory } from 'react-router'
@@ -8,6 +9,7 @@ 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 eventEmitter from 'browser/main/lib/eventEmitter' import eventEmitter from 'browser/main/lib/eventEmitter'
import _ from 'lodash'
const { remote } = require('electron') const { remote } = require('electron')
const { Menu, MenuItem, dialog } = remote const { Menu, MenuItem, dialog } = remote
@@ -22,7 +24,7 @@ class StorageItem extends React.Component {
} }
handleHeaderContextMenu (e) { handleHeaderContextMenu (e) {
let menu = new Menu() const menu = new Menu()
menu.append(new MenuItem({ menu.append(new MenuItem({
label: 'Add Folder', label: 'Add Folder',
click: (e) => this.handleAddFolderButtonClick(e) click: (e) => this.handleAddFolderButtonClick(e)
@@ -38,7 +40,7 @@ class StorageItem extends React.Component {
} }
handleUnlinkStorageClick (e) { handleUnlinkStorageClick (e) {
let index = dialog.showMessageBox(remote.getCurrentWindow(), { const index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning', type: 'warning',
message: 'Unlink Storage', message: 'Unlink Storage',
detail: '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.)',
@@ -46,7 +48,7 @@ class StorageItem extends React.Component {
}) })
if (index === 0) { if (index === 0) {
let { storage, dispatch } = this.props const { storage, dispatch } = this.props
dataApi.removeStorage(storage.key) dataApi.removeStorage(storage.key)
.then(() => { .then(() => {
dispatch({ dispatch({
@@ -67,7 +69,7 @@ class StorageItem extends React.Component {
} }
handleAddFolderButtonClick (e) { handleAddFolderButtonClick (e) {
let { storage } = this.props const { storage } = this.props
modal.open(CreateFolderModal, { modal.open(CreateFolderModal, {
storage storage
@@ -75,19 +77,19 @@ class StorageItem extends React.Component {
} }
handleHeaderInfoClick (e) { handleHeaderInfoClick (e) {
let { storage } = this.props const { storage } = this.props
hashHistory.push('/storages/' + storage.key) hashHistory.push('/storages/' + storage.key)
} }
handleFolderButtonClick (folderKey) { handleFolderButtonClick (folderKey) {
return (e) => { return (e) => {
let { storage } = this.props const { storage } = this.props
hashHistory.push('/storages/' + storage.key + '/folders/' + folderKey) hashHistory.push('/storages/' + storage.key + '/folders/' + folderKey)
} }
} }
handleFolderButtonContextMenu (e, folder) { handleFolderButtonContextMenu (e, folder) {
let menu = new Menu() const menu = new Menu()
menu.append(new MenuItem({ menu.append(new MenuItem({
label: 'Rename Folder', label: 'Rename Folder',
click: (e) => this.handleRenameFolderClick(e, folder) click: (e) => this.handleRenameFolderClick(e, folder)
@@ -103,7 +105,7 @@ class StorageItem extends React.Component {
} }
handleRenameFolderClick (e, folder) { handleRenameFolderClick (e, folder) {
let { storage } = this.props const { storage } = this.props
modal.open(RenameFolderModal, { modal.open(RenameFolderModal, {
storage, storage,
folder folder
@@ -111,7 +113,7 @@ class StorageItem extends React.Component {
} }
handleFolderDeleteClick (e, folder) { handleFolderDeleteClick (e, folder) {
let index = dialog.showMessageBox(remote.getCurrentWindow(), { const index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning', type: 'warning',
message: 'Delete Folder', message: 'Delete Folder',
detail: '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.',
@@ -119,7 +121,7 @@ class StorageItem extends React.Component {
}) })
if (index === 0) { if (index === 0) {
let { storage, dispatch } = this.props const { storage, dispatch } = this.props
dataApi dataApi
.deleteFolder(storage.key, folder.key) .deleteFolder(storage.key, folder.key)
.then((data) => { .then((data) => {
@@ -142,48 +144,57 @@ class StorageItem extends React.Component {
e.target.style.backgroundColor = e.dataTransfer.getData('defaultColor') e.target.style.backgroundColor = e.dataTransfer.getData('defaultColor')
} }
handleDrop (e, storage, folder, dispatch, location) { dropNote (storage, folder, dispatch, location, noteData) {
e.target.style.opacity = '1' noteData = noteData.filter((note) => folder.key !== note.folder)
e.target.style.backgroundColor = e.dataTransfer.getData('defaultColor') if (noteData.length === 0) return
const noteData = JSON.parse(e.dataTransfer.getData('note')) const newNoteData = noteData.map((note) => Object.assign({}, note, {storage: storage, folder: folder.key}))
const newNoteData = Object.assign({}, noteData, {storage: storage, folder: folder.key})
if (folder.key === noteData.folder) return Promise.all(
dataApi newNoteData.map((note) => dataApi.createNote(storage.key, note))
.createNote(storage.key, newNoteData) )
.then((note) => { .then((createdNoteData) => {
dataApi createdNoteData.forEach((note) => {
.deleteNote(noteData.storage, noteData.key)
.then((data) => {
let dispatchHandler = () => {
dispatch({
type: 'DELETE_NOTE',
storageKey: data.storageKey,
noteKey: data.noteKey
})
}
eventEmitter.once('list:moved', dispatchHandler)
eventEmitter.emit('list:next')
})
.catch((err) => {
console.error(err)
})
dispatch({ dispatch({
type: 'UPDATE_NOTE', type: 'UPDATE_NOTE',
note: note note: note
}) })
hashHistory.push({
pathname: location.pathname,
query: {key: `${note.storage}-${note.key}`}
}) })
}) })
.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
})
})
})
.catch((err) => {
console.error(`error on delete notes: ${err}`)
})
}
handleDrop (e, storage, folder, dispatch, location) {
e.target.style.opacity = '1'
e.target.style.backgroundColor = e.dataTransfer.getData('defaultColor')
const noteData = JSON.parse(e.dataTransfer.getData('note'))
this.dropNote(storage, folder, dispatch, location, noteData)
} }
render () { render () {
let { storage, location, isFolded, data, dispatch } = this.props const { storage, location, isFolded, data, dispatch } = this.props
let { folderNoteMap, trashedSet } = data const { folderNoteMap, trashedSet } = data
let folderList = storage.folders.map((folder) => { const folderList = storage.folders.map((folder) => {
let 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)))
let noteSet = folderNoteMap.get(storage.key + '-' + folder.key) const noteSet = folderNoteMap.get(storage.key + '-' + folder.key)
let noteCount = 0 let noteCount = 0
if (noteSet) { if (noteSet) {
@@ -211,7 +222,7 @@ class StorageItem extends React.Component {
) )
}) })
let isActive = location.pathname.match(new RegExp('\/storages\/' + storage.key + '$')) const isActive = location.pathname.match(new RegExp('\/storages\/' + storage.key + '$'))
return ( return (
<div styleName={isFolded ? 'root--folded' : 'root'} <div styleName={isFolded ? 'root--folded' : 'root'}
@@ -226,9 +237,9 @@ class StorageItem extends React.Component {
<button styleName='header-toggleButton' <button styleName='header-toggleButton'
onMouseDown={(e) => this.handleToggleButtonClick(e)} onMouseDown={(e) => this.handleToggleButtonClick(e)}
> >
<i className={this.state.isOpen <img src={this.state.isOpen
? 'fa fa-caret-down' ? '../resources/icon/icon-down.svg'
: 'fa fa-caret-right' : '../resources/icon/icon-right.svg'
} }
/> />
</button> </button>
@@ -237,7 +248,7 @@ class StorageItem extends React.Component {
<button styleName='header-addFolderButton' <button styleName='header-addFolderButton'
onClick={(e) => this.handleAddFolderButtonClick(e)} onClick={(e) => this.handleAddFolderButtonClick(e)}
> >
<i className='fa fa-plus' /> <img styleName='iconTag' src='../resources/icon/icon-plus.svg' />
</button> </button>
} }
@@ -245,7 +256,7 @@ class StorageItem extends React.Component {
onClick={(e) => this.handleHeaderInfoClick(e)} onClick={(e) => this.handleHeaderInfoClick(e)}
> >
<span styleName='header-info-name'> <span styleName='header-info-name'>
{isFolded ? storage.name.substring(0, 1) : storage.name} {isFolded ? _.truncate(storage.name, {length: 1, omission: ''}) : storage.name}
</span> </span>
{isFolded && {isFolded &&
<span styleName='header-info--folded-tooltip'> <span styleName='header-info--folded-tooltip'>

View File

@@ -5,27 +5,23 @@
.header .header
position relative position relative
height 25px height 36px
width 100% width 100%
margin-bottom 5px margin-bottom 5px
transition 0.15s transition 0.15s
display flex
align-items center
.header--active .header--active
margin-bottom 5px margin-bottom 5px
background-color $ui-button--active-backgroundColor background-color alpha($ui-button-default--active-backgroundColor, 20%)
transition color background-color 0.15s transition color background-color 0.15s
display flex
.header--active align-items center
.header-toggleButton .header-toggleButton
color $ui-text-color
.header--active
.header-info .header-info
color $ui-text-color
.header--active
.header-addFolderButton .header-addFolderButton
color $ui-text-color color #1EC38B
.header-toggleButton .header-toggleButton
navButtonColor() navButtonColor()
@@ -38,23 +34,31 @@
border-radius 50% border-radius 50%
&:hover &:hover
transition 0.2s transition 0.2s
background-color alpha($ui-button--active-backgroundColor, 40%) background-color alpha($ui-button-default--hover-backgroundColor, 40%)
color $ui-text-color color $ui-text-color
.header-info .header-info
navButtonColor() navButtonColor()
display block display block
width 100% width 100%
height 25px height 36px
padding-left 23px padding-left 25px
padding-right 10px padding-right 15px
line-height 22px line-height 22px
cursor pointer cursor pointer
font-size 13px font-size 14px
border none border none
overflow ellipsis overflow ellipsis
text-align left text-align left
background-color alpha($ui-button--active-backgroundColor, 20%) font-weight 600;
background-color transparent
&:hover
color #1EC38B
background-color alpha($ui-button-default--active-backgroundColor, 20%)
transition background-color 0.15s
&:active, &:active:hover
color #1EC38B
background-color alpha($ui-button-default--active-backgroundColor, 20%)
.header-info-path .header-info-path
font-size 10px font-size 10px
@@ -63,22 +67,20 @@
.header-addFolderButton .header-addFolderButton
navButtonColor() navButtonColor()
position absolute position absolute
right 0 right 7px
width 25px width 25px
height 25px height 25px
padding 0 padding 0
border none border none
margin-right 5px
border-radius 50% border-radius 50%
&:hover &:hover
transition 0.2s transition 0.2s
background-color alpha($ui-button--active-backgroundColor, 40%)
color $ui-text-color
.root--folded .root--folded
@extend .root @extend .root
.header .header
width 100% width 100%
padding-left 5px
.header-info .header-info
overflow ellipsis overflow ellipsis
padding 0 0 0 18px padding 0 0 0 18px
@@ -88,6 +90,7 @@
display none display none
.header-toggleButton .header-toggleButton
width 15px width 15px
padding-left 9px
.header-info--folded-tooltip .header-info--folded-tooltip
tooltip() tooltip()
position fixed position fixed
@@ -102,6 +105,33 @@
font-size 10px font-size 10px
margin 0 5px margin 0 5px
body[data-theme="white"]
.header--active
background-color $ui-button--active-backgroundColor
transition color background-color 0.15s
.header-toggleButton
color $ui-text-color
.header-info
color $ui-text-color
.header-addFolderButton
color $ui-text-color
.header-toggleButton
navWhiteButtonColor()
&:hover
background-color alpha($ui-button--active-backgroundColor, 40%)
color $ui-text-color
.header-info
navWhiteButtonColor()
background-color alpha($ui-button--active-backgroundColor, 20%)
.header-addFolderButton
navWhiteButtonColor()
&:hover
background-color alpha($ui-button--active-backgroundColor, 40%)
color $ui-text-color
body[data-theme="dark"] body[data-theme="dark"]
.header--active .header--active
background-color $ui-dark-button--active-backgroundColor background-color $ui-dark-button--active-backgroundColor
@@ -150,3 +180,7 @@ body[data-theme="dark"]
&:active, &:active:hover &:active, &:active:hover
color $ui-dark-text-color color $ui-dark-text-color
background-color $ui-dark-button--active-backgroundColor background-color $ui-dark-button--active-backgroundColor

View File

@@ -0,0 +1,59 @@
.non-active-button
color $ui-inactive-text-color
font-size 16px
border 0
background-color transparent
transition 0.2s
display flex
text-align center
margin-right 4px
position relative
&:hover
color alpha(#239F86, 60%)
.tooltip
opacity 1
.active-button
@extend .non-active-button
color $ui-button-default--active-backgroundColor
.tooltip
tooltip()
position absolute
pointer-events none
top 22px
left -2px
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s
body[data-theme="white"]
.non-active-button
color $ui-inactive-text-color
&:hover
color alpha(#0B99F1, 60%)
.tag-title
p
color $ui-text-color
.non-active-button
&:hover
color alpha(#0B99F1, 60%)
.active-button
@extend .non-active-button
color #0B99F1
body[data-theme="dark"]
.non-active-button
color alpha($ui-dark-text-color, 60%)
&:hover
color alpha(#0B99F1, 60%)
.tag-title
p
color alpha($ui-dark-text-color, 60%)

View File

@@ -0,0 +1,24 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './SwitchButton.styl'
const TagButton = ({
onClick, isTagActive
}) => (
<button styleName={isTagActive ? 'active-button' : 'non-active-button'} onClick={onClick}>
<img src={isTagActive
? '../resources/icon/icon-tag-active.svg'
: '../resources/icon/icon-tag.svg'
}
/>
<span styleName='tooltip'>Tags</span>
</button>
)
TagButton.propTypes = {
onClick: PropTypes.func.isRequired,
isTagActive: PropTypes.bool.isRequired
}
export default CSSModules(TagButton, styles)

View File

@@ -1,30 +1,47 @@
import React, { PropTypes } from 'react' import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
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'
import ConfigManager from 'browser/main/lib/ConfigManager' import ConfigManager from 'browser/main/lib/ConfigManager'
import StorageItem from './StorageItem' import StorageItem from './StorageItem'
import TagListItem from 'browser/components/TagListItem'
import SideNavFilter from 'browser/components/SideNavFilter' import SideNavFilter from 'browser/components/SideNavFilter'
import StorageList from 'browser/components/StorageList'
import NavToggleButton from 'browser/components/NavToggleButton'
import EventEmitter from 'browser/main/lib/eventEmitter'
import PreferenceButton from './PreferenceButton'
import ListButton from './ListButton'
import TagButton from './TagButton'
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
componentDidMount () {
EventEmitter.on('side:preferences', this.handleMenuButtonClick)
}
componentWillUnmount () {
EventEmitter.off('side:preferences', this.handleMenuButtonClick)
}
handleMenuButtonClick (e) { handleMenuButtonClick (e) {
openModal(PreferencesModal) openModal(PreferencesModal)
} }
handleHomeButtonClick (e) { handleHomeButtonClick (e) {
let { router } = this.context const { router } = this.context
router.push('/home') router.push('/home')
} }
handleStarredButtonClick (e) { handleStarredButtonClick (e) {
let { router } = this.context const { router } = this.context
router.push('/starred') router.push('/starred')
} }
handleToggleButtonClick (e) { handleToggleButtonClick (e) {
let { dispatch, config } = this.props const { dispatch, config } = this.props
ConfigManager.set({isSideNavFolded: !config.isSideNavFolded}) ConfigManager.set({isSideNavFolded: !config.isSideNavFolded})
dispatch({ dispatch({
@@ -34,19 +51,100 @@ class SideNav extends React.Component {
} }
handleTrashedButtonClick (e) { handleTrashedButtonClick (e) {
let { router } = this.context const { router } = this.context
router.push('/trashed') router.push('/trashed')
} }
handleSwitchFoldersButtonClick () {
const { router } = this.context
router.push('/home')
}
handleSwitchTagsButtonClick () {
const { router } = this.context
router.push('/alltags')
}
SideNavComponent (isFolded, storageList) {
const { location, data } = this.props
const isHomeActive = !!location.pathname.match(/^\/home$/)
const isStarredActive = !!location.pathname.match(/^\/starred$/)
const isTrashedActive = !!location.pathname.match(/^\/trashed$/)
let component
// TagsMode is not selected
if (!location.pathname.match('/tags') && !location.pathname.match('/alltags')) {
component = (
<div>
<SideNavFilter
isFolded={isFolded}
isHomeActive={isHomeActive}
handleAllNotesButtonClick={(e) => this.handleHomeButtonClick(e)}
isStarredActive={isStarredActive}
isTrashedActive={isTrashedActive}
handleStarredButtonClick={(e) => this.handleStarredButtonClick(e)}
handleTrashedButtonClick={(e) => this.handleTrashedButtonClick(e)}
counterTotalNote={data.noteMap._map.size}
counterStarredNote={data.starredSet._set.size}
counterDelNote={data.trashedSet._set.size}
/>
<StorageList storageList={storageList} />
<NavToggleButton isFolded={isFolded} handleToggleButtonClick={this.handleToggleButtonClick.bind(this)} />
</div>
)
} else {
component = (
<div styleName='tabBody'>
<div styleName='tag-title'>
<p>Tags</p>
</div>
<div styleName='tagList'>
{this.tagListComponent(data)}
</div>
</div>
)
}
return component
}
tagListComponent () {
const { data, location } = this.props
const tagList = data.tagNoteMap.map((tag, key) => {
return key
})
return (
tagList.map(tag => (
<TagListItem
name={tag}
handleClickTagListItem={this.handleClickTagListItem.bind(this)}
isActive={this.getTagActive(location.pathname, tag)}
key={tag}
/>
))
)
}
getTagActive (path, tag) {
const pathSegments = path.split('/')
const pathTag = pathSegments[pathSegments.length - 1]
return pathTag === tag
}
handleClickTagListItem (name) {
const { router } = this.context
router.push(`/tags/${name}`)
}
render () { render () {
let { data, location, config, dispatch } = this.props const { data, location, config, dispatch } = this.props
let isFolded = config.isSideNavFolded const isFolded = config.isSideNavFolded
let isHomeActive = !!location.pathname.match(/^\/home$/)
let isStarredActive = !!location.pathname.match(/^\/starred$/)
let isTrashedActive = !!location.pathname.match(/^\/trashed$/)
let storageList = data.storageMap.map((storage, key) => { const storageList = data.storageMap.map((storage, key) => {
return <StorageItem return <StorageItem
key={storage.key} key={storage.key}
storage={storage} storage={storage}
@@ -56,8 +154,9 @@ class SideNav extends React.Component {
dispatch={dispatch} dispatch={dispatch}
/> />
}) })
let style = {} const style = {}
if (!isFolded) style.width = this.props.width if (!isFolded) style.width = this.props.width
const isTagActive = location.pathname.match(/tag/)
return ( return (
<div className='SideNav' <div className='SideNav'
styleName={isFolded ? 'root--folded' : 'root'} styleName={isFolded ? 'root--folded' : 'root'}
@@ -65,37 +164,15 @@ class SideNav extends React.Component {
style={style} style={style}
> >
<div styleName='top'> <div styleName='top'>
<button styleName='top-menu' <div styleName='switch-buttons'>
onClick={(e) => this.handleMenuButtonClick(e)} <ListButton onClick={this.handleSwitchFoldersButtonClick.bind(this)} isTagActive={isTagActive} />
> <TagButton onClick={this.handleSwitchTagsButtonClick.bind(this)} isTagActive={isTagActive} />
<i className='fa fa-wrench fa-fw' />
<span styleName='top-menu-label'>Preferences</span>
</button>
</div> </div>
<div>
<SideNavFilter <PreferenceButton onClick={this.handleMenuButtonClick} />
isFolded={isFolded}
isHomeActive={isHomeActive}
handleAllNotesButtonClick={(e) => this.handleHomeButtonClick(e)}
isStarredActive={isStarredActive}
isTrashedActive={isTrashedActive}
handleStarredButtonClick={(e) => this.handleStarredButtonClick(e)}
handleTrashedButtonClick={(e) => this.handleTrashedButtonClick(e)}
/>
<div styleName='storageList'>
{storageList.length > 0 ? storageList : (
<div styleName='storageList-empty'>No storage mount.</div>
)}
</div> </div>
<button styleName='navToggle' </div>
onClick={(e) => this.handleToggleButtonClick(e)} {this.SideNavComponent(isFolded, storageList)}
>
{isFolded
? <i className='fa fa-angle-double-right' />
: <i className='fa fa-angle-double-left' />
}
</button>
</div> </div>
) )
} }

View File

@@ -1,9 +1,10 @@
@import('../Detail/DetailVars') @import('../Detail/DetailVars')
.root .root
absolute bottom left right position absolute
height $statusBar-height bottom 10px
background-color $ui-noteDetail-backgroundColor right 10px
z-index 100
display flex display flex
.blank .blank
@@ -21,7 +22,18 @@
.zoom .zoom
navButtonColor() navButtonColor()
height 24px color rgba(0,0,0,.54)
height 20px
display flex
padding 0
align-items center
background-color transparent
&:hover
color $ui-active-color
&:active
color $ui-active-color
span
margin-left 5px
.update .update
navButtonColor() navButtonColor()
@@ -37,14 +49,14 @@
body[data-theme="dark"] body[data-theme="dark"]
.root .root
background-color $ui-dark-noteDetail-backgroundColor
border-color $ui-dark-borderColor border-color $ui-dark-borderColor
box-shadow none box-shadow none
.zoom .zoom
border-color $ui-dark-borderColor border-color $ui-dark-borderColor
background-color transparent
color #f9f9f9
&:hover &:hover
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
transition 0.15s transition 0.15s
color $ui-dark-text-color color $ui-dark-text-color

View File

@@ -1,4 +1,5 @@
import React, { PropTypes } from 'react' import PropTypes from 'prop-types'
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'
@@ -11,7 +12,7 @@ const zoomOptions = [0.8, 0.9, 1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2
class StatusBar extends React.Component { class StatusBar extends React.Component {
updateApp () { updateApp () {
let index = dialog.showMessageBox(remote.getCurrentWindow(), { const index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning', type: 'warning',
message: 'Update Boostnote', message: 'Update Boostnote',
detail: 'New Boostnote is ready to be installed.', detail: 'New Boostnote is ready to be installed.',
@@ -24,7 +25,7 @@ class StatusBar extends React.Component {
} }
handleZoomButtonClick (e) { handleZoomButtonClick (e) {
let menu = new Menu() const menu = new Menu()
zoomOptions.forEach((zoom) => { zoomOptions.forEach((zoom) => {
menu.append(new MenuItem({ menu.append(new MenuItem({
@@ -37,7 +38,7 @@ class StatusBar extends React.Component {
} }
handleZoomMenuItemClick (zoomFactor) { handleZoomMenuItemClick (zoomFactor) {
let { dispatch } = this.props const { dispatch } = this.props
ZoomManager.setZoom(zoomFactor) ZoomManager.setZoom(zoomFactor)
dispatch({ dispatch({
type: 'SET_ZOOM', type: 'SET_ZOOM',
@@ -46,7 +47,7 @@ class StatusBar extends React.Component {
} }
render () { render () {
let { config, status } = this.context const { config, status } = this.context
return ( return (
<div className='StatusBar' <div className='StatusBar'
@@ -55,12 +56,10 @@ class StatusBar extends React.Component {
<button styleName='zoom' <button styleName='zoom'
onClick={(e) => this.handleZoomButtonClick(e)} onClick={(e) => this.handleZoomButtonClick(e)}
> >
<i className='fa fa-search-plus' />&nbsp; <img src='../resources/icon/icon-zoom.svg' />
{Math.floor(config.zoom * 100)}% <span>{Math.floor(config.zoom * 100)}%</span>
</button> </button>
<div styleName='blank' />
{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' /> Ready to Update! <i styleName='update-icon' className='fa fa-cloud-download' /> Ready to Update!

View File

@@ -36,7 +36,7 @@ $control-height = 34px
outline none outline none
border none border none
color $ui-text-color color $ui-text-color
font-size 16px font-size 18px
padding-bottom 2px padding-bottom 2px
background-color $ui-noteList-backgroundColor background-color $ui-noteList-backgroundColor
@@ -112,6 +112,21 @@ $control-height = 34px
opacity 0 opacity 0
transition 0.1s transition 0.1s
body[data-theme="white"]
.root, .root--expanded
background-color $ui-white-noteList-backgroundColor
.control
border-color $ui-dark-borderColor
.control-search
background-color $ui-white-noteList-backgroundColor
.control-search-input
background-color $ui-white-noteList-backgroundColor
input
background-color $ui-white-noteList-backgroundColor
body[data-theme="dark"] body[data-theme="dark"]
.root, .root--expanded .root, .root--expanded
background-color $ui-dark-noteList-backgroundColor background-color $ui-dark-noteList-backgroundColor
@@ -170,3 +185,26 @@ body[data-theme="dark"]
.control-newPostButton-tooltip .control-newPostButton-tooltip
darkTooltip() darkTooltip()
body[data-theme="solarized-dark"]
.root, .root--expanded
background-color $ui-solarized-dark-noteList-backgroundColor
.control
border-color $ui-solarized-dark-borderColor
.control-search
background-color $ui-solarized-dark-noteList-backgroundColor
.control-search-icon
absolute top bottom left
line-height 32px
width 35px
color $ui-solarized-dark-inactive-text-color
background-color $ui-solarized-dark-noteList-backgroundColor
.control-search-input
background-color $ui-solarized-dark-noteList-backgroundColor
input
background-color $ui-solarized-dark-noteList-backgroundColor
color $ui-solarized-dark-text-color

View File

@@ -1,16 +1,11 @@
import React, { PropTypes } from 'react' import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
import styles from './TopBar.styl' import styles from './TopBar.styl'
import _ from 'lodash' import _ from 'lodash'
import NewNoteModal from 'browser/main/modals/NewNoteModal'
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'
const { remote } = require('electron')
const { dialog } = remote
const OSX = window.process.platform === 'darwin'
class TopBar extends React.Component { class TopBar extends React.Component {
constructor (props) { constructor (props) {
super(props) super(props)
@@ -18,7 +13,10 @@ class TopBar extends React.Component {
this.state = { this.state = {
search: '', search: '',
searchOptions: [], searchOptions: [],
isSearching: false isSearching: false,
isAlphabet: false,
isIME: false,
isConfirmTranslation: false
} }
this.focusSearchHandler = () => { this.focusSearchHandler = () => {
@@ -34,13 +32,56 @@ class TopBar extends React.Component {
ee.off('top:focus-search', this.focusSearchHandler) ee.off('top:focus-search', this.focusSearchHandler)
} }
handleSearchChange (e) { handleKeyDown (e) {
let { router } = this.context // reset states
this.setState({
isAlphabet: false,
isIME: false
})
// When the key is an alphabet, del, enter or ctr
if (e.keyCode <= 90 || e.keyCode >= 186 && e.keyCode <= 222) {
this.setState({
isAlphabet: true
})
// When the key is an IME input (Japanese, Chinese)
} else if (e.keyCode === 229) {
this.setState({
isIME: true
})
}
}
handleKeyUp (e) {
const { router } = this.context
// reset states
this.setState({
isConfirmTranslation: false
})
// When the key is translation confirmation (Enter, Space)
if (this.state.isIME && (e.keyCode === 32 || e.keyCode === 13)) {
this.setState({
isConfirmTranslation: true
})
router.push('/searched') router.push('/searched')
this.setState({ this.setState({
search: this.refs.searchInput.value search: this.refs.searchInput.value
}) })
} }
}
handleSearchChange (e) {
const { router } = this.context
if (this.state.isAlphabet || this.state.isConfirmTranslation) {
router.push('/searched')
} else {
e.preventDefault()
}
this.setState({
search: this.refs.searchInput.value
})
}
handleSearchFocus (e) { handleSearchFocus (e) {
this.setState({ this.setState({
@@ -75,7 +116,7 @@ class TopBar extends React.Component {
} }
render () { render () {
let { config, style, data, location } = this.props const { config, style, location } = this.props
return ( return (
<div className='TopBar' <div className='TopBar'
styleName={config.isSideNavFolded ? 'root--expanded' : 'root'} styleName={config.isSideNavFolded ? 'root--expanded' : 'root'}
@@ -93,6 +134,8 @@ class TopBar extends React.Component {
ref='searchInput' ref='searchInput'
value={this.state.search} value={this.state.search}
onChange={(e) => this.handleSearchChange(e)} onChange={(e) => this.handleSearchChange(e)}
onKeyDown={(e) => this.handleKeyDown(e)}
onKeyUp={(e) => this.handleKeyUp(e)}
placeholder='Search' placeholder='Search'
type='text' type='text'
className='searchInput' className='searchInput'

View File

@@ -1,4 +1,5 @@
global-reset() global-reset()
@import '../styles/vars.styl'
DEFAULT_FONTS = 'OpenSans', helvetica, arial, sans-serif DEFAULT_FONTS = 'OpenSans', helvetica, arial, sans-serif
@@ -12,6 +13,7 @@ body
color textColor color textColor
font-size fontSize font-size fontSize
font-weight 200 font-weight 200
-webkit-font-smoothing antialiased
button, input, select, textarea button, input, select, textarea
font-family DEFAULT_FONTS font-family DEFAULT_FONTS
@@ -64,7 +66,7 @@ textarea.block-input
fullsize() fullsize()
modalZIndex= 1000 modalZIndex= 1000
modalBackColor = transparentify(white, 65%) modalBackColor = white
.ace_focus .ace_focus
outline-color rgb(59, 153, 252) outline-color rgb(59, 153, 252)
outline-offset 0px outline-offset 0px
@@ -83,15 +85,19 @@ modalBackColor = transparentify(white, 65%)
absolute top left bottom right absolute top left bottom right
background-color modalBackColor background-color modalBackColor
z-index modalZIndex + 1 z-index modalZIndex + 1
body[data-theme="dark"] body[data-theme="dark"]
.ModalBase .ModalBase
.modalBack .modalBack
background-color alpha(black, 60%) background-color $ui-dark-backgroundColor
.sortableItemHelper
color: $ui-dark-text-color
.CodeMirror .CodeMirror
font-family inherit !important font-family inherit !important
line-height 1.4em line-height 1.4em
height 100% height 96%
.CodeMirror > div > textarea .CodeMirror > div > textarea
margin-bottom -1em margin-bottom -1em
.CodeMirror-focused .CodeMirror-selected .CodeMirror-focused .CodeMirror-selected
@@ -102,3 +108,15 @@ body[data-theme="dark"]
background #B1D7FE background #B1D7FE
::selection ::selection
background #B1D7FE background #B1D7FE
.sortableItemHelper
z-index modalZIndex + 5
body[data-theme="solarized-dark"]
.ModalBase
.modalBack
background-color $ui-solarized-dark-backgroundColor
.sortableItemHelper
color: $ui-solarized-dark-text-color

View File

@@ -27,13 +27,16 @@ document.addEventListener('click', function (e) {
const className = e.target.className const className = e.target.className
if (!className && typeof (className) !== 'string') return if (!className && typeof (className) !== 'string') return
const isInfoButton = className.includes('infoButton') const isInfoButton = className.includes('infoButton')
const isInfoPanel = e.target.offsetParent.className.includes('infoPanel') const offsetParent = e.target.offsetParent
const isInfoPanel = offsetParent !== null
? offsetParent.className.includes('infoPanel')
: false
if (isInfoButton || isInfoPanel) return if (isInfoButton || isInfoPanel) return
const infoPanel = document.querySelector('.infoPanel') const infoPanel = document.querySelector('.infoPanel')
if (infoPanel) infoPanel.style.display = 'none' if (infoPanel) infoPanel.style.display = 'none'
}) })
let el = document.getElementById('content') const el = document.getElementById('content')
const history = syncHistoryWithStore(hashHistory, store) const history = syncHistoryWithStore(hashHistory, store)
function notify (...args) { function notify (...args) {
@@ -41,7 +44,7 @@ function notify (...args) {
} }
function updateApp () { function updateApp () {
let index = dialog.showMessageBox(remote.getCurrentWindow(), { const index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning', type: 'warning',
message: 'Update Boostnote', message: 'Update Boostnote',
detail: 'New Boostnote is ready to be installed.', detail: 'New Boostnote is ready to be installed.',
@@ -62,6 +65,11 @@ ReactDOM.render((
<Route path='starred' /> <Route path='starred' />
<Route path='searched' /> <Route path='searched' />
<Route path='trashed' /> <Route path='trashed' />
<Route path='alltags' />
<Route path='tags'>
<IndexRedirect to='/alltags' />
<Route path=':tagname' />
</Route>
<Route path='storages'> <Route path='storages'>
<IndexRedirect to='/home' /> <IndexRedirect to='/home' />
<Route path=':storageKey'> <Route path=':storageKey'>
@@ -73,7 +81,7 @@ ReactDOM.render((
</Router> </Router>
</Provider> </Provider>
), el, function () { ), el, function () {
let loadingCover = document.getElementById('loadingCover') const loadingCover = document.getElementById('loadingCover')
loadingCover.parentNode.removeChild(loadingCover) loadingCover.parentNode.removeChild(loadingCover)
ipcRenderer.on('update-ready', function () { ipcRenderer.on('update-ready', function () {

View File

@@ -2,19 +2,47 @@ const AWS = require('aws-sdk')
const AMA = require('aws-sdk-mobile-analytics') const AMA = require('aws-sdk-mobile-analytics')
const ConfigManager = require('browser/main/lib/ConfigManager') const ConfigManager = require('browser/main/lib/ConfigManager')
const remote = require('electron').remote
const os = require('os')
let mobileAnalyticsClient
AWS.config.region = 'us-east-1' AWS.config.region = 'us-east-1'
if (process.env.NODE_ENV === 'production' && ConfigManager.default.get().amaEnabled) { if (!getSendEventCond()) {
AWS.config.credentials = new AWS.CognitoIdentityCredentials({ AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: 'us-east-1:xxxxxxxxxxxxxxxxxxxxxxxxx' IdentityPoolId: 'us-east-1:xxxxxxxxxxxxxxxxxxxxxxxxx'
}) })
const mobileAnalyticsClient = new AMA.Manager({
const validPlatformName = convertPlatformName(os.platform())
mobileAnalyticsClient = new AMA.Manager({
appId: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx', appId: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
appTitle: 'xxxxxxxxxx' appTitle: 'xxxxxxxxxx',
appVersionName: remote.app.getVersion().toString(),
platform: validPlatformName
}) })
} }
function convertPlatformName (platformName) {
if (platformName === 'darwin') {
return 'MacOS'
} else if (platformName === 'win32') {
return 'Windows'
} else if (platformName === 'linux') {
return 'Linux'
} else {
return ''
}
}
function getSendEventCond () {
const isDev = process.env.NODE_ENV !== 'production'
const isDisable = !ConfigManager.default.get().amaEnabled
const isOffline = !window.navigator.onLine
return isDev || isDisable || isOffline
}
function initAwsMobileAnalytics () { function initAwsMobileAnalytics () {
if (process.env.NODE_ENV !== 'production' || !ConfigManager.default.get().amaEnabled) return if (getSendEventCond()) return
AWS.config.credentials.get((err) => { AWS.config.credentials.get((err) => {
if (!err) { if (!err) {
console.log('Cognito Identity ID: ' + AWS.config.credentials.identityId) console.log('Cognito Identity ID: ' + AWS.config.credentials.identityId)
@@ -24,16 +52,28 @@ function initAwsMobileAnalytics () {
}) })
} }
function recordDynamicCustomEvent (type) { function recordDynamicCustomEvent (type, options = {}) {
if (process.env.NODE_ENV !== 'production' || !ConfigManager.default.get().amaEnabled) return if (getSendEventCond()) return
mobileAnalyticsClient.recordEvent(type) try {
mobileAnalyticsClient.recordEvent(type, options)
} catch (analyticsError) {
if (analyticsError instanceof ReferenceError) {
console.log(analyticsError.name + ': ' + analyticsError.message)
}
}
} }
function recordStaticCustomEvent () { function recordStaticCustomEvent () {
if (process.env.NODE_ENV !== 'production' || !ConfigManager.default.get().amaEnabled) return if (getSendEventCond()) return
try {
mobileAnalyticsClient.recordEvent('UI_COLOR_THEME', { mobileAnalyticsClient.recordEvent('UI_COLOR_THEME', {
uiColorTheme: ConfigManager.default.get().ui.theme uiColorTheme: ConfigManager.default.get().ui.theme
}) })
} catch (analyticsError) {
if (analyticsError instanceof ReferenceError) {
console.log(analyticsError.name + ': ' + analyticsError.message)
}
}
} }
module.exports = { module.exports = {

View File

@@ -13,10 +13,10 @@ function release (el) {
function fire (command) { function fire (command) {
console.info('COMMAND >>', command) console.info('COMMAND >>', command)
let splitted = command.split(':') const splitted = command.split(':')
let target = splitted[0] const target = splitted[0]
let targetCommand = splitted[1] const targetCommand = splitted[1]
let targetCallees = callees const targetCallees = callees
.filter((callee) => callee.name === target) .filter((callee) => callee.name === target)
targetCallees.forEach((callee) => { targetCallees.forEach((callee) => {

View File

@@ -6,8 +6,6 @@ const win = global.process.platform === 'win32'
const electron = require('electron') const electron = require('electron')
const { ipcRenderer } = electron const { ipcRenderer } = electron
const consts = require('browser/lib/consts') const consts = require('browser/lib/consts')
const path = require('path')
const fs = require('fs')
let isInitialized = false let isInitialized = false
@@ -25,6 +23,7 @@ export const DEFAULT_CONFIG = {
}, },
ui: { ui: {
theme: 'default', theme: 'default',
showCopyNotification: true,
disableDirectWrite: false, disableDirectWrite: false,
defaultNote: 'ALWAYS_ASK' // 'ALWAYS_ASK', 'SNIPPET_NOTE', 'MARKDOWN_NOTE' defaultNote: 'ALWAYS_ASK' // 'ALWAYS_ASK', 'SNIPPET_NOTE', 'MARKDOWN_NOTE'
}, },
@@ -36,13 +35,19 @@ export const DEFAULT_CONFIG = {
indentType: 'space', indentType: 'space',
indentSize: '2', indentSize: '2',
displayLineNumbers: true, displayLineNumbers: true,
switchPreview: 'BLUR' // Available value: RIGHTCLICK, BLUR switchPreview: 'BLUR', // Available value: RIGHTCLICK, BLUR
scrollPastEnd: false,
type: 'SPLIT'
}, },
preview: { preview: {
fontSize: '14', fontSize: '14',
fontFamily: win ? 'Segoe UI' : 'Lato', fontFamily: win ? 'Segoe UI' : 'Lato',
codeBlockTheme: 'dracula', codeBlockTheme: 'dracula',
lineNumber: true lineNumber: true,
latexInlineOpen: '$',
latexInlineClose: '$',
latexBlockOpen: '$$',
latexBlockClose: '$$'
} }
} }
@@ -91,21 +96,29 @@ function get () {
: 'default' : 'default'
if (config.editor.theme !== 'default') { if (config.editor.theme !== 'default') {
if (config.editor.theme.startsWith('solarized')) {
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/solarized.css')
} else {
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/' + config.editor.theme + '.css') editorTheme.setAttribute('href', '../node_modules/codemirror/theme/' + config.editor.theme + '.css')
} }
} }
}
return config return config
} }
function set (updates) { function set (updates) {
let currentConfig = get() const currentConfig = get()
let newConfig = Object.assign({}, DEFAULT_CONFIG, currentConfig, updates) const newConfig = Object.assign({}, DEFAULT_CONFIG, currentConfig, updates)
if (!validate(newConfig)) throw new Error('INVALID CONFIG') if (!validate(newConfig)) throw new Error('INVALID CONFIG')
_save(newConfig) _save(newConfig)
if (newConfig.ui.theme === 'dark') { if (newConfig.ui.theme === 'dark') {
document.body.setAttribute('data-theme', 'dark') document.body.setAttribute('data-theme', 'dark')
} else if (newConfig.ui.theme === 'white') {
document.body.setAttribute('data-theme', 'white')
} else if (newConfig.ui.theme === 'solarized-dark') {
document.body.setAttribute('data-theme', 'solarized-dark')
} else { } else {
document.body.setAttribute('data-theme', 'default') document.body.setAttribute('data-theme', 'default')
} }
@@ -117,13 +130,17 @@ function set (updates) {
editorTheme.setAttribute('rel', 'stylesheet') editorTheme.setAttribute('rel', 'stylesheet')
document.head.appendChild(editorTheme) document.head.appendChild(editorTheme)
} }
let newTheme = consts.THEMES.some((theme) => theme === newConfig.editor.theme) const newTheme = consts.THEMES.some((theme) => theme === newConfig.editor.theme)
? newConfig.editor.theme ? newConfig.editor.theme
: 'default' : 'default'
if (newTheme !== 'default') { if (newTheme !== 'default') {
if (newTheme.startsWith('solarized')) {
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/solarized.css')
} else {
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/' + newTheme + '.css') editorTheme.setAttribute('href', '../node_modules/codemirror/theme/' + newTheme + '.css')
} }
}
ipcRenderer.send('config-renew', { ipcRenderer.send('config-renew', {
config: get() config: get()
@@ -131,7 +148,7 @@ function set (updates) {
} }
function assignConfigValues (originalConfig, rcConfig) { function assignConfigValues (originalConfig, rcConfig) {
let 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.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)

View File

@@ -19,7 +19,7 @@ function setZoom (zoomFactor, noSave = false) {
} }
function getZoom () { function getZoom () {
let config = ConfigManager.get() const config = ConfigManager.get()
return config.zoom return config.zoom
} }

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