1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-13 17:56:25 +00:00

Merge branch 'master' of https://github.com/BoostIO/Boostnote into fixIssue2534

This commit is contained in:
Gonçalo Santos
2020-02-25 03:30:40 +00:00
269 changed files with 11601 additions and 8503 deletions

View File

@@ -1,6 +1,6 @@
{
"extends": ["standard", "standard-jsx", "plugin:react/recommended"],
"plugins": ["react"],
"extends": ["standard", "standard-jsx", "plugin:react/recommended", "prettier"],
"plugins": ["react", "prettier"],
"rules": {
"no-useless-escape": 0,
"prefer-const": ["warn", {
@@ -13,7 +13,8 @@
"react/no-string-refs": 0,
"react/no-find-dom-node": "warn",
"react/no-render-return-value": "warn",
"react/no-deprecated": "warn"
"react/no-deprecated": "warn",
"prettier/prettier": ["error"]
},
"globals": {
"FileReader": true,

5
.prettierrc Normal file
View File

@@ -0,0 +1,5 @@
{
"singleQuote": true,
"semi": false,
"jsxSingleQuote": true
}

View File

@@ -6,11 +6,7 @@ import hljs from 'highlight.js'
import 'codemirror-mode-elixir'
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
import convertModeName from 'browser/lib/convertModeName'
import {
options,
TableEditor,
Alignment
} from '@susisu/mte-kernel'
import { options, TableEditor, Alignment } from '@susisu/mte-kernel'
import TextEditorInterface from 'browser/lib/TextEditorInterface'
import eventEmitter from 'browser/main/lib/eventEmitter'
import iconv from 'iconv-lite'
@@ -20,11 +16,15 @@ import styles from '../components/CodeEditor.styl'
const { ipcRenderer, remote, clipboard } = require('electron')
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
const spellcheck = require('browser/lib/spellcheck')
const buildEditorContextMenu = require('browser/lib/contextMenuBuilder').buildEditorContextMenu
const buildEditorContextMenu = require('browser/lib/contextMenuBuilder')
.buildEditorContextMenu
import { createTurndownService } from '../lib/turndown'
import { languageMaps } from '../lib/CMLanguageList'
import snippetManager from '../lib/SnippetManager'
import {generateInEditor, tocExistsInEditor} from 'browser/lib/markdown-toc-generator'
import {
generateInEditor,
tocExistsInEditor
} from 'browser/lib/markdown-toc-generator'
import markdownlint from 'markdownlint'
import Jsonlint from 'jsonlint-mod'
import { DEFAULT_CONFIG } from '../main/lib/ConfigManager'
@@ -33,12 +33,17 @@ import prettier from 'prettier'
CodeMirror.modeURL = '../node_modules/codemirror/mode/%N/%N.js'
const buildCMRulers = (rulers, enableRulers) =>
(enableRulers ? rulers.map(ruler => ({
enableRulers
? rulers.map(ruler => ({
column: ruler
})) : [])
}))
: []
function translateHotkey(hotkey) {
return hotkey.replace(/\s*\+\s*/g, '-').replace(/Command/g, 'Cmd').replace(/Control/g, 'Ctrl')
return hotkey
.replace(/\s*\+\s*/g, '-')
.replace(/Command/g, 'Cmd')
.replace(/Control/g, 'Ctrl')
}
export default class CodeEditor extends React.Component {
@@ -49,12 +54,17 @@ export default class CodeEditor extends React.Component {
leading: false,
trailing: true
})
this.changeHandler = (editor, changeObject) => this.handleChange(editor, changeObject)
this.highlightHandler = (editor, changeObject) => this.handleHighlight(editor, changeObject)
this.changeHandler = (editor, changeObject) =>
this.handleChange(editor, changeObject)
this.highlightHandler = (editor, changeObject) =>
this.handleHighlight(editor, changeObject)
this.focusHandler = () => {
ipcRenderer.send('editor:focused', true)
}
const debouncedDeletionOfAttachments = _.debounce(attachmentManagement.deleteAttachmentsNotPresentInNote, 30000)
const debouncedDeletionOfAttachments = _.debounce(
attachmentManagement.deleteAttachmentsNotPresentInNote,
30000
)
this.blurHandler = (editor, e) => {
ipcRenderer.send('editor:focused', false)
if (e == null) return null
@@ -66,12 +76,13 @@ export default class CodeEditor extends React.Component {
el = el.parentNode
}
this.props.onBlur != null && this.props.onBlur(e)
const {
const { storageKey, noteKey } = this.props
if (this.props.deleteUnusedAttachments === true) {
debouncedDeletionOfAttachments(
this.editor.getValue(),
storageKey,
noteKey
} = this.props
if (this.props.deleteUnusedAttachments === true) {
debouncedDeletionOfAttachments(this.editor.getValue(), storageKey, noteKey)
)
}
}
this.pasteHandler = (editor, e) => {
@@ -139,9 +150,11 @@ export default class CodeEditor extends React.Component {
}
handleFormatTable() {
this.tableEditor.formatAll(options({
this.tableEditor.formatAll(
options({
textWidthOptions: {}
}))
})
)
}
handleEditorActivity() {
@@ -231,7 +244,10 @@ export default class CodeEditor extends React.Component {
currentConfig.cursorOffset = cm.doc.indexFromPos(cursorPos)
// Prettify contents of editor
const formattedTextDetails = prettier.formatWithCursor(cm.doc.getValue(), currentConfig)
const formattedTextDetails = prettier.formatWithCursor(
cm.doc.getValue(),
currentConfig
)
const formattedText = formattedTextDetails.formatted
const formattedCursorPos = formattedTextDetails.cursorOffset
@@ -246,7 +262,8 @@ export default class CodeEditor extends React.Component {
const appendLineBreak = /\n$/.test(selection)
const sorted = _.split(selection.trim(), '\n').sort()
const sortedString = _.join(sorted, '\n') + (appendLineBreak ? '\n' : '')
const sortedString =
_.join(sorted, '\n') + (appendLineBreak ? '\n' : '')
cm.doc.replaceSelection(sortedString)
},
@@ -273,7 +290,7 @@ export default class CodeEditor extends React.Component {
}
componentDidMount() {
const { rulers, enableRulers, enableMarkdownLint } = this.props
const { rulers, enableRulers, enableMarkdownLint, RTL } = this.props
eventEmitter.on('line:jump', this.scrollToLineHandeler)
snippetManager.init()
@@ -294,9 +311,15 @@ export default class CodeEditor extends React.Component {
scrollPastEnd: this.props.scrollPastEnd,
inputStyle: 'textarea',
dragDrop: false,
direction: RTL ? 'rtl' : 'ltr',
rtlMoveVisually: RTL,
foldGutter: true,
lint: enableMarkdownLint ? this.getCodeEditorLintConfig() : false,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'],
gutters: [
'CodeMirror-linenumbers',
'CodeMirror-foldgutter',
'CodeMirror-lint-markers'
],
autoCloseBrackets: {
pairs: this.props.matchingPairs,
triples: this.props.matchingTriples,
@@ -307,7 +330,9 @@ export default class CodeEditor extends React.Component {
prettierConfig: this.props.prettierConfig
})
document.querySelector('.CodeMirror-lint-markers').style.display = enableMarkdownLint ? 'inline-block' : 'none'
document.querySelector(
'.CodeMirror-lint-markers'
).style.display = enableMarkdownLint ? 'inline-block' : 'none'
if (!this.props.mode && this.props.value && this.props.autoDetect) {
this.autoDetectLanguage(this.props.value)
@@ -350,13 +375,13 @@ export default class CodeEditor extends React.Component {
})
this.editorKeyMap = CodeMirror.normalizeKeyMap({
'Tab': () => {
Tab: () => {
this.tableEditor.nextCell(this.tableEditorOptions)
},
'Shift-Tab': () => {
this.tableEditor.previousCell(this.tableEditorOptions)
},
'Enter': () => {
Enter: () => {
this.tableEditor.nextRow(this.tableEditorOptions)
},
'Ctrl-Enter': () => {
@@ -555,13 +580,22 @@ export default class CodeEditor extends React.Component {
if (prevProps.keyMap !== this.props.keyMap) {
needRefresh = true
}
if (prevProps.enableMarkdownLint !== enableMarkdownLint || prevProps.customMarkdownLintConfig !== customMarkdownLintConfig) {
if (prevProps.RTL !== this.props.RTL) {
this.editor.setOption('direction', this.props.RTL ? 'rtl' : 'ltr')
this.editor.setOption('rtlMoveVisually', this.props.RTL)
}
if (
prevProps.enableMarkdownLint !== enableMarkdownLint ||
prevProps.customMarkdownLintConfig !== customMarkdownLintConfig
) {
if (!enableMarkdownLint) {
this.editor.setOption('lint', { default: false })
document.querySelector('.CodeMirror-lint-markers').style.display = 'none'
document.querySelector('.CodeMirror-lint-markers').style.display =
'none'
} else {
this.editor.setOption('lint', this.getCodeEditorLintConfig())
document.querySelector('.CodeMirror-lint-markers').style.display = 'inline-block'
document.querySelector('.CodeMirror-lint-markers').style.display =
'inline-block'
}
needRefresh = true
}
@@ -593,9 +627,11 @@ export default class CodeEditor extends React.Component {
this.editor.setOption('scrollPastEnd', this.props.scrollPastEnd)
}
if (prevProps.matchingPairs !== this.props.matchingPairs ||
if (
prevProps.matchingPairs !== this.props.matchingPairs ||
prevProps.matchingTriples !== this.props.matchingTriples ||
prevProps.explodingPairs !== this.props.explodingPairs) {
prevProps.explodingPairs !== this.props.explodingPairs
) {
const bracketObject = {
pairs: this.props.matchingPairs,
triples: this.props.matchingTriples,
@@ -640,11 +676,18 @@ export default class CodeEditor extends React.Component {
const elem = document.getElementById('editor-bottom-panel')
elem.parentNode.removeChild(elem)
} else {
this.editor.addPanel(this.createSpellCheckPanel(), {position: 'bottom'})
this.editor.addPanel(this.createSpellCheckPanel(), {
position: 'bottom'
})
}
}
if (prevProps.deleteUnusedAttachments !== this.props.deleteUnusedAttachments) {
this.editor.setOption('deleteUnusedAttachments', this.props.deleteUnusedAttachments)
if (
prevProps.deleteUnusedAttachments !== this.props.deleteUnusedAttachments
) {
this.editor.setOption(
'deleteUnusedAttachments',
this.props.deleteUnusedAttachments
)
}
if (needRefresh) {
@@ -656,10 +699,12 @@ export default class CodeEditor extends React.Component {
const { mode } = this.props
const checkMarkdownNoteIsOpen = mode === 'Boost Flavored Markdown'
return checkMarkdownNoteIsOpen ? {
return checkMarkdownNoteIsOpen
? {
getAnnotations: this.validatorOfMarkdown,
async: true
} : false
}
: false
}
validatorOfMarkdown(text, updateLinting) {
@@ -687,7 +732,7 @@ export default class CodeEditor extends React.Component {
let ruleNames = ''
item.ruleNames.map((ruleName, index) => {
ruleNames += ruleName
ruleNames += (index === item.ruleNames.length - 1) ? ': ' : '/'
ruleNames += index === item.ruleNames.length - 1 ? ': ' : '/'
})
const lineNumber = item.lineNumber - 1
foundIssues.push({
@@ -720,7 +765,11 @@ export default class CodeEditor extends React.Component {
// Check if one of the changed lines contains a headline
for (let line = 0; line < changeObject.text.length; line++) {
if (this.linePossibleContainsHeadline(editor.getLine(changeObject.from.line + line))) {
if (
this.linePossibleContainsHeadline(
editor.getLine(changeObject.from.line + line)
)
) {
requireTocUpdate = true
break
}
@@ -776,7 +825,7 @@ export default class CodeEditor extends React.Component {
highlightedLines.splice(highlightedLines.indexOf(lineNumber), 1)
// Lines that need to be relocated
if (lineNumber >= (start + linesRemoved)) {
if (lineNumber >= start + linesRemoved) {
newLines.push(lineNumber + offset)
}
}
@@ -795,10 +844,18 @@ export default class CodeEditor extends React.Component {
if (!lines.includes(changeObject)) {
lines.push(changeObject)
editor.addLineClass(changeObject, 'text', 'CodeMirror-activeline-background')
editor.addLineClass(
changeObject,
'text',
'CodeMirror-activeline-background'
)
} else {
lines.splice(lines.indexOf(changeObject), 1)
editor.removeLineClass(changeObject, 'text', 'CodeMirror-activeline-background')
editor.removeLineClass(
changeObject,
'text',
'CodeMirror-activeline-background'
)
}
if (this.props.onChange) {
this.props.onChange(editor)
@@ -882,15 +939,16 @@ export default class CodeEditor extends React.Component {
setLineContent(lineNumber, content) {
const prevContent = this.editor.getLine(lineNumber)
const prevContentLength = prevContent ? prevContent.length : 0
this.editor.replaceRange(content, { line: lineNumber, ch: 0 }, { line: lineNumber, ch: prevContentLength })
this.editor.replaceRange(
content,
{ line: lineNumber, ch: 0 },
{ line: lineNumber, ch: prevContentLength }
)
}
handleDropImage(dropEvent) {
dropEvent.preventDefault()
const {
storageKey,
noteKey
} = this.props
const { storageKey, noteKey } = this.props
attachmentManagement.handleAttachmentDrop(
this,
storageKey,
@@ -911,25 +969,32 @@ export default class CodeEditor extends React.Component {
handlePaste(editor, forceSmartPaste) {
const { storageKey, noteKey, fetchUrlTitle, enableSmartPaste } = this.props
const isURL = str => /(?:^\w+:|^)\/\/(?:[^\s\.]+\.\S{2}|localhost[\:?\d]*)/.test(str)
const isURL = str =>
/(?:^\w+:|^)\/\/(?:[^\s\.]+\.\S{2}|localhost[\:?\d]*)/.test(str)
const isInLinkTag = editor => {
const startCursor = editor.getCursor('start')
const prevChar = editor.getRange({
const prevChar = editor.getRange(
{
line: startCursor.line,
ch: startCursor.ch - 2
}, {
},
{
line: startCursor.line,
ch: startCursor.ch
})
}
)
const endCursor = editor.getCursor('end')
const nextChar = editor.getRange({
const nextChar = editor.getRange(
{
line: endCursor.line,
ch: endCursor.ch
}, {
},
{
line: endCursor.line,
ch: endCursor.ch + 1
})
}
)
return prevChar === '](' && nextChar === ')'
}
@@ -941,7 +1006,7 @@ export default class CodeEditor extends React.Component {
return true
}
let line = line = cursor.line - 1
let line = (line = cursor.line - 1)
while (line >= 0) {
token = editor.getTokenAt({
ch: 3,
@@ -973,7 +1038,11 @@ export default class CodeEditor extends React.Component {
if (isInFencedCodeBlock(editor)) {
this.handlePasteText(editor, pastedTxt)
} else if (fetchUrlTitle && isMarkdownTitleURL(pastedTxt) && !isInLinkTag(editor)) {
} else if (
fetchUrlTitle &&
isMarkdownTitleURL(pastedTxt) &&
!isInLinkTag(editor)
) {
this.handlePasteUrl(editor, pastedTxt)
} else if (fetchUrlTitle && isURL(pastedTxt) && !isInLinkTag(editor)) {
this.handlePasteUrl(editor, pastedTxt)
@@ -1082,10 +1151,12 @@ export default class CodeEditor extends React.Component {
body,
'text/html'
)
const escapePipe = (str) => {
const escapePipe = str => {
return str.replace('|', '\\|')
}
const linkWithTitle = `[${escapePipe(parsedBody.title)}](${pastedTxt})`
const linkWithTitle = `[${escapePipe(
parsedBody.title
)}](${pastedTxt})`
resolve(linkWithTitle)
} catch (e) {
reject(e)
@@ -1108,7 +1179,11 @@ export default class CodeEditor extends React.Component {
// make sure that we skip the invalid lines althrough this case should not be happened.
continue
}
this.editor.addLineClass(lineNumber, 'text', 'CodeMirror-activeline-background')
this.editor.addLineClass(
lineNumber,
'text',
'CodeMirror-activeline-background'
)
}
}
@@ -1138,8 +1213,8 @@ export default class CodeEditor extends React.Component {
return response.arrayBuffer().then(buff => {
return new Promise((resolve, reject) => {
try {
const charset = _charset !== undefined &&
iconv.encodingExists(_charset)
const charset =
_charset !== undefined && iconv.encodingExists(_charset)
? _charset
: 'utf-8'
resolve(iconv.decode(Buffer.from(buff), charset).toString())
@@ -1154,7 +1229,10 @@ export default class CodeEditor extends React.Component {
return contentType
.split(';')
.filter(str => {
return str.trim().toLowerCase().startsWith('charset')
return str
.trim()
.toLowerCase()
.startsWith('charset')
})
.map(str => {
return str.replace(/['"]/g, '').split('=')[1]
@@ -1162,16 +1240,12 @@ export default class CodeEditor extends React.Component {
}
render() {
const {
className,
fontSize
} = this.props
const { className, fontSize } = this.props
const fontFamily = normalizeEditorFontFamily(this.props.fontFamily)
const width = this.props.width
return (<
div className={
className == null ? 'CodeEditor' : `CodeEditor ${className}`
}
return (
<div
className={className == null ? 'CodeEditor' : `CodeEditor ${className}`}
ref='root'
tabIndex='-1'
style={{
@@ -1179,9 +1253,7 @@ export default class CodeEditor extends React.Component {
fontSize: fontSize,
width: width
}}
onDrop={
e => this.handleDropImage(e)
}
onDrop={e => this.handleDropImage(e)}
/>
)
}
@@ -1193,7 +1265,9 @@ export default class CodeEditor extends React.Component {
const dropdown = document.createElement('select')
dropdown.title = 'Spellcheck'
dropdown.className = styles['spellcheck-select']
dropdown.addEventListener('change', (e) => spellcheck.setLanguage(this.editor, dropdown.value))
dropdown.addEventListener('change', e =>
spellcheck.setLanguage(this.editor, dropdown.value)
)
const options = spellcheck.getAvailableDictionaries()
for (const op of options) {
const option = document.createElement('option')
@@ -1219,7 +1293,8 @@ CodeEditor.propTypes = {
spellCheck: PropTypes.bool,
enableMarkdownLint: PropTypes.bool,
customMarkdownLintConfig: PropTypes.string,
deleteUnusedAttachments: PropTypes.bool
deleteUnusedAttachments: PropTypes.bool,
RTL: PropTypes.bool
}
CodeEditor.defaultProps = {
@@ -1235,5 +1310,6 @@ CodeEditor.defaultProps = {
enableMarkdownLint: DEFAULT_CONFIG.editor.enableMarkdownLint,
customMarkdownLintConfig: DEFAULT_CONFIG.editor.customMarkdownLintConfig,
prettierConfig: DEFAULT_CONFIG.editor.prettierConfig,
deleteUnusedAttachments: DEFAULT_CONFIG.editor.deleteUnusedAttachments
deleteUnusedAttachments: DEFAULT_CONFIG.editor.deleteUnusedAttachments,
RTL: false
}

View File

@@ -44,13 +44,22 @@ class ColorPicker extends React.Component {
}
return (
<div styleName='colorPicker' style={{top: `${alignY}px`, left: `${alignX}px`}}>
<div
styleName='colorPicker'
style={{ top: `${alignY}px`, left: `${alignX}px` }}
>
<div styleName='cover' onClick={onCancel} />
<SketchPicker color={color} onChange={this.onColorChange} />
<div styleName='footer'>
<button styleName='btn-reset' onClick={onReset}>Reset</button>
<button styleName='btn-cancel' onClick={onCancel}>Cancel</button>
<button styleName='btn-confirm' onClick={this.handleConfirm}>Confirm</button>
<button styleName='btn-reset' onClick={onReset}>
Reset
</button>
<button styleName='btn-cancel' onClick={onCancel}>
Cancel
</button>
<button styleName='btn-confirm' onClick={this.handleConfirm}>
Confirm
</button>
</div>
</div>
)

View File

@@ -20,7 +20,10 @@ class MarkdownEditor extends React.Component {
this.supportMdSelectionBold = [16, 17, 186]
this.state = {
status: props.config.editor.switchPreview === 'RIGHTCLICK' ? props.config.editor.delfaultStatus : 'CODE',
status:
props.config.editor.switchPreview === 'RIGHTCLICK'
? props.config.editor.delfaultStatus
: 'CODE',
renderValue: props.value,
keyPressed: new Set(),
isLocked: props.isLocked
@@ -52,11 +55,14 @@ class MarkdownEditor extends React.Component {
}
focusEditor() {
this.setState({
this.setState(
{
status: 'CODE'
}, () => {
},
() => {
this.refs.code.focus()
})
}
)
}
queueRendering(value) {
@@ -90,9 +96,11 @@ class MarkdownEditor extends React.Component {
const { config } = this.props
if (config.editor.switchPreview === 'RIGHTCLICK') {
const newStatus = this.state.status === 'PREVIEW' ? 'CODE' : 'PREVIEW'
this.setState({
this.setState(
{
status: newStatus
}, () => {
},
() => {
if (newStatus === 'CODE') {
this.refs.code.focus()
} else {
@@ -103,7 +111,8 @@ class MarkdownEditor extends React.Component {
const newConfig = Object.assign({}, config)
newConfig.editor.delfaultStatus = newStatus
ConfigManager.set(newConfig)
})
}
)
}
}
@@ -111,16 +120,21 @@ class MarkdownEditor extends React.Component {
if (this.state.isLocked) return
this.setState({ keyPressed: new Set() })
const { config } = this.props
if (config.editor.switchPreview === 'BLUR' ||
(config.editor.switchPreview === 'DBL_CLICK' && this.state.status === 'CODE')
if (
config.editor.switchPreview === 'BLUR' ||
(config.editor.switchPreview === 'DBL_CLICK' &&
this.state.status === 'CODE')
) {
const cursorPosition = this.refs.code.editor.getCursor()
this.setState({
this.setState(
{
status: 'PREVIEW'
}, () => {
},
() => {
this.refs.preview.focus()
this.refs.preview.scrollToRow(cursorPosition.line)
})
}
)
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
}
}
@@ -130,12 +144,15 @@ class MarkdownEditor extends React.Component {
this.setState({ keyPressed: new Set() })
const { config } = this.props
if (config.editor.switchPreview === 'DBL_CLICK') {
this.setState({
this.setState(
{
status: 'CODE'
}, () => {
},
() => {
this.refs.code.focus()
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
})
}
)
}
}
@@ -145,12 +162,18 @@ class MarkdownEditor extends React.Component {
handlePreviewMouseUp(e) {
const { config } = this.props
if (config.editor.switchPreview === 'BLUR' && new Date() - this.previewMouseDownedAt < 200) {
this.setState({
if (
config.editor.switchPreview === 'BLUR' &&
new Date() - this.previewMouseDownedAt < 200
) {
this.setState(
{
status: 'CODE'
}, () => {
},
() => {
this.refs.code.focus()
})
}
)
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
}
}
@@ -164,9 +187,9 @@ class MarkdownEditor extends React.Component {
const checkReplace = /\[x]/i
const uncheckReplace = /\[ ]/
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 lineIndex =
parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
const lines = this.refs.code.value.split('\n')
const targetLine = lines[lineIndex]
let newLine = targetLine
@@ -183,11 +206,14 @@ class MarkdownEditor extends React.Component {
focus() {
if (this.state.status === 'PREVIEW') {
this.setState({
this.setState(
{
status: 'CODE'
}, () => {
},
() => {
this.refs.code.focus()
})
}
)
} else {
this.refs.code.focus()
}
@@ -206,15 +232,23 @@ class MarkdownEditor extends React.Component {
const keyPressed = this.state.keyPressed
keyPressed.add(e.keyCode)
this.setState({ keyPressed })
const isNoteHandlerKey = (el) => { return keyPressed.has(el) }
const isNoteHandlerKey = el => {
return keyPressed.has(el)
}
// These conditions are for ctrl-e and ctrl-w
if (keyPressed.size === this.escapeFromEditor.length &&
!this.state.isLocked && this.state.status === 'CODE' &&
this.escapeFromEditor.every(isNoteHandlerKey)) {
if (
keyPressed.size === this.escapeFromEditor.length &&
!this.state.isLocked &&
this.state.status === 'CODE' &&
this.escapeFromEditor.every(isNoteHandlerKey)
) {
this.handleContextMenu()
if (config.editor.switchPreview === 'BLUR') document.activeElement.blur()
}
if (keyPressed.size === this.supportMdSelectionBold.length && this.supportMdSelectionBold.every(isNoteHandlerKey)) {
if (
keyPressed.size === this.supportMdSelectionBold.length &&
this.supportMdSelectionBold.every(isNoteHandlerKey)
) {
this.addMdAroundWord('**')
}
}
@@ -227,20 +261,27 @@ class MarkdownEditor extends React.Component {
const word = this.refs.code.editor.findWordAt(currentCaret)
const cmDoc = this.refs.code.editor.getDoc()
cmDoc.replaceRange(mdElement, word.anchor)
cmDoc.replaceRange(mdElement, { line: word.head.line, ch: word.head.ch + mdElement.length })
cmDoc.replaceRange(mdElement, {
line: word.head.line,
ch: word.head.ch + mdElement.length
})
}
addMdAroundSelection(mdElement) {
this.refs.code.editor.replaceSelection(`${mdElement}${this.refs.code.editor.getSelection()}${mdElement}`)
this.refs.code.editor.replaceSelection(
`${mdElement}${this.refs.code.editor.getSelection()}${mdElement}`
)
}
handleDropImage(dropEvent) {
dropEvent.preventDefault()
const { storageKey, noteKey } = this.props
this.setState({
this.setState(
{
status: 'CODE'
}, () => {
},
() => {
this.refs.code.focus()
this.refs.code.editor.execCommand('goDocEnd')
@@ -253,7 +294,8 @@ class MarkdownEditor extends React.Component {
noteKey,
dropEvent
)
})
}
)
}
handleKeyUp(e) {
@@ -267,7 +309,15 @@ class MarkdownEditor extends React.Component {
}
render() {
const {className, value, config, storageKey, noteKey, linesHighlighted} = this.props
const {
className,
value,
config,
storageKey,
noteKey,
linesHighlighted,
RTL
} = this.props
let editorFontSize = parseInt(config.editor.fontSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
@@ -275,23 +325,24 @@ class MarkdownEditor extends React.Component {
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
const previewStyle = {}
if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none'
if (this.props.ignorePreviewPointerEvents)
previewStyle.pointerEvents = 'none'
const storage = findStorage(storageKey)
return (
<div className={className == null
? 'MarkdownEditor'
: `MarkdownEditor ${className}`
<div
className={
className == null ? 'MarkdownEditor' : `MarkdownEditor ${className}`
}
onContextMenu={(e) => this.handleContextMenu(e)}
onContextMenu={e => this.handleContextMenu(e)}
tabIndex='-1'
onKeyDown={(e) => this.handleKeyDown(e)}
onKeyUp={(e) => this.handleKeyUp(e)}
onKeyDown={e => this.handleKeyDown(e)}
onKeyUp={e => this.handleKeyUp(e)}
>
<CodeEditor styleName={this.state.status === 'CODE'
? 'codeEditor'
: 'codeEditor--hide'
<CodeEditor
styleName={
this.state.status === 'CODE' ? 'codeEditor' : 'codeEditor--hide'
}
ref='code'
mode='Boost Flavored Markdown'
@@ -315,8 +366,8 @@ class MarkdownEditor extends React.Component {
fetchUrlTitle={config.editor.fetchUrlTitle}
enableTableEditor={config.editor.enableTableEditor}
linesHighlighted={linesHighlighted}
onChange={(e) => this.handleChange(e)}
onBlur={(e) => this.handleBlur(e)}
onChange={e => this.handleChange(e)}
onBlur={e => this.handleBlur(e)}
spellCheck={config.editor.spellcheck}
enableSmartPaste={config.editor.enableSmartPaste}
hotkey={config.hotkey}
@@ -325,10 +376,11 @@ class MarkdownEditor extends React.Component {
customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
prettierConfig={config.editor.prettierConfig}
deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
RTL={RTL}
/>
<MarkdownPreview styleName={this.state.status === 'PREVIEW'
? 'preview'
: 'preview--hide'
<MarkdownPreview
styleName={
this.state.status === 'PREVIEW' ? 'preview' : 'preview--hide'
}
style={previewStyle}
theme={config.ui.theme}
@@ -346,20 +398,21 @@ class MarkdownEditor extends React.Component {
sanitize={config.preview.sanitize}
mermaidHTMLLabel={config.preview.mermaidHTMLLabel}
ref='preview'
onContextMenu={(e) => this.handleContextMenu(e)}
onDoubleClick={(e) => this.handleDoubleClick(e)}
onContextMenu={e => this.handleContextMenu(e)}
onDoubleClick={e => this.handleDoubleClick(e)}
tabIndex='0'
value={this.state.renderValue}
onMouseUp={(e) => this.handlePreviewMouseUp(e)}
onMouseDown={(e) => this.handlePreviewMouseDown(e)}
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
onMouseUp={e => this.handlePreviewMouseUp(e)}
onMouseDown={e => this.handlePreviewMouseDown(e)}
onCheckboxClick={e => this.handleCheckboxClick(e)}
showCopyNotification={config.ui.showCopyNotification}
storagePath={storage.path}
noteKey={noteKey}
customCSS={config.preview.customCSS}
allowCustomCSS={config.preview.allowCustomCSS}
lineThroughCheckbox={config.preview.lineThroughCheckbox}
onDrop={(e) => this.handleDropImage(e)}
onDrop={e => this.handleDropImage(e)}
RTL={RTL}
/>
</div>
)

View File

@@ -11,6 +11,7 @@ import mermaidRender from './render/MermaidRender'
import SequenceDiagram from '@rokt33r/js-sequence-diagrams'
import Chart from 'chart.js'
import eventEmitter from 'browser/main/lib/eventEmitter'
import config from 'browser/main/lib/ConfigManager'
import htmlTextHelper from 'browser/lib/htmlTextHelper'
import convertModeName from 'browser/lib/convertModeName'
import copy from 'copy-to-clipboard'
@@ -21,10 +22,13 @@ import yaml from 'js-yaml'
import { render } from 'react-dom'
import Carousel from 'react-image-carousel'
import ConfigManager from '../main/lib/ConfigManager'
import uiThemes from 'browser/lib/ui-themes'
import i18n from 'browser/lib/i18n'
const { remote, shell } = require('electron')
const attachmentManagement = require('../main/lib/dataApi/attachmentManagement')
const buildMarkdownPreviewContextMenu = require('browser/lib/contextMenuBuilder').buildMarkdownPreviewContextMenu
const buildMarkdownPreviewContextMenu = require('browser/lib/contextMenuBuilder')
.buildMarkdownPreviewContextMenu
const { app } = remote
const path = require('path')
@@ -63,7 +67,8 @@ function buildStyle (opts) {
scrollPastEnd,
theme,
allowCustomCSS,
customCSS
customCSS,
RTL
} = opts
return `
@font-face {
@@ -100,11 +105,17 @@ ${markdownStyle}
body {
font-family: '${fontFamily.join("','")}';
font-size: ${fontSize}px;
${scrollPastEnd ? `
${
scrollPastEnd
? `
padding-bottom: 90vh;
box-sizing: border-box;
`
: ''}
: ''
}
${RTL ? 'direction: rtl;' : ''}
${RTL ? 'text-align: right;' : ''}
}
@media print {
body {
@@ -114,6 +125,8 @@ body {
code {
font-family: '${codeBlockFontFamily.join("','")}';
background-color: rgba(0,0,0,0.04);
text-align: left;
direction: ltr;
}
.lineNumber {
${lineNumber && 'display: block !important;'}
@@ -144,14 +157,22 @@ h1, h2 {
border: none;
}
h3 {
margin: 1em 0 0.8em;
}
h4, h5, h6 {
margin: 1.1em 0 0.5em;
}
h1 {
padding-bottom: 4px;
padding: 0.2em 0 0.2em;
margin: 1em 0 8px;
}
h2 {
padding-bottom: 0.2em;
margin: 1em 0 0.37em;
padding: 0.2em 0 0.2em;
margin: 1em 0 0.7em;
}
body p {
@@ -174,10 +195,12 @@ ${allowCustomCSS ? customCSS : ''}
const scrollBarStyle = `
::-webkit-scrollbar {
${config.get().ui.showScrollBar ? '' : 'display: none;'}
width: 12px;
}
::-webkit-scrollbar-thumb {
${config.get().ui.showScrollBar ? '' : 'display: none;'}
background-color: rgba(0, 0, 0, 0.15);
}
@@ -187,10 +210,12 @@ const scrollBarStyle = `
`
const scrollBarDarkStyle = `
::-webkit-scrollbar {
${config.get().ui.showScrollBar ? '' : 'display: none;'}
width: 12px;
}
::-webkit-scrollbar-thumb {
${config.get().ui.showScrollBar ? '' : 'display: none;'}
background-color: rgba(0, 0, 0, 0.3);
}
@@ -245,6 +270,7 @@ export default class MarkdownPreview extends React.Component {
this.saveAsHtmlHandler = () => this.handleSaveAsHtml()
this.saveAsPdfHandler = () => this.handleSaveAsPdf()
this.printHandler = () => this.handlePrint()
this.resizeHandler = _.throttle(this.handleResize.bind(this), 100)
this.linkClickHandler = this.handleLinkClick.bind(this)
this.initMarkdown = this.initMarkdown.bind(this)
@@ -290,7 +316,11 @@ export default class MarkdownPreview extends React.Component {
const targetTag = clickElement.tagName // The direct parent HTML of where was clicked ie "BODY" or "DIV"
const lineNumber = getSourceLineNumberByElement(clickElement) // Line location of element clicked.
if (config.editor.switchPreview === 'RIGHTCLICK' && e.buttons === 2 && config.editor.type === 'SPLIT') {
if (
config.editor.switchPreview === 'RIGHTCLICK' &&
e.buttons === 2 &&
config.editor.type === 'SPLIT'
) {
eventEmitter.emit('topbar:togglemodebutton', 'CODE')
}
if (e.ctrlKey) {
@@ -306,7 +336,8 @@ export default class MarkdownPreview extends React.Component {
}
}
if (this.props.onMouseDown != null && targetTag === 'BODY') this.props.onMouseDown(e)
if (this.props.onMouseDown != null && targetTag === 'BODY')
this.props.onMouseDown(e)
}
handleMouseUp(e) {
@@ -335,7 +366,8 @@ export default class MarkdownPreview extends React.Component {
scrollPastEnd,
theme,
allowCustomCSS,
customCSS
customCSS,
RTL
} = this.getStyleParams()
const inlineStyles = buildStyle({
@@ -346,13 +378,11 @@ export default class MarkdownPreview extends React.Component {
scrollPastEnd,
theme,
allowCustomCSS,
customCSS
customCSS,
RTL
})
let body = this.markdown.render(noteContent)
body = attachmentManagement.fixLocalURLS(
body,
this.props.storagePath
)
let body = this.refs.root.contentWindow.document.body.innerHTML
body = attachmentManagement.fixLocalURLS(body, this.props.storagePath)
const files = [this.getCodeThemeLink(codeBlockTheme), ...CSS_FILES]
files.forEach(file => {
if (global.process.platform === 'win32') {
@@ -368,7 +398,7 @@ export default class MarkdownPreview extends React.Component {
let styles = ''
files.forEach(file => {
styles += `<link rel="stylesheet" href="css/${path.basename(file)}">`
styles += `<link rel="stylesheet" href="../css/${path.basename(file)}">`
})
return `<html>
@@ -384,13 +414,23 @@ export default class MarkdownPreview extends React.Component {
}
handleSaveAsHtml() {
this.exportAsDocument('html', (noteContent, exportTasks, targetDir) => Promise.resolve(this.htmlContentFormatter(noteContent, exportTasks, targetDir)))
this.exportAsDocument('html', (noteContent, exportTasks, targetDir) =>
Promise.resolve(
this.htmlContentFormatter(noteContent, exportTasks, targetDir)
)
)
}
handleSaveAsPdf() {
this.exportAsDocument('pdf', (noteContent, exportTasks, targetDir) => {
const printout = new remote.BrowserWindow({show: false, webPreferences: {webSecurity: false, javascript: false}})
printout.loadURL('data:text/html;charset=UTF-8,' + this.htmlContentFormatter(noteContent, exportTasks, targetDir))
const printout = new remote.BrowserWindow({
show: false,
webPreferences: { webSecurity: false, javascript: false }
})
printout.loadURL(
'data:text/html;charset=UTF-8,' +
this.htmlContentFormatter(noteContent, exportTasks, targetDir)
)
return new Promise((resolve, reject) => {
printout.webContents.on('did-finish-load', () => {
printout.webContents.printToPDF({}, (err, data) => {
@@ -423,7 +463,8 @@ export default class MarkdownPreview extends React.Component {
.then(res => {
dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'info',
message: `Exported to ${filename}`
message: `Exported to ${filename}`,
buttons: [i18n.__('Ok')]
})
})
.catch(err => {
@@ -456,15 +497,16 @@ export default class MarkdownPreview extends React.Component {
*/
escapeHtmlCharactersInCodeTag(splitWithCodeTag) {
for (let index = 0; index < splitWithCodeTag.length; index++) {
const codeTagRequired = (splitWithCodeTag[index] !== '\`\`\`' && index < splitWithCodeTag.length - 1)
const codeTagRequired =
splitWithCodeTag[index] !== '```' && index < splitWithCodeTag.length - 1
if (codeTagRequired) {
splitWithCodeTag.splice((index + 1), 0, '\`\`\`')
splitWithCodeTag.splice(index + 1, 0, '```')
}
}
let inCodeTag = false
let result = ''
for (let content of splitWithCodeTag) {
if (content === '\`\`\`') {
if (content === '```') {
inCodeTag = !inCodeTag
} else if (inCodeTag) {
content = escapeHtmlCharacters(content)
@@ -477,15 +519,9 @@ export default class MarkdownPreview extends React.Component {
getScrollBarStyle() {
const { theme } = this.props
switch (theme) {
case 'dark':
case 'solarized-dark':
case 'monokai':
case 'dracula':
return scrollBarDarkStyle
default:
return scrollBarStyle
}
return uiThemes.some(item => item.name === theme && item.isDark)
? scrollBarDarkStyle
: scrollBarStyle
}
componentDidMount() {
@@ -538,6 +574,7 @@ export default class MarkdownPreview extends React.Component {
'scroll',
this.scrollHandler
)
this.refs.root.contentWindow.addEventListener('resize', this.resizeHandler)
eventEmitter.on('export:save-text', this.saveAsTextHandler)
eventEmitter.on('export:save-md', this.saveAsMdHandler)
eventEmitter.on('export:save-html', this.saveAsHtmlHandler)
@@ -576,6 +613,10 @@ export default class MarkdownPreview extends React.Component {
'scroll',
this.scrollHandler
)
this.refs.root.contentWindow.removeEventListener(
'resize',
this.resizeHandler
)
eventEmitter.off('export:save-text', this.saveAsTextHandler)
eventEmitter.off('export:save-md', this.saveAsMdHandler)
eventEmitter.off('export:save-html', this.saveAsHtmlHandler)
@@ -608,7 +649,8 @@ export default class MarkdownPreview extends React.Component {
prevProps.theme !== this.props.theme ||
prevProps.scrollPastEnd !== this.props.scrollPastEnd ||
prevProps.allowCustomCSS !== this.props.allowCustomCSS ||
prevProps.customCSS !== this.props.customCSS
prevProps.customCSS !== this.props.customCSS ||
prevProps.RTL !== this.props.RTL
) {
this.applyStyle()
needsRewriteIframe = true
@@ -632,17 +674,19 @@ export default class MarkdownPreview extends React.Component {
scrollPastEnd,
theme,
allowCustomCSS,
customCSS
customCSS,
RTL
} = this.props
let { fontFamily, codeBlockFontFamily } = this.props
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
fontFamily =
_.isString(fontFamily) && fontFamily.trim().length > 0
? fontFamily
.split(',')
.map(fontName => fontName.trim())
.concat(defaultFontFamily)
: defaultFontFamily
codeBlockFontFamily = _.isString(codeBlockFontFamily) &&
codeBlockFontFamily.trim().length > 0
codeBlockFontFamily =
_.isString(codeBlockFontFamily) && codeBlockFontFamily.trim().length > 0
? codeBlockFontFamily
.split(',')
.map(fontName => fontName.trim())
@@ -658,7 +702,8 @@ export default class MarkdownPreview extends React.Component {
scrollPastEnd,
theme,
allowCustomCSS,
customCSS
customCSS,
RTL
}
}
@@ -672,7 +717,8 @@ export default class MarkdownPreview extends React.Component {
scrollPastEnd,
theme,
allowCustomCSS,
customCSS
customCSS,
RTL
} = this.getStyleParams()
this.getWindow().document.getElementById(
@@ -686,7 +732,8 @@ export default class MarkdownPreview extends React.Component {
scrollPastEnd,
theme,
allowCustomCSS,
customCSS
customCSS,
RTL
})
}
@@ -756,7 +803,9 @@ export default class MarkdownPreview extends React.Component {
codeBlockTheme = consts.THEMES.find(theme => theme.name === codeBlockTheme)
const codeBlockThemeClassName = codeBlockTheme ? codeBlockTheme.className : 'cm-s-default'
const codeBlockThemeClassName = codeBlockTheme
? codeBlockTheme.className
: 'cm-s-default'
_.forEach(
this.refs.root.contentWindow.document.querySelectorAll('.code code'),
@@ -842,7 +891,10 @@ export default class MarkdownPreview extends React.Component {
el => {
try {
const format = el.attributes.getNamedItem('data-format').value
const chartConfig = format === 'yaml' ? yaml.load(el.innerHTML) : JSON.parse(el.innerHTML)
const chartConfig =
format === 'yaml'
? yaml.load(el.innerHTML)
: JSON.parse(el.innerHTML)
el.innerHTML = ''
const canvas = document.createElement('canvas')
@@ -865,7 +917,12 @@ export default class MarkdownPreview extends React.Component {
_.forEach(
this.refs.root.contentWindow.document.querySelectorAll('.mermaid'),
el => {
mermaidRender(el, htmlTextHelper.decodeEntities(el.innerHTML), theme, mermaidHTMLLabel)
mermaidRender(
el,
htmlTextHelper.decodeEntities(el.innerHTML),
theme,
mermaidHTMLLabel
)
}
)
@@ -887,20 +944,14 @@ export default class MarkdownPreview extends React.Component {
autoplay = 0
}
render(
<Carousel
images={images}
autoplay={autoplay}
/>,
el
)
render(<Carousel images={images} autoplay={autoplay} />, el)
}
)
const markdownPreviewIframe = document.querySelector('.MarkdownPreview')
const rect = markdownPreviewIframe.getBoundingClientRect()
const config = { attributes: true, subtree: true }
const imgObserver = new MutationObserver((mutationList) => {
const imgObserver = new MutationObserver(mutationList => {
for (const mu of mutationList) {
if (mu.target.className === 'carouselContent-enter-done') {
this.setImgOnClickEventHelper(mu.target, rect)
@@ -909,14 +960,18 @@ export default class MarkdownPreview extends React.Component {
}
})
const imgList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll('img')
const imgList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll(
'img'
)
for (const img of imgList) {
const parentEl = img.parentElement
this.setImgOnClickEventHelper(img, rect)
imgObserver.observe(parentEl, config)
}
const aList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll('a')
const aList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll(
'a'
)
for (const a of aList) {
a.removeEventListener('click', this.linkClickHandler)
a.addEventListener('click', this.linkClickHandler)
@@ -928,7 +983,9 @@ export default class MarkdownPreview extends React.Component {
const widthMagnification = document.body.clientWidth / img.width
const heightMagnification = document.body.clientHeight / img.height
const baseOnWidth = widthMagnification < heightMagnification
const magnification = baseOnWidth ? widthMagnification : heightMagnification
const magnification = baseOnWidth
? widthMagnification
: heightMagnification
const zoomImgWidth = img.width * magnification
const zoomImgHeight = img.height * magnification
@@ -959,10 +1016,7 @@ export default class MarkdownPreview extends React.Component {
width: ${zoomImgWidth};
height: ${zoomImgHeight}px;
`
zoomImg.animate([
originalImgRect,
zoomInImgRect
], animationSpeed)
zoomImg.animate([originalImgRect, zoomInImgRect], animationSpeed)
const overlay = document.createElement('div')
overlay.style = `
@@ -983,10 +1037,10 @@ export default class MarkdownPreview extends React.Component {
width: ${img.width}px;
height: ${img.height}px;
`
const zoomOutImgAnimation = zoomImg.animate([
zoomInImgRect,
originalImgRect
], animationSpeed)
const zoomOutImgAnimation = zoomImg.animate(
[zoomInImgRect, originalImgRect],
animationSpeed
)
zoomOutImgAnimation.onfinish = () => overlay.remove()
}
@@ -995,6 +1049,15 @@ export default class MarkdownPreview extends React.Component {
}
}
handleResize() {
_.forEach(
this.refs.root.contentWindow.document.querySelectorAll('svg[ratio]'),
el => {
el.setAttribute('height', el.clientWidth / el.getAttribute('ratio'))
}
)
}
focus() {
this.refs.root.focus()
}
@@ -1069,9 +1132,7 @@ export default class MarkdownPreview extends React.Component {
if (posOfHash > -1) {
const extractedId = linkHash.slice(posOfHash + 1)
const targetId = mdurl.encode(extractedId)
const targetElement = this.getWindow().document.getElementById(
targetId
)
const targetElement = this.getWindow().document.getElementById(targetId)
if (targetElement != null) {
this.scrollTo(0, targetElement.offsetTop)
@@ -1109,7 +1170,18 @@ export default class MarkdownPreview extends React.Component {
}
// other case
shell.openExternal(href)
this.openExternal(href)
}
openExternal(href) {
try {
const success =
shell.openExternal(href) || shell.openExternal(decodeURI(href))
if (!success) console.error('failed to open url ' + href)
} catch (e) {
// URI Error threw from decodeURI
console.error(e)
}
}
render() {

View File

@@ -32,7 +32,10 @@ class MarkdownSplitEditor extends React.Component {
handleScroll(e) {
if (!this.props.config.preview.scrollSync) return
const previewDoc = _.get(this, 'refs.preview.refs.root.contentWindow.document')
const previewDoc = _.get(
this,
'refs.preview.refs.root.contentWindow.document'
)
const codeDoc = _.get(this, 'refs.code.editor.doc')
let srcTop, srcHeight, targetTop, targetHeight
@@ -49,7 +52,7 @@ class MarkdownSplitEditor extends React.Component {
targetHeight = _.get(codeDoc, 'height')
}
const distance = (targetHeight * srcTop / srcHeight) - targetTop
const distance = (targetHeight * srcTop) / srcHeight - targetTop
const framerate = 1000 / 60
const frames = 20
const refractory = frames * framerate
@@ -60,14 +63,22 @@ class MarkdownSplitEditor extends React.Component {
let scrollPos, time
const timer = setInterval(() => {
time = frame / frames
scrollPos = time < 0.5
scrollPos =
time < 0.5
? 2 * time * time // ease in
: -1 + (4 - 2 * time) * time // ease out
if (e.doc) _.set(previewDoc, 'body.scrollTop', targetTop + scrollPos * distance)
else _.get(this, 'refs.code.editor').scrollTo(0, targetTop + scrollPos * distance)
if (e.doc)
_.set(previewDoc, 'body.scrollTop', targetTop + scrollPos * distance)
else
_.get(this, 'refs.code.editor').scrollTo(
0,
targetTop + scrollPos * distance
)
if (frame >= frames) {
clearInterval(timer)
setTimeout(() => { this.userScroll = true }, refractory)
setTimeout(() => {
this.userScroll = true
}, refractory)
}
frame++
}, framerate)
@@ -83,9 +94,9 @@ class MarkdownSplitEditor extends React.Component {
const checkReplace = /\[x]/i
const uncheckReplace = /\[ ]/
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 lineIndex =
parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
const lines = this.refs.code.value.split('\n')
const targetLine = lines[lineIndex]
let newLine = targetLine
@@ -105,7 +116,7 @@ class MarkdownSplitEditor extends React.Component {
const rootRect = this.refs.root.getBoundingClientRect()
const rootWidth = rootRect.width
const offset = rootRect.left
let newCodeEditorWidthInPercent = (e.pageX - offset) / rootWidth * 100
let newCodeEditorWidthInPercent = ((e.pageX - offset) / rootWidth) * 100
// limit minSize to 10%, maxSize to 90%
if (newCodeEditorWidthInPercent <= 10) {
@@ -137,19 +148,30 @@ class MarkdownSplitEditor extends React.Component {
}
render() {
const {config, value, storageKey, noteKey, linesHighlighted} = this.props
const {
config,
value,
storageKey,
noteKey,
linesHighlighted,
RTL
} = this.props
const storage = findStorage(storageKey)
let editorFontSize = parseInt(config.editor.fontSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
let editorIndentSize = parseInt(config.editor.indentSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
const previewStyle = {}
previewStyle.width = (100 - this.state.codeEditorWidthInPercent) + '%'
if (this.props.ignorePreviewPointerEvents || this.state.isSliderFocused) previewStyle.pointerEvents = 'none'
previewStyle.width = 100 - this.state.codeEditorWidthInPercent + '%'
if (this.props.ignorePreviewPointerEvents || this.state.isSliderFocused)
previewStyle.pointerEvents = 'none'
return (
<div styleName='root' ref='root'
<div
styleName='root'
ref='root'
onMouseMove={e => this.handleMouseMove(e)}
onMouseUp={e => this.handleMouseUp(e)}>
onMouseUp={e => this.handleMouseUp(e)}
>
<CodeEditor
ref='code'
width={this.state.codeEditorWidthInPercent + '%'}
@@ -174,7 +196,7 @@ class MarkdownSplitEditor extends React.Component {
storageKey={storageKey}
noteKey={noteKey}
linesHighlighted={linesHighlighted}
onChange={(e) => this.handleOnChange(e)}
onChange={e => this.handleOnChange(e)}
onScroll={this.handleScroll.bind(this)}
spellCheck={config.editor.spellcheck}
enableSmartPaste={config.editor.enableSmartPaste}
@@ -183,8 +205,13 @@ class MarkdownSplitEditor extends React.Component {
enableMarkdownLint={config.editor.enableMarkdownLint}
customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
RTL={RTL}
/>
<div styleName='slider' style={{left: this.state.codeEditorWidthInPercent + '%'}} onMouseDown={e => this.handleMouseDown(e)} >
<div
styleName='slider'
style={{ left: this.state.codeEditorWidthInPercent + '%' }}
onMouseDown={e => this.handleMouseDown(e)}
>
<div styleName='slider-hitbox' />
</div>
<MarkdownPreview
@@ -205,7 +232,7 @@ class MarkdownSplitEditor extends React.Component {
ref='preview'
tabInde='0'
value={value}
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
onCheckboxClick={e => this.handleCheckboxClick(e)}
onScroll={this.handleScroll.bind(this)}
showCopyNotification={config.ui.showCopyNotification}
storagePath={storage.path}
@@ -213,6 +240,7 @@ class MarkdownSplitEditor extends React.Component {
customCSS={config.preview.customCSS}
allowCustomCSS={config.preview.allowCustomCSS}
lineThroughCheckbox={config.preview.lineThroughCheckbox}
RTL={RTL}
/>
</div>
)

View File

@@ -16,22 +16,14 @@
z-index 10
cursor col-resize
body[data-theme="dark"]
apply-theme(theme)
body[data-theme={theme}]
.root
.slider
border-left 1px solid $ui-dark-borderColor
border-left 1px solid get-theme-var(theme, 'borderColor')
body[data-theme="solarized-dark"]
.root
.slider
border-left 1px solid $ui-solarized-dark-borderColor
for theme in 'dark' 'dracula' 'solarized-dark'
apply-theme(theme)
body[data-theme="monokai"]
.root
.slider
border-left 1px solid $ui-monokai-borderColor
body[data-theme="dracula"]
.root
.slider
border-left 1px solid $ui-dracula-borderColor
for theme in $themes
apply-theme(theme)

View File

@@ -3,9 +3,7 @@ import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './ModalEscButton.styl'
const ModalEscButton = ({
handleEscButtonClick
}) => (
const ModalEscButton = ({ handleEscButtonClick }) => (
<button styleName='escButton' onClick={handleEscButtonClick}>
<div styleName='esc-mark'>×</div>
<div>esc</div>

View File

@@ -12,13 +12,12 @@ import CSSModules from 'browser/lib/CSSModules'
*/
const NavToggleButton = ({ isFolded, handleToggleButtonClick }) => (
<button styleName='navToggle'
onClick={(e) => handleToggleButtonClick(e)}
>
{isFolded
? <i className='fa fa-angle-double-right fa-2x' />
: <i className='fa fa-angle-double-left fa-2x' />
}
<button styleName='navToggle' onClick={e => handleToggleButtonClick(e)}>
{isFolded ? (
<i className='fa fa-angle-double-right fa-2x' />
) : (
<i className='fa fa-angle-double-left fa-2x' />
)}
</button>
)

View File

@@ -17,10 +17,16 @@
body[data-theme="white"]
navWhiteButtonColor()
body[data-theme="dark"]
.navToggle
&:hover
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
apply-theme(theme)
body[data-theme={theme}]
.navToggle:hover
background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 20%)
border 1px solid get-theme-var(theme, 'button--active-backgroundColor')
transition 0.15s
color $ui-dark-text-color
color get-theme-var(theme, 'text-color')
for theme in 'dark' 'dracula' 'solarized-dark'
apply-theme(theme)
for theme in $themes
apply-theme(theme)

View File

@@ -5,6 +5,7 @@ import PropTypes from 'prop-types'
import React from 'react'
import { isArray, sortBy } from 'lodash'
import invertColor from 'invert-color'
import Emoji from 'react-emoji-render'
import CSSModules from 'browser/lib/CSSModules'
import { getTodoStatus } from 'browser/lib/getTodoStatus'
import styles from './NoteItem.styl'
@@ -21,7 +22,11 @@ const TagElement = ({ tagName, color }) => {
const style = {}
if (color) {
style.backgroundColor = color
style.color = invertColor(color, { black: '#222', white: '#f1f1f1', threshold: 0.3 })
style.color = invertColor(color, {
black: '#222',
white: '#f1f1f1',
threshold: 0.3
})
}
return (
<span styleName='item-bottom-tagList-item' key={tagName} style={style}>
@@ -43,9 +48,13 @@ const TagElementList = (tags, showTagsAlphabetically, coloredTags) => {
}
if (showTagsAlphabetically) {
return sortBy(tags).map(tag => TagElement({ tagName: tag, color: coloredTags[tag] }))
return sortBy(tags).map(tag =>
TagElement({ tagName: tag, color: coloredTags[tag] })
)
} else {
return tags.map(tag => TagElement({ tagName: tag, color: coloredTags[tag] }))
return tags.map(tag =>
TagElement({ tagName: tag, color: coloredTags[tag] })
)
}
}
@@ -82,13 +91,17 @@ const NoteItem = ({
draggable='true'
>
<div styleName='item-wrapper'>
{note.type === 'SNIPPET_NOTE'
? <i styleName='item-title-icon' className='fa fa-fw fa-code' />
: <i styleName='item-title-icon' className='fa fa-fw fa-file-text-o' />}
{note.type === 'SNIPPET_NOTE' ? (
<i styleName='item-title-icon' className='fa fa-fw fa-code' />
) : (
<i styleName='item-title-icon' className='fa fa-fw fa-file-text-o' />
)}
<div styleName='item-title'>
{note.title.trim().length > 0
? note.title
: <span styleName='item-title-empty'>{i18n.__('Empty note')}</span>}
{note.title.trim().length > 0 ? (
<Emoji text={note.title} />
) : (
<span styleName='item-title-empty'>{i18n.__('Empty note')}</span>
)}
</div>
<div styleName='item-middle'>
<div styleName='item-middle-time'>{dateDisplay}</div>
@@ -97,7 +110,9 @@ const NoteItem = ({
title={
viewType === 'ALL'
? storageName
: viewType === 'STORAGE' ? folderName : null
: viewType === 'STORAGE'
? folderName
: null
}
styleName='item-middle-app-meta-label'
>
@@ -108,28 +123,36 @@ const NoteItem = ({
</div>
<div styleName='item-bottom'>
<div styleName='item-bottom-tagList'>
{note.tags.length > 0
? TagElementList(note.tags, showTagsAlphabetically, coloredTags)
: <span
{note.tags.length > 0 ? (
TagElementList(note.tags, showTagsAlphabetically, coloredTags)
) : (
<span
style={{ fontStyle: 'italic', opacity: 0.5 }}
styleName='item-bottom-tagList-empty'
>
{i18n.__('No tags')}
</span>}
</span>
)}
</div>
<div>
{note.isStarred
? <img
{note.isStarred ? (
<img
styleName='item-star'
src='../resources/icon/icon-starred.svg'
/>
: ''}
{note.isPinned && !pathname.match(/\/starred|\/trash/)
? <i styleName='item-pin' className='fa fa-thumb-tack' />
: ''}
{note.type === 'MARKDOWN_NOTE'
? <TodoProcess todoStatus={getTodoStatus(note.content)} />
: ''}
) : (
''
)}
{note.isPinned && !pathname.match(/\/starred|\/trash/) ? (
<i styleName='item-pin' className='fa fa-thumb-tack' />
) : (
''
)}
{note.type === 'MARKDOWN_NOTE' ? (
<TodoProcess todoStatus={getTodoStatus(note.content)} />
) : (
''
)}
</div>
</div>
</div>

View File

@@ -194,7 +194,7 @@ body[data-theme="dark"]
color $ui-dark-text-color
.item-bottom-tagList-item
transition 0.15s
background-color alpha(#fff, 20%)
background-color alpha($ui-dark-tagList-backgroundColor, 20%)
color $ui-dark-text-color
&:active
transition 0.15s
@@ -207,7 +207,7 @@ body[data-theme="dark"]
color $ui-dark-text-color
.item-bottom-tagList-item
transition 0.15s
background-color alpha(white, 10%)
background-color alpha($ui-dark-tagList-backgroundColor, 10%)
color $ui-dark-text-color
.item-wrapper
@@ -223,13 +223,13 @@ body[data-theme="dark"]
.item-bottom-time
color $ui-dark-text-color
.item-bottom-tagList-item
background-color alpha(white, 10%)
background-color alpha($ui-dark-tagList-backgroundColor, 10%)
color $ui-dark-text-color
&:hover
background-color alpha($ui-dark-button--active-backgroundColor, 60%)
color #c0392b
color $ui-dark-button--hover-color
.item-bottom-tagList-item
background-color alpha(#fff, 20%)
background-color alpha($ui-dark-tagList-backgroundColor, 20%)
.item-title
color $ui-inactive-text-color
@@ -322,61 +322,62 @@ body[data-theme="solarized-dark"]
color $ui-inactive-text-color
vertical-align middle
body[data-theme="monokai"]
apply-theme(theme)
body[data-theme={theme}]
.root
border-color $ui-monokai-borderColor
background-color $ui-monokai-noteList-backgroundColor
border-color get-theme-var(theme, 'borderColor')
background-color get-theme-var(theme, 'noteList-backgroundColor')
.item
border-color $ui-monokai-borderColor
background-color $ui-monokai-noteList-backgroundColor
border-color get-theme-var(theme, 'borderColor')
background-color get-theme-var(theme, 'noteList-backgroundColor')
&:hover
transition 0.15s
// background-color alpha($ui-monokai-noteList-backgroundColor, 20%)
color $ui-monokai-text-color
// background-color alpha(get-theme-var(theme, 'noteList-backgroundColor'), 20%)
color get-theme-var(theme, 'text-color')
.item-title
.item-title-icon
.item-bottom-time
transition 0.15s
color $ui-monokai-text-color
color get-theme-var(theme, 'text-color')
.item-bottom-tagList-item
transition 0.15s
background-color alpha($ui-monokai-noteList-backgroundColor, 20%)
color $ui-monokai-text-color
background-color alpha(get-theme-var(theme, 'noteList-backgroundColor'), 20%)
color get-theme-var(theme, 'text-color')
&:active
transition 0.15s
background-color $ui-monokai-noteList-backgroundColor
color $ui-monokai-text-color
background-color get-theme-var(theme, 'noteList-backgroundColor')
color get-theme-var(theme, 'text-color')
.item-title
.item-title-icon
.item-bottom-time
transition 0.15s
color $ui-monokai-text-color
color get-theme-var(theme, 'text-color')
.item-bottom-tagList-item
transition 0.15s
background-color alpha($ui-monokai-noteList-backgroundColor, 10%)
color $ui-monokai-text-color
background-color alpha(get-theme-var(theme, 'noteList-backgroundColor'), 10%)
color get-theme-var(theme, 'text-color')
.item-wrapper
border-color alpha($ui-monokai-button-backgroundColor, 60%)
border-color alpha(get-theme-var(theme, 'button-backgroundColor'), 60%)
.item--active
border-color $ui-monokai-borderColor
background-color $ui-monokai-button-backgroundColor
border-color get-theme-var(theme, 'borderColor')
background-color get-theme-var(theme, 'button-backgroundColor')
.item-wrapper
border-color transparent
.item-title
.item-title-icon
.item-bottom-time
color $ui-monokai-active-color
color get-theme-var(theme, 'active-color')
.item-bottom-tagList-item
background-color alpha(white, 10%)
color $ui-monokai-text-color
background-color alpha(get-theme-var(theme, 'tagList-backgroundColor'), 10%)
color get-theme-var(theme, 'text-color')
&:hover
// background-color alpha($ui-monokai-button--active-backgroundColor, 60%)
color #f92672
// background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 60%)
color get-theme-var(theme, 'button--hover-color')
.item-bottom-tagList-item
background-color alpha(#fff, 20%)
background-color alpha(get-theme-var(theme, 'tagList-backgroundColor'), 20%)
.item-title
color $ui-inactive-text-color
@@ -395,75 +396,8 @@ body[data-theme="monokai"]
color $ui-inactive-text-color
vertical-align middle
body[data-theme="dracula"]
.root
border-color $ui-dracula-borderColor
background-color $ui-dracula-noteList-backgroundColor
for theme in 'dracula'
apply-theme(theme)
.item
border-color $ui-dracula-borderColor
background-color $ui-dracula-noteList-backgroundColor
&:hover
transition 0.15s
// background-color alpha($ui-dracula-noteList-backgroundColor, 20%)
color $ui-dracula-text-color
.item-title
.item-title-icon
.item-bottom-time
transition 0.15s
color $ui-dracula-text-color
.item-bottom-tagList-item
transition 0.15s
background-color alpha($ui-dracula-noteList-backgroundColor, 20%)
color $ui-dracula-text-color
&:active
transition 0.15s
background-color $ui-dracula-noteList-backgroundColor
color $ui-dracula-text-color
.item-title
.item-title-icon
.item-bottom-time
transition 0.15s
color $ui-dracula-text-color
.item-bottom-tagList-item
transition 0.15s
background-color alpha($ui-dracula-noteList-backgroundColor, 10%)
color $ui-dracula-text-color
.item-wrapper
border-color alpha($ui-dracula-button-backgroundColor, 60%)
.item--active
border-color $ui-dracula-borderColor
background-color $ui-dracula-button-backgroundColor
.item-wrapper
border-color transparent
.item-title
.item-title-icon
.item-bottom-time
color $ui-dracula-active-color
.item-bottom-tagList-item
background-color alpha(#f8f8f2, 10%)
color $ui-dracula-text-color
&:hover
// background-color alpha($ui-dracula-button--active-backgroundColor, 60%)
color #ff79c6
.item-bottom-tagList-item
background-color alpha(#f8f8f2, 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
for theme in $themes
apply-theme(theme)

View File

@@ -25,10 +25,8 @@ const NoteItemSimple = ({
pathname,
storage
}) => (
<div styleName={isActive
? 'item-simple--active'
: 'item-simple'
}
<div
styleName={isActive ? 'item-simple--active' : 'item-simple'}
key={note.key}
onClick={e => handleNoteClick(e, note.key)}
onContextMenu={e => handleNoteContextMenu(e, note.key)}
@@ -36,23 +34,29 @@ const NoteItemSimple = ({
draggable='true'
>
<div styleName='item-simple-title'>
{note.type === 'SNIPPET_NOTE'
? <i styleName='item-simple-title-icon' className='fa fa-fw fa-code' />
: <i styleName='item-simple-title-icon' className='fa fa-fw fa-file-text-o' />
}
{note.isPinned && !pathname.match(/\/starred|\/trash/)
? <i styleName='item-pin' className='fa fa-thumb-tack' />
: ''
}
{note.title.trim().length > 0
? note.title
: <span styleName='item-simple-title-empty'>{i18n.__('Empty note')}</span>
}
{isAllNotesView && <div styleName='item-simple-right'>
<span styleName='item-simple-right-storageName'>
{storage.name}
</span>
</div>}
{note.type === 'SNIPPET_NOTE' ? (
<i styleName='item-simple-title-icon' className='fa fa-fw fa-code' />
) : (
<i
styleName='item-simple-title-icon'
className='fa fa-fw fa-file-text-o'
/>
)}
{note.isPinned && !pathname.match(/\/starred|\/trash/) ? (
<i styleName='item-pin' className='fa fa-thumb-tack' />
) : (
''
)}
{note.title.trim().length > 0 ? (
note.title
) : (
<span styleName='item-simple-title-empty'>{i18n.__('Empty note')}</span>
)}
{isAllNotesView && (
<div styleName='item-simple-right'>
<span styleName='item-simple-right-storageName'>{storage.name}</span>
</div>
)}
</div>
</div>
)

View File

@@ -223,61 +223,62 @@ body[data-theme="solarized-dark"]
padding-left 4px
opacity 0.4
body[data-theme="monokai"]
apply-theme(theme)
body[data-theme={theme}]
.root
border-color $ui-monokai-borderColor
background-color $ui-monokai-noteList-backgroundColor
border-color get-theme-var(theme, 'borderColor')
background-color get-theme-var(theme, 'noteList-backgroundColor')
.item-simple
border-color $ui-monokai-borderColor
background-color $ui-monokai-noteList-backgroundColor
border-color get-theme-var(theme, 'borderColor')
background-color get-theme-var(theme, 'noteList-backgroundColor')
&:hover
transition 0.15s
background-color alpha($ui-monokai-button-backgroundColor, 60%)
color $ui-monokai-text-color
background-color alpha(get-theme-var(theme, 'button-backgroundColor'), 60%)
color get-theme-var(theme, 'text-color')
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
color $ui-monokai-text-color
color get-theme-var(theme, 'text-color')
.item-simple-bottom-tagList-item
transition 0.15s
background-color alpha(#fff, 20%)
color $ui-monokai-text-color
background-color alpha(get-theme-var(theme, 'tagList-backgroundColor'), 20%)
color get-theme-var(theme, 'text-color')
&:active
transition 0.15s
background-color $ui-monokai-button--active-backgroundColor
color $ui-monokai-text-color
background-color get-theme-var(theme, 'button--active-backgroundColor')
color get-theme-var(theme, 'text-color')
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
color $ui-monokai-text-color
color get-theme-var(theme, 'text-color')
.item-simple-bottom-tagList-item
transition 0.15s
background-color alpha(white, 10%)
color $ui-monokai-text-color
background-color alpha(get-theme-var(theme, 'tagList-backgroundColor'), 10%)
color get-theme-var(theme, 'text-color')
.item-simple--active
border-color $ui-monokai-borderColor
background-color $ui-monokai-button--active-backgroundColor
border-color get-theme-var(theme, 'borderColor')
background-color get-theme-var(theme, 'button--active-backgroundColor')
.item-simple-wrapper
border-color transparent
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
color $ui-monokai-text-color
color get-theme-var(theme, 'text-color')
.item-simple-bottom-tagList-item
background-color alpha(white, 10%)
color $ui-monokai-text-color
background-color alpha(get-theme-var(theme, 'tagList-backgroundColor'), 10%)
color get-theme-var(theme, 'text-color')
&:hover
// background-color alpha($ui-dark-button--active-backgroundColor, 60%)
// background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 60%)
color #c0392b
.item-simple-bottom-tagList-item
background-color alpha(#fff, 20%)
background-color alpha(get-theme-var(theme, 'tagList-backgroundColor'), 20%)
.item-simple-title
color $ui-dark-text-color
border-bottom $ui-dark-borderColor
@@ -287,66 +288,8 @@ body[data-theme="monokai"]
padding-left 4px
opacity 0.4
body[data-theme="dracula"]
.root
border-color $ui-dracula-borderColor
background-color $ui-dracula-noteList-backgroundColor
for theme in 'dracula'
apply-theme(theme)
.item-simple
border-color $ui-dracula-borderColor
background-color $ui-dracula-noteList-backgroundColor
&:hover
transition 0.15s
background-color alpha($ui-dracula-button-backgroundColor, 60%)
color $ui-dracula-text-color
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
color $ui-dracula-text-color
.item-simple-bottom-tagList-item
transition 0.15s
background-color alpha(#f8f8f2, 20%)
color $ui-dracula-text-color
&:active
transition 0.15s
background-color $ui-dracula-button--active-backgroundColor
color $ui-dracula-text-color
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
color $ui-dracula-text-color
.item-simple-bottom-tagList-item
transition 0.15s
background-color alpha(#f8f8f2, 10%)
color $ui-dracula-text-color
.item-simple--active
border-color $ui-dracula-borderColor
background-color $ui-dracula-button--active-backgroundColor
.item-simple-wrapper
border-color transparent
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
color $ui-dracula-text-color
.item-simple-bottom-tagList-item
background-color alpha(#f8f8f2, 10%)
color $ui-dracula-text-color
&:hover
// background-color alpha($ui-dark-button--active-backgroundColor, 60%)
color #c0392b
.item-simple-bottom-tagList-item
background-color alpha(#f8f8f2, 20%)
.item-simple-title
color $ui-dark-text-color
border-bottom $ui-dark-borderColor
.item-simple-right
float right
.item-simple-right-storageName
padding-left 4px
opacity 0.4
for theme in $themes
apply-theme(theme)

View File

@@ -19,7 +19,8 @@ class RealtimeNotification extends React.Component {
}
fetchNotifications() {
const notificationsUrl = 'https://raw.githubusercontent.com/BoostIO/notification/master/notification.json'
const notificationsUrl =
'https://raw.githubusercontent.com/BoostIO/notification/master/notification.json'
fetch(notificationsUrl)
.then(response => {
return response.json()
@@ -36,16 +37,23 @@ class RealtimeNotification extends React.Component {
render() {
const { notifications } = this.state
const link = notifications.length > 0
? <a styleName='notification-link' href={notifications[0].linkUrl}
onClick={(e) => this.handleLinkClick(e)}
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>
<div styleName='notification-area' style={this.props.style}>
{link}
</div>
)
}
}

View File

@@ -30,36 +30,20 @@ body[data-theme="dark"]
&:hover
color #5CB85C
body[data-theme="solarized-dark"]
apply-theme(theme)
body[data-theme={theme}]
.notification-area
background-color none
.notification-link
color $ui-solarized-dark-text-color
color get-theme-var(theme, 'text-color')
border none
background-color $ui-solarized-dark-button-backgroundColor
background-color get-theme-var(theme, 'button-backgroundColor')
&:hover
color #5CB85C
color get-theme-var(theme, 'button--hover-color')
body[data-theme="monokai"]
.notification-area
background-color none
for theme in 'solarized-dark' 'dracula'
apply-theme(theme)
.notification-link
color $ui-monokai-text-color
border none
background-color $ui-monokai-button-backgroundColor
&:hover
color #5CB85C
body[data-theme="dracula"]
.notification-area
background-color none
.notification-link
color $ui-dracula-text-color
border none
background-color $ui-dracula-button-backgroundColor
&:hover
color #ff79c6
for theme in $themes
apply-theme(theme)

View File

@@ -16,17 +16,27 @@ import i18n from 'browser/lib/i18n'
* @return {React.Component}
*/
const SideNavFilter = ({
isFolded, isHomeActive, handleAllNotesButtonClick,
isStarredActive, handleStarredButtonClick, isTrashedActive, handleTrashedButtonClick, counterDelNote,
counterTotalNote, counterStarredNote, handleFilterButtonContextMenu
isFolded,
isHomeActive,
handleAllNotesButtonClick,
isStarredActive,
handleStarredButtonClick,
isTrashedActive,
handleTrashedButtonClick,
counterDelNote,
counterTotalNote,
counterStarredNote,
handleFilterButtonContextMenu
}) => (
<div styleName={isFolded ? 'menu--folded' : 'menu'}>
<button styleName={isHomeActive ? 'menu-button--active' : 'menu-button'}
<button
styleName={isHomeActive ? 'menu-button--active' : 'menu-button'}
onClick={handleAllNotesButtonClick}
>
<div styleName='iconWrap'>
<img src={isHomeActive
<img
src={
isHomeActive
? '../resources/icon/icon-all-active.svg'
: '../resources/icon/icon-all.svg'
}
@@ -36,11 +46,14 @@ const SideNavFilter = ({
<span styleName='counters'>{counterTotalNote}</span>
</button>
<button styleName={isStarredActive ? 'menu-button-star--active' : 'menu-button'}
<button
styleName={isStarredActive ? 'menu-button-star--active' : 'menu-button'}
onClick={handleStarredButtonClick}
>
<div styleName='iconWrap'>
<img src={isStarredActive
<img
src={
isStarredActive
? '../resources/icon/icon-star-active.svg'
: '../resources/icon/icon-star-sidenav.svg'
}
@@ -50,11 +63,15 @@ const SideNavFilter = ({
<span styleName='counters'>{counterStarredNote}</span>
</button>
<button styleName={isTrashedActive ? 'menu-button-trash--active' : 'menu-button'}
onClick={handleTrashedButtonClick} onContextMenu={handleFilterButtonContextMenu}
<button
styleName={isTrashedActive ? 'menu-button-trash--active' : 'menu-button'}
onClick={handleTrashedButtonClick}
onContextMenu={handleFilterButtonContextMenu}
>
<div styleName='iconWrap'>
<img src={isTrashedActive
<img
src={
isTrashedActive
? '../resources/icon/icon-trash-active.svg'
: '../resources/icon/icon-trash-sidenav.svg'
}
@@ -63,7 +80,6 @@ const SideNavFilter = ({
<span styleName='menu-button-label'>{i18n.__('Trash')}</span>
<span styleName='counters'>{counterDelNote}</span>
</button>
</div>
)

View File

@@ -1,5 +1,5 @@
.menu
margin-bottom 30px
margin-bottom 20px
.menu-button
navButtonColor()
@@ -180,129 +180,51 @@ body[data-theme="dark"]
.menu-button-label
color $ui-dark-text-color
body[data-theme="solarized-dark"]
apply-theme(theme)
body[data-theme={theme}]
.menu-button
&:active
background-color $ui-solarized-dark-noteList-backgroundColor
color $ui-solarized-dark-text-color
background-color get-theme-var(theme, 'noteList-backgroundColor')
color get-theme-var(theme, 'text-color')
&:hover
background-color $ui-solarized-dark-button-backgroundColor
color $ui-solarized-dark-text-color
background-color get-theme-var(theme, 'button-backgroundColor')
color get-theme-var(theme, 'text-color')
.menu-button--active
color $ui-solarized-dark-text-color
background-color $ui-solarized-dark-button-backgroundColor
color get-theme-var(theme, 'text-color')
background-color get-theme-var(theme, 'button-backgroundColor')
.menu-button-label
color $ui-solarized-dark-text-color
color get-theme-var(theme, 'text-color')
&:hover
background-color $ui-solarized-dark-button-backgroundColor
color $ui-solarized-dark-text-color
background-color get-theme-var(theme, 'button-backgroundColor')
color get-theme-var(theme, 'text-color')
.menu-button-label
color $ui-solarized-dark-text-color
color get-theme-var(theme, 'text-color')
.menu-button-star--active
color $ui-solarized-dark-text-color
background-color $ui-solarized-dark-button-backgroundColor
color get-theme-var(theme, 'text-color')
background-color get-theme-var(theme, 'button-backgroundColor')
.menu-button-label
color $ui-solarized-dark-text-color
color get-theme-var(theme, 'text-color')
&:hover
background-color $ui-solarized-dark-button-backgroundColor
color $ui-solarized-dark-text-color
background-color get-theme-var(theme, 'button-backgroundColor')
color get-theme-var(theme, 'text-color')
.menu-button-label
color $ui-solarized-dark-text-color
color get-theme-var(theme, 'text-color')
.menu-button-trash--active
color $ui-solarized-dark-text-color
background-color $ui-solarized-dark-button-backgroundColor
color get-theme-var(theme, 'text-color')
background-color get-theme-var(theme, 'button-backgroundColor')
.menu-button-label
color $ui-solarized-dark-text-color
color get-theme-var(theme, 'text-color')
&:hover
background-color $ui-solarized-dark-button-backgroundColor
color $ui-solarized-dark-text-color
background-color get-theme-var(theme, 'button-backgroundColor')
color get-theme-var(theme, 'text-color')
.menu-button-label
color $ui-solarized-dark-text-color
color get-theme-var(theme, 'text-color')
body[data-theme="monokai"]
.menu-button
&:active
background-color $ui-monokai-noteList-backgroundColor
color $ui-monokai-text-color
&:hover
background-color $ui-monokai-button-backgroundColor
color $ui-monokai-text-color
for theme in 'solarized-dark' 'dracula'
apply-theme(theme)
.menu-button--active
color $ui-monokai-text-color
background-color $ui-monokai-button-backgroundColor
.menu-button-label
color $ui-monokai-text-color
&:hover
background-color $ui-monokai-button-backgroundColor
color $ui-monokai-text-color
.menu-button-label
color $ui-monokai-text-color
.menu-button-star--active
color $ui-monokai-text-color
background-color $ui-monokai-button-backgroundColor
.menu-button-label
color $ui-monokai-text-color
&:hover
background-color $ui-monokai-button-backgroundColor
color $ui-monokai-text-color
.menu-button-label
color $ui-monokai-text-color
.menu-button-trash--active
color $ui-monokai-text-color
background-color $ui-monokai-button-backgroundColor
.menu-button-label
color $ui-monokai-text-color
&:hover
background-color $ui-monokai-button-backgroundColor
color $ui-monokai-text-color
.menu-button-label
color $ui-monokai-text-color
body[data-theme="dracula"]
.menu-button
&:active
background-color $ui-dracula-noteList-backgroundColor
color $ui-dracula-text-color
&:hover
background-color $ui-dracula-button-backgroundColor
color $ui-dracula-text-color
.menu-button--active
color $ui-dracula-text-color
background-color $ui-dracula-button-backgroundColor
.menu-button-label
color $ui-dracula-text-color
&:hover
background-color $ui-dracula-button-backgroundColor
color $ui-dracula-text-color
.menu-button-label
color $ui-dracula-text-color
.menu-button-star--active
color $ui-dracula-text-color
background-color $ui-dracula-button-backgroundColor
.menu-button-label
color $ui-dracula-text-color
&:hover
background-color $ui-dracula-button-backgroundColor
color $ui-dracula-text-color
.menu-button-label
color $ui-dracula-text-color
.menu-button-trash--active
color $ui-dracula-text-color
background-color $ui-dracula-button-backgroundColor
.menu-button-label
color $ui-dracula-text-color
&:hover
background-color $ui-dracula-button-backgroundColor
color $ui-dracula-text-color
.menu-button-label
color $ui-dracula-text-color
for theme in $themes
apply-theme(theme)

View File

@@ -30,7 +30,7 @@ class SnippetTab extends React.Component {
context.popup([
{
label: i18n.__('Rename'),
click: (e) => this.handleRenameClick(e)
click: e => this.handleRenameClick(e)
}
])
}
@@ -64,13 +64,16 @@ class SnippetTab extends React.Component {
}
handleRename() {
this.setState({
this.setState(
{
isRenaming: false
}, () => {
},
() => {
if (this.props.snippet.name !== this.state.name) {
this.props.onRename(this.state.name)
}
})
}
)
}
handleDeleteButtonClick(e) {
@@ -78,12 +81,15 @@ class SnippetTab extends React.Component {
}
startRenaming() {
this.setState({
this.setState(
{
isRenaming: true
}, () => {
},
() => {
this.refs.name.focus()
this.refs.name.select()
})
}
)
}
handleDragStart(e) {
@@ -98,49 +104,46 @@ class SnippetTab extends React.Component {
render() {
const { isActive, snippet, isDeletable } = this.props
return (
<div styleName={isActive
? 'root--active'
: 'root'
}
>
{!this.state.isRenaming
? <button styleName='button'
onClick={(e) => this.handleClick(e)}
onDoubleClick={(e) => this.handleRenameClick(e)}
onContextMenu={(e) => this.handleContextMenu(e)}
onDragStart={(e) => this.handleDragStart(e)}
onDrop={(e) => this.handleDrop(e)}
<div styleName={isActive ? 'root--active' : 'root'}>
{!this.state.isRenaming ? (
<button
styleName='button'
onClick={e => this.handleClick(e)}
onDoubleClick={e => this.handleRenameClick(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
: <span>
{i18n.__('Unnamed')}
</span>
}
{snippet.name.trim().length > 0 ? (
snippet.name
) : (
<span>{i18n.__('Unnamed')}</span>
)}
</button>
: <input styleName='input'
) : (
<input
styleName='input'
ref='name'
value={this.state.name}
onChange={(e) => this.handleNameInputChange(e)}
onBlur={(e) => this.handleNameInputBlur(e)}
onKeyDown={(e) => this.handleNameInputKeyDown(e)}
onChange={e => this.handleNameInputChange(e)}
onBlur={e => this.handleNameInputBlur(e)}
onKeyDown={e => this.handleNameInputKeyDown(e)}
/>
}
{isDeletable &&
<button styleName='deleteButton'
onClick={(e) => this.handleDeleteButtonClick(e)}
)}
{isDeletable && (
<button
styleName='deleteButton'
onClick={e => this.handleDeleteButtonClick(e)}
>
<i className='fa fa-times' />
</button>
}
)}
</div>
)
}
}
SnippetTab.propTypes = {
}
SnippetTab.propTypes = {}
export default CSSModules(SnippetTab, styles)

View File

@@ -100,61 +100,28 @@ body[data-theme="dark"]
color $ui-dark-text-color
transition 0.15s
body[data-theme="solarized-dark"]
apply-theme(theme)
body[data-theme={theme}]
.root
border-color $ui-solarized-dark-borderColor
border-color get-theme-var(theme, 'borderColor')
&:hover
background-color $ui-solarized-dark-noteDetail-backgroundColor
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
transition 0.15s
.deleteButton
color $ui-solarized-dark-button--active-color
color get-theme-var(theme, 'text-color')
transition 0.15s
.button
color $ui-solarized-dark-button--active-color
color get-theme-var(theme, 'text-color')
transition 0.15s
.root--active
color $ui-solarized-dark-button--active-color
background-color $ui-solarized-dark-button-backgroundColor
border-color $ui-solarized-dark-borderColor
color get-theme-var(theme, 'active-color')
background-color get-theme-var(theme, 'button-backgroundColor')
border-color get-theme-var(theme, 'borderColor')
.deleteButton
color $ui-solarized-dark-button--active-color
color get-theme-var(theme, 'text-color')
.button
color $ui-solarized-dark-button--active-color
.button
border none
color $ui-solarized-dark-text-color
background-color transparent
transition color background-color 0.15s
border-left 4px solid transparent
.input
background-color $ui-solarized-dark-noteDetail-backgroundColor
color $ui-solarized-dark-button--active-color
transition 0.15s
body[data-theme="monokai"]
.root
border-color $ui-monokai-borderColor
&:hover
background-color $ui-monokai-noteDetail-backgroundColor
transition 0.15s
.deleteButton
color $ui-monokai-text-color
transition 0.15s
.button
color $ui-monokai-text-color
transition 0.15s
.root--active
color $ui-monokai-active-color
background-color $ui-monokai-button-backgroundColor
border-color $ui-monokai-borderColor
.deleteButton
color $ui-monokai-text-color
.button
color $ui-monokai-active-color
color get-theme-var(theme, 'active-color')
.button
border none
@@ -164,39 +131,12 @@ body[data-theme="monokai"]
border-left 4px solid transparent
.input
background-color $ui-monokai-noteDetail-backgroundColor
color $ui-monokai-text-color
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
color get-theme-var(theme, 'text-color')
transition 0.15s
body[data-theme="dracula"]
.root
border-color $ui-dracula-borderColor
&:hover
background-color $ui-dracula-noteDetail-backgroundColor
transition 0.15s
.deleteButton
color $ui-dracula-text-color
transition 0.15s
.button
color $ui-dracula-text-color
transition 0.15s
for theme in 'solarized-dark' 'dracula'
apply-theme(theme)
.root--active
color $ui-dracula-text-color
background-color $ui-dracula-button-backgroundColor
border-color $ui-dracula-borderColor
.deleteButton
color $ui-dracula-text-color
.button
color $ui-dracula-active-color
.button
border none
color $ui-inactive-text-color
background-color transparent
transition color background-color 0.15s
border-left 4px solid transparent
.input
background-color $ui-dracula-noteDetail-backgroundColor
color $ui-dracula-text-color
for theme in $themes
apply-theme(theme)

View File

@@ -54,8 +54,9 @@ const StorageItem = ({
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
>
{!isFolded &&
<DraggableIcon className={styles['folderList-item-reorder']} />}
{!isFolded && (
<DraggableIcon className={styles['folderList-item-reorder']} />
)}
<span
styleName={
isFolded ? 'folderList-item-name--folded' : 'folderList-item-name'
@@ -70,11 +71,12 @@ const StorageItem = ({
? _.truncate(folderName, { length: 1, omission: '' })
: folderName}
</span>
{!isFolded &&
_.isNumber(noteCount) &&
<span styleName='folderList-item-noteCount'>{noteCount}</span>}
{isFolded &&
<span styleName='folderList-item-tooltip'>{folderName}</span>}
{!isFolded && _.isNumber(noteCount) && (
<span styleName='folderList-item-noteCount'>{noteCount}</span>
)}
{isFolded && (
<span styleName='folderList-item-tooltip'>{folderName}</span>
)}
</button>
)
}

View File

@@ -120,59 +120,28 @@ body[data-theme="dark"]
color $ui-dark-text-color
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
body[data-theme="solarized-dark"]
apply-theme(theme)
body[data-theme={theme}]
.folderList-item
&:hover
background-color $ui-solarized-dark-button-backgroundColor
color $ui-solarized-dark-text-color
background-color get-theme-var(theme, 'button-backgroundColor')
color get-theme-var(theme, 'text-color')
&:active
color $ui-solarized-dark-text-color
background-color $ui-solarized-dark-button-backgroundColor
color get-theme-var(theme, 'text-color')
background-color get-theme-var(theme, 'button-backgroundColor')
.folderList-item--active
@extend .folderList-item
color $ui-solarized-dark-text-color
background-color $ui-solarized-dark-button-backgroundColor
color get-theme-var(theme, 'text-color')
background-color get-theme-var(theme, 'button-backgroundColor')
&:active
background-color $ui-solarized-dark-button-backgroundColor
background-color get-theme-var(theme, 'button-backgroundColor')
&:hover
color $ui-solarized-dark-text-color
background-color $ui-solarized-dark-button-backgroundColor
color get-theme-var(theme, 'text-color')
background-color get-theme-var(theme, 'button-backgroundColor')
body[data-theme="monokai"]
.folderList-item
&:hover
background-color $ui-monokai-button-backgroundColor
color $ui-monokai-text-color
&:active
color $ui-monokai-text-color
background-color $ui-monokai-button-backgroundColor
for theme in 'solarized-dark' 'dracula'
apply-theme(theme)
.folderList-item--active
@extend .folderList-item
color $ui-monokai-text-color
background-color $ui-monokai-button-backgroundColor
&:active
background-color $ui-monokai-button-backgroundColor
&:hover
color $ui-monokai-text-color
background-color $ui-monokai-button-backgroundColor
body[data-theme="dracula"]
.folderList-item
&:hover
background-color $ui-dracula-button-backgroundColor
color $ui-dracula-text-color
&:active
color $ui-dracula-text-color
background-color $ui-dracula-button-backgroundColor
.folderList-item--active
@extend .folderList-item
color $ui-dracula-text-color
background-color $ui-dracula-button-backgroundColor
&:active
background-color $ui-dracula-button-backgroundColor
&:hover
color $ui-dracula-text-color
background-color $ui-dracula-button-backgroundColor
for theme in $themes
apply-theme(theme)

View File

@@ -12,7 +12,9 @@ import CSSModules from 'browser/lib/CSSModules'
const StorageList = ({ storageList, isFolded }) => (
<div styleName={isFolded ? 'storageList-folded' : 'storageList'}>
{storageList.length > 0 ? storageList : (
{storageList.length > 0 ? (
storageList
) : (
<div styleName='storageList-empty'>No storage mount.</div>
)}
</div>

View File

@@ -1,7 +1,5 @@
.storageList
absolute left right
bottom 37px
top 180px
margin-bottom 37px
overflow-y auto
.storageList-folded

View File

@@ -15,16 +15,44 @@ import CSSModules from 'browser/lib/CSSModules'
* @param {string} bgColor tab backgroundColor
*/
const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, handleContextMenu, isActive, isRelated, count, color}) => (
<div styleName='tagList-itemContainer' onContextMenu={e => handleContextMenu(e, name)}>
{isRelated
? <button styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} onClick={() => handleClickNarrowToTag(name)}>
const TagListItem = ({
name,
handleClickTagListItem,
handleClickNarrowToTag,
handleContextMenu,
isActive,
isRelated,
count,
color
}) => (
<div
styleName='tagList-itemContainer'
onContextMenu={e => handleContextMenu(e, name)}
>
{isRelated ? (
<button
styleName={
isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'
}
onClick={() => handleClickNarrowToTag(name)}
>
<i className={isActive ? 'fa fa-minus-circle' : 'fa fa-plus-circle'} />
</button>
: <div styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} />
) : (
<div
styleName={
isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'
}
<button styleName={isActive ? 'tagList-item-active' : 'tagList-item'} onClick={() => handleClickTagListItem(name)}>
<span styleName='tagList-item-color' style={{backgroundColor: color || 'transparent'}} />
/>
)}
<button
styleName={isActive ? 'tagList-item-active' : 'tagList-item'}
onClick={() => handleClickTagListItem(name)}
>
<span
styleName='tagList-item-color'
style={{ backgroundColor: color || 'transparent' }}
/>
<span styleName='tagList-item-name'>
{`# ${name}`}
<span styleName='tagList-item-count'>{count !== 0 ? count : ''}</span>

View File

@@ -94,23 +94,30 @@ body[data-theme="white"]
.tagList-item-count
color $ui-text-color
body[data-theme="dark"]
apply-theme(theme)
body[data-theme={theme}]
.tagList-item
color $ui-dark-inactive-text-color
color get-theme-var(theme, 'inactive-text-color')
&:hover
color $ui-dark-text-color
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
color get-theme-var(theme, 'text-color')
background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 20%)
&:active
color $ui-dark-text-color
background-color $ui-dark-button--active-backgroundColor
color get-theme-var(theme, 'text-color')
background-color get-theme-var(theme, 'button--active-backgroundColor')
.tagList-item-active
background-color $ui-dark-button--active-backgroundColor
color $ui-dark-text-color
background-color get-theme-var(theme, 'button--active-backgroundColor')
color get-theme-var(theme, 'text-color')
&:active
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 50%)
&:hover
color $ui-dark-text-color
background-color alpha($ui-dark-button--active-backgroundColor, 50%)
color get-theme-var(theme, 'text-color')
background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 50%)
.tagList-item-count
color $ui-dark-button--active-color
color get-theme-var(theme, 'button--active-color')
for theme in 'dark'
apply-theme(theme)
for theme in $themes
apply-theme(theme)

View File

@@ -11,17 +11,20 @@ import styles from './TodoListPercentage.styl'
* @param {number} percentageOfTodo
*/
const TodoListPercentage = ({
percentageOfTodo, onClearCheckboxClick
}) => (
<div styleName='percentageBar' style={{display: isNaN(percentageOfTodo) ? 'none' : ''}}>
const TodoListPercentage = ({ percentageOfTodo, onClearCheckboxClick }) => (
<div
styleName='percentageBar'
style={{ display: isNaN(percentageOfTodo) ? 'none' : '' }}
>
<div styleName='progressBar' style={{ width: `${percentageOfTodo}%` }}>
<div styleName='progressBarInner'>
<p styleName='percentageText'>{percentageOfTodo}%</p>
</div>
</div>
<div styleName='todoClear'>
<p styleName='todoClearText' onClick={(e) => onClearCheckboxClick(e)}>clear</p>
<p styleName='todoClearText' onClick={e => onClearCheckboxClick(e)}>
clear
</p>
</div>
</div>
)

View File

@@ -71,25 +71,19 @@ body[data-theme="solarized-dark"]
.todoClearText
color #fdf6e3
body[data-theme="monokai"]
apply-theme(theme)
body[data-theme={theme}]
.percentageBar
background-color: $ui-monokai-borderColor
background-color: get-theme-var(theme, 'borderColor')
.progressBar
background-color $ui-monokai-active-color
background-color get-theme-var(theme, 'active-color')
.percentageText
color $ui-monokai-text-color
color get-theme-var(theme, 'text-color')
body[data-theme="dracula"]
.percentageBar
background-color $ui-dracula-borderColor
for theme in 'dracula'
apply-theme(theme)
.progressBar
background-color: $ui-dracula-active-color
.percentageText
color $ui-dracula-text-color
.percentageText
color $ui-dracula-text-color
for theme in $themes
apply-theme(theme)

View File

@@ -8,18 +8,21 @@ import CSSModules from 'browser/lib/CSSModules'
import styles from './TodoProcess.styl'
const TodoProcess = ({
todoStatus: {
total: totalTodo,
completed: completedTodo
}
todoStatus: { total: totalTodo, completed: completedTodo }
}) => (
<div styleName='todo-process' style={{display: totalTodo > 0 ? '' : 'none'}}>
<div
styleName='todo-process'
style={{ display: totalTodo > 0 ? '' : 'none' }}
>
<div styleName='todo-process-text'>
<i className='fa fa-fw fa-check-square-o' />
{completedTodo} of {totalTodo}
</div>
<div styleName='todo-process-bar'>
<div styleName='todo-process-bar--inner' style={{width: parseInt(completedTodo / totalTodo * 100) + '%'}} />
<div
styleName='todo-process-bar--inner'
style={{ width: parseInt((completedTodo / totalTodo) * 100) + '%' }}
/>
</div>
</div>
)

View File

@@ -124,40 +124,34 @@ hr
border-bottom solid 1px borderColor
margin 15px 0
h1, h2, h3, h4, h5, h6
margin 1em 0 1.5em
line-height 1.4
font-weight bold
word-wrap break-word
padding .2em 0 .2em
h1
font-size 2.55em
padding-bottom 0.3em
line-height 1.2em
line-height 1.2
border-bottom solid 1px borderColor
margin 1em 0 0.44em
&:first-child
margin-top 0
h2
font-size 1.75em
padding-bottom 0.3em
line-height 1.225em
line-height 1.225
border-bottom solid 1px borderColor
margin 1em 0 0.57em
&:first-child
margin-top 0
h3
font-size 1.5em
line-height 1.43em
margin 1em 0 0.66em
line-height 1.43
h4
font-size 1.25em
line-height 1.4em
margin 1em 0 0.8em
line-height 1.4
h5
font-size 1em
line-height 1.4em
margin 1em 0 1em
line-height 1.1
h6
font-size 1em
line-height 1.4em
margin 1em 0 1em
color #777
p
line-height 1.6em
@@ -427,6 +421,9 @@ pre.fence
canvas, svg
max-width 100% !important
svg[ratio]
width 100%
.gallery
width 100%
height 50vh
@@ -447,6 +444,44 @@ pre.fence
color $ui-text-color
background-color $ui-tag-backgroundColor
.markdownIt-TOC-wrapper
list-style none
position fixed
right 0
top 0
margin-left 15px
z-index 1000
transition transform .2s ease-in-out
transform translateX(100%)
.markdownIt-TOC
display block
max-height 90vh
overflow-y auto
padding 25px
padding-left 38px
&,
&:before
background-color $ui-dark-backgroundColor
color: $ui-dark-text-color
&:hover
transform translateX(-15px)
&:before
content 'TOC'
position absolute
width 60px
height 30px
top 60px
left -29px
display flex
align-items center
justify-content center
transform-origin top left
transform rotate(-90deg)
themeDarkBackground = darken(#21252B, 10%)
themeDarkText = #f9f9f9
themeDarkBorder = lighten(themeDarkBackground, 20%)
@@ -514,137 +549,63 @@ body[data-theme="dark"]
color $ui-dark-text-color
background-color $ui-dark-tag-backgroundColor
themeSolarizedDarkTableOdd = $ui-solarized-dark-noteDetail-backgroundColor
themeSolarizedDarkTableEven = darken($ui-solarized-dark-noteDetail-backgroundColor, 10%)
themeSolarizedDarkTableHead = themeSolarizedDarkTableEven
themeSolarizedDarkTableBorder = themeDarkBorder
.markdownIt-TOC-wrapper
&,
&:before
background-color darken(themeDarkBackground, 5%)
color themeDarkText
body[data-theme="solarized-dark"]
color $ui-solarized-dark-text-color
apply-theme(theme)
body[data-theme={theme}]
color get-theme-var(theme, 'text-color')
border-color themeDarkBorder
background-color $ui-solarized-dark-noteDetail-backgroundColor
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
table
thead
tr
background-color themeSolarizedDarkTableHead
background-color get-theme-var(theme, 'table-head-backgroundColor')
th
border-color themeSolarizedDarkTableBorder
border-color get-theme-var(theme, 'table-borderColor')
&:last-child
border-right solid 1px themeSolarizedDarkTableBorder
border-right solid 1px get-theme-var(theme, 'table-borderColor')
tbody
tr:nth-child(2n + 1)
background-color themeSolarizedDarkTableOdd
background-color get-theme-var(theme, 'table-odd-backgroundColor')
tr:nth-child(2n)
background-color themeSolarizedDarkTableEven
background-color get-theme-var(theme, 'table-even-backgroundColor')
td
border-color themeSolarizedDarkTableBorder
border-color get-theme-var(theme, 'table-borderColor')
&:last-child
border-right solid 1px themeSolarizedDarkTableBorder
dl
border-color themeDarkBorder
background-color themeSolarizedDarkTableHead
dt
border-color themeDarkBorder
dd
border-color themeDarkBorder
background-color $ui-solarized-dark-noteDetail-backgroundColor
pre.fence
.gallery
.carousel-main, .carousel-footer
background-color $ui-solarized-dark-noteDetail-backgroundColor
.prev, .next
color $ui-solarized-dark-button--active-color
background-color $ui-solarized-dark-button-backgroundColor
themeMonokaiTableOdd = $ui-monokai-noteDetail-backgroundColor
themeMonokaiTableEven = darken($ui-monokai-noteDetail-backgroundColor, 10%)
themeMonokaiTableHead = themeMonokaiTableEven
themeMonokaiTableBorder = themeDarkBorder
body[data-theme="monokai"]
color $ui-monokai-text-color
border-color themeDarkBorder
background-color $ui-monokai-noteDetail-backgroundColor
table
thead
tr
background-color themeMonokaiTableHead
th
border-color themeMonokaiTableBorder
&:last-child
border-right solid 1px themeMonokaiTableBorder
tbody
tr:nth-child(2n + 1)
background-color themeMonokaiTableOdd
tr:nth-child(2n)
background-color themeMonokaiTableEven
td
border-color themeMonokaiTableBorder
&:last-child
border-right solid 1px themeMonokaiTableBorder
border-right solid 1px get-theme-var(theme, 'table-borderColor')
kbd
background-color themeDarkBackground
background-color get-theme-var(theme, 'kbd-backgroundColor')
color get-theme-var(theme, 'kbd-color')
dl
border-color themeDarkBorder
background-color themeMonokaiTableHead
background-color get-theme-var(theme, 'table-head-backgroundColor')
dt
border-color themeDarkBorder
dd
border-color themeDarkBorder
background-color $ui-monokai-noteDetail-backgroundColor
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
pre.fence
.gallery
.carousel-main, .carousel-footer
background-color $ui-monokai-noteDetail-backgroundColor
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
.prev, .next
color $ui-monokai-button--active-color
background-color $ui-monokai-button-backgroundColor
color get-theme-var(theme, 'button--active-color')
background-color get-theme-var(theme, 'button-backgroundColor')
themeDraculaTableOdd = $ui-dracula-noteDetail-backgroundColor
themeDraculaTableEven = darken($ui-dracula-noteDetail-backgroundColor, 10%)
themeDraculaTableHead = themeDraculaTableEven
themeDraculaTableBorder = themeDarkBorder
.markdownIt-TOC-wrapper
&,
&:before
background-color darken(get-theme-var(theme, 'noteDetail-backgroundColor'), 15%)
color themeDarkText
body[data-theme="dracula"]
color $ui-dracula-text-color
border-color themeDarkBorder
background-color $ui-dracula-noteDetail-backgroundColor
table
thead
tr
background-color themeDraculaTableHead
th
border-color themeDraculaTableBorder
&:last-child
border-right solid 1px themeDraculaTableBorder
tbody
tr:nth-child(2n + 1)
background-color themeDraculaTableOdd
tr:nth-child(2n)
background-color themeDraculaTableEven
td
border-color themeDraculaTableBorder
&:last-child
border-right solid 1px themeDraculaTableBorder
kbd
background-color themeDarkBackground
for theme in 'solarized-dark' 'dracula'
apply-theme(theme)
dl
border-color themeDarkBorder
background-color themeDraculaTableHead
dt
border-color themeDarkBorder
dd
border-color themeDarkBorder
background-color $ui-dracula-noteDetail-backgroundColor
pre.fence
.gallery
.carousel-main, .carousel-footer
background-color $ui-dracula-noteDetail-backgroundColor
.prev, .next
color $ui-dracula-button--active-color
background-color $ui-dracula-button-backgroundColor
for theme in $themes
apply-theme(theme)

View File

@@ -1,4 +1,5 @@
import mermaidAPI from 'mermaid'
import uiThemes from 'browser/lib/ui-themes'
// fixes bad styling in the mermaid dark theme
const darkThemeStyling = `
@@ -22,18 +23,46 @@ function getId () {
function render(element, content, theme, enableHTMLLabel) {
try {
const height = element.attributes.getNamedItem('data-height')
if (height && height.value !== 'undefined') {
const isPredefined = height && height.value !== 'undefined'
if (isPredefined) {
element.style.height = height.value + 'vh'
}
const isDarkTheme = theme === 'dark' || theme === 'solarized-dark' || theme === 'monokai' || theme === 'dracula'
const isDarkTheme = uiThemes.some(
item => item.name === theme && item.isDark
)
mermaidAPI.initialize({
theme: isDarkTheme ? 'dark' : 'default',
themeCSS: isDarkTheme ? darkThemeStyling : '',
useMaxWidth: false,
flowchart: { htmlLabels: enableHTMLLabel }
flowchart: {
htmlLabels: enableHTMLLabel
},
gantt: {
useWidth: element.clientWidth
}
})
mermaidAPI.render(getId(), content, (svgGraph) => {
mermaidAPI.render(getId(), content, svgGraph => {
element.innerHTML = svgGraph
if (!isPredefined) {
const el = element.firstChild
const viewBox = el.getAttribute('viewBox').split(' ')
let ratio = viewBox[2] / viewBox[3]
if (el.style.maxWidth) {
const maxWidth = parseFloat(el.style.maxWidth)
ratio *= el.parentNode.clientWidth / maxWidth
}
el.setAttribute('ratio', ratio)
el.setAttribute('height', el.parentNode.clientWidth / ratio)
console.log(el)
}
})
} catch (e) {
element.className = 'mermaid-error'

View File

@@ -11,7 +11,7 @@ export function parse (boostnotercPath = _boostnotercPath) {
return JSON.parse(sander.readFileSync(boostnotercPath).toString())
} catch (e) {
console.warn(e)
console.warn('Your .boostnoterc is broken so it\'s not used.')
console.warn("Your .boostnoterc is broken so it's not used.")
return {}
}
}

View File

@@ -9,7 +9,8 @@ class SnippetManager {
id: crypto.randomBytes(16).toString('hex'),
name: 'Dummy text',
prefix: ['lorem', 'ipsum'],
content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
content:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
}
]
this.snippets = []

View File

@@ -76,11 +76,7 @@ export default class TextEditorInterface {
)
}
} else {
this.doc.replaceRange(
'',
{ line: row, ch: 0 },
{ line: row + 1, ch: 0 }
)
this.doc.replaceRange('', { line: row, ch: 0 }, { line: row + 1, ch: 0 })
}
}

View File

@@ -13,7 +13,8 @@ export function confirmDeleteNote (confirmDeletion, permanent) {
}
const dialogButtonIndex = dialog.showMessageBox(
remote.getCurrentWindow(), alertConfig
remote.getCurrentWindow(),
alertConfig
)
return dialogButtonIndex === 0

View File

@@ -9,12 +9,17 @@ const CODEMIRROR_EXTRA_THEME_PATH = 'extra_scripts/codemirror/theme'
const isProduction = process.env.NODE_ENV === 'production'
const paths = [
isProduction ? path.join(app.getAppPath(), CODEMIRROR_THEME_PATH) : path.resolve(CODEMIRROR_THEME_PATH),
isProduction ? path.join(app.getAppPath(), CODEMIRROR_EXTRA_THEME_PATH) : path.resolve(CODEMIRROR_EXTRA_THEME_PATH)
isProduction
? path.join(app.getAppPath(), CODEMIRROR_THEME_PATH)
: path.resolve(CODEMIRROR_THEME_PATH),
isProduction
? path.join(app.getAppPath(), CODEMIRROR_EXTRA_THEME_PATH)
: path.resolve(CODEMIRROR_EXTRA_THEME_PATH)
]
const themes = paths
.map(directory => fs.readdirSync(directory).map(file => {
.map(directory =>
fs.readdirSync(directory).map(file => {
const name = file.substring(0, file.lastIndexOf('.'))
return {
@@ -22,26 +27,33 @@ const themes = paths
path: path.join(directory, file),
className: `cm-s-${name}`
}
}))
})
)
.reduce((accumulator, value) => accumulator.concat(value), [])
.sort((a, b) => a.name.localeCompare(b.name))
themes.splice(themes.findIndex(({ name }) => name === 'solarized'), 1, {
themes.splice(
themes.findIndex(({ name }) => name === 'solarized'),
1,
{
name: 'solarized dark',
path: path.join(paths[0], 'solarized.css'),
className: `cm-s-solarized cm-s-dark`
}, {
},
{
name: 'solarized light',
path: path.join(paths[0], 'solarized.css'),
className: `cm-s-solarized cm-s-light`
})
}
)
themes.splice(0, 0, {
name: 'default',
path: path.join(paths[0], 'elegant.css'),
className: `cm-s-default`
})
const snippetFile = process.env.NODE_ENV !== 'test'
const snippetFile =
process.env.NODE_ENV !== 'test'
? path.join(app.getPath('userData'), 'snippets.json')
: '' // return nothing as we specified different path to snippets.json in test

View File

@@ -3,7 +3,7 @@ const { Menu, MenuItem } = remote
function popup(templates) {
const menu = new Menu()
templates.forEach((item) => {
templates.forEach(item => {
menu.append(new MenuItem(item))
})
menu.popup(remote.getCurrentWindow())

View File

@@ -17,7 +17,12 @@ const uri2path = require('file-uri-to-path')
* @returns {Electron.Menu} The created electron context menu
*/
const buildEditorContextMenu = function(editor, event) {
if (editor == null || event == null || event.pageX == null || event.pageY == null) {
if (
editor == null ||
event == null ||
event.pageX == null ||
event.pageY == null
) {
return null
}
const cursor = editor.coordsChar({ left: event.pageX, top: event.pageY })
@@ -40,30 +45,44 @@ const buildEditorContextMenu = function (editor, event) {
isMisspelled: isMisspelled,
spellingSuggestions: suggestion
}
const template = [{
const template = [
{
role: 'cut'
}, {
},
{
role: 'copy'
}, {
},
{
role: 'paste'
}, {
},
{
role: 'selectall'
}]
}
]
if (selection.isMisspelled) {
const suggestions = selection.spellingSuggestions
template.unshift.apply(template, suggestions.map(function (suggestion) {
template.unshift.apply(
template,
suggestions
.map(function(suggestion) {
return {
label: suggestion,
click: function(suggestion) {
if (editor != null) {
editor.replaceRange(suggestion.label, wordRange.anchor, wordRange.head)
editor.replaceRange(
suggestion.label,
wordRange.anchor,
wordRange.head
)
}
}
}
}).concat({
})
.concat({
type: 'separator'
}))
})
)
}
return Menu.buildFromTemplate(template)
}
@@ -75,18 +94,29 @@ const buildEditorContextMenu = function (editor, event) {
* @returns {Electron.Menu} The created electron context menu
*/
const buildMarkdownPreviewContextMenu = function(markdownPreview, event) {
if (markdownPreview == null || event == null || event.pageX == null || event.pageY == null) {
if (
markdownPreview == null ||
event == null ||
event.pageX == null ||
event.pageY == null
) {
return null
}
// Default context menu inclusions
const template = [{
const template = [
{
role: 'copy'
}, {
},
{
role: 'selectall'
}]
}
]
if (event.target.tagName.toLowerCase() === 'a' && event.target.getAttribute('href')) {
if (
event.target.tagName.toLowerCase() === 'a' &&
event.target.getAttribute('href')
) {
// Link opener for files on the local system pointed to by href
const href = event.target.href
const isLocalFile = href.startsWith('file:')
@@ -94,31 +124,29 @@ const buildMarkdownPreviewContextMenu = function (markdownPreview, event) {
const absPath = uri2path(href)
try {
if (fs.lstatSync(absPath).isFile()) {
template.push(
{
template.push({
label: i18n.__('Show in explorer'),
click: (e) => shell.showItemInFolder(absPath)
}
)
click: e => shell.showItemInFolder(absPath)
})
}
} catch (e) {
console.log('Error while evaluating if the file is locally available', e)
console.log(
'Error while evaluating if the file is locally available',
e
)
}
}
// Add option to context menu to copy url
template.push(
{
template.push({
label: i18n.__('Copy Url'),
click: (e) => clipboard.writeText(href)
}
)
click: e => clipboard.writeText(href)
})
}
return Menu.buildFromTemplate(template)
}
module.exports =
{
module.exports = {
buildEditorContextMenu: buildEditorContextMenu,
buildMarkdownPreviewContextMenu: buildMarkdownPreviewContextMenu
}

View File

@@ -3,8 +3,19 @@ import 'codemirror-mode-elixir'
const stylusCodeInfo = CodeMirror.modeInfo.find(info => info.name === 'Stylus')
if (stylusCodeInfo == null) {
CodeMirror.modeInfo.push({name: 'Stylus', mime: 'text/x-styl', mode: 'stylus', ext: ['styl'], alias: ['styl']})
CodeMirror.modeInfo.push({
name: 'Stylus',
mime: 'text/x-styl',
mode: 'stylus',
ext: ['styl'],
alias: ['styl']
})
} else {
stylusCodeInfo.alias = ['styl']
}
CodeMirror.modeInfo.push({name: 'Elixir', mime: 'text/x-elixir', mode: 'elixir', ext: ['ex']})
CodeMirror.modeInfo.push({
name: 'Elixir',
mime: 'text/x-elixir',
mode: 'elixir',
ext: ['ex']
})

View File

@@ -1,4 +1,8 @@
export function findNoteTitle (value, enableFrontMatterTitle, frontMatterTitleField = 'title') {
export function findNoteTitle(
value,
enableFrontMatterTitle,
frontMatterTitleField = 'title'
) {
const splitted = value.split('\n')
let title = null
let isInsideCodeBlock = false
@@ -6,8 +10,13 @@ export function findNoteTitle (value, enableFrontMatterTitle, frontMatterTitleFi
if (splitted[0] === '---') {
let line = 0
while (++line < splitted.length) {
if (enableFrontMatterTitle && splitted[line].startsWith(frontMatterTitleField + ':')) {
title = splitted[line].substring(frontMatterTitleField.length + 1).trim()
if (
enableFrontMatterTitle &&
splitted[line].startsWith(frontMatterTitleField + ':')
) {
title = splitted[line]
.substring(frontMatterTitleField.length + 1)
.trim()
break
}
@@ -22,11 +31,15 @@ export function findNoteTitle (value, enableFrontMatterTitle, frontMatterTitleFi
if (title === null) {
splitted.some((line, index) => {
const trimmedLine = line.trim()
const trimmedNextLine = splitted[index + 1] === undefined ? '' : splitted[index + 1].trim()
const trimmedNextLine =
splitted[index + 1] === undefined ? '' : splitted[index + 1].trim()
if (trimmedLine.match('```')) {
isInsideCodeBlock = !isInsideCodeBlock
}
if (isInsideCodeBlock === false && (trimmedLine.match(/^# +/) || trimmedNextLine.match(/^=+$/))) {
if (
isInsideCodeBlock === false &&
(trimmedLine.match(/^# +/) || trimmedNextLine.match(/^=+$/))
) {
title = trimmedLine
return true
}
@@ -35,7 +48,7 @@ export function findNoteTitle (value, enableFrontMatterTitle, frontMatterTitleFi
if (title === null) {
title = ''
splitted.some((line) => {
splitted.some(line => {
if (line.trim().length > 0) {
title = line.trim()
return true

View File

@@ -2,9 +2,10 @@ const _ = require('lodash')
export function findStorage(storageKey) {
const cachedStorageList = JSON.parse(localStorage.getItem('storages'))
if (!_.isArray(cachedStorageList)) throw new Error('Target storage doesn\'t exist.')
if (!_.isArray(cachedStorageList))
throw new Error("Target storage doesn't exist.")
const storage = _.find(cachedStorageList, { key: storageKey })
if (storage === undefined) throw new Error('Target storage doesn\'t exist.')
if (storage === undefined) throw new Error("Target storage doesn't exist.")
return storage
}

View File

@@ -3,7 +3,7 @@ export function getTodoStatus (content) {
let numberOfTodo = 0
let numberOfCompletedTodo = 0
splitted.forEach((line) => {
splitted.forEach(line => {
const trimmedLine = line.trim().replace(/^(>\s*)*/, '')
if (trimmedLine.match(/^[+\-*] \[(\s|x)] ./i)) {
numberOfTodo++
@@ -21,5 +21,5 @@ export function getTodoStatus (content) {
export function getTodoPercentageOfCompleted(content) {
const state = getTodoStatus(content)
return Math.floor(state.completed / state.total * 100)
return Math.floor((state.completed / state.total) * 100)
}

View File

@@ -9,7 +9,7 @@
export function decodeEntities(text) {
var entities = [
['apos', '\''],
['apos', "'"],
['amp', '&'],
['lt', '<'],
['gt', '>'],
@@ -26,14 +26,14 @@ export function decodeEntities (text) {
export function encodeEntities(text) {
const entities = [
['\'', 'apos'],
["'", 'apos'],
['<', 'lt'],
['>', 'gt'],
['\\?', '#63'],
['\\$', '#36']
]
entities.forEach((entity) => {
entities.forEach(entity => {
text = text.replace(new RegExp(entity[0], 'g'), `&${entity[1]};`)
})
return text

View File

@@ -8,7 +8,8 @@ const i18n = new (require('i18n-2'))({
// setup some locales - other locales default to the first locale
locales: getLocales(),
extension: '.json',
directory: process.env.NODE_ENV === 'production'
directory:
process.env.NODE_ENV === 'production'
? path.join(app.getAppPath(), './locales')
: path.resolve('./locales'),
devMode: false

View File

@@ -9,16 +9,22 @@ module.exports = function definitionListPlugin (md) {
let start = state.bMarks[line] + state.tShift[line]
const max = state.eMarks[line]
if (start >= max) { return -1 }
if (start >= max) {
return -1
}
// Check bullet
const marker = state.src.charCodeAt(start++)
if (marker !== 0x7E/* ~ */ && marker !== 0x3A/* : */) { return -1 }
if (marker !== 0x7e /* ~ */ && marker !== 0x3a /* : */) {
return -1
}
const pos = state.skipSpaces(start)
// require space after ":"
if (start === pos) { return -1 }
if (start === pos) {
return -1
}
return start
}
@@ -29,7 +35,10 @@ module.exports = function definitionListPlugin (md) {
let i
let l
for (i = idx + 2, l = state.tokens.length - 2; i < l; i++) {
if (state.tokens[i].level === level && state.tokens[i].type === 'paragraph_open') {
if (
state.tokens[i].level === level &&
state.tokens[i].type === 'paragraph_open'
) {
state.tokens[i + 2].hidden = true
state.tokens[i].hidden = true
i += 2
@@ -63,21 +72,31 @@ module.exports = function definitionListPlugin (md) {
if (silent) {
// quirk: validation mode validates a dd block only, not a whole deflist
if (state.ddIndent < 0) { return false }
if (state.ddIndent < 0) {
return false
}
return skipMarker(state, startLine) >= 0
}
nextLine = startLine + 1
if (nextLine >= endLine) { return false }
if (nextLine >= endLine) {
return false
}
if (state.isEmpty(nextLine)) {
nextLine++
if (nextLine >= endLine) { return false }
if (nextLine >= endLine) {
return false
}
}
if (state.sCount[nextLine] < state.blkIndent) { return false }
if (state.sCount[nextLine] < state.blkIndent) {
return false
}
contentStart = skipMarker(state, nextLine)
if (contentStart < 0) { return false }
if (contentStart < 0) {
return false
}
// Start list
listTokIdx = state.tokens.length
@@ -100,8 +119,7 @@ module.exports = function definitionListPlugin (md) {
// needed to break out of the second one
//
/* eslint no-labels:0,block-scoped-var:0 */
OUTER:
for (;;) {
OUTER: for (;;) {
prevEmptyEnd = false
token = state.push('dt_open', 'dt', 1)
@@ -109,7 +127,9 @@ module.exports = function definitionListPlugin (md) {
token = state.push('inline', '', 0)
token.map = [dtLine, dtLine]
token.content = state.getLines(dtLine, dtLine + 1, state.blkIndent, false).trim()
token.content = state
.getLines(dtLine, dtLine + 1, state.blkIndent, false)
.trim()
token.children = []
token = state.push('dt_close', 'dt', -1)
@@ -120,14 +140,17 @@ module.exports = function definitionListPlugin (md) {
pos = contentStart
max = state.eMarks[ddLine]
offset = state.sCount[ddLine] + contentStart - (state.bMarks[ddLine] + state.tShift[ddLine])
offset =
state.sCount[ddLine] +
contentStart -
(state.bMarks[ddLine] + state.tShift[ddLine])
while (pos < max) {
ch = state.src.charCodeAt(pos)
if (isSpace(ch)) {
if (ch === 0x09) {
offset += 4 - offset % 4
offset += 4 - (offset % 4)
} else {
offset++
}
@@ -153,8 +176,11 @@ module.exports = function definitionListPlugin (md) {
state.parentType = 'deflist'
newEndLine = ddLine
while (++newEndLine < endLine && (state.sCount[newEndLine] >= state.sCount[ddLine] || state.isEmpty(newEndLine))) {
}
while (
++newEndLine < endLine &&
(state.sCount[newEndLine] >= state.sCount[ddLine] ||
state.isEmpty(newEndLine))
) {}
oldLineMax = state.lineMax
state.lineMax = newEndLine
@@ -169,7 +195,7 @@ module.exports = function definitionListPlugin (md) {
}
// Item become loose if finish with empty line,
// but we should filter last element, because it means list finish
prevEmptyEnd = (state.line - ddLine) > 1 && state.isEmpty(state.line - 1)
prevEmptyEnd = state.line - ddLine > 1 && state.isEmpty(state.line - 1)
state.tShift[ddLine] = oldTShift
state.sCount[ddLine] = oldSCount
@@ -182,11 +208,17 @@ module.exports = function definitionListPlugin (md) {
itemLines[1] = nextLine = state.line
if (nextLine >= endLine) { break OUTER }
if (nextLine >= endLine) {
break OUTER
}
if (state.sCount[nextLine] < state.blkIndent) { break OUTER }
if (state.sCount[nextLine] < state.blkIndent) {
break OUTER
}
contentStart = skipMarker(state, nextLine)
if (contentStart < 0) { break }
if (contentStart < 0) {
break
}
ddLine = nextLine
@@ -194,20 +226,36 @@ module.exports = function definitionListPlugin (md) {
// insert DD tag and repeat checking
}
if (nextLine >= endLine) { break }
if (nextLine >= endLine) {
break
}
dtLine = nextLine
if (state.isEmpty(dtLine)) { break }
if (state.sCount[dtLine] < state.blkIndent) { break }
if (state.isEmpty(dtLine)) {
break
}
if (state.sCount[dtLine] < state.blkIndent) {
break
}
ddLine = dtLine + 1
if (ddLine >= endLine) { break }
if (state.isEmpty(ddLine)) { ddLine++ }
if (ddLine >= endLine) { break }
if (ddLine >= endLine) {
break
}
if (state.isEmpty(ddLine)) {
ddLine++
}
if (ddLine >= endLine) {
break
}
if (state.sCount[ddLine] < state.blkIndent) { break }
if (state.sCount[ddLine] < state.blkIndent) {
break
}
contentStart = skipMarker(state, ddLine)
if (contentStart < 0) { break }
if (contentStart < 0) {
break
}
// go to the next loop iteration:
// insert DT and DD tags and repeat checking
@@ -228,5 +276,7 @@ module.exports = function definitionListPlugin (md) {
return true
}
md.block.ruler.before('paragraph', 'deflist', deflist, { alt: [ 'paragraph', 'reference' ] })
md.block.ruler.before('paragraph', 'deflist', deflist, {
alt: ['paragraph', 'reference']
})
}

View File

@@ -12,7 +12,7 @@ module.exports = function (md, renderers, defaultRenderer) {
}
const marker = state.src.charCodeAt(pos)
if (marker !== 0x7E/* ~ */ && marker !== 0x60 /* ` */) {
if (marker !== 0x7e /* ~ */ && marker !== 0x60 /* ` */) {
return false
}
@@ -46,7 +46,10 @@ module.exports = function (md, renderers, defaultRenderer) {
if (pos < max && state.sCount[nextLine] < state.blkIndent) {
break
}
if (state.src.charCodeAt(pos) !== marker || state.sCount[nextLine] - state.blkIndent >= 4) {
if (
state.src.charCodeAt(pos) !== marker ||
state.sCount[nextLine] - state.blkIndent >= 4
) {
continue
}
@@ -127,10 +130,12 @@ module.exports = function (md, renderers, defaultRenderer) {
})
for (const name in renderers) {
md.renderer.rules[`${name}_fence`] = (tokens, index) => renderers[name](tokens[index])
md.renderer.rules[`${name}_fence`] = (tokens, index) =>
renderers[name](tokens[index])
}
if (defaultRenderer) {
md.renderer.rules['_fence'] = (tokens, index) => defaultRenderer(tokens[index])
md.renderer.rules['_fence'] = (tokens, index) =>
defaultRenderer(tokens[index])
}
}

View File

@@ -2,13 +2,18 @@
module.exports = function frontMatterPlugin(md) {
function frontmatter(state, startLine, endLine, silent) {
if (startLine !== 0 || state.src.substr(startLine, state.eMarks[0]) !== '---') {
if (
startLine !== 0 ||
state.src.substr(startLine, state.eMarks[0]) !== '---'
) {
return false
}
let line = 0
while (++line < state.lineMax) {
if (state.src.substring(state.bMarks[line], state.eMarks[line]) === '---') {
if (
state.src.substring(state.bMarks[line], state.eMarks[line]) === '---'
) {
state.line = line + 1
return true

View File

@@ -38,7 +38,7 @@ module.exports = function sanitizePlugin (md, options) {
}
const tagRegex = /<([A-Z][A-Z0-9]*)\s*((?:\s*[A-Z][A-Z0-9]*(?:=("|')(?:[^\3]+?)\3)?)*)\s*\/?>|<\/([A-Z][A-Z0-9]*)\s*>/i
const attributesRegex = /([A-Z][A-Z0-9]*)(?:=("|')([^\2]+?)\2)?/ig
const attributesRegex = /([A-Z][A-Z0-9]*)(?:=("|')([^\2]+?)\2)?/gi
function sanitizeInline(html, options) {
let match = tagRegex.exec(html)
@@ -46,7 +46,12 @@ function sanitizeInline (html, options) {
return ''
}
const { allowedTags, allowedAttributes, selfClosing, allowedSchemesAppliedToAttributes } = options
const {
allowedTags,
allowedAttributes,
selfClosing,
allowedSchemesAppliedToAttributes
} = options
if (match[1] !== undefined) {
// opening tag
@@ -65,9 +70,17 @@ function sanitizeInline (html, options) {
name = match[1].toLowerCase()
value = match[3]
if (allowedAttributes['*'].indexOf(name) !== -1 || (allowedAttributes[tag] && allowedAttributes[tag].indexOf(name) !== -1)) {
if (
allowedAttributes['*'].indexOf(name) !== -1 ||
(allowedAttributes[tag] && allowedAttributes[tag].indexOf(name) !== -1)
) {
if (allowedSchemesAppliedToAttributes.indexOf(name) !== -1) {
if (naughtyHRef(value, options) || (tag === 'iframe' && name === 'src' && naughtyIFrame(value, options))) {
if (
naughtyHRef(value, options) ||
(tag === 'iframe' &&
name === 'src' &&
naughtyIFrame(value, options))
) {
continue
}
}

View File

@@ -46,7 +46,9 @@ export function generateInEditor (editor) {
}
function addTocAtCursorPosition() {
const toc = generate(editor.getRange(editor.getCursor(), {line: Infinity}))
const toc = generate(
editor.getRange(editor.getCursor(), { line: Infinity })
)
editor.replaceRange(wrapTocWithEol(toc, editor), editor.getCursor())
}
@@ -88,7 +90,10 @@ export function generate (markdownText) {
function wrapTocWithEol(toc, editor) {
const leftWrap = editor.getCursor().ch === 0 ? '' : EOL
const rightWrap = editor.getLine(editor.getCursor().line).length === editor.getCursor().ch ? '' : EOL
const rightWrap =
editor.getLine(editor.getCursor().line).length === editor.getCursor().ch
? ''
: EOL
return leftWrap + toc + rightWrap
}

View File

@@ -17,7 +17,9 @@ function createGutter (str, firstLineNumber) {
for (let i = firstLineNumber; i <= lastLineNumber; i++) {
lines.push('<span class="CodeMirror-linenumber">' + i + '</span>')
}
return '<span class="lineNumber CodeMirror-gutters">' + lines.join('') + '</span>'
return (
'<span class="lineNumber CodeMirror-gutters">' + lines.join('') + '</span>'
)
}
class Markdown {
@@ -37,29 +39,129 @@ class Markdown {
this.md.linkify.set({ fuzzyLink: false })
if (updatedOptions.sanitize !== 'NONE') {
const allowedTags = ['iframe', 'input', 'b',
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'h7', 'h8', 'br', 'b', 'i', 'strong', 'em', 'a', 'pre', 'code', 'img', 'tt',
'div', 'ins', 'del', 'sup', 'sub', 'p', 'ol', 'ul', 'table', 'thead', 'tbody', 'tfoot', 'blockquote',
'dl', 'dt', 'dd', 'kbd', 'q', 'samp', 'var', 'hr', 'ruby', 'rt', 'rp', 'li', 'tr', 'td', 'th', 's', 'strike', 'summary', 'details'
const allowedTags = [
'iframe',
'input',
'b',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'h7',
'h8',
'br',
'b',
'i',
'strong',
'em',
'a',
'pre',
'code',
'img',
'tt',
'div',
'ins',
'del',
'sup',
'sub',
'p',
'ol',
'ul',
'table',
'thead',
'tbody',
'tfoot',
'blockquote',
'dl',
'dt',
'dd',
'kbd',
'q',
'samp',
'var',
'hr',
'ruby',
'rt',
'rp',
'li',
'tr',
'td',
'th',
's',
'strike',
'summary',
'details'
]
const allowedAttributes = [
'abbr', 'accept', 'accept-charset',
'accesskey', 'action', 'align', 'alt', 'axis',
'border', 'cellpadding', 'cellspacing', 'char',
'charoff', 'charset', 'checked',
'clear', 'cols', 'colspan', 'color',
'compact', 'coords', 'datetime', 'dir',
'disabled', 'enctype', 'for', 'frame',
'headers', 'height', 'hreflang',
'hspace', 'ismap', 'label', 'lang',
'maxlength', 'media', 'method',
'multiple', 'name', 'nohref', 'noshade',
'nowrap', 'open', 'prompt', 'readonly', 'rel', 'rev',
'rows', 'rowspan', 'rules', 'scope',
'selected', 'shape', 'size', 'span',
'start', 'summary', 'tabindex', 'target',
'title', 'type', 'usemap', 'valign', 'value',
'vspace', 'width', 'itemprop'
'abbr',
'accept',
'accept-charset',
'accesskey',
'action',
'align',
'alt',
'axis',
'border',
'cellpadding',
'cellspacing',
'char',
'charoff',
'charset',
'checked',
'clear',
'cols',
'colspan',
'color',
'compact',
'coords',
'datetime',
'dir',
'disabled',
'enctype',
'for',
'frame',
'headers',
'height',
'hreflang',
'hspace',
'ismap',
'label',
'lang',
'maxlength',
'media',
'method',
'multiple',
'name',
'nohref',
'noshade',
'nowrap',
'open',
'prompt',
'readonly',
'rel',
'rev',
'rows',
'rowspan',
'rules',
'scope',
'selected',
'shape',
'size',
'span',
'start',
'summary',
'tabindex',
'target',
'title',
'type',
'usemap',
'valign',
'value',
'vspace',
'width',
'itemprop'
]
if (updatedOptions.sanitize === 'ALLOW_STYLES') {
@@ -72,15 +174,15 @@ class Markdown {
allowedTags,
allowedAttributes: {
'*': allowedAttributes,
'a': ['href'],
'div': ['itemscope', 'itemtype'],
'blockquote': ['cite'],
'del': ['cite'],
'ins': ['cite'],
'q': ['cite'],
'img': ['src', 'width', 'height'],
'iframe': ['src', 'width', 'height', 'frameborder', 'allowfullscreen'],
'input': ['type', 'id', 'checked']
a: ['href'],
div: ['itemscope', 'itemtype'],
blockquote: ['cite'],
del: ['cite'],
ins: ['cite'],
q: ['cite'],
img: ['src', 'width', 'height'],
iframe: ['src', 'width', 'height', 'frameborder', 'allowfullscreen'],
input: ['type', 'id', 'checked']
},
allowedIframeHostnames: ['www.youtube.com'],
selfClosing: ['img', 'br', 'hr', 'input'],
@@ -124,20 +226,41 @@ class Markdown {
slugify: require('./slugify')
})
this.md.use(require('markdown-it-kbd'))
this.md.use(require('markdown-it-admonition'), {types: ['note', 'hint', 'attention', 'caution', 'danger', 'error', 'quote', 'abstract', 'question']})
this.md.use(require('markdown-it-admonition'), {
types: [
'note',
'hint',
'attention',
'caution',
'danger',
'error',
'quote',
'abstract',
'question'
]
})
this.md.use(require('markdown-it-abbr'))
this.md.use(require('markdown-it-sub'))
this.md.use(require('markdown-it-sup'))
this.md.use(markdownItTocAndAnchor, {
this.md.use(md => {
markdownItTocAndAnchor(md, {
toc: true,
tocPattern: /\[TOC\]/i,
anchorLink: false,
appendIdToHeading: false
})
md.renderer.rules.toc_open = () => '<div class="markdownIt-TOC-wrapper">'
md.renderer.rules.toc_close = () => '</div>'
})
this.md.use(require('./markdown-it-deflist'))
this.md.use(require('./markdown-it-frontmatter'))
this.md.use(require('./markdown-it-fence'), {
this.md.use(
require('./markdown-it-fence'),
{
chart: token => {
if (token.parameters.hasOwnProperty('yaml')) {
token.parameters.format = 'yaml'
@@ -145,54 +268,75 @@ class Markdown {
return `<pre class="fence" data-line="${token.map[0]}">
<span class="filename">${token.fileName}</span>
<div class="chart" data-height="${token.parameters.height}" data-format="${token.parameters.format || 'json'}">${token.content}</div>
<div class="chart" data-height="${
token.parameters.height
}" data-format="${token.parameters.format || 'json'}">${
token.content
}</div>
</pre>`
},
flowchart: token => {
return `<pre class="fence" data-line="${token.map[0]}">
<span class="filename">${token.fileName}</span>
<div class="flowchart" data-height="${token.parameters.height}">${token.content}</div>
<div class="flowchart" data-height="${token.parameters.height}">${
token.content
}</div>
</pre>`
},
gallery: token => {
const content = token.content.split('\n').slice(0, -1).map(line => {
const content = token.content
.split('\n')
.slice(0, -1)
.map(line => {
const match = /!\[[^\]]*]\(([^\)]*)\)/.exec(line)
if (match) {
return mdurl.encode(match[1])
} else {
return mdurl.encode(line)
}
}).join('\n')
})
.join('\n')
return `<pre class="fence" data-line="${token.map[0]}">
<span class="filename">${token.fileName}</span>
<div class="gallery" data-autoplay="${token.parameters.autoplay}" data-height="${token.parameters.height}">${content}</div>
<div class="gallery" data-autoplay="${
token.parameters.autoplay
}" data-height="${token.parameters.height}">${content}</div>
</pre>`
},
mermaid: token => {
return `<pre class="fence" data-line="${token.map[0]}">
<span class="filename">${token.fileName}</span>
<div class="mermaid" data-height="${token.parameters.height}">${token.content}</div>
<div class="mermaid" data-height="${token.parameters.height}">${
token.content
}</div>
</pre>`
},
sequence: token => {
return `<pre class="fence" data-line="${token.map[0]}">
<span class="filename">${token.fileName}</span>
<div class="sequence" data-height="${token.parameters.height}">${token.content}</div>
<div class="sequence" data-height="${token.parameters.height}">${
token.content
}</div>
</pre>`
}
}, token => {
},
token => {
return `<pre class="code CodeMirror" data-line="${token.map[0]}">
<span class="filename">${token.fileName}</span>
${createGutter(token.content, token.firstLineNumber)}
<code class="${token.langType}">${token.content}</code>
</pre>`
})
}
)
const deflate = require('markdown-it-plantuml/lib/deflate')
const plantuml = require('markdown-it-plantuml')
const plantUmlStripTrailingSlash = (url) => url.endsWith('/') ? url.slice(0, -1) : url
const plantUmlServerAddress = plantUmlStripTrailingSlash(config.preview.plantUMLServerAddress)
const plantUmlStripTrailingSlash = url =>
url.endsWith('/') ? url.slice(0, -1) : url
const plantUmlServerAddress = plantUmlStripTrailingSlash(
config.preview.plantUMLServerAddress
)
const parsePlantUml = function(umlCode, openMarker, closeMarker, type) {
const s = unescape(encodeURIComponent(umlCode))
const zippedCode = deflate.encode64(
@@ -202,39 +346,47 @@ class Markdown {
}
this.md.use(plantuml, {
generateSource: (umlCode) => parsePlantUml(umlCode, '@startuml', '@enduml', 'svg')
generateSource: umlCode =>
parsePlantUml(umlCode, '@startuml', '@enduml', 'svg')
})
// Ditaa support. PlantUML server doesn't support Ditaa in SVG, so we set the format as PNG at the moment.
this.md.use(plantuml, {
openMarker: '@startditaa',
closeMarker: '@endditaa',
generateSource: (umlCode) => parsePlantUml(umlCode, '@startditaa', '@endditaa', 'png')
generateSource: umlCode =>
parsePlantUml(umlCode, '@startditaa', '@endditaa', 'png')
})
// Mindmap support
this.md.use(plantuml, {
openMarker: '@startmindmap',
closeMarker: '@endmindmap',
generateSource: (umlCode) => parsePlantUml(umlCode, '@startmindmap', '@endmindmap', 'svg')
generateSource: umlCode =>
parsePlantUml(umlCode, '@startmindmap', '@endmindmap', 'svg')
})
// WBS support
this.md.use(plantuml, {
openMarker: '@startwbs',
closeMarker: '@endwbs',
generateSource: (umlCode) => parsePlantUml(umlCode, '@startwbs', '@endwbs', 'svg')
generateSource: umlCode =>
parsePlantUml(umlCode, '@startwbs', '@endwbs', 'svg')
})
// Gantt support
this.md.use(plantuml, {
openMarker: '@startgantt',
closeMarker: '@endgantt',
generateSource: (umlCode) => parsePlantUml(umlCode, '@startgantt', '@endgantt', 'svg')
generateSource: umlCode =>
parsePlantUml(umlCode, '@startgantt', '@endgantt', 'svg')
})
// Override task item
this.md.block.ruler.at('paragraph', function (state, startLine/*, endLine */) {
this.md.block.ruler.at('paragraph', function(
state,
startLine /*, endLine */
) {
let content, terminate, i, l, token
let nextLine = startLine + 1
const terminatorRules = state.md.block.ruler.getRules('paragraph')
@@ -244,10 +396,14 @@ class Markdown {
for (; nextLine < endLine && !state.isEmpty(nextLine); nextLine++) {
// this would be a code block normally, but after paragraph
// it's considered a lazy continuation regardless of what's there
if (state.sCount[nextLine] - state.blkIndent > 3) { continue }
if (state.sCount[nextLine] - state.blkIndent > 3) {
continue
}
// quirk for blockquotes, this line should already be checked by that rule
if (state.sCount[nextLine] < 0) { continue }
if (state.sCount[nextLine] < 0) {
continue
}
// Some tags can terminate paragraph without empty line.
terminate = false
@@ -257,10 +413,14 @@ class Markdown {
break
}
}
if (terminate) { break }
if (terminate) {
break
}
}
content = state.getLines(startLine, nextLine, state.blkIndent, false).trim()
content = state
.getLines(startLine, nextLine, state.blkIndent, false)
.trim()
state.line = nextLine
@@ -270,18 +430,31 @@ class Markdown {
if (state.parentType === 'list') {
const match = content.match(/^\[( |x)\] ?(.+)/i)
if (match) {
const liToken = lastFindInArray(state.tokens, token => token.type === 'list_item_open')
const liToken = lastFindInArray(
state.tokens,
token => token.type === 'list_item_open'
)
if (liToken) {
if (!liToken.attrs) {
liToken.attrs = []
}
if (config.preview.lineThroughCheckbox) {
liToken.attrs.push(['class', `taskListItem${match[1] !== ' ' ? ' checked' : ''}`])
liToken.attrs.push([
'class',
`taskListItem${match[1] !== ' ' ? ' checked' : ''}`
])
} else {
liToken.attrs.push(['class', 'taskListItem'])
}
}
content = `<label class='taskListItem${match[1] !== ' ' ? ' checked' : ''}' for='checkbox-${startLine + 1}'><input type='checkbox'${match[1] !== ' ' ? ' checked' : ''} id='checkbox-${startLine + 1}'/> ${content.substring(4, content.length)}</label>`
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>`
}
}
@@ -302,7 +475,7 @@ class Markdown {
// Add line number attribute for scrolling
const originalRender = this.md.renderer.render
this.md.renderer.render = (tokens, options, env) => {
tokens.forEach((token) => {
tokens.forEach(token => {
switch (token.type) {
case 'blockquote_open':
case 'dd_open':

View File

@@ -4,12 +4,22 @@ import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import queryString from 'query-string'
import { push } from 'connected-react-router'
export function createMarkdownNote (storage, folder, dispatch, location, params, config) {
export function createMarkdownNote(
storage,
folder,
dispatch,
location,
params,
config
) {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_MARKDOWN')
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
let tags = []
if (config.ui.tagNewNoteWithFilteringTags && location.pathname.match(/\/tags/)) {
if (
config.ui.tagNewNoteWithFilteringTags &&
location.pathname.match(/\/tags/)
) {
tags = params.tagname.split(' ')
}
@@ -29,25 +39,40 @@ export function createMarkdownNote (storage, folder, dispatch, location, params,
note: note
})
dispatch(push({
dispatch(
push({
pathname: location.pathname,
search: queryString.stringify({ key: noteHash })
}))
})
)
ee.emit('list:jump', noteHash)
ee.emit('detail:focus')
})
}
export function createSnippetNote (storage, folder, dispatch, location, params, config) {
export function createSnippetNote(
storage,
folder,
dispatch,
location,
params,
config
) {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_SNIPPET')
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
let tags = []
if (config.ui.tagNewNoteWithFilteringTags && location.pathname.match(/\/tags/)) {
if (
config.ui.tagNewNoteWithFilteringTags &&
location.pathname.match(/\/tags/)
) {
tags = params.tagname.split(' ')
}
const defaultLanguage = config.editor.snippetDefaultLanguage === 'Auto Detect' ? null : config.editor.snippetDefaultLanguage
const defaultLanguage =
config.editor.snippetDefaultLanguage === 'Auto Detect'
? null
: config.editor.snippetDefaultLanguage
return dataApi
.createNote(storage, {
@@ -71,10 +96,12 @@ export function createSnippetNote (storage, folder, dispatch, location, params,
type: 'UPDATE_NOTE',
note: note
})
dispatch(push({
dispatch(
push({
pathname: location.pathname,
search: queryString.stringify({ key: noteHash })
}))
})
)
ee.emit('list:jump', noteHash)
ee.emit('detail:focus')
})

View File

@@ -2,10 +2,12 @@ import _ from 'lodash'
export default function searchFromNotes(notes, search) {
if (search.trim().length === 0) return []
const searchBlocks = search.split(' ').filter(block => { return block !== '' })
const searchBlocks = search.split(' ').filter(block => {
return block !== ''
})
let foundNotes = notes
searchBlocks.forEach((block) => {
searchBlocks.forEach(block => {
foundNotes = findByWordOrTag(foundNotes, block)
})
return foundNotes
@@ -18,14 +20,19 @@ function findByWordOrTag (notes, block) {
}
const tagRegExp = new RegExp(_.escapeRegExp(tag), 'i')
const wordRegExp = new RegExp(_.escapeRegExp(block), 'i')
return notes.filter((note) => {
if (_.isArray(note.tags) && note.tags.some((_tag) => _tag.match(tagRegExp))) {
return notes.filter(note => {
if (_.isArray(note.tags) && note.tags.some(_tag => _tag.match(tagRegExp))) {
return true
}
if (note.type === 'SNIPPET_NOTE') {
return note.description.match(wordRegExp) || note.snippets.some((snippet) => {
return snippet.name.match(wordRegExp) || snippet.content.match(wordRegExp)
return (
note.description.match(wordRegExp) ||
note.snippets.some(snippet => {
return (
snippet.name.match(wordRegExp) || snippet.content.match(wordRegExp)
)
})
)
} else if (note.type === 'MARKDOWN_NOTE') {
return note.content.match(wordRegExp)
}

View File

@@ -1,10 +1,14 @@
module.exports = function slugify(title) {
const slug = encodeURI(
title.trim()
title
.trim()
.replace(/^\s+/, '')
.replace(/\s+$/, '')
.replace(/\s+/g, '-')
.replace(/[\]\[\!\'\#\$\%\&\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\{\|\}\~\`]/g, '')
.replace(
/[\]\[\!\'\#\$\%\&\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\{\|\}\~\`]/g,
''
)
)
return slug

View File

@@ -50,8 +50,7 @@ function setLanguage (editor, lang) {
dictionary = new Typo(lang, false, false, {
dictionaryPath: DICTIONARY_PATH,
asyncLoad: true,
loadedCallback: () =>
checkWholeDocument(editor)
loadedCallback: () => checkWholeDocument(editor)
})
}
}
@@ -77,7 +76,10 @@ function checkWholeDocument (editor) {
*/
function checkMultiLineRange(editor, from, to) {
function sortRange(pos1, pos2) {
if (pos1.line > pos2.line || (pos1.line === pos2.line && pos1.ch > pos2.ch)) {
if (
pos1.line > pos2.line ||
(pos1.line === pos2.line && pos1.ch > pos2.ch)
) {
return { from: pos2, to: pos1 }
}
return { from: pos1, to: pos2 }
@@ -97,7 +99,7 @@ function checkMultiLineRange (editor, from, to) {
while (w <= wEnd) {
const wordRange = editor.findWordAt({ line: l, ch: w })
self.checkWord(editor, wordRange)
w += (wordRange.head.ch - wordRange.anchor.ch) + 1
w += wordRange.head.ch - wordRange.anchor.ch + 1
}
}
}
@@ -116,7 +118,9 @@ function checkWord (editor, wordRange) {
return
}
if (!dictionary.check(word)) {
editor.markText(wordRange.anchor, wordRange.head, {className: styles[CSS_ERROR_CLASS]})
editor.markText(wordRange.anchor, wordRange.head, {
className: styles[CSS_ERROR_CLASS]
})
}
}
@@ -138,17 +142,25 @@ function checkChangeRange (editor, fromChangeObject, toChangeObject) {
let smallest = start.from
let biggest = end.to
for (const currentPos of possiblePositions) {
if (currentPos.line < smallest.line || (currentPos.line === smallest.line && currentPos.ch < smallest.ch)) {
if (
currentPos.line < smallest.line ||
(currentPos.line === smallest.line && currentPos.ch < smallest.ch)
) {
smallest = currentPos
}
if (currentPos.line > biggest.line || (currentPos.line === biggest.line && currentPos.ch > biggest.ch)) {
if (
currentPos.line > biggest.line ||
(currentPos.line === biggest.line && currentPos.ch > biggest.ch)
) {
biggest = currentPos
}
}
return { start: smallest, end: biggest }
}
if (dictionary === null || editor == null) { return }
if (dictionary === null || editor == null) {
return
}
try {
const { start, end } = getStartAndEnd(fromChangeObject, toChangeObject)
@@ -165,7 +177,10 @@ function checkChangeRange (editor, fromChangeObject, toChangeObject) {
self.checkMultiLineRange(editor, start, end)
} catch (e) {
console.info('Error during the spell check. It might be due to problems figuring out the range of the new text..', e)
console.info(
'Error during the spell check. It might be due to problems figuring out the range of the new text..',
e
)
}
}
@@ -173,14 +188,22 @@ function saveLiveSpellCheckFrom (changeObject) {
liveSpellCheckFrom = changeObject
}
let liveSpellCheckFrom
const debouncedSpellCheckLeading = _.debounce(saveLiveSpellCheckFrom, MILLISECONDS_TILL_LIVECHECK, {
'leading': true,
'trailing': false
})
const debouncedSpellCheck = _.debounce(checkChangeRange, MILLISECONDS_TILL_LIVECHECK, {
'leading': false,
'trailing': true
})
const debouncedSpellCheckLeading = _.debounce(
saveLiveSpellCheckFrom,
MILLISECONDS_TILL_LIVECHECK,
{
leading: true,
trailing: false
}
)
const debouncedSpellCheck = _.debounce(
checkChangeRange,
MILLISECONDS_TILL_LIVECHECK,
{
leading: false,
trailing: true
}
)
/**
* Handles a keystroke. Buffers the input and performs a live spell check after a certain time. Uses _debounce from lodash to buffer the input

44
browser/lib/ui-themes.js Normal file
View File

@@ -0,0 +1,44 @@
import i18n from 'browser/lib/i18n'
export default [
{
name: 'dark',
label: i18n.__('Dark'),
isDark: true
},
{
name: 'default',
label: i18n.__('Default'),
isDark: false
},
{
name: 'dracula',
label: i18n.__('Dracula'),
isDark: true
},
{
name: 'monokai',
label: i18n.__('Monokai'),
isDark: true
},
{
name: 'nord',
label: i18n.__('Nord'),
isDark: true
},
{
name: 'solarized-dark',
label: i18n.__('Solarized Dark'),
isDark: true
},
{
name: 'vulcan',
label: i18n.__('Vulcan'),
isDark: true
},
{
name: 'white',
label: i18n.__('White'),
isDark: false
}
]

View File

@@ -133,7 +133,9 @@ export function isObjectEqual (a, b) {
}
export function isMarkdownTitleURL(str) {
return /(^#{1,6}\s)(?:\w+:|^)\/\/(?:[^\s\.]+\.\S{2}|localhost[\:?\d]*)/.test(str)
return /(^#{1,6}\s)(?:\w+:|^)\/\/(?:[^\s\.]+\.\S{2}|localhost[\:?\d]*)/.test(
str
)
}
export function humanFileSize(bytes) {

View File

@@ -24,23 +24,16 @@ body[data-theme="dark"]
.empty-message
color $ui-dark-inactive-text-color
body[data-theme="solarized-dark"]
apply-theme(theme)
body[data-theme={theme}]
.root
background-color $ui-solarized-dark-noteDetail-backgroundColor
border-left 1px solid $ui-solarized-dark-borderColor
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
border-left 1px solid get-theme-var(theme, 'borderColor')
.empty-message
color $ui-solarized-dark-text-color
color get-theme-var(theme, 'text-color')
body[data-theme="monokai"]
.root
background-color $ui-monokai-noteDetail-backgroundColor
border-left 1px solid $ui-monokai-borderColor
.empty-message
color $ui-monokai-text-color
for theme in 'solarized-dark' 'dracula'
apply-theme(theme)
body[data-theme="dracula"]
.root
background-color $ui-dracula-noteDetail-backgroundColor
border-left 1px solid $ui-dracula-borderColor
.empty-message
color $ui-dracula-text-color
for theme in $themes
apply-theme(theme)

View File

@@ -25,12 +25,15 @@ class FolderSelect extends React.Component {
}
handleClick(e) {
this.setState({
this.setState(
{
status: 'SEARCH',
optionIndex: -1
}, () => {
},
() => {
this.refs.search.focus()
})
}
)
}
handleFocus(e) {
@@ -53,30 +56,39 @@ class FolderSelect extends React.Component {
switch (e.keyCode) {
case 13:
if (this.state.status === 'FOCUS') {
this.setState({
this.setState(
{
status: 'SEARCH',
optionIndex: -1
}, () => {
},
() => {
this.refs.search.focus()
})
}
)
}
break
case 40:
case 38:
if (this.state.status === 'FOCUS') {
this.setState({
this.setState(
{
status: 'SEARCH',
optionIndex: 0
}, () => {
},
() => {
this.refs.search.focus()
})
}
)
}
break
case 9:
if (e.shiftKey) {
e.preventDefault()
const tabbable = document.querySelectorAll('a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])')
const previousEl = tabbable[Array.prototype.indexOf.call(tabbable, this.refs.root) - 1]
const tabbable = document.querySelectorAll(
'a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])'
)
const previousEl =
tabbable[Array.prototype.indexOf.call(tabbable, this.refs.root) - 1]
if (previousEl != null) previousEl.focus()
}
}
@@ -93,9 +105,12 @@ class FolderSelect extends React.Component {
handleSearchInputChange(e) {
const { folders } = this.props
const search = this.refs.search.value
const optionIndex = search.length > 0
? _.findIndex(folders, (folder) => {
return folder.name.match(new RegExp('^' + _.escapeRegExp(search), 'i'))
const optionIndex =
search.length > 0
? _.findIndex(folders, folder => {
return folder.name.match(
new RegExp('^' + _.escapeRegExp(search), 'i')
)
})
: -1
@@ -121,11 +136,14 @@ class FolderSelect extends React.Component {
break
case 27:
e.stopPropagation()
this.setState({
this.setState(
{
status: 'FOCUS'
}, () => {
},
() => {
this.refs.root.focus()
})
}
)
}
}
@@ -159,24 +177,30 @@ class FolderSelect extends React.Component {
const folder = folders[optionIndex]
if (folder != null) {
this.setState({
this.setState(
{
status: 'FOCUS'
}, () => {
},
() => {
this.setValue(folder.key)
this.refs.root.focus()
})
}
)
}
}
handleOptionClick(storageKey, folderKey) {
return (e) => {
return e => {
e.stopPropagation()
this.setState({
this.setState(
{
status: 'FOCUS'
}, () => {
},
() => {
this.setValue(storageKey + '-' + folderKey)
this.refs.root.focus()
})
}
)
}
}
@@ -192,7 +216,7 @@ class FolderSelect extends React.Component {
const folderKey = splitted.shift()
let options = []
data.storageMap.forEach((storage, index) => {
storage.folders.forEach((folder) => {
storage.folders.forEach(folder => {
options.push({
storage: storage,
folder: folder
@@ -200,39 +224,49 @@ class FolderSelect extends React.Component {
})
})
const 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) {
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))
}
const optionList = options
.map((option, index) => {
const optionList = options.map((option, index) => {
return (
<div styleName={index === this.state.optionIndex
<div
styleName={
index === this.state.optionIndex
? 'search-optionList-item--active'
: 'search-optionList-item'
}
key={option.storage.key + '-' + option.folder.key}
onClick={(e) => this.handleOptionClick(option.storage.key, option.folder.key)(e)}
onClick={e =>
this.handleOptionClick(option.storage.key, option.folder.key)(e)
}
>
<span styleName='search-optionList-item-name'
<span
styleName='search-optionList-item-name'
style={{ borderColor: option.folder.color }}
>
{option.folder.name}
<span styleName='search-optionList-item-name-surfix'>in {option.storage.name}</span>
<span styleName='search-optionList-item-name-surfix'>
in {option.storage.name}
</span>
</span>
</div>
)
})
return (
<div className={_.isString(className)
? 'FolderSelect ' + className
: 'FolderSelect'
<div
className={
_.isString(className) ? 'FolderSelect ' + className : 'FolderSelect'
}
styleName={this.state.status === 'SEARCH'
styleName={
this.state.status === 'SEARCH'
? 'root--search'
: this.state.status === 'FOCUS'
? 'root--focus'
@@ -240,28 +274,28 @@ class FolderSelect extends React.Component {
}
ref='root'
tabIndex='0'
onClick={(e) => this.handleClick(e)}
onFocus={(e) => this.handleFocus(e)}
onBlur={(e) => this.handleBlur(e)}
onKeyDown={(e) => this.handleKeyDown(e)}
onClick={e => this.handleClick(e)}
onFocus={e => this.handleFocus(e)}
onBlur={e => this.handleBlur(e)}
onKeyDown={e => this.handleKeyDown(e)}
>
{this.state.status === 'SEARCH'
? <div styleName='search'>
<input styleName='search-input'
{this.state.status === 'SEARCH' ? (
<div styleName='search'>
<input
styleName='search-input'
ref='search'
value={this.state.search}
placeholder={i18n.__('Folder...')}
onChange={(e) => this.handleSearchInputChange(e)}
onBlur={(e) => this.handleSearchInputBlur(e)}
onKeyDown={(e) => this.handleSearchInputKeyDown(e)}
onChange={e => this.handleSearchInputChange(e)}
onBlur={e => this.handleSearchInputBlur(e)}
onKeyDown={e => this.handleSearchInputKeyDown(e)}
/>
<div styleName='search-optionList'
ref='optionList'
>
<div styleName='search-optionList' ref='optionList'>
{optionList}
</div>
</div>
: <div styleName='idle' style={{color: currentOption.folder.color}}>
) : (
<div styleName='idle' style={{ color: currentOption.folder.color }}>
<div styleName='idle-label'>
<i className='fa fa-folder' />
<span styleName='idle-label-name'>
@@ -269,8 +303,7 @@ class FolderSelect extends React.Component {
</span>
</div>
</div>
}
)}
</div>
)
}
@@ -280,11 +313,13 @@ FolderSelect.propTypes = {
className: PropTypes.string,
onChange: PropTypes.func,
value: PropTypes.string,
folders: PropTypes.arrayOf(PropTypes.shape({
folders: PropTypes.arrayOf(
PropTypes.shape({
key: PropTypes.string,
name: PropTypes.string,
color: PropTypes.string
}))
})
)
}
export default CSSModules(FolderSelect, styles)

View File

@@ -134,54 +134,39 @@ body[data-theme="dark"]
.search-optionList-item-name-surfix
color $ui-dark-inactive-text-color
body[data-theme="monokai"]
apply-theme(theme)
body[data-theme={theme}]
.root
color $ui-dark-text-color
&:hover
color white
background-color $ui-monokai-button--hover-backgroundColor
border-color $ui-monokai-borderColor
background-color get-theme-var(theme, 'button--hover-backgroundColor')
border-color get-theme-var(theme, 'borderColor')
.search-input
color get-theme-var(theme, 'text-color')
background-color transparent
border-color get-theme-var(theme, 'borderColor')
.search-optionList
color white
border-color $ui-monokai-borderColor
background-color $ui-monokai-button-backgroundColor
color get-theme-var(theme, 'text-color')
border-color get-theme-var(theme, 'borderColor')
background-color get-theme-var(theme, 'button-backgroundColor')
.search-optionList-item
&:hover
background-color lighten($ui-monokai-button--hover-backgroundColor, 15%)
background-color lighten(get-theme-var(theme, 'button--hover-backgroundColor'), 15%)
.search-optionList-item--active
background-color $ui-monokai-button--active-backgroundColor
color $ui-monokai-button--active-color
background-color get-theme-var(theme, 'button--active-backgroundColor')
color get-theme-var(theme, 'button--active-color')
&:hover
background-color $ui-monokai-button--active-backgroundColor
color $ui-monokai-button--active-color
background-color get-theme-var(theme, 'button--active-backgroundColor')
color get-theme-var(theme, 'button--active-color')
.search-optionList-item-name-surfix
color $ui-monokai-inactive-text-color
color get-theme-var(theme, 'inactive-text-color')
body[data-theme="dracula"]
.root
color $ui-dracula-text-color
&:hover
color #f8f8f2
background-color $ui-dark-button--hover-backgroundColor
border-color $ui-dracula-borderColor
for theme in 'solarized-dark' 'dracula'
apply-theme(theme)
.search-optionList
color #f8f8f2
border-color $ui-dracula-borderColor
background-color $ui-dracula-button-backgroundColor
.search-optionList-item
&:hover
background-color lighten($ui-dracula-button--hover-backgroundColor, 15%)
.search-optionList-item--active
background-color $ui-dracula-button--active-backgroundColor
color $ui-dracula-button--active-color
&:hover
background-color $ui-dark-button--hover-backgroundColor
color $ui-dracula-button--active-color
.search-optionList-item-name-surfix
color $ui-dracula-inactive-text-color
for theme in $themes
apply-theme(theme)

View File

@@ -36,20 +36,22 @@ class FromUrlButton extends React.Component {
const { className } = this.props
return (
<button className={_.isString(className)
? 'FromUrlButton ' + className
: 'FromUrlButton'
<button
className={
_.isString(className) ? 'FromUrlButton ' + className : 'FromUrlButton'
}
styleName={this.state.isActive || this.props.isActive
? 'root--active'
: 'root'
styleName={
this.state.isActive || this.props.isActive ? 'root--active' : 'root'
}
onMouseDown={(e) => this.handleMouseDown(e)}
onMouseUp={(e) => this.handleMouseUp(e)}
onMouseLeave={(e) => this.handleMouseLeave(e)}
onClick={this.props.onClick}>
<img styleName='icon'
src={this.state.isActive || this.props.isActive
onMouseDown={e => this.handleMouseDown(e)}
onMouseUp={e => this.handleMouseUp(e)}
onMouseLeave={e => this.handleMouseLeave(e)}
onClick={this.props.onClick}
>
<img
styleName='icon'
src={
this.state.isActive || this.props.isActive
? '../resources/icon/icon-external.svg'
: '../resources/icon/icon-external.svg'
}

View File

@@ -5,14 +5,18 @@ import styles from './FullscreenButton.styl'
import i18n from 'browser/lib/i18n'
const OSX = global.process.platform === 'darwin'
const FullscreenButton = ({
onClick
}) => {
const FullscreenButton = ({ onClick }) => {
const hotkey = (OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')) + '+B'
return (
<button styleName='control-fullScreenButton' title={i18n.__('Fullscreen')} onMouseDown={(e) => onClick(e)}>
<button
styleName='control-fullScreenButton'
title={i18n.__('Fullscreen')}
onMouseDown={e => onClick(e)}
>
<img src='../resources/icon/icon-full.svg' />
<span lang={i18n.locale} styleName='tooltip'>{i18n.__('Fullscreen')}({hotkey})</span>
<span lang={i18n.locale} styleName='tooltip'>
{i18n.__('Fullscreen')}({hotkey})
</span>
</button>
)
}

View File

@@ -4,12 +4,8 @@ import CSSModules from 'browser/lib/CSSModules'
import styles from './InfoButton.styl'
import i18n from 'browser/lib/i18n'
const InfoButton = ({
onClick
}) => (
<button styleName='control-infoButton'
onClick={(e) => onClick(e)}
>
const InfoButton = ({ onClick }) => (
<button styleName='control-infoButton' onClick={e => onClick(e)}>
<img className='infoButton' src='../resources/icon/icon-info.svg' />
<span styleName='tooltip'>{i18n.__('Info')}</span>
</button>

View File

@@ -14,20 +14,39 @@ class InfoPanel extends React.Component {
render() {
const {
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, exportAsPdf, wordCount, letterCount, type, print
storageName,
folderName,
noteLink,
updatedAt,
createdAt,
exportAsMd,
exportAsTxt,
exportAsHtml,
exportAsPdf,
wordCount,
letterCount,
type,
print
} = this.props
return (
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
<div
className='infoPanel'
styleName='control-infoButton-panel'
style={{ display: 'none' }}
>
<div>
<p styleName='modification-date'>{updatedAt}</p>
<p styleName='modification-date-desc'>{i18n.__('MODIFICATION DATE')}</p>
<p styleName='modification-date-desc'>
{i18n.__('MODIFICATION DATE')}
</p>
</div>
<hr />
{type === 'SNIPPET_NOTE'
? ''
: <div styleName='count-wrap'>
{type === 'SNIPPET_NOTE' ? (
''
) : (
<div styleName='count-wrap'>
<div styleName='count-number'>
<p styleName='infoPanel-defaul-count'>{wordCount}</p>
<p styleName='infoPanel-sub-count'>{i18n.__('Words')}</p>
@@ -37,12 +56,9 @@ class InfoPanel extends React.Component {
<p styleName='infoPanel-sub-count'>{i18n.__('Letters')}</p>
</div>
</div>
}
)}
{type === 'SNIPPET_NOTE'
? ''
: <hr />
}
{type === 'SNIPPET_NOTE' ? '' : <hr />}
<div>
<p styleName='infoPanel-default'>{storageName}</p>
@@ -60,8 +76,18 @@ class InfoPanel extends React.Component {
</div>
<div>
<input styleName='infoPanel-noteLink' ref='noteLink' defaultValue={noteLink} onClick={(e) => { e.target.select() }} />
<button onClick={() => this.copyNoteLink()} styleName='infoPanel-copyButton'>
<input
styleName='infoPanel-noteLink'
ref='noteLink'
defaultValue={noteLink}
onClick={e => {
e.target.select()
}}
/>
<button
onClick={() => this.copyNoteLink()}
styleName='infoPanel-copyButton'
>
<i className='fa fa-clipboard' />
</button>
<p styleName='infoPanel-sub'>{i18n.__('NOTE LINK')}</p>
@@ -70,27 +96,39 @@ class InfoPanel extends React.Component {
<hr />
<div id='export-wrap'>
<button styleName='export--enable' onClick={(e) => exportAsMd(e, 'export-md')}>
<button
styleName='export--enable'
onClick={e => exportAsMd(e, 'export-md')}
>
<i className='fa fa-file-code-o' />
<p>{i18n.__('.md')}</p>
</button>
<button styleName='export--enable' onClick={(e) => exportAsTxt(e, 'export-txt')}>
<button
styleName='export--enable'
onClick={e => exportAsTxt(e, 'export-txt')}
>
<i className='fa fa-file-text-o' />
<p>{i18n.__('.txt')}</p>
</button>
<button styleName='export--enable' onClick={(e) => exportAsHtml(e, 'export-html')}>
<button
styleName='export--enable'
onClick={e => exportAsHtml(e, 'export-html')}
>
<i className='fa fa-html5' />
<p>{i18n.__('.html')}</p>
</button>
<button styleName='export--enable' onClick={(e) => exportAsPdf(e, 'export-pdf')}>
<button
styleName='export--enable'
onClick={e => exportAsPdf(e, 'export-pdf')}
>
<i className='fa fa-file-pdf-o' />
<p>{i18n.__('.pdf')}</p>
</button>
<button styleName='export--enable' onClick={(e) => print(e, 'print')}>
<button styleName='export--enable' onClick={e => print(e, 'print')}>
<i className='fa fa-print' />
<p>{i18n.__('Print')}</p>
</button>

View File

@@ -138,162 +138,49 @@
.export--unable
cursor not-allowed
body[data-theme="dark"]
apply-theme(theme)
body[data-theme={theme}]
.control-infoButton-panel
background-color $ui-dark-noteList-backgroundColor
background-color get-theme-var(theme, 'noteList-backgroundColor')
.control-infoButton-panel-trash
background-color $ui-dark-noteList-backgroundColor
background-color get-theme-var(theme, 'noteList-backgroundColor')
.modification-date
color $ui-dark-text-color
color get-theme-var(theme, 'text-color')
.modification-date-desc
color $ui-inactive-text-color
.infoPanel-defaul-count
color $ui-dark-text-color
color get-theme-var(theme, 'text-color')
.infoPanel-sub-count
color $ui-inactive-text-color
.infoPanel-default
color $ui-dark-text-color
color get-theme-var(theme, 'text-color')
.infoPanel-sub
color $ui-inactive-text-color
.infoPanel-noteLink
background-color alpha($ui-dark-borderColor, 60%)
color $ui-dark-text-color
background-color alpha(get-theme-var(theme, 'borderColor'), 20%)
color get-theme-var(theme, 'text-color')
[id=export-wrap]
button
color $ui-dark-inactive-text-color
&:hover
background-color alpha($ui-dark-borderColor, 20%)
color $ui-dark-text-color
background-color alpha(get-theme-var(theme, 'borderColor'), 20%)
color get-theme-var(theme, 'text-color')
p
color $ui-dark-inactive-text-color
&:hover
color $ui-dark-text-color
color get-theme-var(theme, 'text-color')
body[data-theme="solarized-dark"]
.control-infoButton-panel
background-color $ui-solarized-dark-noteList-backgroundColor
for theme in 'dark' 'solarized-dark' 'dracula'
apply-theme(theme)
.control-infoButton-panel-trash
background-color $ui-solarized-ark-noteList-backgroundColor
.modification-date
color $ui-solarized-ark-text-color
.modification-date-desc
color $ui-inactive-text-color
.infoPanel-defaul-count
color $ui-solarized-dark-text-color
.infoPanel-sub-count
color $ui-inactive-text-color
.infoPanel-default
color $ui-solarized-ark-text-color
.infoPanel-sub
color $ui-inactive-text-color
.infoPanel-noteLink
background-color alpha($ui-solarized-dark-borderColor, 20%)
color $ui-solarized-dark-text-color
[id=export-wrap]
button
color $ui-dark-inactive-text-color
&:hover
background-color alpha($ui-solarized-dark-borderColor, 20%)
color $ui-solarized-ark-text-color
p
color $ui-dark-inactive-text-color
&:hover
color $ui-solarized-ark-text-color
body[data-theme="monokai"]
.control-infoButton-panel
background-color $ui-monokai-noteList-backgroundColor
.control-infoButton-panel-trash
background-color $ui-monokai-noteList-backgroundColor
.modification-date
color $ui-monokai-text-color
.modification-date-desc
color $ui-inactive-text-color
.infoPanel-defaul-count
color $ui-monokai-text-color
.infoPanel-sub-count
color $ui-inactive-text-color
.infoPanel-default
color $ui-monokai-text-color
.infoPanel-sub
color $ui-inactive-text-color
.infoPanel-noteLink
background-color alpha($ui-monokai-borderColor, 20%)
color $ui-monokai-text-color
[id=export-wrap]
button
color $ui-dark-inactive-text-color
&:hover
background-color alpha($ui-monokai-borderColor, 20%)
color $ui-monokai-text-color
p
color $ui-dark-inactive-text-color
&:hover
color $ui-monokai-text-color
body[data-theme="dracula"]
.control-infoButton-panel
background-color $ui-dracula-noteList-backgroundColor
.control-infoButton-panel-trash
background-color $ui-dracula-noteList-backgroundColor
.modification-date
color $ui-dracula-text-color
.modification-date-desc
color $ui-inactive-text-color
.infoPanel-defaul-count
color $ui-dracula-text-color
.infoPanel-sub-count
color $ui-inactive-text-color
.infoPanel-default
color $ui-dracula-text-color
.infoPanel-sub
color $ui-inactive-text-color
.infoPanel-noteLink
background-color alpha($ui-dracula-borderColor, 20%)
color $ui-dracula-text-color
[id=export-wrap]
button
color $ui-dark-inactive-text-color
&:hover
background-color alpha($ui-dracula-borderColor, 20%)
color $ui-dracula-text-color
p
color $ui-dark-inactive-text-color
&:hover
color $ui-dracula-text-color
for theme in $themes
apply-theme(theme)

View File

@@ -5,9 +5,20 @@ import styles from './InfoPanel.styl'
import i18n from 'browser/lib/i18n'
const InfoPanelTrashed = ({
storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, exportAsPdf
storageName,
folderName,
updatedAt,
createdAt,
exportAsMd,
exportAsTxt,
exportAsHtml,
exportAsPdf
}) => (
<div className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}>
<div
className='infoPanel'
styleName='control-infoButton-panel-trash'
style={{ display: 'none' }}
>
<div>
<p styleName='modification-date'>{updatedAt}</p>
<p styleName='modification-date-desc'>{i18n.__('MODIFICATION DATE')}</p>
@@ -21,7 +32,10 @@ const InfoPanelTrashed = ({
</div>
<div>
<p styleName='infoPanel-default'><text styleName='infoPanel-trash'>Trash</text>{folderName}</p>
<p styleName='infoPanel-default'>
<text styleName='infoPanel-trash'>Trash</text>
{folderName}
</p>
<p styleName='infoPanel-sub'>{i18n.__('FOLDER')}</p>
</div>
@@ -31,22 +45,34 @@ const InfoPanelTrashed = ({
</div>
<div id='export-wrap'>
<button styleName='export--enable' onClick={(e) => exportAsMd(e, 'export-md')}>
<button
styleName='export--enable'
onClick={e => exportAsMd(e, 'export-md')}
>
<i className='fa fa-file-code-o' />
<p>.md</p>
</button>
<button styleName='export--enable' onClick={(e) => exportAsTxt(e, 'export-txt')}>
<button
styleName='export--enable'
onClick={e => exportAsTxt(e, 'export-txt')}
>
<i className='fa fa-file-text-o' />
<p>.txt</p>
</button>
<button styleName='export--enable' onClick={(e) => exportAsHtml(e, 'export-html')}>
<button
styleName='export--enable'
onClick={e => exportAsHtml(e, 'export-html')}
>
<i className='fa fa-html5' />
<p>.html</p>
</button>
<button styleName='export--enable' onClick={(e) => exportAsPdf(e, 'export-pdf')}>
<button
styleName='export--enable'
onClick={e => exportAsPdf(e, 'export-pdf')}
>
<i className='fa fa-file-pdf-o' />
<p>.pdf</p>
</button>

View File

@@ -31,6 +31,7 @@ import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
import markdownToc from 'browser/lib/markdown-toc-generator'
import queryString from 'query-string'
import { replace } from 'connected-react-router'
import ToggleDirectionButton from 'browser/main/Detail/ToggleDirectionButton'
class MarkdownNoteDetail extends React.Component {
constructor(props) {
@@ -38,15 +39,19 @@ class MarkdownNoteDetail extends React.Component {
this.state = {
isMovingNote: false,
note: Object.assign({
note: Object.assign(
{
title: '',
content: '',
linesHighlighted: []
}, props.note),
},
props.note
),
isLockButtonShown: props.config.editor.type !== 'SPLIT',
isLocked: false,
editorType: props.config.editor.type,
switchPreview: props.config.editor.switchPreview
switchPreview: props.config.editor.switchPreview,
RTL: false
}
this.dispatchTimer = null
@@ -61,8 +66,10 @@ class MarkdownNoteDetail extends React.Component {
componentDidMount() {
ee.on('topbar:togglelockbutton', this.toggleLockButton)
ee.on('topbar:toggledirectionbutton', () => this.handleSwitchDirection())
ee.on('topbar:togglemodebutton', () => {
const reversedType = this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
const reversedType =
this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
this.handleSwitchMode(reversedType)
})
ee.on('hotkey:deletenote', this.handleDeleteNote.bind(this))
@@ -71,15 +78,19 @@ class MarkdownNoteDetail extends React.Component {
componentWillReceiveProps(nextProps) {
const isNewNote = nextProps.note.key !== this.props.note.key
const hasDeletedTags = nextProps.note.tags.length < this.props.note.tags.length
const hasDeletedTags =
nextProps.note.tags.length < this.props.note.tags.length
if (!this.state.isMovingNote && (isNewNote || hasDeletedTags)) {
if (this.saveQueue != null) this.saveNow()
this.setState({
this.setState(
{
note: Object.assign({ linesHighlighted: [] }, nextProps.note)
}, () => {
},
() => {
this.refs.content.reload()
if (this.refs.tags) this.refs.tags.reset()
})
}
)
}
// Focus content if using blur or double click
@@ -99,6 +110,7 @@ class MarkdownNoteDetail extends React.Component {
componentWillUnmount() {
ee.off('topbar:togglelockbutton', this.toggleLockButton)
ee.on('topbar:toggledirectionbutton', this.handleSwitchDirection)
ee.off('code:generate-toc', this.generateToc)
if (this.saveQueue != null) this.saveNow()
}
@@ -113,7 +125,11 @@ class MarkdownNoteDetail extends React.Component {
const { note } = this.state
note.content = this.refs.content.value
let title = findNoteTitle(note.content, this.props.config.editor.enableFrontMatterTitle, this.props.config.editor.frontMatterTitleField)
let title = findNoteTitle(
note.content,
this.props.config.editor.enableFrontMatterTitle,
this.props.config.editor.frontMatterTitleField
)
title = striptags(title)
title = markdown.strip(title)
note.title = title
@@ -140,9 +156,7 @@ class MarkdownNoteDetail extends React.Component {
clearTimeout(this.saveQueue)
this.saveQueue = null
dataApi
.updateNote(note.storage, note.key, this.state.note)
.then((note) => {
dataApi.updateNote(note.storage, note.key, this.state.note).then(note => {
dispatch({
type: 'UPDATE_NOTE',
note: note
@@ -160,46 +174,53 @@ class MarkdownNoteDetail extends React.Component {
dataApi
.moveNote(note.storage, note.key, newStorageKey, newFolderKey)
.then((newNote) => {
this.setState({
.then(newNote => {
this.setState(
{
isMovingNote: true,
note: Object.assign({}, newNote)
}, () => {
},
() => {
const { dispatch, location } = this.props
dispatch({
type: 'MOVE_NOTE',
originNote: note,
note: newNote
})
dispatch(replace({
dispatch(
replace({
pathname: location.pathname,
search: queryString.stringify({
key: newNote.key
})
}))
})
)
this.setState({
isMovingNote: false
})
})
}
)
})
}
handleStarButtonClick(e) {
const { note } = this.state
if (!note.isStarred) AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_STAR')
if (!note.isStarred)
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_STAR')
note.isStarred = !note.isStarred
this.setState({
this.setState(
{
note
}, () => {
},
() => {
this.save()
})
}
)
}
exportAsFile () {
}
exportAsFile() {}
exportAsMd() {
ee.emit('export:save-md')
@@ -227,7 +248,11 @@ class MarkdownNoteDetail extends React.Component {
} else if (e.ctrlKey && e.shiftKey) {
e.preventDefault()
this.jumpPrevTab()
} else if (!e.ctrlKey && !e.shiftKey && e.target === this.refs.description) {
} else if (
!e.ctrlKey &&
!e.shiftKey &&
e.target === this.refs.description
) {
e.preventDefault()
this.focusEditor()
}
@@ -235,9 +260,8 @@ class MarkdownNoteDetail extends React.Component {
// I key
case 73:
{
const isSuper = global.process.platform === 'darwin'
? e.metaKey
: e.ctrlKey
const isSuper =
global.process.platform === 'darwin' ? e.metaKey : e.ctrlKey
if (isSuper) {
e.preventDefault()
this.handleInfoButtonClick(e)
@@ -257,7 +281,7 @@ class MarkdownNoteDetail extends React.Component {
const { note, dispatch } = this.props
dataApi
.deleteNote(note.storage, note.key)
.then((data) => {
.then(data => {
const dispatchHandler = () => {
dispatch({
type: 'DELETE_NOTE',
@@ -273,11 +297,14 @@ class MarkdownNoteDetail extends React.Component {
if (confirmDeleteNote(confirmDeletion, false)) {
note.isTrashed = true
this.setState({
this.setState(
{
note
}, () => {
},
() => {
this.save()
})
}
)
ee.emit('list:next')
}
@@ -289,13 +316,16 @@ class MarkdownNoteDetail extends React.Component {
note.isTrashed = false
this.setState({
this.setState(
{
note
}, () => {
},
() => {
this.save()
this.refs.content.reload()
ee.emit('list:next')
})
}
)
}
handleFullScreenButton(e) {
@@ -310,7 +340,9 @@ class MarkdownNoteDetail extends React.Component {
}
getToggleLockButton() {
return this.state.isLocked ? '../resources/icon/icon-lock.svg' : '../resources/icon/icon-unlock.svg'
return this.state.isLocked
? '../resources/icon/icon-lock.svg'
: '../resources/icon/icon-unlock.svg'
}
handleDeleteKeyDown(e) {
@@ -337,7 +369,9 @@ class MarkdownNoteDetail extends React.Component {
handleInfoButtonClick(e) {
const infoPanel = document.querySelector('.infoPanel')
if (infoPanel.style) infoPanel.style.display = infoPanel.style.display === 'none' ? 'inline' : 'none'
if (infoPanel.style)
infoPanel.style.display =
infoPanel.style.display === 'none' ? 'inline' : 'none'
}
print(e) {
@@ -346,12 +380,21 @@ class MarkdownNoteDetail extends React.Component {
handleSwitchMode(type) {
// If in split mode, hide the lock button
this.setState({ editorType: type, isLockButtonShown: !(type === 'SPLIT') }, () => {
this.setState(
{ editorType: type, isLockButtonShown: !(type === 'SPLIT') },
() => {
this.focus()
const newConfig = Object.assign({}, this.props.config)
newConfig.editor.type = type
ConfigManager.set(newConfig)
})
}
)
}
handleSwitchDirection() {
// If in split mode, hide the lock button
const direction = this.state.RTL
this.setState({ RTL: !direction })
}
handleDeleteNote() {
@@ -362,14 +405,16 @@ class MarkdownNoteDetail extends React.Component {
const { note } = this.state
const splitted = note.content.split('\n')
const clearTodoContent = splitted.map((line) => {
const clearTodoContent = splitted
.map(line => {
const trimmedLine = line.trim()
if (trimmedLine.match(/\[x\]/i)) {
return line.replace(/\[x\]/i, '[ ]')
} else {
return line
}
}).join('\n')
})
.join('\n')
note.content = clearTodoContent
this.refs.content.setValue(note.content)
@@ -382,7 +427,8 @@ class MarkdownNoteDetail extends React.Component {
const { note } = this.state
if (this.state.editorType === 'EDITOR_PREVIEW') {
return <MarkdownEditor
return (
<MarkdownEditor
ref='content'
styleName='body-noteEditor'
config={config}
@@ -393,9 +439,12 @@ class MarkdownNoteDetail extends React.Component {
onChange={this.handleUpdateContent.bind(this)}
isLocked={this.state.isLocked}
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
RTL={this.state.RTL}
/>
)
} else {
return <MarkdownSplitEditor
return (
<MarkdownSplitEditor
ref='content'
config={config}
value={note.content}
@@ -404,7 +453,9 @@ class MarkdownNoteDetail extends React.Component {
linesHighlighted={note.linesHighlighted}
onChange={this.handleUpdateContent.bind(this)}
ignorePreviewPointerEvents={ignorePreviewPointerEvents}
RTL={this.state.RTL}
/>
)
}
}
@@ -416,24 +467,28 @@ class MarkdownNoteDetail extends React.Component {
const options = []
data.storageMap.forEach((storage, index) => {
storage.folders.forEach((folder) => {
storage.folders.forEach(folder => {
options.push({
storage: storage,
folder: folder
})
})
})
const 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'>
<RestoreButton onClick={(e) => this.handleUndoButtonClick(e)} />
<RestoreButton onClick={e => this.handleUndoButtonClick(e)} />
</div>
<div styleName='info-right'>
<PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} />
<InfoButton
onClick={(e) => this.handleInfoButtonClick(e)}
<PermanentDeleteButton
onClick={e => this.handleTrashButtonClick(e)}
/>
<InfoButton onClick={e => this.handleInfoButtonClick(e)} />
<InfoPanelTrashed
storageName={currentOption.storage.name}
folderName={currentOption.folder.name}
@@ -446,15 +501,18 @@ class MarkdownNoteDetail extends React.Component {
/>
</div>
</div>
)
const detailTopBar = <div styleName='info'>
const detailTopBar = (
<div styleName='info'>
<div styleName='info-left'>
<div>
<FolderSelect styleName='info-left-top-folderSelect'
<FolderSelect
styleName='info-left-top-folderSelect'
value={this.state.note.storage + '-' + this.state.note.folder}
ref='folder'
data={data}
onChange={(e) => this.handleFolderChange(e)}
onChange={e => this.handleFolderChange(e)}
/>
</div>
@@ -468,44 +526,57 @@ class MarkdownNoteDetail extends React.Component {
onChange={this.handleUpdateTag.bind(this)}
coloredTags={config.coloredTags}
/>
<TodoListPercentage onClearCheckboxClick={(e) => this.handleClearTodo(e)} percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
<TodoListPercentage
onClearCheckboxClick={e => this.handleClearTodo(e)}
percentageOfTodo={getTodoPercentageOfCompleted(note.content)}
/>
</div>
<div styleName='info-right'>
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
<ToggleModeButton
onClick={e => this.handleSwitchMode(e)}
editorType={editorType}
/>
<ToggleDirectionButton
onClick={e => this.handleSwitchDirection(e)}
isRTL={this.state.RTL}
/>
<StarButton
onClick={(e) => this.handleStarButtonClick(e)}
onClick={e => this.handleStarButtonClick(e)}
isActive={note.isStarred}
/>
{(() => {
const imgSrc = `${this.getToggleLockButton()}`
const lockButtonComponent =
<button styleName='control-lockButton'
onFocus={(e) => this.handleFocus(e)}
onMouseDown={(e) => this.handleLockButtonMouseDown(e)}
const lockButtonComponent = (
<button
styleName='control-lockButton'
onFocus={e => this.handleFocus(e)}
onMouseDown={e => this.handleLockButtonMouseDown(e)}
>
<img src={imgSrc} />
{this.state.isLocked ? <span styleName='tooltip'>Unlock</span> : <span styleName='tooltip'>Lock</span>}
{this.state.isLocked ? (
<span styleName='tooltip'>Unlock</span>
) : (
<span styleName='tooltip'>Lock</span>
)}
</button>
return (
this.state.isLockButtonShown ? lockButtonComponent : ''
)
return this.state.isLockButtonShown ? lockButtonComponent : ''
})()}
<FullscreenButton onClick={(e) => this.handleFullScreenButton(e)} />
<FullscreenButton onClick={e => this.handleFullScreenButton(e)} />
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
<TrashButton onClick={e => this.handleTrashButtonClick(e)} />
<InfoButton
onClick={(e) => this.handleInfoButtonClick(e)}
/>
<InfoButton onClick={e => this.handleInfoButtonClick(e)} />
<InfoPanel
storageName={currentOption.storage.name}
folderName={currentOption.folder.name}
noteLink={`[${note.title}](:note:${queryString.parse(location.search).key})`}
noteLink={`[${note.title}](:note:${
queryString.parse(location.search).key
})`}
updatedAt={formatDate(note.updatedAt)}
createdAt={formatDate(note.createdAt)}
exportAsMd={this.exportAsMd}
@@ -519,19 +590,18 @@ class MarkdownNoteDetail extends React.Component {
/>
</div>
</div>
)
return (
<div className='NoteDetail'
<div
className='NoteDetail'
style={this.props.style}
styleName='root'
onKeyDown={(e) => this.handleKeyDown(e)}
onKeyDown={e => this.handleKeyDown(e)}
>
{location.pathname === '/trashed' ? trashTopBar : detailTopBar}
<div styleName='body'>
{this.renderEditor()}
</div>
<div styleName='body'>{this.renderEditor()}</div>
<StatusBar
{..._.pick(this.props, ['config', 'location', 'dispatch'])}
@@ -545,9 +615,7 @@ class MarkdownNoteDetail extends React.Component {
MarkdownNoteDetail.propTypes = {
dispatch: PropTypes.func,
repositories: PropTypes.array,
note: PropTypes.shape({
}),
note: PropTypes.shape({}),
style: PropTypes.shape({
left: PropTypes.number
}),

View File

@@ -15,7 +15,7 @@
.control-lockButton
topBarButtonRight()
position absolute
right 225px
right 265px
&:hover .tooltip
opacity 1
@@ -66,19 +66,14 @@ body[data-theme="dark"]
.control-fullScreenButton
topBarButtonDark()
body[data-theme="solarized-dark"]
apply-theme(theme)
body[data-theme={theme}]
.root
border-left 1px solid $ui-solarized-dark-borderColor
background-color $ui-solarized-dark-noteDetail-backgroundColor
border-left 1px solid get-theme-var(theme, 'borderColor')
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
body[data-theme="monokai"]
.root
border-left 1px solid $ui-monokai-borderColor
background-color $ui-monokai-noteDetail-backgroundColor
body[data-theme="dracula"]
.root
border-left 1px solid $ui-dracula-borderColor
background-color $ui-dracula-noteDetail-backgroundColor
for theme in 'solarized-dark' 'dracula'
apply-theme(theme)
for theme in $themes
apply-theme(theme)

View File

@@ -15,6 +15,14 @@ $info-margin-under-border = 30px
padding 0 20px
z-index 99
.info > div
> button
-webkit-user-drag none
user-select none
> img, span
-webkit-user-drag none
user-select none
.info-left
padding 0 10px
width 100%
@@ -94,25 +102,14 @@ body[data-theme="dark"]
.undo-button
topBarButtonDark()
body[data-theme="solarized-dark"]
apply-theme(theme)
body[data-theme={theme}]
.info
border-color $ui-solarized-dark-borderColor
background-color $ui-solarized-dark-noteDetail-backgroundColor
border-color get-theme-var(theme, 'borderColor')
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
body[data-theme="monokai"]
.info
border-color $ui-monokai-borderColor
background-color $ui-monokai-noteDetail-backgroundColor
for theme in 'solarized-dark' 'dracula'
apply-theme(theme)
body[data-theme="dracula"]
.info
border-color $ui-dracula-borderColor
background-color $ui-dracula-noteDetail-backgroundColor
.info > div
> button
-webkit-user-drag none
user-select none
> img, span
-webkit-user-drag none
user-select none
for theme in $themes
apply-theme(theme)

View File

@@ -4,12 +4,8 @@ import CSSModules from 'browser/lib/CSSModules'
import styles from './TrashButton.styl'
import i18n from 'browser/lib/i18n'
const PermanentDeleteButton = ({
onClick
}) => (
<button styleName='control-trashButton--in-trash'
onClick={(e) => onClick(e)}
>
const PermanentDeleteButton = ({ onClick }) => (
<button styleName='control-trashButton--in-trash' onClick={e => onClick(e)}>
<img src='../resources/icon/icon-trash.svg' />
<span styleName='tooltip'>{i18n.__('Permanent Delete')}</span>
</button>

View File

@@ -4,12 +4,8 @@ import CSSModules from 'browser/lib/CSSModules'
import styles from './RestoreButton.styl'
import i18n from 'browser/lib/i18n'
const RestoreButton = ({
onClick
}) => (
<button styleName='control-restoreButton'
onClick={onClick}
>
const RestoreButton = ({ onClick }) => (
<button styleName='control-restoreButton' onClick={onClick}>
<i className='fa fa-undo fa-fw' styleName='iconRestore' />
<span styleName='tooltip'>{i18n.__('Restore')}</span>
</button>

View File

@@ -46,11 +46,17 @@ class SnippetNoteDetail extends React.Component {
showArrows: false,
enableLeftArrow: false,
enableRightArrow: false,
note: Object.assign({
note: Object.assign(
{
description: ''
}, props.note, {
snippets: props.note.snippets.map((snippet) => Object.assign({linesHighlighted: []}, snippet))
})
},
props.note,
{
snippets: props.note.snippets.map(snippet =>
Object.assign({ linesHighlighted: [] }, snippet)
)
}
)
}
this.scrollToNextTabThreshold = 0.7
@@ -64,7 +70,8 @@ class SnippetNoteDetail extends React.Component {
if (visibleTabs.offsetWidth < allTabs.scrollWidth) {
this.setState({
showArrows: visibleTabs.offsetWidth < allTabs.scrollWidth,
enableRightArrow: allTabs.offsetLeft !== visibleTabs.offsetWidth - allTabs.scrollWidth,
enableRightArrow:
allTabs.offsetLeft !== visibleTabs.offsetWidth - allTabs.scrollWidth,
enableLeftArrow: allTabs.offsetLeft !== 0
})
}
@@ -72,25 +79,37 @@ class SnippetNoteDetail extends React.Component {
}
componentWillReceiveProps(nextProps) {
if (nextProps.note.key !== this.props.note.key && !this.state.isMovingNote) {
if (
nextProps.note.key !== this.props.note.key &&
!this.state.isMovingNote
) {
if (this.saveQueue != null) this.saveNow()
const nextNote = Object.assign({
const nextNote = Object.assign(
{
description: ''
}, nextProps.note, {
snippets: nextProps.note.snippets.map((snippet) => Object.assign({linesHighlighted: []}, snippet))
})
},
nextProps.note,
{
snippets: nextProps.note.snippets.map(snippet =>
Object.assign({ linesHighlighted: [] }, snippet)
)
}
)
this.setState({
this.setState(
{
snippetIndex: 0,
note: nextNote
}, () => {
},
() => {
const { snippets } = this.state.note
snippets.forEach((snippet, index) => {
this.refs['code-' + index].reload()
})
if (this.refs.tags) this.refs.tags.reset()
this.setState(this.getArrowsState())
})
}
)
}
}
@@ -116,11 +135,14 @@ class SnippetNoteDetail extends React.Component {
note.updatedAt = new Date()
note.title = findNoteTitle(note.description, false)
this.setState({
this.setState(
{
note
}, () => {
},
() => {
this.save()
})
}
)
}
save() {
@@ -135,9 +157,7 @@ class SnippetNoteDetail extends React.Component {
clearTimeout(this.saveQueue)
this.saveQueue = null
dataApi
.updateNote(note.storage, note.key, this.state.note)
.then((note) => {
dataApi.updateNote(note.storage, note.key, this.state.note).then(note => {
dispatch({
type: 'UPDATE_NOTE',
note: note
@@ -155,46 +175,53 @@ class SnippetNoteDetail extends React.Component {
dataApi
.moveNote(note.storage, note.key, newStorageKey, newFolderKey)
.then((newNote) => {
this.setState({
.then(newNote => {
this.setState(
{
isMovingNote: true,
note: Object.assign({}, newNote)
}, () => {
},
() => {
const { dispatch, location } = this.props
dispatch({
type: 'MOVE_NOTE',
originNote: note,
note: newNote
})
dispatch(replace({
dispatch(
replace({
pathname: location.pathname,
search: queryString.stringify({
key: newNote.key
})
}))
})
)
this.setState({
isMovingNote: false
})
})
}
)
})
}
handleStarButtonClick(e) {
const { note } = this.state
if (!note.isStarred) AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_STAR')
if (!note.isStarred)
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_STAR')
note.isStarred = !note.isStarred
this.setState({
this.setState(
{
note
}, () => {
},
() => {
this.save()
})
}
)
}
exportAsFile () {
}
exportAsFile() {}
handleTrashButtonClick(e) {
const { note } = this.state
@@ -206,7 +233,7 @@ class SnippetNoteDetail extends React.Component {
const { note, dispatch } = this.props
dataApi
.deleteNote(note.storage, note.key)
.then((data) => {
.then(data => {
const dispatchHandler = () => {
dispatch({
type: 'DELETE_NOTE',
@@ -222,11 +249,14 @@ class SnippetNoteDetail extends React.Component {
if (confirmDeleteNote(confirmDeletion, false)) {
note.isTrashed = true
this.setState({
this.setState(
{
note
}, () => {
},
() => {
this.save()
})
}
)
ee.emit('list:next')
}
@@ -238,12 +268,15 @@ class SnippetNoteDetail extends React.Component {
note.isTrashed = false
this.setState({
this.setState(
{
note
}, () => {
},
() => {
this.save()
ee.emit('list:next')
})
}
)
}
handleFullScreenButton(e) {
@@ -255,14 +288,18 @@ class SnippetNoteDetail extends React.Component {
const left = this.visibleTabs.scrollLeft
const tabs = this.allTabs.querySelectorAll('div')
const lastVisibleTab = Array.from(tabs).find((tab) => {
const lastVisibleTab = Array.from(tabs).find(tab => {
return tab.offsetLeft + tab.offsetWidth >= left
})
if (lastVisibleTab) {
const visiblePart = lastVisibleTab.offsetWidth + lastVisibleTab.offsetLeft - left
const isFullyVisible = visiblePart > lastVisibleTab.offsetWidth * this.scrollToNextTabThreshold
const scrollToTab = (isFullyVisible && lastVisibleTab.previousSibling)
const visiblePart =
lastVisibleTab.offsetWidth + lastVisibleTab.offsetLeft - left
const isFullyVisible =
visiblePart >
lastVisibleTab.offsetWidth * this.scrollToNextTabThreshold
const scrollToTab =
isFullyVisible && lastVisibleTab.previousSibling
? lastVisibleTab.previousSibling
: lastVisibleTab
@@ -278,14 +315,16 @@ class SnippetNoteDetail extends React.Component {
const width = this.visibleTabs.offsetWidth
const tabs = this.allTabs.querySelectorAll('div')
const lastVisibleTab = Array.from(tabs).find((tab) => {
const lastVisibleTab = Array.from(tabs).find(tab => {
return tab.offsetLeft + tab.offsetWidth >= width + left
})
if (lastVisibleTab) {
const visiblePart = width + left - lastVisibleTab.offsetLeft
const isFullyVisible = visiblePart > lastVisibleTab.offsetWidth * this.scrollToNextTabThreshold
const scrollToTab = (isFullyVisible && lastVisibleTab.nextSibling)
const isFullyVisible =
visiblePart > lastVisibleTab.offsetWidth * this.scrollToNextTabThreshold
const scrollToTab =
isFullyVisible && lastVisibleTab.nextSibling
? lastVisibleTab.nextSibling
: lastVisibleTab
@@ -348,7 +387,8 @@ class SnippetNoteDetail extends React.Component {
const snippets = this.state.note.snippets.slice()
snippets.splice(index, 1)
const note = Object.assign({}, this.state.note, { snippets })
const snippetIndex = this.state.snippetIndex >= snippets.length
const snippetIndex =
this.state.snippetIndex >= snippets.length
? snippets.length - 1
: this.state.snippetIndex
this.setState({ note, snippetIndex }, () => {
@@ -359,7 +399,10 @@ class SnippetNoteDetail extends React.Component {
this.moveTabBarBy(0)
} else {
const lastTab = this.allTabs.lastChild
if (lastTab.offsetLeft + lastTab.offsetWidth < this.visibleTabs.offsetWidth) {
if (
lastTab.offsetLeft + lastTab.offsetWidth <
this.visibleTabs.offsetWidth
) {
const width = this.visibleTabs.offsetWidth
const newLeft = lastTab.offsetLeft + lastTab.offsetWidth - width
this.moveTabBarBy(newLeft > 0 ? -newLeft : 0)
@@ -381,26 +424,36 @@ class SnippetNoteDetail extends React.Component {
name: mode
})
}
this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
this.setState(state => ({
note: Object.assign(state.note, { snippets: snippets })
}))
this.setState(
state => ({
note: state.note
}), () => {
}),
() => {
this.save()
})
}
)
}
handleModeOptionClick(index, name) {
return (e) => {
return e => {
const snippets = this.state.note.snippets.slice()
snippets[index].mode = name
this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
this.setState(state => ({
note: Object.assign(state.note, { snippets: snippets })
}))
this.setState(
state => ({
note: state.note
}), () => {
}),
() => {
this.save()
})
}
)
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('SELECT_LANG', {
name
@@ -409,17 +462,22 @@ class SnippetNoteDetail extends React.Component {
}
handleCodeChange(index) {
return (e) => {
return e => {
const snippets = this.state.note.snippets.slice()
snippets[index].content = this.refs['code-' + index].value
snippets[index].linesHighlighted = e.options.linesHighlighted
this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
this.setState(state => ({
note: Object.assign(state.note, { snippets: snippets })
}))
this.setState(
state => ({
note: state.note
}), () => {
}),
() => {
this.save()
})
}
)
}
}
@@ -433,7 +491,11 @@ class SnippetNoteDetail extends React.Component {
} else if (e.ctrlKey && e.shiftKey) {
e.preventDefault()
this.jumpPrevTab()
} else if (!e.ctrlKey && !e.shiftKey && e.target === this.refs.description) {
} else if (
!e.ctrlKey &&
!e.shiftKey &&
e.target === this.refs.description
) {
e.preventDefault()
this.focusEditor()
}
@@ -441,9 +503,8 @@ class SnippetNoteDetail extends React.Component {
// I key
case 73:
{
const isSuper = global.process.platform === 'darwin'
? e.metaKey
: e.ctrlKey
const isSuper =
global.process.platform === 'darwin' ? e.metaKey : e.ctrlKey
if (isSuper) {
e.preventDefault()
this.handleInfoButtonClick(e)
@@ -453,9 +514,8 @@ class SnippetNoteDetail extends React.Component {
// L key
case 76:
{
const isSuper = global.process.platform === 'darwin'
? e.metaKey
: e.ctrlKey
const isSuper =
global.process.platform === 'darwin' ? e.metaKey : e.ctrlKey
if (isSuper) {
e.preventDefault()
this.focus()
@@ -465,9 +525,8 @@ class SnippetNoteDetail extends React.Component {
// T key
case 84:
{
const isSuper = global.process.platform === 'darwin'
? e.metaKey
: e.ctrlKey
const isSuper =
global.process.platform === 'darwin' ? e.metaKey : e.ctrlKey
if (isSuper && !e.shiftKey && !e.altKey) {
e.preventDefault()
this.addSnippet()
@@ -479,10 +538,14 @@ class SnippetNoteDetail extends React.Component {
handleModeButtonClick(e, index) {
const templetes = []
CodeMirror.modeInfo.sort(function (a, b) { return a.name.localeCompare(b.name) }).forEach((mode) => {
CodeMirror.modeInfo
.sort(function(a, b) {
return a.name.localeCompare(b.name)
})
.forEach(mode => {
templetes.push({
label: mode.name,
click: (e) => this.handleModeOptionClick(index, mode.name)(e)
click: e => this.handleModeOptionClick(index, mode.name)(e)
})
})
context.popup(templetes)
@@ -492,11 +555,11 @@ class SnippetNoteDetail extends React.Component {
context.popup([
{
label: 'tab',
click: (e) => this.handleIndentTypeItemClick(e, 'tab')
click: e => this.handleIndentTypeItemClick(e, 'tab')
},
{
label: 'space',
click: (e) => this.handleIndentTypeItemClick(e, 'space')
click: e => this.handleIndentTypeItemClick(e, 'space')
}
])
}
@@ -505,15 +568,15 @@ class SnippetNoteDetail extends React.Component {
context.popup([
{
label: '2',
click: (e) => this.handleIndentSizeItemClick(e, 2)
click: e => this.handleIndentSizeItemClick(e, 2)
},
{
label: '4',
click: (e) => this.handleIndentSizeItemClick(e, 4)
click: e => this.handleIndentSizeItemClick(e, 4)
},
{
label: '8',
click: (e) => this.handleIndentSizeItemClick(e, 8)
click: e => this.handleIndentSizeItemClick(e, 8)
}
])
}
@@ -522,11 +585,11 @@ class SnippetNoteDetail extends React.Component {
context.popup([
{
label: 'on',
click: (e) => this.handleWrapLineItemClick(e, true)
click: e => this.handleWrapLineItemClick(e, true)
},
{
label: 'off',
click: (e) => this.handleWrapLineItemClick(e, false)
click: e => this.handleWrapLineItemClick(e, false)
}
])
}
@@ -584,12 +647,12 @@ class SnippetNoteDetail extends React.Component {
}
moveToTab(tab) {
const easeOutCubic = t => (--t) * t * t + 1
const easeOutCubic = t => --t * t * t + 1
const startScrollPosition = this.visibleTabs.scrollLeft
const animationTiming = 300
const scrollMoreCoeff = 1.4 // introduce coefficient, because we want to scroll a bit further to see next tab
let scrollBy = (tab.offsetLeft - startScrollPosition)
let scrollBy = tab.offsetLeft - startScrollPosition
if (tab.offsetLeft > startScrollPosition) {
// if tab is on the right side and we want to show the whole tab in visible area,
@@ -598,7 +661,7 @@ class SnippetNoteDetail extends React.Component {
// |____|_______|________|________|_show_this_|
// ↑_____________________↑
// visible area
scrollBy += (tab.offsetWidth - this.visibleTabs.offsetWidth)
scrollBy += tab.offsetWidth - this.visibleTabs.offsetWidth
}
let startTime = null
@@ -606,7 +669,8 @@ class SnippetNoteDetail extends React.Component {
startTime = startTime || time
const elapsed = (time - startTime) / animationTiming
this.visibleTabs.scrollLeft = startScrollPosition + easeOutCubic(elapsed) * scrollBy * scrollMoreCoeff
this.visibleTabs.scrollLeft =
startScrollPosition + easeOutCubic(elapsed) * scrollBy * scrollMoreCoeff
if (elapsed < 1) {
window.requestAnimationFrame(scrollAnimation)
} else {
@@ -622,30 +686,43 @@ class SnippetNoteDetail extends React.Component {
const visibleTabs = this.visibleTabs
const showArrows = visibleTabs.offsetWidth < allTabs.scrollWidth
const enableRightArrow = visibleTabs.scrollLeft !== allTabs.scrollWidth - visibleTabs.offsetWidth
const enableRightArrow =
visibleTabs.scrollLeft !== allTabs.scrollWidth - visibleTabs.offsetWidth
const enableLeftArrow = visibleTabs.scrollLeft !== 0
return { showArrows, enableRightArrow, enableLeftArrow }
}
addSnippet() {
const { config: { editor: { snippetDefaultLanguage } } } = this.props
const {
config: {
editor: { snippetDefaultLanguage }
}
} = this.props
const { note } = this.state
const defaultLanguage = snippetDefaultLanguage === 'Auto Detect' ? null : snippetDefaultLanguage
const defaultLanguage =
snippetDefaultLanguage === 'Auto Detect' ? null : snippetDefaultLanguage
note.snippets = note.snippets.concat([{
note.snippets = note.snippets.concat([
{
name: '',
mode: defaultLanguage,
content: '',
linesHighlighted: []
}])
}
])
const snippetIndex = note.snippets.length - 1
this.setState(Object.assign({
this.setState(
Object.assign(
{
note,
snippetIndex
}, this.getArrowsState()), () => {
},
this.getArrowsState()
),
() => {
if (this.state.showArrows) {
const tabs = this.allTabs.querySelectorAll('div')
if (tabs) {
@@ -653,23 +730,32 @@ class SnippetNoteDetail extends React.Component {
}
}
this.refs['tab-' + snippetIndex].startRenaming()
})
}
)
}
jumpNextTab() {
this.setState(state => ({
this.setState(
state => ({
snippetIndex: (state.snippetIndex + 1) % state.note.snippets.length
}), () => {
}),
() => {
this.focusEditor()
})
}
)
}
jumpPrevTab() {
this.setState(state => ({
snippetIndex: (state.snippetIndex - 1 + state.note.snippets.length) % state.note.snippets.length
}), () => {
this.setState(
state => ({
snippetIndex:
(state.snippetIndex - 1 + state.note.snippets.length) %
state.note.snippets.length
}),
() => {
this.focusEditor()
})
}
)
}
focusEditor() {
@@ -678,22 +764,27 @@ class SnippetNoteDetail extends React.Component {
handleInfoButtonClick(e) {
const infoPanel = document.querySelector('.infoPanel')
if (infoPanel.style) infoPanel.style.display = infoPanel.style.display === 'none' ? 'inline' : 'none'
if (infoPanel.style)
infoPanel.style.display =
infoPanel.style.display === 'none' ? 'inline' : 'none'
}
showWarning(e, msg) {
const warningMessage = (msg) => ({
const warningMessage = msg =>
({
'export-txt': 'Text export',
'export-md': 'Markdown export',
'export-html': 'HTML export',
'export-pdf': 'PDF export',
'print': 'Print'
})[msg]
print: 'Print'
}[msg])
dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: i18n.__('Sorry!'),
detail: i18n.__(warningMessage(msg) + ' is available only in markdown notes.'),
detail: i18n.__(
warningMessage(msg) + ' is available only in markdown notes.'
),
buttons: [i18n.__('OK')]
})
}
@@ -715,38 +806,49 @@ class SnippetNoteDetail extends React.Component {
const tabList = note.snippets.map((snippet, index) => {
const isActive = this.state.snippetIndex === index
return <SnippetTab
return (
<SnippetTab
key={index}
ref={'tab-' + index}
snippet={snippet}
isActive={isActive}
onClick={(e) => this.handleTabButtonClick(e, index)}
onDelete={(e) => this.handleTabDeleteButtonClick(e, index)}
onRename={(name) => this.renameSnippetByIndex(index, name)}
onClick={e => this.handleTabButtonClick(e, index)}
onDelete={e => this.handleTabDeleteButtonClick(e, index)}
onRename={name => this.renameSnippetByIndex(index, name)}
isDeletable={note.snippets.length > 1}
onDragStart={(e) => this.handleTabDragStart(e, index)}
onDrop={(e) => this.handleTabDrop(e, index)}
onDragStart={e => this.handleTabDragStart(e, index)}
onDrop={e => this.handleTabDrop(e, index)}
/>
)
})
const viewList = note.snippets.map((snippet, index) => {
const isActive = this.state.snippetIndex === index
return <div styleName='tabView'
return (
<div
styleName='tabView'
key={index}
style={{ zIndex: isActive ? 5 : 4 }}
>
{snippet.mode === 'Markdown' || snippet.mode === 'GitHub Flavored Markdown'
? <MarkdownEditor styleName='tabView-content'
{snippet.mode === 'Markdown' ||
snippet.mode === 'GitHub Flavored Markdown' ? (
<MarkdownEditor
styleName='tabView-content'
value={snippet.content}
config={config}
linesHighlighted={snippet.linesHighlighted}
onChange={(e) => this.handleCodeChange(index)(e)}
onChange={e => this.handleCodeChange(index)(e)}
ref={'code-' + index}
ignorePreviewPointerEvents={this.props.ignorePreviewPointerEvents}
storageKey={storageKey}
/>
: <CodeEditor styleName='tabView-content'
mode={snippet.mode || (autoDetect ? null : config.editor.snippetDefaultLanguage)}
) : (
<CodeEditor
styleName='tabView-content'
mode={
snippet.mode ||
(autoDetect ? null : config.editor.snippetDefaultLanguage)
}
value={snippet.content}
linesHighlighted={snippet.linesHighlighted}
lineWrapping={config.editor.lineWrapping}
@@ -763,36 +865,41 @@ class SnippetNoteDetail extends React.Component {
scrollPastEnd={config.editor.scrollPastEnd}
fetchUrlTitle={config.editor.fetchUrlTitle}
enableTableEditor={config.editor.enableTableEditor}
onChange={(e) => this.handleCodeChange(index)(e)}
onChange={e => this.handleCodeChange(index)(e)}
ref={'code-' + index}
enableSmartPaste={config.editor.enableSmartPaste}
hotkey={config.hotkey}
autoDetect={autoDetect}
/>
}
)}
</div>
)
})
const options = []
data.storageMap.forEach((storage, index) => {
storage.folders.forEach((folder) => {
storage.folders.forEach(folder => {
options.push({
storage: storage,
folder: folder
})
})
})
const 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'>
<RestoreButton onClick={(e) => this.handleUndoButtonClick(e)} />
<RestoreButton onClick={e => this.handleUndoButtonClick(e)} />
</div>
<div styleName='info-right'>
<PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} />
<InfoButton
onClick={(e) => this.handleInfoButtonClick(e)}
<PermanentDeleteButton
onClick={e => this.handleTrashButtonClick(e)}
/>
<InfoButton onClick={e => this.handleInfoButtonClick(e)} />
<InfoPanelTrashed
storageName={currentOption.storage.name}
folderName={currentOption.folder.name}
@@ -805,15 +912,18 @@ class SnippetNoteDetail extends React.Component {
/>
</div>
</div>
)
const detailTopBar = <div styleName='info'>
const detailTopBar = (
<div styleName='info'>
<div styleName='info-left'>
<div>
<FolderSelect styleName='info-left-top-folderSelect'
<FolderSelect
styleName='info-left-top-folderSelect'
value={this.state.note.storage + '-' + this.state.note.folder}
ref='folder'
data={data}
onChange={(e) => this.handleFolderChange(e)}
onChange={e => this.handleFolderChange(e)}
/>
</div>
@@ -824,28 +934,28 @@ class SnippetNoteDetail extends React.Component {
showTagsAlphabetically={config.ui.showTagsAlphabetically}
data={data}
dispatch={dispatch}
onChange={(e) => this.handleChange(e)}
onChange={e => this.handleChange(e)}
coloredTags={config.coloredTags}
/>
</div>
<div styleName='info-right'>
<StarButton
onClick={(e) => this.handleStarButtonClick(e)}
onClick={e => this.handleStarButtonClick(e)}
isActive={note.isStarred}
/>
<FullscreenButton onClick={(e) => this.handleFullScreenButton(e)} />
<FullscreenButton onClick={e => this.handleFullScreenButton(e)} />
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
<TrashButton onClick={e => this.handleTrashButtonClick(e)} />
<InfoButton
onClick={(e) => this.handleInfoButtonClick(e)}
/>
<InfoButton onClick={e => this.handleInfoButtonClick(e)} />
<InfoPanel
storageName={currentOption.storage.name}
folderName={currentOption.folder.name}
noteLink={`[${note.title}](:note:${queryString.parse(location.search).key})`}
noteLink={`[${note.title}](:note:${
queryString.parse(location.search).key
})`}
updatedAt={formatDate(note.updatedAt)}
createdAt={formatDate(note.createdAt)}
exportAsMd={this.showWarning}
@@ -857,12 +967,14 @@ class SnippetNoteDetail extends React.Component {
/>
</div>
</div>
)
return (
<div className='NoteDetail'
<div
className='NoteDetail'
style={this.props.style}
styleName='root'
onKeyDown={(e) => this.handleKeyDown(e)}
onKeyDown={e => this.handleKeyDown(e)}
>
{location.pathname === '/trashed' ? trashTopBar : detailTopBar}
@@ -876,31 +988,47 @@ class SnippetNoteDetail extends React.Component {
ref='description'
placeholder={i18n.__('Description...')}
value={this.state.note.description}
onChange={(e) => this.handleChange(e)}
onChange={e => this.handleChange(e)}
/>
</div>
<div styleName='tabList'>
<button styleName='tabButton'
<button
styleName='tabButton'
hidden={!this.state.showArrows}
disabled={!this.state.enableLeftArrow}
onClick={(e) => this.handleTabMoveLeftButtonClick(e)}
onClick={e => this.handleTabMoveLeftButtonClick(e)}
>
<i className='fa fa-chevron-left' />
</button>
<div styleName='list' onScroll={(e) => { this.setState(this.getArrowsState()) }} ref={(tabs) => { this.visibleTabs = tabs }}>
<div styleName='allTabs' ref={(tabs) => { this.allTabs = tabs }}>
<div
styleName='list'
onScroll={e => {
this.setState(this.getArrowsState())
}}
ref={tabs => {
this.visibleTabs = tabs
}}
>
<div
styleName='allTabs'
ref={tabs => {
this.allTabs = tabs
}}
>
{tabList}
</div>
</div>
<button styleName='tabButton'
<button
styleName='tabButton'
hidden={!this.state.showArrows}
disabled={!this.state.enableRightArrow}
onClick={(e) => this.handleTabMoveRightButtonClick(e)}
onClick={e => this.handleTabMoveRightButtonClick(e)}
>
<i className='fa fa-chevron-right' />
</button>
<button styleName='tabButton'
onClick={(e) => this.handleTabPlusButtonClick(e)}
<button
styleName='tabButton'
onClick={e => this.handleTabPlusButtonClick(e)}
>
<i className='fa fa-plus' />
</button>
@@ -910,29 +1038,25 @@ class SnippetNoteDetail extends React.Component {
<div styleName='override'>
<button
onClick={(e) => this.handleModeButtonClick(e, this.state.snippetIndex)}
onClick={e =>
this.handleModeButtonClick(e, this.state.snippetIndex)
}
>
{this.state.note.snippets[this.state.snippetIndex].mode == null
? i18n.__('Select Syntax...')
: this.state.note.snippets[this.state.snippetIndex].mode
}&nbsp;
: this.state.note.snippets[this.state.snippetIndex].mode}
&nbsp;
<i className='fa fa-caret-down' />
</button>
<button
onClick={(e) => this.handleIndentTypeButtonClick(e)}
>
<button onClick={e => this.handleIndentTypeButtonClick(e)}>
Indent: {config.editor.indentType}&nbsp;
<i className='fa fa-caret-down' />
</button>
<button
onClick={(e) => this.handleIndentSizeButtonClick(e)}
>
<button onClick={e => this.handleIndentSizeButtonClick(e)}>
size: {config.editor.indentSize}&nbsp;
<i className='fa fa-caret-down' />
</button>
<button
onClick={(e) => this.handleWrapLineButtonClick(e)}
>
<button onClick={e => this.handleWrapLineButtonClick(e)}>
Wrap Line: {config.editor.lineWrapping ? 'on' : 'off'}&nbsp;
<i className='fa fa-caret-down' />
</button>
@@ -950,9 +1074,7 @@ class SnippetNoteDetail extends React.Component {
SnippetNoteDetail.propTypes = {
dispatch: PropTypes.func,
repositories: PropTypes.array,
note: PropTypes.shape({
}),
note: PropTypes.shape({}),
style: PropTypes.shape({
left: PropTypes.number
}),

View File

@@ -156,78 +156,35 @@ body[data-theme="dark"]
.control-fullScreenButton
topBarButtonDark()
body[data-theme="solarized-dark"]
apply-theme(theme)
body[data-theme={theme}]
.root
border-left 1px solid $ui-solarized-dark-borderColor
background-color $ui-solarized-dark-noteDetail-backgroundColor
border-left 1px solid get-theme-var(theme, 'borderColor')
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
.body
background-color $ui-solarized-dark-noteDetail-backgroundColor
background-color get-theme-var(theme, '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
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
color get-theme-var(theme, 'text-color')
border 1px solid get-theme-var(theme, 'borderColor')
.tabList .tabButton
border-color $ui-solarized-dark-borderColor
border-color get-theme-var(theme, 'borderColor')
.tabButton
&:hover
color $ui-solarized-dark-button--active-color
background-color $ui-solarized-dark-noteDetail-backgroundColor
color get-theme-var(theme, 'text-color')
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
transition 0.15s
.tabList
background-color $ui-solarized-dark-noteDetail-backgroundColor
color $ui-solarized-dark-text-color
background-color get-theme-var(theme, 'noteDetail-backgroundColor')
color get-theme-var(theme, 'text-color')
body[data-theme="monokai"]
.root
border-left 1px solid $ui-monokai-borderColor
background-color $ui-monokai-noteDetail-backgroundColor
for theme in 'solarized-dark' 'dracula'
apply-theme(theme)
.body
background-color $ui-monokai-noteDetail-backgroundColor
.body .description textarea
background-color $ui-monokai-noteDetail-backgroundColor
color $ui-monokai-text-color
border 1px solid $ui-monokai-borderColor
.tabList .tabButton
border-color $ui-monokai-borderColor
.tabButton
&:hover
color $ui-monokai-text-color
background-color $ui-monokai-noteDetail-backgroundColor
.tabList
background-color $ui-monokai-noteDetail-backgroundColor
color $ui-monokai-text-color
body[data-theme="dracula"]
.root
border-left 1px solid $ui-dracula-borderColor
background-color $ui-dracula-noteDetail-backgroundColor
.body
background-color $ui-dracula-noteDetail-backgroundColor
.body .description textarea
background-color $ui-dracula-noteDetail-backgroundColor
color $ui-dracula-text-color
border 1px solid $ui-dracula-borderColor
.tabList .tabButton
border-color $ui-dracula-borderColor
.tabButton
&:hover
color $ui-dracula-text-color
background-color $ui-dracula-noteDetail-backgroundColor
.tabList
background-color $ui-dracula-noteDetail-backgroundColor
color $ui-dracula-text-color
for theme in $themes
apply-theme(theme)

View File

@@ -36,25 +36,29 @@ class StarButton extends React.Component {
const { className } = this.props
return (
<button className={_.isString(className)
? 'StarButton ' + className
: 'StarButton'
<button
className={
_.isString(className) ? 'StarButton ' + className : 'StarButton'
}
styleName={this.state.isActive || this.props.isActive
? 'root--active'
: 'root'
styleName={
this.state.isActive || this.props.isActive ? 'root--active' : 'root'
}
onMouseDown={(e) => this.handleMouseDown(e)}
onMouseUp={(e) => this.handleMouseUp(e)}
onMouseLeave={(e) => this.handleMouseLeave(e)}
onClick={this.props.onClick}>
<img styleName='icon'
src={this.state.isActive || this.props.isActive
onMouseDown={e => this.handleMouseDown(e)}
onMouseUp={e => this.handleMouseUp(e)}
onMouseLeave={e => this.handleMouseLeave(e)}
onClick={this.props.onClick}
>
<img
styleName='icon'
src={
this.state.isActive || this.props.isActive
? '../resources/icon/icon-starred.svg'
: '../resources/icon/icon-star.svg'
}
/>
<span lang={i18n.locale} styleName='tooltip'>{i18n.__('Star')}</span>
<span lang={i18n.locale} styleName='tooltip'>
{i18n.__('Star')}
</span>
</button>
)
}

View File

@@ -23,8 +23,12 @@ class TagSelect extends React.Component {
this.onInputBlur = this.onInputBlur.bind(this)
this.onInputChange = this.onInputChange.bind(this)
this.onInputKeyDown = this.onInputKeyDown.bind(this)
this.onSuggestionsClearRequested = this.onSuggestionsClearRequested.bind(this)
this.onSuggestionsFetchRequested = this.onSuggestionsFetchRequested.bind(this)
this.onSuggestionsClearRequested = this.onSuggestionsClearRequested.bind(
this
)
this.onSuggestionsFetchRequested = this.onSuggestionsFetchRequested.bind(
this
)
this.onSuggestionSelected = this.onSuggestionSelected.bind(this)
}
@@ -44,9 +48,7 @@ class TagSelect extends React.Component {
}
let { value } = this.props
value = _.isArray(value)
? value.slice()
: []
value = _.isArray(value) ? value.slice() : []
if (!_.includes(value, newTag)) {
value.push(newTag)
@@ -56,24 +58,28 @@ class TagSelect extends React.Component {
value = _.sortBy(value)
}
this.setState({
this.setState(
{
newTag: ''
}, () => {
},
() => {
this.value = value
this.props.onChange()
})
}
)
}
buildSuggestions() {
this.suggestions = _.sortBy(this.props.data.tagNoteMap.map(
(tag, name) => ({
this.suggestions = _.sortBy(
this.props.data.tagNoteMap
.map((tag, name) => ({
name,
nameLC: name.toLowerCase(),
size: tag.size
})
).filter(
tag => tag.size > 0
), ['name'])
}))
.filter(tag => tag.size > 0),
['name']
)
}
componentDidMount() {
@@ -146,7 +152,8 @@ class TagSelect extends React.Component {
const valueLC = value.toLowerCase()
const suggestions = _.filter(
this.suggestions,
tag => !_.includes(this.value, tag.name) && tag.nameLC.indexOf(valueLC) !== -1
tag =>
!_.includes(this.value, tag.name) && tag.nameLC.indexOf(valueLC) !== -1
)
this.setState({
@@ -159,7 +166,7 @@ class TagSelect extends React.Component {
}
removeLastTag() {
this.removeTagByCallback((value) => {
this.removeTagByCallback(value => {
value.pop()
})
}
@@ -167,9 +174,7 @@ class TagSelect extends React.Component {
removeTagByCallback(callback, tag = null) {
let { value } = this.props
value = _.isArray(value)
? value.slice()
: []
value = _.isArray(value) ? value.slice() : []
callback(value, tag)
value = _.uniq(value)
@@ -193,13 +198,14 @@ class TagSelect extends React.Component {
const { value, className, showTagsAlphabetically, coloredTags } = this.props
const tagList = _.isArray(value)
? (showTagsAlphabetically ? _.sortBy(value) : value).map((tag) => {
? (showTagsAlphabetically ? _.sortBy(value) : value).map(tag => {
const wrapperStyle = {}
const textStyle = {}
const BLACK = '#333333'
const WHITE = '#f1f1f1'
const color = coloredTags[tag]
const invertedColor = color && invertColor(color, { black: BLACK, white: WHITE })
const invertedColor =
color && invertColor(color, { black: BLACK, white: WHITE })
let iconRemove = '../resources/icon/icon-x.svg'
if (color) {
wrapperStyle.backgroundColor = color
@@ -209,15 +215,23 @@ class TagSelect extends React.Component {
iconRemove = '../resources/icon/icon-x-light.svg'
}
return (
<span styleName='tag'
key={tag}
style={wrapperStyle}
<span styleName='tag' key={tag} style={wrapperStyle}>
<span
styleName='tag-label'
style={textStyle}
onClick={e => this.handleTagLabelClick(tag)}
>
<span styleName='tag-label' style={textStyle} onClick={(e) => this.handleTagLabelClick(tag)}>#{tag}</span>
<button styleName='tag-removeButton'
onClick={(e) => this.handleTagRemoveButtonClick(tag)}
#{tag}
</span>
<button
styleName='tag-removeButton'
onClick={e => this.handleTagRemoveButtonClick(tag)}
>
<img className='tag-removeButton-icon' src={iconRemove} width='8px' />
<img
className='tag-removeButton-icon'
src={iconRemove}
width='8px'
/>
</button>
</span>
)
@@ -227,9 +241,9 @@ class TagSelect extends React.Component {
const { newTag, suggestions } = this.state
return (
<div className={_.isString(className)
? 'TagSelect ' + className
: 'TagSelect'
<div
className={
_.isString(className) ? 'TagSelect ' + className : 'TagSelect'
}
styleName='root'
>
@@ -241,11 +255,7 @@ class TagSelect extends React.Component {
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
onSuggestionSelected={this.onSuggestionSelected}
getSuggestionValue={suggestion => suggestion.name}
renderSuggestion={suggestion => (
<div>
{suggestion.name}
</div>
)}
renderSuggestion={suggestion => <div>{suggestion.name}</div>}
inputProps={{
placeholder: i18n.__('Add tag...'),
value: newTag,

View File

@@ -54,35 +54,20 @@ body[data-theme="dark"]
.tag-label
color $ui-dark-text-color
body[data-theme="solarized-dark"]
apply-theme(theme)
body[data-theme={theme}]
.tag
background-color $ui-solarized-dark-tag-backgroundColor
background-color get-theme-var(theme, 'tag-backgroundColor')
.tag-removeButton
border-color $ui-button--focus-borderColor
background-color transparent
.tag-label
color $ui-solarized-dark-text-color
color get-theme-var(theme, 'text-color')
body[data-theme="monokai"]
.tag
background-color $ui-monokai-tag-backgroundColor
for theme in 'solarized-dark' 'dracula'
apply-theme(theme)
.tag-removeButton
border-color $ui-button--focus-borderColor
background-color transparent
.tag-label
color $ui-monokai-text-color
body[data-theme="dracula"]
.tag
background-color $ui-dracula-tag-backgroundColor
.tag-removeButton
border-color $ui-dracula-button--focus-borderColor
background-color transparent
.tag-label
color $ui-dracula-borderColor
for theme in $themes
apply-theme(theme)

View File

@@ -0,0 +1,26 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './ToggleDirectionButton.styl'
import i18n from 'browser/lib/i18n'
const ToggleDirectionButton = ({ onClick, isRTL }) => (
<div styleName='control-toggleModeButton'>
<div styleName={isRTL ? 'active' : undefined} onClick={() => onClick()}>
<img src={!isRTL ? '../resources/icon/icon-left-to-right.svg' : ''} />
</div>
<div styleName={!isRTL ? 'active' : undefined} onClick={() => onClick()}>
<img src={!isRTL ? '' : '../resources/icon/icon-right-to-left.svg'} />
</div>
<span lang={i18n.locale} styleName='tooltip'>
{i18n.__('Toggle Direction')}
</span>
</div>
)
ToggleDirectionButton.propTypes = {
onClick: PropTypes.func.isRequired,
isRTL: PropTypes.string.isRequired
}
export default CSSModules(ToggleDirectionButton, styles)

View File

@@ -0,0 +1,85 @@
.control-toggleModeButton
height 25px
border-radius 50px
background-color #F4F4F4
width 52px
display flex
align-items center
position: relative
top 2px
margin-left 5px
.active
background-color #1EC38B
width 33px
height 24px
box-shadow 2px 0px 7px #eee
z-index 1
div
width 40px
height 100%
border-radius 50%
display flex
align-items center
justify-content center
cursor pointer
&:hover .tooltip
opacity 1
.tooltip
tooltip()
position absolute
pointer-events none
top 33px
left -10px
z-index 200
width 80px
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s
.tooltip:lang(ja)
@extend .tooltip
left -8px
width 70px
.control-toggleModeButton
-webkit-user-drag none
user-select none
> div img
-webkit-user-drag none
user-select none
body[data-theme="dark"]
.control-fullScreenButton
topBarButtonDark()
.control-toggleModeButton
background-color #3A404C
.active
background-color #1EC38B
box-shadow 2px 0px 7px #444444
body[data-theme="solarized-dark"]
.control-toggleModeButton
background-color #002B36
.active
background-color #1EC38B
box-shadow 2px 0px 7px #222222
apply-theme(theme)
body[data-theme={theme}]
.control-toggleModeButton
background-color get-theme-var(theme, 'borderColor')
.active
background-color get-theme-var(theme, 'active-color')
box-shadow 2px 0px 7px #222222
for theme in 'dracula'
apply-theme(theme)
for theme in $themes
apply-theme(theme)

View File

@@ -4,17 +4,35 @@ import CSSModules from 'browser/lib/CSSModules'
import styles from './ToggleModeButton.styl'
import i18n from 'browser/lib/i18n'
const ToggleModeButton = ({
onClick, editorType
}) => (
const ToggleModeButton = ({ onClick, editorType }) => (
<div styleName='control-toggleModeButton'>
<div styleName={editorType === 'SPLIT' ? 'active' : undefined} onClick={() => onClick('SPLIT')}>
<img src={editorType === 'EDITOR_PREVIEW' ? '../resources/icon/icon-mode-markdown-off-active.svg' : ''} />
<div
styleName={editorType === 'SPLIT' ? 'active' : undefined}
onClick={() => onClick('SPLIT')}
>
<img
src={
editorType === 'EDITOR_PREVIEW'
? '../resources/icon/icon-mode-markdown-off-active.svg'
: ''
}
/>
</div>
<div styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : undefined} onClick={() => onClick('EDITOR_PREVIEW')}>
<img src={editorType === 'EDITOR_PREVIEW' ? '' : '../resources/icon/icon-mode-split-on-active.svg'} />
<div
styleName={editorType === 'EDITOR_PREVIEW' ? 'active' : undefined}
onClick={() => onClick('EDITOR_PREVIEW')}
>
<img
src={
editorType === 'EDITOR_PREVIEW'
? ''
: '../resources/icon/icon-mode-split-on-active.svg'
}
/>
</div>
<span lang={i18n.locale} styleName='tooltip'>{i18n.__('Toggle Mode')}</span>
<span lang={i18n.locale} styleName='tooltip'>
{i18n.__('Toggle Mode')}
</span>
</div>
)

View File

@@ -26,6 +26,13 @@
&:hover .tooltip
opacity 1
.control-toggleModeButton
-webkit-user-drag none
user-select none
> div img
-webkit-user-drag none
user-select none
.tooltip
tooltip()
position absolute
@@ -62,23 +69,16 @@ body[data-theme="solarized-dark"]
background-color #1EC38B
box-shadow 2px 0px 7px #222222
body[data-theme="monokai"]
apply-theme(theme)
body[data-theme={theme}]
.control-toggleModeButton
background-color #373831
background-color get-theme-var(theme, 'borderColor')
.active
background-color #f92672
background-color get-theme-var(theme, 'active-color')
box-shadow 2px 0px 7px #222222
body[data-theme="dracula"]
.control-toggleModeButton
background-color #44475a
.active
background-color #bd93f9
box-shadow 2px 0px 7px #222222
for theme in 'dracula'
apply-theme(theme)
.control-toggleModeButton
-webkit-user-drag none
user-select none
> div img
-webkit-user-drag none
user-select none
for theme in $themes
apply-theme(theme)

View File

@@ -4,14 +4,12 @@ import CSSModules from 'browser/lib/CSSModules'
import styles from './TrashButton.styl'
import i18n from 'browser/lib/i18n'
const TrashButton = ({
onClick
}) => (
<button styleName='control-trashButton'
onClick={(e) => onClick(e)}
>
const TrashButton = ({ onClick }) => (
<button styleName='control-trashButton' onClick={e => onClick(e)}>
<img src='../resources/icon/icon-trash.svg' />
<span lang={i18n.locale} styleName='tooltip'>{i18n.__('Trash')}</span>
<span lang={i18n.locale} styleName='tooltip'>
{i18n.__('Trash')}
</span>
</button>
)

View File

@@ -37,30 +37,44 @@ class Detail extends React.Component {
}
render() {
const { location, data, match: { params }, config } = this.props
const noteKey = location.search !== '' && queryString.parse(location.search).key
const {
location,
data,
match: { params },
config
} = this.props
const noteKey =
location.search !== '' && queryString.parse(location.search).key
let note = null
if (location.search !== '') {
const allNotes = data.noteMap.map(note => note)
const trashedNotes = data.trashedSet.toJS().map(uniqueKey => data.noteMap.get(uniqueKey))
const trashedNotes = data.trashedSet
.toJS()
.map(uniqueKey => data.noteMap.get(uniqueKey))
let displayedNotes = allNotes
if (location.pathname.match(/\/searched/)) {
const searchStr = params.searchword
displayedNotes = searchStr === undefined || searchStr === '' ? allNotes
displayedNotes =
searchStr === undefined || searchStr === ''
? allNotes
: searchFromNotes(allNotes, searchStr)
} else if (location.pathname.match(/^\/tags/)) {
const listOfTags = params.tagname.split(' ')
displayedNotes = data.noteMap.map(note => note).filter(note =>
listOfTags.every(tag => note.tags.includes(tag))
)
displayedNotes = data.noteMap
.map(note => note)
.filter(note => listOfTags.every(tag => note.tags.includes(tag)))
}
if (location.pathname.match(/^\/trashed/)) {
displayedNotes = trashedNotes
} else {
displayedNotes = _.differenceWith(displayedNotes, trashedNotes, (note, trashed) => note.key === trashed.key)
displayedNotes = _.differenceWith(
displayedNotes,
trashedNotes,
(note, trashed) => note.key === trashed.key
)
}
const noteKeys = displayedNotes.map(note => note.key)
@@ -71,12 +85,12 @@ class Detail extends React.Component {
if (note == null) {
return (
<div styleName='root'
style={this.props.style}
tabIndex='0'
>
<div styleName='root' style={this.props.style} tabIndex='0'>
<div styleName='empty'>
<div styleName='empty-message'>{OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')} + N<br />{i18n.__('to create a new note')}</div>
<div styleName='empty-message'>
{OSX ? i18n.__('Command(⌘)') : i18n.__('Ctrl(^)')} + N<br />
{i18n.__('to create a new note')}
</div>
</div>
<StatusBar
{..._.pick(this.props, ['config', 'location', 'dispatch'])}

View File

@@ -18,6 +18,7 @@ import { getLocales } from 'browser/lib/Languages'
import applyShortcuts from 'browser/main/lib/shortcutManager'
import { chooseTheme, applyTheme } from 'browser/main/lib/ThemeManager'
import { push } from 'connected-react-router'
const path = require('path')
const electron = require('electron')
const { remote } = electron
@@ -92,18 +93,21 @@ class Main extends React.Component {
type: 'SNIPPET_NOTE',
folder: data.storage.folders[0].key,
title: 'Snippet note example',
description: 'Snippet note example\nYou can store a series of snippets as a single note, like Gist.',
description:
'Snippet note example\nYou can store a series of snippets as a single note, like Gist.',
snippets: [
{
name: 'example.html',
mode: 'html',
content: "<html>\n<body>\n<h1 id='hello'>Enjoy Boostnote!</h1>\n</body>\n</html>",
content:
"<html>\n<body>\n<h1 id='hello'>Enjoy Boostnote!</h1>\n</body>\n</html>",
linesHighlighted: []
},
{
name: 'example.js',
mode: 'javascript',
content: "var boostnote = document.getElementById('hello').innerHTML\n\nconsole.log(boostnote)",
content:
"var boostnote = document.getElementById('hello').innerHTML\n\nconsole.log(boostnote)",
linesHighlighted: []
}
]
@@ -119,7 +123,8 @@ class Main extends React.Component {
type: 'MARKDOWN_NOTE',
folder: data.storage.folders[0].key,
title: 'Welcome to Boostnote!',
content: '# Welcome to Boostnote!\n## Click here to edit markdown :wave:\n\n<iframe width="560" height="315" src="https://www.youtube.com/embed/L0qNPLsvmyM" frameborder="0" allowfullscreen></iframe>\n\n## Docs :memo:\n- [Boostnote | Boost your happiness, productivity and creativity.](https://hackernoon.com/boostnote-boost-your-happiness-productivity-and-creativity-315034efeebe)\n- [Cloud Syncing & Backups](https://github.com/BoostIO/Boostnote/wiki/Cloud-Syncing-and-Backup)\n- [How to sync your data across Desktop and Mobile apps](https://github.com/BoostIO/Boostnote/wiki/Sync-Data-Across-Desktop-and-Mobile-apps)\n- [Convert data from **Evernote** to Boostnote.](https://github.com/BoostIO/Boostnote/wiki/Evernote)\n- [Keyboard Shortcuts](https://github.com/BoostIO/Boostnote/wiki/Keyboard-Shortcuts)\n- [Keymaps in Editor mode](https://github.com/BoostIO/Boostnote/wiki/Keymaps-in-Editor-mode)\n- [How to set syntax highlight in Snippet note](https://github.com/BoostIO/Boostnote/wiki/Syntax-Highlighting)\n\n---\n\n## Article Archive :books:\n- [Reddit English](http://bit.ly/2mOJPu7)\n- [Reddit Spanish](https://www.reddit.com/r/boostnote_es/)\n- [Reddit Chinese](https://www.reddit.com/r/boostnote_cn/)\n- [Reddit Japanese](https://www.reddit.com/r/boostnote_jp/)\n\n---\n\n## Community :beers:\n- [GitHub](http://bit.ly/2AWWzkD)\n- [Twitter](http://bit.ly/2z8BUJZ)\n- [Facebook Group](http://bit.ly/2jcca8t)'
content:
'# Welcome to Boostnote!\n## Click here to edit markdown :wave:\n\n<iframe width="560" height="315" src="https://www.youtube.com/embed/L0qNPLsvmyM" frameborder="0" allowfullscreen></iframe>\n\n## Docs :memo:\n- [Boostnote | Boost your happiness, productivity and creativity.](https://hackernoon.com/boostnote-boost-your-happiness-productivity-and-creativity-315034efeebe)\n- [Cloud Syncing & Backups](https://github.com/BoostIO/Boostnote/wiki/Cloud-Syncing-and-Backup)\n- [How to sync your data across Desktop and Mobile apps](https://github.com/BoostIO/Boostnote/wiki/Sync-Data-Across-Desktop-and-Mobile-apps)\n- [Convert data from **Evernote** to Boostnote.](https://github.com/BoostIO/Boostnote/wiki/Evernote)\n- [Keyboard Shortcuts](https://github.com/BoostIO/Boostnote/wiki/Keyboard-Shortcuts)\n- [Keymaps in Editor mode](https://github.com/BoostIO/Boostnote/wiki/Keymaps-in-Editor-mode)\n- [How to set syntax highlight in Snippet note](https://github.com/BoostIO/Boostnote/wiki/Syntax-Highlighting)\n\n---\n\n## Article Archive :books:\n- [Reddit English](http://bit.ly/2mOJPu7)\n- [Reddit Spanish](https://www.reddit.com/r/boostnote_es/)\n- [Reddit Chinese](https://www.reddit.com/r/boostnote_cn/)\n- [Reddit Japanese](https://www.reddit.com/r/boostnote_jp/)\n\n---\n\n## Community :beers:\n- [GitHub](http://bit.ly/2AWWzkD)\n- [Twitter](http://bit.ly/2z8BUJZ)\n- [Facebook Group](http://bit.ly/2jcca8t)'
})
.then(note => {
store.dispatch({
@@ -173,12 +178,18 @@ class Main extends React.Component {
delete CodeMirror.keyMap.emacs['Ctrl-V']
eventEmitter.on('editor:fullscreen', this.toggleFullScreen)
eventEmitter.on('menubar:togglemenubar', this.toggleMenuBarVisible.bind(this))
eventEmitter.on(
'menubar:togglemenubar',
this.toggleMenuBarVisible.bind(this)
)
}
componentWillUnmount() {
eventEmitter.off('editor:fullscreen', this.toggleFullScreen)
eventEmitter.off('menubar:togglemenubar', this.toggleMenuBarVisible.bind(this))
eventEmitter.off(
'menubar:togglemenubar',
this.toggleMenuBarVisible.bind(this)
)
}
toggleMenuBarVisible() {
@@ -312,10 +323,16 @@ class Main extends React.Component {
onMouseUp={e => this.handleMouseUp(e)}
>
<SideNav
{..._.pick(this.props, ['dispatch', 'data', 'config', 'match', 'location'])}
{..._.pick(this.props, [
'dispatch',
'data',
'config',
'match',
'location'
])}
width={this.state.navWidth}
/>
{!config.isSideNavFolded &&
{!config.isSideNavFolded && (
<div
styleName={
this.state.isLeftSliderFocused ? 'slider--active' : 'slider'
@@ -325,7 +342,8 @@ class Main extends React.Component {
draggable='false'
>
<div styleName='slider-hitbox' />
</div>}
</div>
)}
<div
styleName={config.isSideNavFolded ? 'body--expanded' : 'body'}
id='main-body'

View File

@@ -72,14 +72,13 @@ body[data-theme="dark"]
.control-newNoteButton-tooltip
darkTooltip()
body[data-theme="solarized-dark"]
apply-theme(theme)
body[data-theme={theme}]
.root, .root--expanded
background-color $ui-solarized-dark-noteList-backgroundColor
background-color get-theme-var(theme, 'noteList-backgroundColor')
body[data-theme="monokai"]
.root, .root--expanded
background-color $ui-monokai-noteList-backgroundColor
for theme in 'solarized-dark' 'dracula'
apply-theme(theme)
body[data-theme="dracula"]
.root, .root--expanded
background-color $ui-dracula-noteList-backgroundColor
for theme in $themes
apply-theme(theme)

View File

@@ -18,8 +18,7 @@ class NewNoteButton extends React.Component {
constructor(props) {
super(props)
this.state = {
}
this.state = {}
this.handleNewNoteButtonClick = this.handleNewNoteButtonClick.bind(this)
}
@@ -33,12 +32,31 @@ class NewNoteButton extends React.Component {
}
handleNewNoteButtonClick(e) {
const { location, dispatch, match: { params }, config } = this.props
const {
location,
dispatch,
match: { params },
config
} = this.props
const { storage, folder } = this.resolveTargetFolder()
if (config.ui.defaultNote === 'MARKDOWN_NOTE') {
createMarkdownNote(storage.key, folder.key, dispatch, location, params, config)
createMarkdownNote(
storage.key,
folder.key,
dispatch,
location,
params,
config
)
} else if (config.ui.defaultNote === 'SNIPPET_NOTE') {
createSnippetNote(storage.key, folder.key, dispatch, location, params, config)
createSnippetNote(
storage.key,
folder.key,
dispatch,
location,
params,
config
)
} else {
modal.open(NewNoteModal, {
storage: storage.key,
@@ -52,7 +70,10 @@ class NewNoteButton extends React.Component {
}
resolveTargetFolder() {
const { data, match: { params } } = this.props
const {
data,
match: { params }
} = this.props
let storage = data.storageMap.get(params.storageKey)
// Find first storage
if (storage == null) {
@@ -62,9 +83,12 @@ class NewNoteButton extends React.Component {
}
}
if (storage == null) this.showMessageBox(i18n.__('No storage to create a note'))
const folder = _.find(storage.folders, {key: params.folderKey}) || storage.folders[0]
if (folder == null) this.showMessageBox(i18n.__('No folder to create a note'))
if (storage == null)
this.showMessageBox(i18n.__('No storage to create a note'))
const folder =
_.find(storage.folders, { key: params.folderKey }) || storage.folders[0]
if (folder == null)
this.showMessageBox(i18n.__('No folder to create a note'))
return {
storage,
@@ -83,13 +107,16 @@ class NewNoteButton extends React.Component {
render() {
const { config, style } = this.props
return (
<div className='NewNoteButton'
<div
className='NewNoteButton'
styleName={config.isSideNavFolded ? 'root--expanded' : 'root'}
style={style}
>
<div styleName='control'>
<button styleName='control-newNoteButton'
onClick={this.handleNewNoteButtonClick}>
<button
styleName='control-newNoteButton'
onClick={this.handleNewNoteButtonClick}
>
<img src='../resources/icon/icon-newnote.svg' />
<span styleName='control-newNoteButton-tooltip'>
{i18n.__('Make a note')} {OSX ? '⌘' : i18n.__('Ctrl')} + N

View File

@@ -59,106 +59,34 @@ $control-height = 30px
top $control-height
overflow auto
body[data-theme="white"]
apply-theme(theme)
body[data-theme={theme}]
.root
background-color $ui-white-noteList-backgroundColor
border-color get-theme-var(theme, 'borderColor')
background-color get-theme-var(theme, 'noteList-backgroundColor')
.control
background-color $ui-white-noteList-backgroundColor
body[data-theme="dark"]
.root
border-color $ui-dark-borderColor
background-color $ui-dark-noteList-backgroundColor
.control
background-color $ui-dark-noteList-backgroundColor
border-color $ui-dark-borderColor
background-color get-theme-var(theme, 'noteList-backgroundColor')
border-color get-theme-var(theme, 'borderColor')
.control-sortBy-select
&:hover
transition 0.2s
color $ui-dark-text-color
color get-theme-var(theme, 'text-color')
background-color: get-theme-var(theme, 'noteList-backgroundColor')
.control-button
color $ui-dark-inactive-text-color
color get-theme-var(theme, 'inactive-text-color')
&:hover
color $ui-dark-text-color
color get-theme-var(theme, 'text-color')
.control-button--active
color $ui-dark-text-color
color get-theme-var(theme, 'text-color')
&:active
color $ui-dark-text-color
color get-theme-var(theme, 'text-color')
for theme in 'white' 'dark' 'solarized-dark' 'dracula'
apply-theme(theme)
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
body[data-theme="monokai"]
.root
border-color $ui-monokai-borderColor
background-color $ui-monokai-noteList-backgroundColor
.control
background-color $ui-monokai-noteList-backgroundColor
border-color $ui-monokai-borderColor
.control-sortBy-select
&:hover
transition 0.2s
color $ui-monokai-text-color
.control-button
color $ui-monokai-inactive-text-color
&:hover
color $ui-monokai-text-color
.control-button--active
color $ui-monokai-text-color
&:active
color $ui-monokai-text-color
body[data-theme="dracula"]
.root
border-color $ui-dracula-borderColor
background-color $ui-dracula-noteList-backgroundColor
.control
background-color $ui-dracula-noteList-backgroundColor
border-color $ui-dracula-borderColor
.control-sortBy-select
&:hover
transition 0.2s
color $ui-dracula-text-color
.control-button
color $ui-dracula-inactive-text-color
&:hover
color $ui-dracula-text-color
.control-button--active
color $ui-dracula-text-color
&:active
color $ui-dracula-text-color
for theme in $themes
apply-theme(theme)

View File

@@ -27,7 +27,7 @@ const { remote } = require('electron')
const { dialog } = remote
const WP_POST_PATH = '/wp/v2/posts'
const regexMatchStartingTitleNumber = new RegExp('^([0-9]*\.?[0-9]+).*$')
const regexMatchStartingTitleNumber = new RegExp('^([0-9]*.?[0-9]+).*$')
function sortByCreatedAt(a, b) {
return new Date(b.createdAt) - new Date(a.createdAt)
@@ -58,11 +58,11 @@ function sortByUpdatedAt (a, b) {
}
function findNoteByKey(notes, noteKey) {
return notes.find((note) => note.key === noteKey)
return notes.find(note => note.key === noteKey)
}
function findNotesByKeys(notes, noteKeys) {
return notes.filter((note) => noteKeys.includes(getNoteKey(note)))
return notes.filter(note => noteKeys.includes(getNoteKey(note)))
}
function getNoteKey(note) {
@@ -152,11 +152,16 @@ class NoteList extends React.Component {
const visibleNoteKeys = this.notes && this.notes.map(note => note.key)
const note = this.notes && this.notes[0]
const key = location.search && queryString.parse(location.search).key
const prevKey = prevProps.location.search && queryString.parse(prevProps.location.search).key
const noteKey = visibleNoteKeys.includes(prevKey) ? prevKey : note && note.key
const prevKey =
prevProps.location.search &&
queryString.parse(prevProps.location.search).key
const noteKey = visibleNoteKeys.includes(prevKey)
? prevKey
: note && note.key
if (note && location.search === '') {
if (!location.pathname.match(/\/searched/)) this.contextNotes = this.getContextNotes()
if (!location.pathname.match(/\/searched/))
this.contextNotes = this.getContextNotes()
// A visible note is an active note
if (!selectedNoteKeys.includes(noteKey)) {
@@ -165,12 +170,15 @@ class NoteList extends React.Component {
ee.emit('list:moved')
}
dispatch(replace({ // was passed with context - we can use connected router here
dispatch(
replace({
// was passed with context - we can use connected router here
pathname: location.pathname,
search: queryString.stringify({
key: noteKey
})
}))
})
)
return
}
@@ -183,9 +191,15 @@ class NoteList extends React.Component {
if (item == null) return false
const overflowBelow = item.offsetTop + item.clientHeight - list.clientHeight - list.scrollTop > 0
const overflowBelow =
item.offsetTop +
item.clientHeight -
list.clientHeight -
list.scrollTop >
0
if (overflowBelow) {
list.scrollTop = item.offsetTop + item.clientHeight - list.clientHeight
list.scrollTop =
item.offsetTop + item.clientHeight - list.clientHeight
}
const overflowAbove = list.scrollTop > item.offsetTop
if (overflowAbove) {
@@ -202,12 +216,14 @@ class NoteList extends React.Component {
selectedNoteKeys
})
dispatch(push({
dispatch(
push({
pathname,
search: queryString.stringify({
key: noteKey
})
}))
})
)
}
getNoteKeyFromTargetIndex(targetIndex) {
@@ -231,7 +247,9 @@ class NoteList extends React.Component {
}
targetIndex--
if (!shiftKeyDown) { selectedNoteKeys = [] }
if (!shiftKeyDown) {
selectedNoteKeys = []
}
const priorNoteKey = this.getNoteKeyFromTargetIndex(targetIndex)
if (selectedNoteKeys.includes(priorNoteKey)) {
selectedNoteKeys.pop()
@@ -262,10 +280,13 @@ class NoteList extends React.Component {
} else {
targetIndex++
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
}
if (!shiftKeyDown) { selectedNoteKeys = [] }
if (!shiftKeyDown) {
selectedNoteKeys = []
}
const nextNoteKey = this.getNoteKeyFromTargetIndex(targetIndex)
if (selectedNoteKeys.includes(nextNoteKey)) {
selectedNoteKeys.pop()
@@ -289,9 +310,12 @@ class NoteList extends React.Component {
const selectedNoteKeys = [noteHash]
let locationToSelect = '/home'
const noteByHash = data.noteMap.map((note) => note).find(note => note.key === noteHash)
const noteByHash = data.noteMap
.map(note => note)
.find(note => note.key === noteHash)
if (noteByHash !== undefined) {
locationToSelect = '/storages/' + noteByHash.storage + '/folders/' + noteByHash.folder
locationToSelect =
'/storages/' + noteByHash.storage + '/folders/' + noteByHash.folder
}
this.focusNote(selectedNoteKeys, noteHash, locationToSelect)
@@ -357,22 +381,31 @@ class NoteList extends React.Component {
}
getNotes() {
const { data, match: { params }, location } = this.props
if (location.pathname.match(/\/home/) || location.pathname.match(/alltags/)) {
const allNotes = data.noteMap.map((note) => note)
const {
data,
match: { params },
location
} = this.props
if (
location.pathname.match(/\/home/) ||
location.pathname.match(/alltags/)
) {
const allNotes = data.noteMap.map(note => note)
this.contextNotes = allNotes
return allNotes
}
if (location.pathname.match(/\/starred/)) {
const starredNotes = data.starredSet.toJS().map((uniqueKey) => data.noteMap.get(uniqueKey))
const starredNotes = data.starredSet
.toJS()
.map(uniqueKey => data.noteMap.get(uniqueKey))
this.contextNotes = starredNotes
return starredNotes
}
if (location.pathname.match(/\/searched/)) {
const searchInputText = params.searchword
const allNotes = data.noteMap.map((note) => note)
const allNotes = data.noteMap.map(note => note)
this.contextNotes = allNotes
if (searchInputText === undefined || searchInputText === '') {
return this.sortByPin(this.contextNotes)
@@ -381,16 +414,20 @@ class NoteList extends React.Component {
}
if (location.pathname.match(/\/trashed/)) {
const trashedNotes = data.trashedSet.toJS().map((uniqueKey) => data.noteMap.get(uniqueKey))
const trashedNotes = data.trashedSet
.toJS()
.map(uniqueKey => data.noteMap.get(uniqueKey))
this.contextNotes = trashedNotes
return trashedNotes
}
if (location.pathname.match(/\/tags/)) {
const listOfTags = params.tagname.split(' ')
return data.noteMap.map(note => {
return data.noteMap
.map(note => {
return note
}).filter(note => listOfTags.every(tag => note.tags.includes(tag)))
})
.filter(note => listOfTags.every(tag => note.tags.includes(tag)))
}
return this.getContextNotes()
@@ -398,7 +435,10 @@ class NoteList extends React.Component {
// get notes in the current folder
getContextNotes() {
const { data, match: { params } } = this.props
const {
data,
match: { params }
} = this.props
const storageKey = params.storageKey
const folderKey = params.folderKey
const storage = data.storageMap.get(storageKey)
@@ -407,18 +447,19 @@ class NoteList extends React.Component {
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))
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))
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) => {
unorderedNotes.forEach(note => {
if (note.isPinned) {
pinnedNotes.push(note)
} else {
@@ -430,7 +471,7 @@ class NoteList extends React.Component {
}
getNoteIndexByKey(noteKey) {
return this.notes.findIndex((note) => {
return this.notes.findIndex(note => {
if (!note) return -1
return note.key === noteKey
@@ -444,7 +485,9 @@ class NoteList extends React.Component {
const hasSelectedNoteKey = selectedNoteKeys.length > 0
if (ctrlKeyDown && selectedNoteKeys.includes(uniqueKey)) {
const newSelectedNoteKeys = selectedNoteKeys.filter((noteKey) => noteKey !== uniqueKey)
const newSelectedNoteKeys = selectedNoteKeys.filter(
noteKey => noteKey !== uniqueKey
)
this.setState({
selectedNoteKeys: newSelectedNoteKeys
})
@@ -464,15 +507,21 @@ class NoteList extends React.Component {
let firstShiftNoteIndex = this.getNoteIndexByKey(selectedNoteKeys[0])
// Shift selection can either start from first note in the exisiting selectedNoteKeys
// or previous first shift note index
firstShiftNoteIndex = firstShiftNoteIndex > prevShiftNoteIndex
? firstShiftNoteIndex : prevShiftNoteIndex
firstShiftNoteIndex =
firstShiftNoteIndex > prevShiftNoteIndex
? firstShiftNoteIndex
: prevShiftNoteIndex
const lastShiftNoteIndex = this.getNoteIndexByKey(uniqueKey)
const startIndex = firstShiftNoteIndex < lastShiftNoteIndex
? firstShiftNoteIndex : lastShiftNoteIndex
const endIndex = firstShiftNoteIndex > lastShiftNoteIndex
? firstShiftNoteIndex : lastShiftNoteIndex
const startIndex =
firstShiftNoteIndex < lastShiftNoteIndex
? firstShiftNoteIndex
: lastShiftNoteIndex
const endIndex =
firstShiftNoteIndex > lastShiftNoteIndex
? firstShiftNoteIndex
: lastShiftNoteIndex
selectedNoteKeys = []
for (let i = startIndex; i <= endIndex; i++) {
@@ -489,16 +538,23 @@ class NoteList extends React.Component {
prevShiftNoteIndex
})
dispatch(push({
dispatch(
push({
pathname: location.pathname,
search: queryString.stringify({
key: uniqueKey
})
}))
})
)
}
handleSortByChange(e) {
const { dispatch, match: { params: { folderKey } } } = this.props
const {
dispatch,
match: {
params: { folderKey }
}
} = this.props
const config = {
[folderKey]: { sortBy: e.target.value }
@@ -525,21 +581,38 @@ class NoteList extends React.Component {
})
}
handleListDirectionButtonClick(e, direction) {
const { dispatch } = this.props
const config = {
listDirection: direction
}
ConfigManager.set(config)
dispatch({
type: 'SET_CONFIG',
config
})
}
alertIfSnippet(msg) {
const warningMessage = (msg) => ({
const warningMessage = msg =>
({
'export-txt': 'Text export',
'export-md': 'Markdown export',
'export-html': 'HTML export',
'export-pdf': 'PDF export',
'print': 'Print'
})[msg]
print: 'Print'
}[msg])
const targetIndex = this.getTargetIndex()
if (this.notes[targetIndex].type === 'SNIPPET_NOTE') {
dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: i18n.__('Sorry!'),
detail: i18n.__(warningMessage(msg) + ' is available only in markdown notes.'),
detail: i18n.__(
warningMessage(msg) + ' is available only in markdown notes.'
),
buttons: [i18n.__('OK')]
})
}
@@ -554,7 +627,7 @@ class NoteList extends React.Component {
selectedNoteKeys.push(noteKey)
}
const notes = this.notes.map((note) => Object.assign({}, note))
const notes = this.notes.map(note => Object.assign({}, note))
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
const noteData = JSON.stringify(selectedNotes)
e.dataTransfer.setData('note', noteData)
@@ -571,7 +644,9 @@ class NoteList extends React.Component {
this.handleNoteClick(e, uniqueKey)
}
const pinLabel = note.isPinned ? i18n.__('Remove pin') : i18n.__('Pin to Top')
const pinLabel = note.isPinned
? i18n.__('Remove pin')
: i18n.__('Pin to Top')
const deleteLabel = i18n.__('Delete Note')
const cloneNote = i18n.__('Clone Note')
const restoreNote = i18n.__('Restore Note')
@@ -583,13 +658,16 @@ class NoteList extends React.Component {
const templates = []
if (location.pathname.match(/\/trash/)) {
templates.push({
templates.push(
{
label: restoreNote,
click: this.restoreNote
}, {
},
{
label: deleteLabel,
click: this.deleteNote
})
}
)
} else {
if (!location.pathname.match(/\/starred/)) {
templates.push({
@@ -597,25 +675,32 @@ class NoteList extends React.Component {
click: this.pinToTop
})
}
templates.push({
templates.push(
{
label: deleteLabel,
click: this.deleteNote
}, {
},
{
label: cloneNote,
click: this.cloneNote.bind(this)
}, {
},
{
label: copyNoteLink,
click: this.copyNoteLink.bind(this, note)
})
}
)
if (note.type === 'MARKDOWN_NOTE') {
if (note.blog && note.blog.blogLink && note.blog.blogId) {
templates.push({
templates.push(
{
label: updateLabel,
click: this.publishMarkdown.bind(this)
}, {
},
{
label: openBlogLabel,
click: () => this.openBlog.bind(this)(note)
})
}
)
} else {
templates.push({
label: publishLabel,
@@ -630,23 +715,23 @@ class NoteList extends React.Component {
updateSelectedNotes(updateFunc, cleanSelection = true) {
const { selectedNoteKeys } = this.state
const { dispatch } = this.props
const notes = this.notes.map((note) => Object.assign({}, note))
const notes = this.notes.map(note => Object.assign({}, note))
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
if (!_.isFunction(updateFunc)) {
console.warn('Update function is not defined. No update will happen')
updateFunc = (note) => { return note }
updateFunc = note => {
return note
}
}
Promise.all(
selectedNotes.map((note) => {
selectedNotes.map(note => {
note = updateFunc(note)
return dataApi
.updateNote(note.storage, note.key, note)
return dataApi.updateNote(note.storage, note.key, note)
})
)
.then((updatedNotes) => {
updatedNotes.forEach((note) => {
).then(updatedNotes => {
updatedNotes.forEach(note => {
dispatch({
type: 'UPDATE_NOTE',
note
@@ -660,14 +745,14 @@ class NoteList extends React.Component {
}
pinToTop() {
this.updateSelectedNotes((note) => {
this.updateSelectedNotes(note => {
note.isPinned = !note.isPinned
return note
})
}
restoreNote() {
this.updateSelectedNotes((note) => {
this.updateSelectedNotes(note => {
note.isTrashed = false
return note
})
@@ -676,7 +761,7 @@ class NoteList extends React.Component {
deleteNote() {
const { dispatch } = this.props
const { selectedNoteKeys } = this.state
const notes = this.notes.map((note) => Object.assign({}, note))
const notes = this.notes.map(note => Object.assign({}, note))
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
const firstNote = selectedNotes[0]
const { confirmDeletion } = this.props.config.ui
@@ -685,14 +770,13 @@ class NoteList extends React.Component {
if (!confirmDeleteNote(confirmDeletion, true)) return
Promise.all(
selectedNotes.map((note) => {
return dataApi
.deleteNote(note.storage, note.key)
selectedNotes.map(note => {
return dataApi.deleteNote(note.storage, note.key)
})
)
.then((data) => {
.then(data => {
const dispatchHandler = () => {
data.forEach((item) => {
data.forEach(item => {
dispatch({
type: 'DELETE_NOTE',
storageKey: item.storageKey,
@@ -703,22 +787,21 @@ class NoteList extends React.Component {
ee.once('list:next', dispatchHandler)
})
.then(() => ee.emit('list:next'))
.catch((err) => {
.catch(err => {
console.error('Cannot Delete note: ' + err)
})
} else {
if (!confirmDeleteNote(confirmDeletion, false)) return
Promise.all(
selectedNotes.map((note) => {
selectedNotes.map(note => {
note.isTrashed = true
return dataApi
.updateNote(note.storage, note.key, note)
return dataApi.updateNote(note.storage, note.key, note)
})
)
.then((newNotes) => {
newNotes.forEach((newNote) => {
.then(newNotes => {
newNotes.forEach(newNote => {
dispatch({
type: 'UPDATE_NOTE',
note: newNote
@@ -727,7 +810,7 @@ class NoteList extends React.Component {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('EDIT_NOTE')
})
.then(() => ee.emit('list:next'))
.catch((err) => {
.catch(err => {
console.error('Notes could not go to trash: ' + err)
})
}
@@ -738,10 +821,11 @@ class NoteList extends React.Component {
const { selectedNoteKeys } = this.state
const { dispatch, location } = this.props
const { storage, folder } = this.resolveTargetFolder()
const notes = this.notes.map((note) => Object.assign({}, note))
const notes = this.notes.map(note => Object.assign({}, note))
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
const firstNote = selectedNotes[0]
const eventName = firstNote.type === 'MARKDOWN_NOTE' ? 'ADD_MARKDOWN' : 'ADD_SNIPPET'
const eventName =
firstNote.type === 'MARKDOWN_NOTE' ? 'ADD_MARKDOWN' : 'ADD_SNIPPET'
AwsMobileAnalyticsConfig.recordDynamicCustomEvent(eventName)
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
@@ -757,11 +841,11 @@ class NoteList extends React.Component {
tags: firstNote.tags,
isStarred: firstNote.isStarred
})
.then((note) => {
.then(note => {
attachmentManagement.cloneAttachments(firstNote, note)
return note
})
.then((note) => {
.then(note => {
dispatch({
type: 'UPDATE_NOTE',
note: note
@@ -771,10 +855,12 @@ class NoteList extends React.Component {
selectedNoteKeys: [note.key]
})
dispatch(push({
dispatch(
push({
pathname: location.pathname,
search: queryString.stringify({ key: note.key })
}))
})
)
})
}
@@ -785,19 +871,19 @@ class NoteList extends React.Component {
navigate(sender, pathname) {
const { dispatch } = this.props
dispatch(push({
dispatch(
push({
pathname,
search: queryString.stringify({
// key: noteKey
})
}))
})
)
}
save(note) {
const { dispatch } = this.props
dataApi
.updateNote(note.storage, note.key, note)
.then((note) => {
dataApi.updateNote(note.storage, note.key, note).then(note => {
dispatch({
type: 'UPDATE_NOTE',
note: note
@@ -816,7 +902,7 @@ class NoteList extends React.Component {
publishMarkdownNow() {
const { selectedNoteKeys } = this.state
const notes = this.notes.map((note) => Object.assign({}, note))
const notes = this.notes.map(note => Object.assign({}, note))
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
const firstNote = selectedNotes[0]
const config = ConfigManager.get()
@@ -827,7 +913,10 @@ class NoteList extends React.Component {
} else {
authToken = `Bearer ${token}`
}
const contentToRender = firstNote.content.replace(`# ${firstNote.title}`, '')
const contentToRender = firstNote.content.replace(
`# ${firstNote.title}`,
''
)
const markdown = new Markdown()
const data = {
title: firstNote.title,
@@ -849,10 +938,11 @@ class NoteList extends React.Component {
method: method,
body: JSON.stringify(data),
headers: {
'Authorization': authToken,
Authorization: authToken,
'Content-Type': 'application/json'
}
}).then(res => res.json())
})
.then(res => res.json())
.then(response => {
if (_.isNil(response.link) || _.isNil(response.id)) {
return Promise.reject()
@@ -864,7 +954,7 @@ class NoteList extends React.Component {
this.save(firstNote)
this.confirmPublish(firstNote)
})
.catch((error) => {
.catch(error => {
console.error(error)
this.confirmPublishError()
})
@@ -902,13 +992,11 @@ class NoteList extends React.Component {
importFromFile() {
const options = {
filters: [
{ name: 'Documents', extensions: ['md', 'txt'] }
],
filters: [{ name: 'Documents', extensions: ['md', 'txt'] }],
properties: ['openFile', 'multiSelections']
}
dialog.showOpenDialog(remote.getCurrentWindow(), options, (filepaths) => {
dialog.showOpenDialog(remote.getCurrentWindow(), options, filepaths => {
this.addNotesFromFiles(filepaths)
})
}
@@ -916,7 +1004,9 @@ class NoteList extends React.Component {
handleDrop(e) {
e.preventDefault()
const { location } = this.props
const filepaths = Array.from(e.dataTransfer.files).map(file => { return file.path })
const filepaths = Array.from(e.dataTransfer.files).map(file => {
return file.path
})
if (!location.pathname.match(/\/trashed/)) this.addNotesFromFiles(filepaths)
}
@@ -926,7 +1016,7 @@ class NoteList extends React.Component {
const { storage, folder } = this.resolveTargetFolder()
if (filepaths === undefined) return
filepaths.forEach((filepath) => {
filepaths.forEach(filepath => {
fs.readFile(filepath, (err, data) => {
if (err) throw Error('File reading error: ', err)
@@ -942,10 +1032,10 @@ class NoteList extends React.Component {
createdAt: birthtime,
updatedAt: mtime
}
dataApi.createNote(storage.key, newNote)
.then((note) => {
attachmentManagement.importAttachments(note.content, filepath, storage.key, note.key)
.then((newcontent) => {
dataApi.createNote(storage.key, newNote).then(note => {
attachmentManagement
.importAttachments(note.content, filepath, storage.key, note.key)
.then(newcontent => {
note.content = newcontent
dataApi.updateNote(storage.key, note.key, note)
@@ -954,10 +1044,12 @@ class NoteList extends React.Component {
type: 'UPDATE_NOTE',
note: note
})
dispatch(push({
dispatch(
push({
pathname: location.pathname,
search: queryString.stringify({ key: getNoteKey(note) })
}))
})
)
})
})
})
@@ -968,14 +1060,17 @@ class NoteList extends React.Component {
getTargetIndex() {
const { location } = this.props
const key = queryString.parse(location.search).key
const targetIndex = _.findIndex(this.notes, (note) => {
const targetIndex = _.findIndex(this.notes, note => {
return getNoteKey(note) === key
})
return targetIndex
}
resolveTargetFolder() {
const { data, match: { params } } = this.props
const {
data,
match: { params }
} = this.props
let storage = data.storageMap.get(params.storageKey)
// Find first storage
@@ -987,7 +1082,8 @@ class NoteList extends React.Component {
}
if (storage == null) this.showMessageBox('No storage for importing note(s)')
const folder = _.find(storage.folders, {key: params.folderKey}) || storage.folders[0]
const folder =
_.find(storage.folders, { key: params.folderKey }) || storage.folders[0]
if (folder == null) this.showMessageBox('No folder for importing note(s)')
return {
@@ -1004,12 +1100,17 @@ class NoteList extends React.Component {
})
}
getNoteStorage (note) { // note.storage = storage key
getNoteStorage(note) {
// note.storage = storage key
return this.props.data.storageMap.toJS()[note.storage]
}
getNoteFolder (note) { // note.folder = folder key
return _.find(this.getNoteStorage(note).folders, ({ key }) => key === note.folder)
getNoteFolder(note) {
// note.folder = folder key
return _.find(
this.getNoteStorage(note).folders,
({ key }) => key === note.folder
)
}
getViewType() {
@@ -1023,11 +1124,19 @@ class NoteList extends React.Component {
}
render() {
const { location, config, match: { params: { folderKey } } } = this.props
const {
location,
config,
match: {
params: { folderKey }
}
} = this.props
let { notes } = this.props
const { selectedNoteKeys } = this.state
const sortBy = _.get(config, [folderKey, 'sortBy'], config.sortBy.default)
const sortFunc = sortBy === 'CREATED_AT'
const sortDir = config.listDirection
const sortFunc =
sortBy === 'CREATED_AT'
? sortByCreatedAt
: sortBy === 'ALPHABETICAL'
? sortByAlphabetical
@@ -1035,10 +1144,12 @@ class NoteList extends React.Component {
const sortedNotes = location.pathname.match(/\/starred|\/trash/)
? this.getNotes().sort(sortFunc)
: this.sortByPin(this.getNotes().sort(sortFunc))
this.notes = notes = sortedNotes.filter((note) => {
this.notes = notes = sortedNotes.filter(note => {
// this is for the trash box
if (note.isTrashed !== true || location.pathname === '/trashed') return true
if (note.isTrashed !== true || location.pathname === '/trashed')
return true
})
if (sortDir === 'DESCENDING') this.notes.reverse()
moment.updateLocale('en', {
relativeTime: {
@@ -1066,8 +1177,7 @@ class NoteList extends React.Component {
selectedNoteKeys.length === 0 ||
notes.every(note => !selectedNoteKeys.includes(note.key))
const noteList = notes
.map((note, index) => {
const noteList = notes.map((note, index) => {
if (note == null) {
return null
}
@@ -1080,8 +1190,7 @@ class NoteList extends React.Component {
notes.length === 1 ||
(autoSelectFirst && index === 0)
const dateDisplay = moment(
sortBy === 'CREATED_AT'
? note.createdAt : note.updatedAt
sortBy === 'CREATED_AT' ? note.createdAt : note.updatedAt
).fromNow('D')
if (isDefault) {
@@ -1121,47 +1230,86 @@ class NoteList extends React.Component {
})
return (
<div className='NoteList'
<div
className='NoteList'
styleName='root'
style={this.props.style}
onDrop={(e) => this.handleDrop(e)}
onDrop={e => this.handleDrop(e)}
>
<div styleName='control'>
<div styleName='control-sortBy'>
<i className='fa fa-angle-down' />
<select styleName='control-sortBy-select'
<select
styleName='control-sortBy-select'
title={i18n.__('Select filter mode')}
value={sortBy}
onChange={(e) => this.handleSortByChange(e)}
onChange={e => this.handleSortByChange(e)}
>
<option title='Sort by update time' value='UPDATED_AT'>{i18n.__('Updated')}</option>
<option title='Sort by create time' value='CREATED_AT'>{i18n.__('Created')}</option>
<option title='Sort alphabetically' value='ALPHABETICAL'>{i18n.__('Alphabetically')}</option>
<option title='Sort by update time' value='UPDATED_AT'>
{i18n.__('Updated')}
</option>
<option title='Sort by create time' value='CREATED_AT'>
{i18n.__('Created')}
</option>
<option title='Sort alphabetically' value='ALPHABETICAL'>
{i18n.__('Alphabetically')}
</option>
</select>
</div>
<div styleName='control-button-area'>
<button title={i18n.__('Default View')} styleName={config.listStyle === 'DEFAULT'
<button
title={i18n.__('Ascending Order')}
styleName={
config.listDirection === 'ASCENDING'
? 'control-button--active'
: 'control-button'
}
onClick={(e) => this.handleListStyleButtonClick(e, 'DEFAULT')}
onClick={e => this.handleListDirectionButtonClick(e, 'ASCENDING')}
>
<img src='../resources/icon/icon-up.svg' />
</button>
<button
title={i18n.__('Descending Order')}
styleName={
config.listDirection === 'DESCENDING'
? 'control-button--active'
: 'control-button'
}
onClick={e =>
this.handleListDirectionButtonClick(e, 'DESCENDING')
}
>
<img src='../resources/icon/icon-down.svg' />
</button>
<button
title={i18n.__('Default View')}
styleName={
config.listStyle === 'DEFAULT'
? 'control-button--active'
: 'control-button'
}
onClick={e => this.handleListStyleButtonClick(e, 'DEFAULT')}
>
<img src='../resources/icon/icon-column.svg' />
</button>
<button title={i18n.__('Compressed View')} styleName={config.listStyle === 'SMALL'
<button
title={i18n.__('Compressed View')}
styleName={
config.listStyle === 'SMALL'
? 'control-button--active'
: 'control-button'
}
onClick={(e) => this.handleListStyleButtonClick(e, 'SMALL')}
onClick={e => this.handleListStyleButtonClick(e, 'SMALL')}
>
<img src='../resources/icon/icon-column-list.svg' />
</button>
</div>
</div>
<div styleName='list'
<div
styleName='list'
ref='list'
tabIndex='-1'
onKeyDown={(e) => this.handleNoteListKeyDown(e)}
onKeyDown={e => this.handleNoteListKeyDown(e)}
onKeyUp={this.handleNoteListKeyUp}
onBlur={this.handleNoteListBlur}
>

View File

@@ -4,11 +4,14 @@ import CSSModules from 'browser/lib/CSSModules'
import styles from './SwitchButton.styl'
import i18n from 'browser/lib/i18n'
const ListButton = ({
onClick, isTagActive
}) => (
<button styleName={isTagActive ? 'non-active-button' : 'active-button'} onClick={onClick}>
<img src={isTagActive
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'
}

View File

@@ -4,10 +4,8 @@ import CSSModules from 'browser/lib/CSSModules'
import styles from './PreferenceButton.styl'
import i18n from 'browser/lib/i18n'
const PreferenceButton = ({
onClick
}) => (
<button styleName='top-menu-preference' onClick={(e) => onClick(e)}>
const PreferenceButton = ({ onClick }) => (
<button styleName='top-menu-preference' onClick={e => onClick(e)}>
<img src='../resources/icon/icon-setting.svg' />
<span styleName='tooltip'>{i18n.__('Preferences')}</span>
</button>

View File

@@ -1,8 +1,5 @@
.top-menu-preference
navButtonColor()
position absolute
top 22px
right 10px
width 2em
background-color transparent
&:hover
@@ -35,8 +32,6 @@ body[data-theme="dark"]
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
background-color transparent
.tooltip
tooltip()
position absolute

View File

@@ -0,0 +1,26 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './SearchButton.styl'
import i18n from 'browser/lib/i18n'
const SearchButton = ({ onClick, isActive }) => (
<button styleName='top-menu-search' onClick={e => onClick(e)}>
<img
styleName='icon-search'
src={
isActive
? '../resources/icon/icon-search-active.svg'
: '../resources/icon/icon-search.svg'
}
/>
<span styleName='tooltip'>{i18n.__('Search')}</span>
</button>
)
SearchButton.propTypes = {
onClick: PropTypes.func.isRequired,
isActive: PropTypes.bool
}
export default CSSModules(SearchButton, styles)

View File

@@ -0,0 +1,55 @@
.top-menu-search
navButtonColor()
position relative
margin-right 6px
top 3px
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
.icon-search
width 16px
body[data-theme="white"]
.top-menu-search
navWhiteButtonColor()
background-color transparent
&:hover
color #0B99F1
background-color transparent
&:active, &:active:hover
color #0B99F1
background-color transparent
body[data-theme="dark"]
.top-menu-search
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
white-space nowrap

View File

@@ -9,16 +9,47 @@
flex-direction column
.top
padding-bottom 15px
display flex
align-items top
justify-content space-between
padding-bottom 10px
margin 14px 14px 4px
.switch-buttons
background-color transparent
border 0
margin 24px auto 4px 14px
display flex
align-items center
text-align center
.extra-buttons
position relative
display flex
align-items center
.search
position relative
flex 1
display flex
max-height 0
overflow hidden
transition max-height .4s
margin -5px 10px 0
.search-input
flex 1
height 2em
vertical-align middle
font-size 14px
border solid 1px $border-color
border-radius 2px
padding 2px 6px
outline none
.search-clear
width 10px
position absolute
right 8px
top 9px
cursor pointer
.top-menu-label
margin-left 5px
@@ -68,8 +99,15 @@
background-color #2E3235
.switch-buttons
display none
.extra-buttons > button:first-of-type // hide search icon
display none
.top
height 60px
align-items center
margin 0
justify-content center
position relative
left -4px
.top-menu
position static
width $sideNav--folded-width
@@ -98,32 +136,52 @@
.top-menu-preference
position absolute
left 7px
.search
height 28px
.search-input
display none
.search-clear
display none
.search-folded
width 16px
padding-left 4px
margin-bottom 8px
cursor pointer
body[data-theme="white"]
.root, .root--folded
background-color #f9f9f9
color $ui-text-color
.search .search-input
background-color #f9f9f9
color $ui-text-color
body[data-theme="dark"]
.root, .root--folded
border-right 1px solid $ui-dark-borderColor
background-color $ui-dark-backgroundColor
color $ui-dark-text-color
.search .search-input
background-color $ui-dark-backgroundColor
color $ui-dark-text-color
border-color $ui-dark-borderColor
.top
border-color $ui-dark-borderColor
body[data-theme="solarized-dark"]
apply-theme(theme)
body[data-theme={theme}]
.root, .root--folded
background-color $ui-solarized-dark-backgroundColor
border-right 1px solid $ui-solarized-dark-borderColor
background-color get-theme-var(theme, 'backgroundColor')
border-right 1px solid get-theme-var(theme, 'borderColor')
body[data-theme="monokai"]
.root, .root--folded
background-color $ui-monokai-backgroundColor
border-right 1px solid $ui-monokai-borderColor
.search .search-input
background-color get-theme-var(theme, 'backgroundColor')
color get-theme-var(theme, 'text-color')
border-color get-theme-var(theme, 'borderColor')
body[data-theme="dracula"]
.root, .root--folded
background-color $ui-dracula-backgroundColor
border-right 1px solid $ui-dracula-borderColor
for theme in 'solarized-dark' 'dracula'
apply-theme(theme)
for theme in $themes
apply-theme(theme)

View File

@@ -34,7 +34,7 @@ class StorageItem extends React.Component {
context.popup([
{
label: i18n.__('Add Folder'),
click: (e) => this.handleAddFolderButtonClick(e)
click: e => this.handleAddFolderButtonClick(e)
},
{
type: 'separator'
@@ -44,11 +44,11 @@ class StorageItem extends React.Component {
submenu: [
{
label: i18n.__('Export as txt'),
click: (e) => this.handleExportStorageClick(e, 'txt')
click: e => this.handleExportStorageClick(e, 'txt')
},
{
label: i18n.__('Export as md'),
click: (e) => this.handleExportStorageClick(e, 'md')
click: e => this.handleExportStorageClick(e, 'md')
}
]
},
@@ -57,7 +57,7 @@ class StorageItem extends React.Component {
},
{
label: i18n.__('Unlink Storage'),
click: (e) => this.handleUnlinkStorageClick(e)
click: e => this.handleUnlinkStorageClick(e)
}
])
}
@@ -66,20 +66,23 @@ class StorageItem extends React.Component {
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: i18n.__('Unlink Storage'),
detail: i18n.__('This work will just detatches a storage from Boostnote. (Any data won\'t be deleted.)'),
detail: i18n.__(
"This work will just detatches a storage from Boostnote. (Any data won't be deleted.)"
),
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
})
if (index === 0) {
const { storage, dispatch } = this.props
dataApi.removeStorage(storage.key)
dataApi
.removeStorage(storage.key)
.then(() => {
dispatch({
type: 'REMOVE_STORAGE',
storageKey: storage.key
})
})
.catch((err) => {
.catch(err => {
throw err
})
}
@@ -92,13 +95,10 @@ class StorageItem extends React.Component {
title: i18n.__('Select a folder to export the files to'),
multiSelections: false
}
dialog.showOpenDialog(remote.getCurrentWindow(), options,
(paths) => {
dialog.showOpenDialog(remote.getCurrentWindow(), options, paths => {
if (paths && paths.length === 1) {
const { storage, dispatch } = this.props
dataApi
.exportStorage(storage.key, fileType, paths[0])
.then(data => {
dataApi.exportStorage(storage.key, fileType, paths[0]).then(data => {
dispatch({
type: 'EXPORT_STORAGE',
storage: data.storage,
@@ -112,8 +112,7 @@ class StorageItem extends React.Component {
handleToggleButtonClick(e) {
const { storage, dispatch } = this.props
const isOpen = !this.state.isOpen
dataApi.toggleStorage(storage.key, isOpen)
.then((storage) => {
dataApi.toggleStorage(storage.key, isOpen).then(storage => {
dispatch({
type: 'EXPAND_STORAGE',
storage,
@@ -139,7 +138,7 @@ class StorageItem extends React.Component {
}
handleFolderButtonClick(folderKey) {
return (e) => {
return e => {
const { storage, dispatch } = this.props
dispatch(push('/storages/' + storage.key + '/folders/' + folderKey))
}
@@ -149,7 +148,7 @@ class StorageItem extends React.Component {
context.popup([
{
label: i18n.__('Rename Folder'),
click: (e) => this.handleRenameFolderClick(e, folder)
click: e => this.handleRenameFolderClick(e, folder)
},
{
type: 'separator'
@@ -159,11 +158,11 @@ class StorageItem extends React.Component {
submenu: [
{
label: i18n.__('Export as txt'),
click: (e) => this.handleExportFolderClick(e, folder, 'txt')
click: e => this.handleExportFolderClick(e, folder, 'txt')
},
{
label: i18n.__('Export as md'),
click: (e) => this.handleExportFolderClick(e, folder, 'md')
click: e => this.handleExportFolderClick(e, folder, 'md')
}
]
},
@@ -172,7 +171,7 @@ class StorageItem extends React.Component {
},
{
label: i18n.__('Delete Folder'),
click: (e) => this.handleFolderDeleteClick(e, folder)
click: e => this.handleFolderDeleteClick(e, folder)
}
])
}
@@ -192,13 +191,12 @@ class StorageItem extends React.Component {
title: i18n.__('Select a folder to export the files to'),
multiSelections: false
}
dialog.showOpenDialog(remote.getCurrentWindow(), options,
(paths) => {
dialog.showOpenDialog(remote.getCurrentWindow(), options, paths => {
if (paths && paths.length === 1) {
const { storage, dispatch } = this.props
dataApi
.exportFolder(storage.key, folder.key, fileType, paths[0])
.then((data) => {
.then(data => {
dispatch({
type: 'EXPORT_FOLDER',
storage: data.storage,
@@ -228,15 +226,15 @@ class StorageItem extends React.Component {
const index = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: i18n.__('Delete Folder'),
detail: i18n.__('This will delete all notes in the folder and can not be undone.'),
detail: i18n.__(
'This will delete all notes in the folder and can not be undone.'
),
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
})
if (index === 0) {
const { storage, dispatch } = this.props
dataApi
.deleteFolder(storage.key, folder.key)
.then((data) => {
dataApi.deleteFolder(storage.key, folder.key).then(data => {
dispatch({
type: 'DELETE_FOLDER',
storage: data.storage,
@@ -248,7 +246,9 @@ class StorageItem extends React.Component {
handleDragEnter(e, key) {
e.preventDefault()
if (this.state.draggedOver === key) { return }
if (this.state.draggedOver === key) {
return
}
this.setState({
draggedOver: key
})
@@ -256,29 +256,35 @@ class StorageItem extends React.Component {
handleDragLeave(e) {
e.preventDefault()
if (this.state.draggedOver === null) { return }
if (this.state.draggedOver === null) {
return
}
this.setState({
draggedOver: null
})
}
dropNote(storage, folder, dispatch, location, noteData) {
noteData = noteData.filter((note) => folder.key !== note.folder)
noteData = noteData.filter(note => folder.key !== note.folder)
if (noteData.length === 0) return
Promise.all(
noteData.map((note) => dataApi.moveNote(note.storage, note.key, storage.key, folder.key))
noteData.map(note =>
dataApi.moveNote(note.storage, note.key, storage.key, folder.key)
)
.then((createdNoteData) => {
createdNoteData.forEach((newNote) => {
)
.then(createdNoteData => {
createdNoteData.forEach(newNote => {
dispatch({
type: 'MOVE_NOTE',
originNote: noteData.find((note) => note.content === newNote.oldContent),
originNote: noteData.find(
note => note.content === newNote.oldContent
),
note: newNote
})
})
})
.catch((err) => {
.catch(err => {
console.error(`error on delete notes: ${err}`)
})
}
@@ -299,16 +305,32 @@ class StorageItem extends React.Component {
const { folderNoteMap, trashedSet } = data
const SortableStorageItemChild = SortableElement(StorageItemChild)
const folderList = storage.folders.map((folder, index) => {
const folderRegex = new RegExp(escapeStringRegexp(path.sep) + 'storages' + escapeStringRegexp(path.sep) + storage.key + escapeStringRegexp(path.sep) + 'folders' + escapeStringRegexp(path.sep) + folder.key)
const isActive = !!(location.pathname.match(folderRegex))
const folderRegex = new RegExp(
escapeStringRegexp(path.sep) +
'storages' +
escapeStringRegexp(path.sep) +
storage.key +
escapeStringRegexp(path.sep) +
'folders' +
escapeStringRegexp(path.sep) +
folder.key
)
const isActive = !!location.pathname.match(folderRegex)
const noteSet = folderNoteMap.get(storage.key + '-' + folder.key)
let noteCount = 0
if (noteSet) {
let trashedNoteCount = 0
const noteKeys = noteSet.map(noteKey => { return noteKey })
const noteKeys = noteSet.map(noteKey => {
return noteKey
})
trashedSet.toJS().forEach(trashedKey => {
if (noteKeys.some(noteKey => { return noteKey === trashedKey })) trashedNoteCount++
if (
noteKeys.some(noteKey => {
return noteKey === trashedKey
})
)
trashedNoteCount++
})
noteCount = noteSet.size - trashedNoteCount
}
@@ -317,73 +339,80 @@ class StorageItem extends React.Component {
key={folder.key}
index={index}
isActive={isActive || folder.key === this.state.draggedOver}
handleButtonClick={(e) => this.handleFolderButtonClick(folder.key)(e)}
handleContextMenu={(e) => this.handleFolderButtonContextMenu(e, folder)}
handleButtonClick={e => this.handleFolderButtonClick(folder.key)(e)}
handleContextMenu={e => this.handleFolderButtonContextMenu(e, folder)}
folderName={folder.name}
folderColor={folder.color}
isFolded={isFolded}
noteCount={noteCount}
handleDrop={(e) => {
handleDrop={e => {
this.handleDrop(e, storage, folder, dispatch, location)
}}
handleDragEnter={(e) => {
handleDragEnter={e => {
this.handleDragEnter(e, folder.key)
}}
handleDragLeave={(e) => {
handleDragLeave={e => {
this.handleDragLeave(e, folder)
}}
/>
)
})
const isActive = location.pathname.match(new RegExp(escapeStringRegexp(path.sep) + 'storages' + escapeStringRegexp(path.sep) + storage.key + '$'))
const isActive = location.pathname.match(
new RegExp(
escapeStringRegexp(path.sep) +
'storages' +
escapeStringRegexp(path.sep) +
storage.key +
'$'
)
)
return (
<div styleName={isFolded ? 'root--folded' : 'root'}
key={storage.key}
<div styleName={isFolded ? 'root--folded' : 'root'} key={storage.key}>
<div
styleName={isActive ? 'header--active' : 'header'}
onContextMenu={e => this.handleHeaderContextMenu(e)}
>
<div styleName={isActive
? 'header--active'
: 'header'
}
onContextMenu={(e) => this.handleHeaderContextMenu(e)}
<button
styleName='header-toggleButton'
onMouseDown={e => this.handleToggleButtonClick(e)}
>
<button styleName='header-toggleButton'
onMouseDown={(e) => this.handleToggleButtonClick(e)}
>
<img src={this.state.isOpen
<img
src={
this.state.isOpen
? '../resources/icon/icon-down.svg'
: '../resources/icon/icon-right.svg'
}
/>
</button>
{!isFolded &&
<button styleName='header-addFolderButton'
onClick={(e) => this.handleAddFolderButtonClick(e)}
{!isFolded && (
<button
styleName='header-addFolderButton'
onClick={e => this.handleAddFolderButtonClick(e)}
>
<img src='../resources/icon/icon-plus.svg' />
</button>
}
)}
<button styleName='header-info'
onClick={(e) => this.handleHeaderInfoClick(e)}
<button
styleName='header-info'
onClick={e => this.handleHeaderInfoClick(e)}
>
<span>
{isFolded ? _.truncate(storage.name, {length: 1, omission: ''}) : storage.name}
{isFolded
? _.truncate(storage.name, { length: 1, omission: '' })
: storage.name}
</span>
{isFolded &&
{isFolded && (
<span styleName='header-info--folded-tooltip'>
{storage.name}
</span>
}
)}
</button>
</div>
{this.state.isOpen &&
<div>
{folderList}
</div>
}
{this.state.isOpen && <div>{folderList}</div>}
</div>
)
}

View File

@@ -132,55 +132,57 @@ body[data-theme="white"]
background-color alpha($ui-button--active-backgroundColor, 40%)
color $ui-text-color
body[data-theme="dark"]
apply-theme(theme)
body[data-theme={theme}]
.header--active
background-color $ui-dark-button--active-backgroundColor
background-color get-theme-var(theme, 'button--active-backgroundColor')
transition color background-color 0.15s
.header--active
.header-toggleButton
color $ui-dark-text-color
color get-theme-var(theme, 'text-color')
.header--active
.header-info
color $ui-dark-text-color
background-color $ui-dark-button--active-backgroundColor
color get-theme-var(theme, 'text-color')
background-color get-theme-var(theme, 'button--active-backgroundColor')
&:active
color $ui-dark-text-color
background-color $ui-dark-button--active-backgroundColor
color get-theme-var(theme, 'text-color')
background-color get-theme-var(theme, 'button--active-backgroundColor')
.header--active
.header-addFolderButton
color $ui-dark-text-color
color get-theme-var(theme, 'text-color')
.header-toggleButton
&:hover
transition 0.2s
color $ui-dark-text-color
background-color alpha($ui-dark-button--active-backgroundColor, 60%)
color get-theme-var(theme, 'text-color')
background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 60%)
&:active, &:active:hover
color $ui-dark-text-color
background-color $ui-dark-button--active-backgroundColor
color get-theme-var(theme, 'text-color')
background-color get-theme-var(theme, 'button--active-backgroundColor')
.header-info
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 20%)
&:hover
transition 0.2s
color $ui-dark-text-color
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
color get-theme-var(theme, 'text-color')
background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 20%)
&:active, &:active:hover
color $ui-dark-text-color
background-color $ui-dark-button--active-backgroundColor
color get-theme-var(theme, 'text-color')
background-color get-theme-var(theme, 'button--active-backgroundColor')
.header-addFolderButton
&:hover
transition 0.2s
color $ui-dark-text-color
background-color alpha($ui-dark-button--active-backgroundColor, 60%)
color get-theme-var(theme, 'text-color')
background-color alpha(get-theme-var(theme, 'button--active-backgroundColor'), 60%)
&:active, &:active:hover
color $ui-dark-text-color
background-color $ui-dark-button--active-backgroundColor
color get-theme-var(theme, 'text-color')
background-color get-theme-var(theme, 'button--active-backgroundColor')
apply-theme('dark')
for theme in $themes
apply-theme(theme)

View File

@@ -4,11 +4,14 @@ import CSSModules from 'browser/lib/CSSModules'
import styles from './SwitchButton.styl'
import i18n from 'browser/lib/i18n'
const TagButton = ({
onClick, isTagActive
}) => (
<button styleName={isTagActive ? 'active-button' : 'non-active-button'} onClick={onClick}>
<img src={isTagActive
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'
}

View File

@@ -14,6 +14,7 @@ import StorageList from 'browser/components/StorageList'
import NavToggleButton from 'browser/components/NavToggleButton'
import EventEmitter from 'browser/main/lib/eventEmitter'
import PreferenceButton from './PreferenceButton'
import SearchButton from './SearchButton'
import ListButton from './ListButton'
import TagButton from './TagButton'
import { SortableContainer } from 'react-sortable-hoc'
@@ -38,13 +39,18 @@ class SideNav extends React.Component {
show: false,
color: null,
tagName: null,
targetRect: null
targetRect: null,
showSearch: false,
searchText: ''
}
}
this.dismissColorPicker = this.dismissColorPicker.bind(this)
this.handleColorPickerConfirm = this.handleColorPickerConfirm.bind(this)
this.handleColorPickerReset = this.handleColorPickerReset.bind(this)
this.handleSearchButtonClick = this.handleSearchButtonClick.bind(this)
this.handleSearchInputChange = this.handleSearchInputChange.bind(this)
this.handleSearchInputClear = this.handleSearchInputClear.bind(this)
}
componentDidMount() {
@@ -56,15 +62,23 @@ class SideNav extends React.Component {
}
deleteTag(tag) {
const selectedButton = remote.dialog.showMessageBox(remote.getCurrentWindow(), {
const selectedButton = remote.dialog.showMessageBox(
remote.getCurrentWindow(),
{
type: 'warning',
message: i18n.__('Confirm tag deletion'),
detail: i18n.__('This will permanently remove this tag.'),
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
})
}
)
if (selectedButton === 0) {
const { data, dispatch, location, match: { params } } = this.props
const {
data,
dispatch,
location,
match: { params }
} = this.props
const notes = data.noteMap
.map(note => note)
@@ -78,9 +92,9 @@ class SideNav extends React.Component {
return note
})
Promise
.all(notes.map(note => dataApi.updateNote(note.storage, note.key, note)))
.then(updatedNotes => {
Promise.all(
notes.map(note => dataApi.updateNote(note.storage, note.key, note))
).then(updatedNotes => {
updatedNotes.forEach(note => {
dispatch({
type: 'UPDATE_NOTE',
@@ -94,7 +108,11 @@ class SideNav extends React.Component {
if (index !== -1) {
tags.splice(index, 1)
dispatch(push(`/tags/${tags.map(tag => encodeURIComponent(tag)).join(' ')}`))
dispatch(
push(
`/tags/${tags.map(tag => encodeURIComponent(tag)).join(' ')}`
)
)
}
}
})
@@ -105,6 +123,26 @@ class SideNav extends React.Component {
openModal(PreferencesModal)
}
handleSearchButtonClick(e) {
const { showSearch } = this.state
this.setState({
showSearch: !showSearch,
searchText: ''
})
}
handleSearchInputClear(e) {
this.setState({
searchText: ''
})
}
handleSearchInputChange(e) {
this.setState({
searchText: e.target.value
})
}
handleHomeButtonClick(e) {
const { dispatch } = this.props
dispatch(push('/home'))
@@ -125,7 +163,11 @@ class SideNav extends React.Component {
menu.push({
label: i18n.__('Customize Color'),
click: this.displayColorPicker.bind(this, tag, e.target.getBoundingClientRect())
click: this.displayColorPicker.bind(
this,
tag,
e.target.getBoundingClientRect()
)
})
context.popup(menu)
@@ -152,9 +194,16 @@ class SideNav extends React.Component {
}
handleColorPickerConfirm(color) {
const { dispatch, config: {coloredTags} } = this.props
const { colorPicker: { tagName } } = this.state
const newColoredTags = Object.assign({}, coloredTags, {[tagName]: color.hex})
const {
dispatch,
config: { coloredTags }
} = this.props
const {
colorPicker: { tagName }
} = this.state
const newColoredTags = Object.assign({}, coloredTags, {
[tagName]: color.hex
})
const config = { coloredTags: newColoredTags }
ConfigManager.set(config)
@@ -166,8 +215,13 @@ class SideNav extends React.Component {
}
handleColorPickerReset() {
const { dispatch, config: {coloredTags} } = this.props
const { colorPicker: { tagName } } = this.state
const {
dispatch,
config: { coloredTags }
} = this.props
const {
colorPicker: { tagName }
} = this.state
const newColoredTags = Object.assign({}, coloredTags)
delete newColoredTags[tagName]
@@ -183,12 +237,19 @@ class SideNav extends React.Component {
handleToggleButtonClick(e) {
const { dispatch, config } = this.props
const { showSearch, searchText } = this.state
ConfigManager.set({ isSideNavFolded: !config.isSideNavFolded })
dispatch({
type: 'SET_IS_SIDENAV_FOLDED',
isFolded: !config.isSideNavFolded
})
if (showSearch && searchText.length === 0) {
this.setState({
showSearch: false
})
}
}
handleTrashedButtonClick(e) {
@@ -209,16 +270,15 @@ class SideNav extends React.Component {
onSortEnd(storage) {
return ({ oldIndex, newIndex }) => {
const { dispatch } = this.props
dataApi
.reorderFolder(storage.key, oldIndex, newIndex)
.then((data) => {
dataApi.reorderFolder(storage.key, oldIndex, newIndex).then(data => {
dispatch({ type: 'REORDER_FOLDER', storage: data.storage })
})
}
}
SideNavComponent (isFolded, storageList) {
const { location, data, config } = this.props
SideNavComponent(isFolded) {
const { location, data, config, dispatch } = this.props
const { showSearch, searchText } = this.state
const isHomeActive = !!location.pathname.match(/^\/home$/)
const isStarredActive = !!location.pathname.match(/^\/starred$/)
@@ -227,25 +287,62 @@ class SideNav extends React.Component {
let component
// TagsMode is not selected
if (!location.pathname.match('/tags') && !location.pathname.match('/alltags')) {
if (
!location.pathname.match('/tags') &&
!location.pathname.match('/alltags')
) {
let storageMap = data.storageMap
if (showSearch && searchText.length > 0) {
storageMap = storageMap.map(storage => {
const folders = storage.folders.filter(
folder =>
folder.name.toLowerCase().indexOf(searchText.toLowerCase()) !== -1
)
return Object.assign({}, storage, { folders })
})
}
const storageList = storageMap.map((storage, key) => {
const SortableStorageItem = SortableContainer(StorageItem)
return (
<SortableStorageItem
key={storage.key}
storage={storage}
data={data}
location={location}
isFolded={isFolded}
dispatch={dispatch}
onSortEnd={this.onSortEnd.bind(this)(storage)}
useDragHandle
/>
)
})
component = (
<div>
<SideNavFilter
isFolded={isFolded}
isHomeActive={isHomeActive}
handleAllNotesButtonClick={(e) => this.handleHomeButtonClick(e)}
handleAllNotesButtonClick={e => this.handleHomeButtonClick(e)}
isStarredActive={isStarredActive}
isTrashedActive={isTrashedActive}
handleStarredButtonClick={(e) => this.handleStarredButtonClick(e)}
handleTrashedButtonClick={(e) => this.handleTrashedButtonClick(e)}
counterTotalNote={data.noteMap._map.size - data.trashedSet._set.size}
handleStarredButtonClick={e => this.handleStarredButtonClick(e)}
handleTrashedButtonClick={e => this.handleTrashedButtonClick(e)}
counterTotalNote={
data.noteMap._map.size - data.trashedSet._set.size
}
counterStarredNote={data.starredSet._set.size}
counterDelNote={data.trashedSet._set.size}
handleFilterButtonContextMenu={this.handleFilterButtonContextMenu.bind(this)}
handleFilterButtonContextMenu={this.handleFilterButtonContextMenu.bind(
this
)}
/>
<StorageList storageList={storageList} isFolded={isFolded} />
<NavToggleButton isFolded={isFolded} handleToggleButtonClick={this.handleToggleButtonClick.bind(this)} />
<NavToggleButton
isFolded={isFolded}
handleToggleButtonClick={this.handleToggleButtonClick.bind(this)}
/>
</div>
)
} else {
@@ -257,22 +354,26 @@ class SideNav extends React.Component {
</div>
<div styleName='tag-control-sortTagsBy'>
<i className='fa fa-angle-down' />
<select styleName='tag-control-sortTagsBy-select'
<select
styleName='tag-control-sortTagsBy-select'
title={i18n.__('Select filter mode')}
value={config.sortTagsBy}
onChange={(e) => this.handleSortTagsByChange(e)}
onChange={e => this.handleSortTagsByChange(e)}
>
<option title='Sort alphabetically'
value='ALPHABETICAL'>{i18n.__('Alphabetically')}</option>
<option title='Sort by update time'
value='COUNTER'>{i18n.__('Counter')}</option>
<option title='Sort alphabetically' value='ALPHABETICAL'>
{i18n.__('Alphabetically')}
</option>
<option title='Sort by update time' value='COUNTER'>
{i18n.__('Counter')}
</option>
</select>
</div>
</div>
<div styleName='tagList'>
{this.tagListComponent(data)}
</div>
<NavToggleButton isFolded={isFolded} handleToggleButtonClick={this.handleToggleButtonClick.bind(this)} />
<div styleName='tagList'>{this.tagListComponent(data)}</div>
<NavToggleButton
isFolded={isFolded}
handleToggleButtonClick={this.handleToggleButtonClick.bind(this)}
/>
</div>
)
}
@@ -282,38 +383,50 @@ class SideNav extends React.Component {
tagListComponent() {
const { data, location, config } = this.props
const { colorPicker } = this.state
const { colorPicker, showSearch, searchText } = this.state
const activeTags = this.getActiveTags(location.pathname)
const relatedTags = this.getRelatedTags(activeTags, data.noteMap)
let tagList = sortBy(data.tagNoteMap.map(
(tag, name) => ({ name, size: tag.size, related: relatedTags.has(name) })
).filter(
tag => tag.size > 0
), ['name'])
let tagList = sortBy(
data.tagNoteMap
.map((tag, name) => ({
name,
size: tag.size,
related: relatedTags.has(name)
}))
.filter(tag => tag.size > 0),
['name']
)
if (showSearch && searchText.length > 0) {
tagList = tagList.filter(
tag => tag.name.toLowerCase().indexOf(searchText.toLowerCase()) !== -1
)
}
if (config.ui.enableLiveNoteCounts && activeTags.length !== 0) {
const notesTags = data.noteMap.map(note => note.tags)
tagList = tagList.map(tag => {
tag.size = notesTags.filter(tags => tags.includes(tag.name) && matchActiveTags(tags, activeTags)).length
tag.size = notesTags.filter(
tags => tags.includes(tag.name) && matchActiveTags(tags, activeTags)
).length
return tag
})
}
if (config.sortTagsBy === 'COUNTER') {
tagList = sortBy(tagList, item => (0 - item.size))
tagList = sortBy(tagList, item => 0 - item.size)
}
if (config.ui.showOnlyRelatedTags && (relatedTags.size > 0)) {
tagList = tagList.filter(
tag => tag.related
)
if (config.ui.showOnlyRelatedTags && relatedTags.size > 0) {
tagList = tagList.filter(tag => tag.related)
}
return (
tagList.map(tag => {
return tagList.map(tag => {
return (
<TagListItem
name={tag.name}
handleClickTagListItem={this.handleClickTagListItem.bind(this)}
handleClickNarrowToTag={this.handleClickNarrowToTag.bind(this)}
handleContextMenu={this.handleTagContextMenu.bind(this)}
isActive={this.getTagActive(location.pathname, tag.name) || (colorPicker.tagName === tag.name)}
isActive={
this.getTagActive(location.pathname, tag.name) ||
colorPicker.tagName === tag.name
}
isRelated={tag.related}
key={tag.name}
count={tag.size}
@@ -321,18 +434,15 @@ class SideNav extends React.Component {
/>
)
})
)
}
getRelatedTags(activeTags, noteMap) {
if (activeTags.length === 0) {
return new Set()
}
const relatedNotes = noteMap.map(
note => ({key: note.key, tags: note.tags})
).filter(
note => activeTags.every(tag => note.tags.includes(tag))
)
const relatedNotes = noteMap
.map(note => ({ key: note.key, tags: note.tags }))
.filter(note => activeTags.every(tag => note.tags.includes(tag)))
const relatedTags = new Set()
relatedNotes.forEach(note => note.tags.map(tag => relatedTags.add(tag)))
return relatedTags
@@ -345,9 +455,7 @@ class SideNav extends React.Component {
getActiveTags(path) {
const pathSegments = path.split('/')
const tags = pathSegments[pathSegments.length - 1]
return (tags === 'alltags')
? []
: decodeURIComponent(tags).split(' ')
return tags === 'alltags' ? [] : decodeURIComponent(tags).split(' ')
}
handleClickTagListItem(name) {
@@ -383,49 +491,38 @@ class SideNav extends React.Component {
emptyTrash(entries) {
const { dispatch } = this.props
const deletionPromises = entries.map((note) => {
const deletionPromises = entries.map(note => {
return dataApi.deleteNote(note.storage, note.key)
})
const { confirmDeletion } = this.props.config.ui
if (!confirmDeleteNote(confirmDeletion, true)) return
Promise.all(deletionPromises)
.then((arrayOfStorageAndNoteKeys) => {
.then(arrayOfStorageAndNoteKeys => {
arrayOfStorageAndNoteKeys.forEach(({ storageKey, noteKey }) => {
dispatch({ type: 'DELETE_NOTE', storageKey, noteKey })
})
})
.catch((err) => {
.catch(err => {
console.error('Cannot Delete note: ' + err)
})
}
handleFilterButtonContextMenu(event) {
const { data } = this.props
const trashedNotes = data.trashedSet.toJS().map((uniqueKey) => data.noteMap.get(uniqueKey))
const trashedNotes = data.trashedSet
.toJS()
.map(uniqueKey => data.noteMap.get(uniqueKey))
context.popup([
{ label: i18n.__('Empty Trash'), click: () => this.emptyTrash(trashedNotes) }
{
label: i18n.__('Empty Trash'),
click: () => this.emptyTrash(trashedNotes)
}
])
}
render() {
const { data, location, config, dispatch } = this.props
const { colorPicker: colorPickerState } = this.state
const isFolded = config.isSideNavFolded
const storageList = data.storageMap.map((storage, key) => {
const SortableStorageItem = SortableContainer(StorageItem)
return <SortableStorageItem
key={storage.key}
storage={storage}
data={data}
location={location}
isFolded={isFolded}
dispatch={dispatch}
onSortEnd={this.onSortEnd.bind(this)(storage)}
useDragHandle
/>
})
const { location, config } = this.props
const { showSearch, searchText, colorPicker: colorPickerState } = this.state
let colorPicker
if (colorPickerState.show) {
@@ -440,25 +537,63 @@ class SideNav extends React.Component {
)
}
const isFolded = config.isSideNavFolded
const style = {}
if (!isFolded) style.width = this.props.width
const isTagActive = /tag/.test(location.pathname)
const navSearch = (
<div styleName='search' style={{ maxHeight: showSearch ? '3em' : '0' }}>
<input
styleName='search-input'
type='text'
onChange={this.handleSearchInputChange}
value={searchText}
placeholder={i18n.__('Filter tags/folders...')}
/>
<img
styleName='search-clear'
src='../resources/icon/icon-x.svg'
onClick={this.handleSearchInputClear}
/>
{isFolded && (
<img
styleName='search-folded'
src='../resources/icon/icon-search-active.svg'
onClick={this.handleSearchButtonClick}
/>
)}
</div>
)
return (
<div className='SideNav'
<div
className='SideNav'
styleName={isFolded ? 'root--folded' : 'root'}
tabIndex='1'
style={style}
>
<div styleName='top'>
<div styleName='switch-buttons'>
<ListButton onClick={this.handleSwitchFoldersButtonClick.bind(this)} isTagActive={isTagActive} />
<TagButton onClick={this.handleSwitchTagsButtonClick.bind(this)} isTagActive={isTagActive} />
<ListButton
onClick={this.handleSwitchFoldersButtonClick.bind(this)}
isTagActive={isTagActive}
/>
<TagButton
onClick={this.handleSwitchTagsButtonClick.bind(this)}
isTagActive={isTagActive}
/>
</div>
<div>
<div styleName='extra-buttons'>
<SearchButton
onClick={this.handleSearchButtonClick}
isActive={showSearch}
/>
<PreferenceButton onClick={this.handleMenuButtonClick} />
</div>
</div>
{this.SideNavComponent(isFolded, storageList)}
{navSearch}
{this.SideNavComponent(isFolded)}
{colorPicker}
</div>
)

View File

@@ -78,24 +78,19 @@ body[data-theme="dark"]
border-color $ui-dark-borderColor
border-left 1px solid $ui-dark-borderColor
body[data-theme="monokai"]
navButtonColor()
apply-theme(theme)
body[data-theme={theme}]
.zoom
border-color $ui-dark-borderColor
color $ui-monokai-text-color
color get-theme-var(theme, 'text-color')
&:hover
transition 0.15s
color $ui-monokai-active-color
color get-theme-var(theme, 'active-color')
&:active
color $ui-monokai-active-color
color get-theme-var(theme, 'active-color')
body[data-theme="dracula"]
navButtonColor()
.zoom
border-color $ui-dark-borderColor
color $ui-dracula-text-color
&:hover
transition 0.15s
color $ui-dracula-active-color
&:active
color $ui-dracula-active-color
for theme in 'dracula' 'solarized-dark'
apply-theme(theme)
for theme in $themes
apply-theme(theme)

View File

@@ -11,10 +11,23 @@ const electron = require('electron')
const { remote, ipcRenderer } = electron
const { dialog } = remote
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.0]
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.0
]
class StatusBar extends React.Component {
constructor(props) {
super(props)
this.handleZoomInMenuItem = this.handleZoomInMenuItem.bind(this)
@@ -50,7 +63,7 @@ class StatusBar extends React.Component {
handleZoomButtonClick(e) {
const templates = []
zoomOptions.forEach((zoom) => {
zoomOptions.forEach(zoom => {
templates.push({
label: Math.floor(zoom * 100) + '%',
click: () => this.handleZoomMenuItemClick(zoom)
@@ -87,22 +100,18 @@ class StatusBar extends React.Component {
const { config, status } = this.context
return (
<div className='StatusBar'
styleName='root'
>
<button styleName='zoom'
onClick={(e) => this.handleZoomButtonClick(e)}
>
<div className='StatusBar' styleName='root'>
<button styleName='zoom' onClick={e => this.handleZoomButtonClick(e)}>
<img src='../resources/icon/icon-zoom.svg' />
<span>{Math.floor(config.zoom * 100)}%</span>
</button>
{status.updateReady
? <button onClick={this.updateApp} styleName='update'>
<i styleName='update-icon' className='fa fa-cloud-download' /> {i18n.__('Ready to Update!')}
{status.updateReady ? (
<button onClick={this.updateApp} styleName='update'>
<i styleName='update-icon' className='fa fa-cloud-download' />{' '}
{i18n.__('Ready to Update!')}
</button>
: null
}
) : null}
</div>
)
}

View File

@@ -212,69 +212,31 @@ body[data-theme="dark"]
.control-newPostButton-tooltip
darkTooltip()
body[data-theme="solarized-dark"]
apply-theme(theme)
body[data-theme={theme}]
.root, .root--expanded
background-color $ui-solarized-dark-noteList-backgroundColor
background-color get-theme-var(theme, 'noteList-backgroundColor')
.control
border-color $ui-solarized-dark-borderColor
border-color get-theme-var(theme, 'borderColor')
.control-search
background-color $ui-solarized-dark-noteList-backgroundColor
background-color get-theme-var(theme, '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
color get-theme-var(theme, 'inactive-text-color')
background-color get-theme-var(theme, 'noteList-backgroundColor')
.control-search-input
background-color $ui-solarized-dark-noteList-backgroundColor
background-color get-theme-var(theme, 'noteList-backgroundColor')
input
background-color $ui-solarized-dark-noteList-backgroundColor
color $ui-solarized-dark-text-color
background-color get-theme-var(theme, 'noteList-backgroundColor')
color get-theme-var(theme, 'text-color')
body[data-theme="monokai"]
.root, .root--expanded
background-color $ui-monokai-noteList-backgroundColor
for theme in 'solarized-dark' 'dracula'
apply-theme(theme)
.control
border-color $ui-monokai-borderColor
.control-search
background-color $ui-monokai-noteList-backgroundColor
.control-search-icon
absolute top bottom left
line-height 32px
width 35px
color $ui-monokai-inactive-text-color
background-color $ui-monokai-noteList-backgroundColor
.control-search-input
background-color $ui-monokai-noteList-backgroundColor
input
background-color $ui-monokai-noteList-backgroundColor
color $ui-monokai-text-color
body[data-theme="dracula"]
.root, .root--expanded
background-color $ui-dracula-noteList-backgroundColor
.control
border-color $ui-dracula-borderColor
.control-search
background-color $ui-dracula-noteList-backgroundColor
.control-search-icon
absolute top bottom left
line-height 32px
width 35px
color $ui-dracula-inactive-text-color
background-color $ui-dracula-noteList-backgroundColor
.control-search-input
background-color $ui-dracula-noteList-backgroundColor
input
background-color $ui-dracula-noteList-backgroundColor
color $ui-dracula-text-color
for theme in $themes
apply-theme(theme)

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