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

Compare commits

..

1 Commits

Author SHA1 Message Date
Flexo013
1b96661cd9 Added link to downloads 2019-10-20 14:40:02 +02:00
280 changed files with 8938 additions and 13442 deletions

View File

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

View File

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

View File

@@ -5,19 +5,19 @@ Let us know what is currently happening.
Please include some **screenshots** with the **developer tools** open (console tab) when you report a bug. Please include some **screenshots** with the **developer tools** open (console tab) when you report a bug.
If your issue is regarding the new Boost Note.next, please open an issue in the new repo 👉 https://github.com/BoostIO/BoostNote.next/issues. If your issue is regarding Boostnote mobile, please open an issue in the Boostnote Mobile repo 👉 https://github.com/BoostIO/boostnote-mobile.
--> -->
# Expected behavior # Expected behavior
<!-- <!--
Let us know what you think should happen. Let us know what you think should happen!
--> -->
# Steps to reproduce # Steps to reproduce
<!-- <!--
Please be thorough, issues we can reproduce are easier to fix. Please be thorough, issues we can reproduce are easier to fix!
--> -->
1. 1.
@@ -26,8 +26,8 @@ Please be thorough, issues we can reproduce are easier to fix.
# Environment # Environment
- Boostnote version: <!-- 0.x.x --> - Version :
- OS version and name: <!-- Windows 10 / Ubuntu 18.04 / etc --> - OS Version and name :
<!-- <!--
Love Boostnote? Please consider supporting us on IssueHunt: Love Boostnote? Please consider supporting us on IssueHunt:

View File

@@ -3,16 +3,13 @@ Before submitting this PR, please make sure that:
- You have read and understand the contributing.md - You have read and understand the contributing.md
- You have checked docs/code_style.md for information on code style - You have checked docs/code_style.md for information on code style
--> -->
## Description ## Description
<!-- <!--
Tell us what your PR does. Tell us what your PR does.
Please attach a screenshot/ video/gif image describing your PR if possible. Please attach a screenshot/ video/gif image describing your PR if possible.
--> -->
## Issue fixed ## Issue fixed
<!-- <!--
Please list out all issue fixed with this PR here. Please list out all issue fixed with this PR here.
--> -->
@@ -23,7 +20,6 @@ your PR will be reviewed faster if we know exactly what it does.
Change :white_circle: to :radio_button: in all the options that apply Change :white_circle: to :radio_button: in all the options that apply
--> -->
## Type of changes ## Type of changes
- :white_circle: Bug fix (Change that fixed an issue) - :white_circle: Bug fix (Change that fixed an issue)
@@ -38,5 +34,3 @@ Change :white_circle: to :radio_button: in all the options that apply
- :white_circle: I have written test for my code and it has been tested - :white_circle: I have written test for my code and it has been tested
- :white_circle: All existing tests have been passed - :white_circle: All existing tests have been passed
- :white_circle: I have attached a screenshot/video to visualize my change if possible - :white_circle: I have attached a screenshot/video to visualize my change if possible
- :white_circle: This PR will modify the UI or affects the UX
- :white_circle: This PR will add/update/delete a keybinding

View File

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

View File

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

View File

@@ -1,4 +1,3 @@
/* eslint-disable camelcase */
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import React from 'react' import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
@@ -21,32 +20,26 @@ class MarkdownEditor extends React.Component {
this.supportMdSelectionBold = [16, 17, 186] this.supportMdSelectionBold = [16, 17, 186]
this.state = { this.state = {
status: status: props.config.editor.switchPreview === 'RIGHTCLICK' ? props.config.editor.delfaultStatus : 'CODE',
props.config.editor.switchPreview === 'RIGHTCLICK'
? props.config.editor.delfaultStatus
: 'CODE',
renderValue: props.value, renderValue: props.value,
keyPressed: new Set(), keyPressed: new Set(),
isLocked: props.isLocked isLocked: props.isLocked
} }
this.lockEditorCode = this.handleLockEditor.bind(this) this.lockEditorCode = () => this.handleLockEditor()
this.focusEditor = this.focusEditor.bind(this)
this.previewRef = React.createRef()
} }
componentDidMount () { componentDidMount () {
this.value = this.refs.code.value this.value = this.refs.code.value
eventEmitter.on('editor:lock', this.lockEditorCode) eventEmitter.on('editor:lock', this.lockEditorCode)
eventEmitter.on('editor:focus', this.focusEditor) eventEmitter.on('editor:focus', this.focusEditor.bind(this))
} }
componentDidUpdate () { componentDidUpdate () {
this.value = this.refs.code.value this.value = this.refs.code.value
} }
UNSAFE_componentWillReceiveProps(props) { componentWillReceiveProps (props) {
if (props.value !== this.props.value) { if (props.value !== this.props.value) {
this.queueRendering(props.value) this.queueRendering(props.value)
} }
@@ -55,21 +48,15 @@ class MarkdownEditor extends React.Component {
componentWillUnmount () { componentWillUnmount () {
this.cancelQueue() this.cancelQueue()
eventEmitter.off('editor:lock', this.lockEditorCode) eventEmitter.off('editor:lock', this.lockEditorCode)
eventEmitter.off('editor:focus', this.focusEditor) eventEmitter.off('editor:focus', this.focusEditor.bind(this))
} }
focusEditor () { focusEditor () {
this.setState( this.setState({
{
status: 'CODE' status: 'CODE'
}, }, () => {
() => {
if (this.refs.code == null) {
return
}
this.refs.code.focus() this.refs.code.focus()
} })
)
} }
queueRendering (value) { queueRendering (value) {
@@ -103,23 +90,20 @@ class MarkdownEditor extends React.Component {
const { config } = this.props const { config } = this.props
if (config.editor.switchPreview === 'RIGHTCLICK') { if (config.editor.switchPreview === 'RIGHTCLICK') {
const newStatus = this.state.status === 'PREVIEW' ? 'CODE' : 'PREVIEW' const newStatus = this.state.status === 'PREVIEW' ? 'CODE' : 'PREVIEW'
this.setState( this.setState({
{
status: newStatus status: newStatus
}, }, () => {
() => {
if (newStatus === 'CODE') { if (newStatus === 'CODE') {
this.refs.code.focus() this.refs.code.focus()
} else { } else {
this.previewRef.current.focus() this.refs.preview.focus()
} }
eventEmitter.emit('topbar:togglelockbutton', this.state.status) eventEmitter.emit('topbar:togglelockbutton', this.state.status)
const newConfig = Object.assign({}, config) const newConfig = Object.assign({}, config)
newConfig.editor.delfaultStatus = newStatus newConfig.editor.delfaultStatus = newStatus
ConfigManager.set(newConfig) ConfigManager.set(newConfig)
} })
)
} }
} }
@@ -127,21 +111,16 @@ class MarkdownEditor extends React.Component {
if (this.state.isLocked) return if (this.state.isLocked) return
this.setState({ keyPressed: new Set() }) this.setState({ keyPressed: new Set() })
const { config } = this.props const { config } = this.props
if ( if (config.editor.switchPreview === 'BLUR' ||
config.editor.switchPreview === 'BLUR' || (config.editor.switchPreview === 'DBL_CLICK' && this.state.status === 'CODE')
(config.editor.switchPreview === 'DBL_CLICK' &&
this.state.status === 'CODE')
) { ) {
const cursorPosition = this.refs.code.editor.getCursor() const cursorPosition = this.refs.code.editor.getCursor()
this.setState( this.setState({
{
status: 'PREVIEW' status: 'PREVIEW'
}, }, () => {
() => { this.refs.preview.focus()
this.previewRef.current.focus() this.refs.preview.scrollTo(cursorPosition.line)
this.previewRef.current.scrollToRow(cursorPosition.line) })
}
)
eventEmitter.emit('topbar:togglelockbutton', this.state.status) eventEmitter.emit('topbar:togglelockbutton', this.state.status)
} }
} }
@@ -151,15 +130,12 @@ class MarkdownEditor extends React.Component {
this.setState({keyPressed: new Set()}) this.setState({keyPressed: new Set()})
const { config } = this.props const { config } = this.props
if (config.editor.switchPreview === 'DBL_CLICK') { if (config.editor.switchPreview === 'DBL_CLICK') {
this.setState( this.setState({
{
status: 'CODE' status: 'CODE'
}, }, () => {
() => {
this.refs.code.focus() this.refs.code.focus()
eventEmitter.emit('topbar:togglelockbutton', this.state.status) eventEmitter.emit('topbar:togglelockbutton', this.state.status)
} })
)
} }
} }
@@ -169,18 +145,12 @@ class MarkdownEditor extends React.Component {
handlePreviewMouseUp (e) { handlePreviewMouseUp (e) {
const { config } = this.props const { config } = this.props
if ( if (config.editor.switchPreview === 'BLUR' && new Date() - this.previewMouseDownedAt < 200) {
config.editor.switchPreview === 'BLUR' && this.setState({
new Date() - this.previewMouseDownedAt < 200
) {
this.setState(
{
status: 'CODE' status: 'CODE'
}, }, () => {
() => {
this.refs.code.focus() this.refs.code.focus()
} })
)
eventEmitter.emit('topbar:togglelockbutton', this.state.status) eventEmitter.emit('topbar:togglelockbutton', this.state.status)
} }
} }
@@ -194,9 +164,9 @@ class MarkdownEditor extends React.Component {
const checkReplace = /\[x]/i const checkReplace = /\[x]/i
const uncheckReplace = /\[ ]/ const uncheckReplace = /\[ ]/
if (idMatch.test(e.target.getAttribute('id'))) { if (idMatch.test(e.target.getAttribute('id'))) {
const lineIndex = const lineIndex = parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1
parseInt(e.target.getAttribute('id').match(idMatch)[1], 10) - 1 const lines = this.refs.code.value
const lines = this.refs.code.value.split('\n') .split('\n')
const targetLine = lines[lineIndex] const targetLine = lines[lineIndex]
let newLine = targetLine let newLine = targetLine
@@ -213,14 +183,11 @@ class MarkdownEditor extends React.Component {
focus () { focus () {
if (this.state.status === 'PREVIEW') { if (this.state.status === 'PREVIEW') {
this.setState( this.setState({
{
status: 'CODE' status: 'CODE'
}, }, () => {
() => {
this.refs.code.focus() this.refs.code.focus()
} })
)
} else { } else {
this.refs.code.focus() this.refs.code.focus()
} }
@@ -239,23 +206,15 @@ class MarkdownEditor extends React.Component {
const keyPressed = this.state.keyPressed const keyPressed = this.state.keyPressed
keyPressed.add(e.keyCode) keyPressed.add(e.keyCode)
this.setState({ keyPressed }) this.setState({ keyPressed })
const isNoteHandlerKey = el => { const isNoteHandlerKey = (el) => { return keyPressed.has(el) }
return keyPressed.has(el)
}
// These conditions are for ctrl-e and ctrl-w // These conditions are for ctrl-e and ctrl-w
if ( if (keyPressed.size === this.escapeFromEditor.length &&
keyPressed.size === this.escapeFromEditor.length && !this.state.isLocked && this.state.status === 'CODE' &&
!this.state.isLocked && this.escapeFromEditor.every(isNoteHandlerKey)) {
this.state.status === 'CODE' &&
this.escapeFromEditor.every(isNoteHandlerKey)
) {
this.handleContextMenu() this.handleContextMenu()
if (config.editor.switchPreview === 'BLUR') document.activeElement.blur() if (config.editor.switchPreview === 'BLUR') document.activeElement.blur()
} }
if ( if (keyPressed.size === this.supportMdSelectionBold.length && this.supportMdSelectionBold.every(isNoteHandlerKey)) {
keyPressed.size === this.supportMdSelectionBold.length &&
this.supportMdSelectionBold.every(isNoteHandlerKey)
) {
this.addMdAroundWord('**') this.addMdAroundWord('**')
} }
} }
@@ -268,27 +227,20 @@ class MarkdownEditor extends React.Component {
const word = this.refs.code.editor.findWordAt(currentCaret) const word = this.refs.code.editor.findWordAt(currentCaret)
const cmDoc = this.refs.code.editor.getDoc() const cmDoc = this.refs.code.editor.getDoc()
cmDoc.replaceRange(mdElement, word.anchor) cmDoc.replaceRange(mdElement, word.anchor)
cmDoc.replaceRange(mdElement, { cmDoc.replaceRange(mdElement, { line: word.head.line, ch: word.head.ch + mdElement.length })
line: word.head.line,
ch: word.head.ch + mdElement.length
})
} }
addMdAroundSelection (mdElement) { addMdAroundSelection (mdElement) {
this.refs.code.editor.replaceSelection( this.refs.code.editor.replaceSelection(`${mdElement}${this.refs.code.editor.getSelection()}${mdElement}`)
`${mdElement}${this.refs.code.editor.getSelection()}${mdElement}`
)
} }
handleDropImage (dropEvent) { handleDropImage (dropEvent) {
dropEvent.preventDefault() dropEvent.preventDefault()
const { storageKey, noteKey } = this.props const { storageKey, noteKey } = this.props
this.setState( this.setState({
{
status: 'CODE' status: 'CODE'
}, }, () => {
() => {
this.refs.code.focus() this.refs.code.focus()
this.refs.code.editor.execCommand('goDocEnd') this.refs.code.editor.execCommand('goDocEnd')
@@ -301,8 +253,7 @@ class MarkdownEditor extends React.Component {
noteKey, noteKey,
dropEvent dropEvent
) )
} })
)
} }
handleKeyUp (e) { handleKeyUp (e) {
@@ -316,15 +267,7 @@ class MarkdownEditor extends React.Component {
} }
render () { render () {
const { const {className, value, config, storageKey, noteKey, linesHighlighted} = this.props
className,
value,
config,
storageKey,
noteKey,
linesHighlighted,
RTL
} = this.props
let editorFontSize = parseInt(config.editor.fontSize, 10) let editorFontSize = parseInt(config.editor.fontSize, 10)
if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14 if (!(editorFontSize > 0 && editorFontSize < 101)) editorFontSize = 14
@@ -332,24 +275,23 @@ class MarkdownEditor extends React.Component {
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4 if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
const previewStyle = {} const previewStyle = {}
if (this.props.ignorePreviewPointerEvents) if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none'
previewStyle.pointerEvents = 'none'
const storage = findStorage(storageKey) const storage = findStorage(storageKey)
return ( return (
<div <div className={className == null
className={ ? 'MarkdownEditor'
className == null ? 'MarkdownEditor' : `MarkdownEditor ${className}` : `MarkdownEditor ${className}`
} }
onContextMenu={e => this.handleContextMenu(e)} onContextMenu={(e) => this.handleContextMenu(e)}
tabIndex='-1' tabIndex='-1'
onKeyDown={e => this.handleKeyDown(e)} onKeyDown={(e) => this.handleKeyDown(e)}
onKeyUp={e => this.handleKeyUp(e)} onKeyUp={(e) => this.handleKeyUp(e)}
> >
<CodeEditor <CodeEditor styleName={this.state.status === 'CODE'
styleName={ ? 'codeEditor'
this.state.status === 'CODE' ? 'codeEditor' : 'codeEditor--hide' : 'codeEditor--hide'
} }
ref='code' ref='code'
mode='Boost Flavored Markdown' mode='Boost Flavored Markdown'
@@ -373,8 +315,8 @@ class MarkdownEditor extends React.Component {
fetchUrlTitle={config.editor.fetchUrlTitle} fetchUrlTitle={config.editor.fetchUrlTitle}
enableTableEditor={config.editor.enableTableEditor} enableTableEditor={config.editor.enableTableEditor}
linesHighlighted={linesHighlighted} linesHighlighted={linesHighlighted}
onChange={e => this.handleChange(e)} onChange={(e) => this.handleChange(e)}
onBlur={e => this.handleBlur(e)} onBlur={(e) => this.handleBlur(e)}
spellCheck={config.editor.spellcheck} spellCheck={config.editor.spellcheck}
enableSmartPaste={config.editor.enableSmartPaste} enableSmartPaste={config.editor.enableSmartPaste}
hotkey={config.hotkey} hotkey={config.hotkey}
@@ -383,12 +325,10 @@ class MarkdownEditor extends React.Component {
customMarkdownLintConfig={config.editor.customMarkdownLintConfig} customMarkdownLintConfig={config.editor.customMarkdownLintConfig}
prettierConfig={config.editor.prettierConfig} prettierConfig={config.editor.prettierConfig}
deleteUnusedAttachments={config.editor.deleteUnusedAttachments} deleteUnusedAttachments={config.editor.deleteUnusedAttachments}
RTL={RTL}
/> />
<MarkdownPreview <MarkdownPreview styleName={this.state.status === 'PREVIEW'
ref={this.previewRef} ? 'preview'
styleName={ : 'preview--hide'
this.state.status === 'PREVIEW' ? 'preview' : 'preview--hide'
} }
style={previewStyle} style={previewStyle}
theme={config.ui.theme} theme={config.ui.theme}
@@ -405,21 +345,21 @@ class MarkdownEditor extends React.Component {
breaks={config.preview.breaks} breaks={config.preview.breaks}
sanitize={config.preview.sanitize} sanitize={config.preview.sanitize}
mermaidHTMLLabel={config.preview.mermaidHTMLLabel} mermaidHTMLLabel={config.preview.mermaidHTMLLabel}
onContextMenu={e => this.handleContextMenu(e)} ref='preview'
onDoubleClick={e => this.handleDoubleClick(e)} onContextMenu={(e) => this.handleContextMenu(e)}
onDoubleClick={(e) => this.handleDoubleClick(e)}
tabIndex='0' tabIndex='0'
value={this.state.renderValue} value={this.state.renderValue}
onMouseUp={e => this.handlePreviewMouseUp(e)} onMouseUp={(e) => this.handlePreviewMouseUp(e)}
onMouseDown={e => this.handlePreviewMouseDown(e)} onMouseDown={(e) => this.handlePreviewMouseDown(e)}
onCheckboxClick={e => this.handleCheckboxClick(e)} onCheckboxClick={(e) => this.handleCheckboxClick(e)}
showCopyNotification={config.ui.showCopyNotification} showCopyNotification={config.ui.showCopyNotification}
storagePath={storage.path} storagePath={storage.path}
noteKey={noteKey} noteKey={noteKey}
customCSS={config.preview.customCSS} customCSS={config.preview.customCSS}
allowCustomCSS={config.preview.allowCustomCSS} allowCustomCSS={config.preview.allowCustomCSS}
lineThroughCheckbox={config.preview.lineThroughCheckbox} lineThroughCheckbox={config.preview.lineThroughCheckbox}
onDrop={e => this.handleDropImage(e)} onDrop={(e) => this.handleDropImage(e)}
RTL={RTL}
/> />
</div> </div>
) )

View File

@@ -1,6 +1,5 @@
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import React from 'react' import React from 'react'
import { connect } from 'react-redux'
import Markdown from 'browser/lib/markdown' import Markdown from 'browser/lib/markdown'
import _ from 'lodash' import _ from 'lodash'
import CodeMirror from 'codemirror' import CodeMirror from 'codemirror'
@@ -12,7 +11,6 @@ import mermaidRender from './render/MermaidRender'
import SequenceDiagram from '@rokt33r/js-sequence-diagrams' import SequenceDiagram from '@rokt33r/js-sequence-diagrams'
import Chart from 'chart.js' import Chart from 'chart.js'
import eventEmitter from 'browser/main/lib/eventEmitter' import eventEmitter from 'browser/main/lib/eventEmitter'
import config from 'browser/main/lib/ConfigManager'
import htmlTextHelper from 'browser/lib/htmlTextHelper' import htmlTextHelper from 'browser/lib/htmlTextHelper'
import convertModeName from 'browser/lib/convertModeName' import convertModeName from 'browser/lib/convertModeName'
import copy from 'copy-to-clipboard' import copy from 'copy-to-clipboard'
@@ -22,15 +20,11 @@ import { escapeHtmlCharacters } from 'browser/lib/utils'
import yaml from 'js-yaml' import yaml from 'js-yaml'
import { render } from 'react-dom' import { render } from 'react-dom'
import Carousel from 'react-image-carousel' import Carousel from 'react-image-carousel'
import { push } from 'connected-react-router'
import ConfigManager from '../main/lib/ConfigManager' 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 { remote, shell } = require('electron')
const attachmentManagement = require('../main/lib/dataApi/attachmentManagement') const attachmentManagement = require('../main/lib/dataApi/attachmentManagement')
const buildMarkdownPreviewContextMenu = require('browser/lib/contextMenuBuilder') const buildMarkdownPreviewContextMenu = require('browser/lib/contextMenuBuilder').buildMarkdownPreviewContextMenu
.buildMarkdownPreviewContextMenu
const { app } = remote const { app } = remote
const path = require('path') const path = require('path')
@@ -56,6 +50,7 @@ const CSS_FILES = [
* @param {String} opts.theme * @param {String} opts.theme
* @param {Boolean} [opts.lineNumber] Should show line number * @param {Boolean} [opts.lineNumber] Should show line number
* @param {Boolean} [opts.scrollPastEnd] * @param {Boolean} [opts.scrollPastEnd]
* @param {Boolean} [opts.optimizeOverflowScroll] Should tweak body style to optimize overflow scrollbar display
* @param {Boolean} [opts.allowCustomCSS] Should add custom css * @param {Boolean} [opts.allowCustomCSS] Should add custom css
* @param {String} [opts.customCSS] Will be added to bottom, only if `opts.allowCustomCSS` is truthy * @param {String} [opts.customCSS] Will be added to bottom, only if `opts.allowCustomCSS` is truthy
* @returns {String} * @returns {String}
@@ -67,10 +62,10 @@ function buildStyle(opts) {
codeBlockFontFamily, codeBlockFontFamily,
lineNumber, lineNumber,
scrollPastEnd, scrollPastEnd,
optimizeOverflowScroll,
theme, theme,
allowCustomCSS, allowCustomCSS,
customCSS, customCSS
RTL
} = opts } = opts
return ` return `
@font-face { @font-face {
@@ -107,17 +102,8 @@ ${markdownStyle}
body { body {
font-family: '${fontFamily.join("','")}'; font-family: '${fontFamily.join("','")}';
font-size: ${fontSize}px; font-size: ${fontSize}px;
${scrollPastEnd ? 'padding-bottom: 90vh;' : ''}
${ ${optimizeOverflowScroll ? 'height: 100%;' : ''}
scrollPastEnd
? `
padding-bottom: 90vh;
box-sizing: border-box;
`
: ''
}
${RTL ? 'direction: rtl;' : ''}
${RTL ? 'text-align: right;' : ''}
} }
@media print { @media print {
body { body {
@@ -127,84 +113,7 @@ body {
code { code {
font-family: '${codeBlockFontFamily.join("','")}'; font-family: '${codeBlockFontFamily.join("','")}';
background-color: rgba(0,0,0,0.04); background-color: rgba(0,0,0,0.04);
text-align: left;
direction: ltr;
} }
p code,
li code,
td code
{
padding: 2px;
border-width: 1px;
border-style: solid;
border-radius: 5px;
}
[data-theme="default"] p code,
[data-theme="default"] li code,
[data-theme="default"] td code
{
background-color: #F4F4F4;
border-color: #d9d9d9;
color: inherit;
}
[data-theme="white"] p code,
[data-theme="white"] li code,
[data-theme="white"] td code
{
background-color: #F4F4F4;
border-color: #d9d9d9;
color: inherit;
}
[data-theme="dark"] p code,
[data-theme="dark"] li code,
[data-theme="dark"] td code
{
background-color: #444444;
border-color: #555;
color: #FFFFFF;
}
[data-theme="dracula"] p code,
[data-theme="dracula"] li code,
[data-theme="dracula"] td code
{
background-color: #444444;
border-color: #555;
color: #FFFFFF;
}
[data-theme="monokai"] p code,
[data-theme="monokai"] li code,
[data-theme="monokai"] td code
{
background-color: #444444;
border-color: #555;
color: #FFFFFF;
}
[data-theme="nord"] p code,
[data-theme="nord"] li code,
[data-theme="nord"] td code
{
background-color: #444444;
border-color: #555;
color: #FFFFFF;
}
[data-theme="solarized-dark"] p code,
[data-theme="solarized-dark"] li code,
[data-theme="solarized-dark"] td code
{
background-color: #444444;
border-color: #555;
color: #FFFFFF;
}
[data-theme="vulcan"] p code,
[data-theme="vulcan"] li code,
[data-theme="vulcan"] td code
{
background-color: #444444;
border-color: #555;
color: #FFFFFF;
}
.lineNumber { .lineNumber {
${lineNumber && 'display: block !important;'} ${lineNumber && 'display: block !important;'}
font-family: '${codeBlockFontFamily.join("','")}'; font-family: '${codeBlockFontFamily.join("','")}';
@@ -234,22 +143,14 @@ h1, h2 {
border: none; border: none;
} }
h3 {
margin: 1em 0 0.8em;
}
h4, h5, h6 {
margin: 1.1em 0 0.5em;
}
h1 { h1 {
padding: 0.2em 0 0.2em; padding-bottom: 4px;
margin: 1em 0 8px; margin: 1em 0 8px;
} }
h2 { h2 {
padding: 0.2em 0 0.2em; padding-bottom: 0.2em;
margin: 1em 0 0.7em; margin: 1em 0 0.37em;
} }
body p { body p {
@@ -272,33 +173,21 @@ ${allowCustomCSS ? customCSS : ''}
const scrollBarStyle = ` const scrollBarStyle = `
::-webkit-scrollbar { ::-webkit-scrollbar {
${config.get().ui.showScrollBar ? '' : 'display: none;'}
width: 12px; width: 12px;
} }
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
${config.get().ui.showScrollBar ? '' : 'display: none;'}
background-color: rgba(0, 0, 0, 0.15); background-color: rgba(0, 0, 0, 0.15);
} }
::-webkit-scrollbar-track-piece {
background-color: inherit;
}
` `
const scrollBarDarkStyle = ` const scrollBarDarkStyle = `
::-webkit-scrollbar { ::-webkit-scrollbar {
${config.get().ui.showScrollBar ? '' : 'display: none;'}
width: 12px; width: 12px;
} }
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
${config.get().ui.showScrollBar ? '' : 'display: none;'}
background-color: rgba(0, 0, 0, 0.3); background-color: rgba(0, 0, 0, 0.3);
} }
::-webkit-scrollbar-track-piece {
background-color: inherit;
}
` `
const OSX = global.process.platform === 'darwin' const OSX = global.process.platform === 'darwin'
@@ -329,7 +218,7 @@ function getSourceLineNumberByElement(element) {
return parent.dataset.line !== undefined ? parseInt(parent.dataset.line) : -1 return parent.dataset.line !== undefined ? parseInt(parent.dataset.line) : -1
} }
class MarkdownPreview extends React.Component { export default class MarkdownPreview extends React.Component {
constructor (props) { constructor (props) {
super(props) super(props)
@@ -347,7 +236,6 @@ class MarkdownPreview extends React.Component {
this.saveAsHtmlHandler = () => this.handleSaveAsHtml() this.saveAsHtmlHandler = () => this.handleSaveAsHtml()
this.saveAsPdfHandler = () => this.handleSaveAsPdf() this.saveAsPdfHandler = () => this.handleSaveAsPdf()
this.printHandler = () => this.handlePrint() this.printHandler = () => this.handlePrint()
this.resizeHandler = _.throttle(this.handleResize.bind(this), 100)
this.linkClickHandler = this.handleLinkClick.bind(this) this.linkClickHandler = this.handleLinkClick.bind(this)
this.initMarkdown = this.initMarkdown.bind(this) this.initMarkdown = this.initMarkdown.bind(this)
@@ -393,11 +281,7 @@ class MarkdownPreview extends React.Component {
const targetTag = clickElement.tagName // The direct parent HTML of where was clicked ie "BODY" or "DIV" 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. const lineNumber = getSourceLineNumberByElement(clickElement) // Line location of element clicked.
if ( if (config.editor.switchPreview === 'RIGHTCLICK' && e.buttons === 2 && config.editor.type === 'SPLIT') {
config.editor.switchPreview === 'RIGHTCLICK' &&
e.buttons === 2 &&
config.editor.type === 'SPLIT'
) {
eventEmitter.emit('topbar:togglemodebutton', 'CODE') eventEmitter.emit('topbar:togglemodebutton', 'CODE')
} }
if (e.ctrlKey) { if (e.ctrlKey) {
@@ -413,8 +297,7 @@ class MarkdownPreview extends React.Component {
} }
} }
if (this.props.onMouseDown != null && targetTag === 'BODY') if (this.props.onMouseDown != null && targetTag === 'BODY') this.props.onMouseDown(e)
this.props.onMouseDown(e)
} }
handleMouseUp (e) { handleMouseUp (e) {
@@ -443,8 +326,7 @@ class MarkdownPreview extends React.Component {
scrollPastEnd, scrollPastEnd,
theme, theme,
allowCustomCSS, allowCustomCSS,
customCSS, customCSS
RTL
} = this.getStyleParams() } = this.getStyleParams()
const inlineStyles = buildStyle({ const inlineStyles = buildStyle({
@@ -455,11 +337,13 @@ class MarkdownPreview extends React.Component {
scrollPastEnd, scrollPastEnd,
theme, theme,
allowCustomCSS, allowCustomCSS,
customCSS, customCSS
RTL
}) })
let body = this.refs.root.contentWindow.document.body.innerHTML let body = this.markdown.render(noteContent)
body = attachmentManagement.fixLocalURLS(body, this.props.storagePath) body = attachmentManagement.fixLocalURLS(
body,
this.props.storagePath
)
const files = [this.getCodeThemeLink(codeBlockTheme), ...CSS_FILES] const files = [this.getCodeThemeLink(codeBlockTheme), ...CSS_FILES]
files.forEach(file => { files.forEach(file => {
if (global.process.platform === 'win32') { if (global.process.platform === 'win32') {
@@ -475,7 +359,7 @@ class MarkdownPreview extends React.Component {
let styles = '' let styles = ''
files.forEach(file => { files.forEach(file => {
styles += `<link rel="stylesheet" href="../css/${path.basename(file)}">` styles += `<link rel="stylesheet" href="css/${path.basename(file)}">`
}) })
return `<html> return `<html>
@@ -491,23 +375,13 @@ class MarkdownPreview extends React.Component {
} }
handleSaveAsHtml () { handleSaveAsHtml () {
this.exportAsDocument('html', (noteContent, exportTasks, targetDir) => this.exportAsDocument('html', (noteContent, exportTasks, targetDir) => Promise.resolve(this.htmlContentFormatter(noteContent, exportTasks, targetDir)))
Promise.resolve(
this.htmlContentFormatter(noteContent, exportTasks, targetDir)
)
)
} }
handleSaveAsPdf () { handleSaveAsPdf () {
this.exportAsDocument('pdf', (noteContent, exportTasks, targetDir) => { this.exportAsDocument('pdf', (noteContent, exportTasks, targetDir) => {
const printout = new remote.BrowserWindow({ const printout = new remote.BrowserWindow({show: false, webPreferences: {webSecurity: false, javascript: false}})
show: false, printout.loadURL('data:text/html;charset=UTF-8,' + this.htmlContentFormatter(noteContent, exportTasks, targetDir))
webPreferences: { webSecurity: false, javascript: false }
})
printout.loadURL(
'data:text/html;charset=UTF-8,' +
this.htmlContentFormatter(noteContent, exportTasks, targetDir)
)
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
printout.webContents.on('did-finish-load', () => { printout.webContents.on('did-finish-load', () => {
printout.webContents.printToPDF({}, (err, data) => { printout.webContents.printToPDF({}, (err, data) => {
@@ -540,8 +414,7 @@ class MarkdownPreview extends React.Component {
.then(res => { .then(res => {
dialog.showMessageBox(remote.getCurrentWindow(), { dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'info', type: 'info',
message: `Exported to ${filename}`, message: `Exported to ${filename}`
buttons: [i18n.__('Ok')]
}) })
}) })
.catch(err => { .catch(err => {
@@ -574,16 +447,15 @@ class MarkdownPreview extends React.Component {
*/ */
escapeHtmlCharactersInCodeTag (splitWithCodeTag) { escapeHtmlCharactersInCodeTag (splitWithCodeTag) {
for (let index = 0; index < splitWithCodeTag.length; index++) { for (let index = 0; index < splitWithCodeTag.length; index++) {
const codeTagRequired = const codeTagRequired = (splitWithCodeTag[index] !== '\`\`\`' && index < splitWithCodeTag.length - 1)
splitWithCodeTag[index] !== '```' && index < splitWithCodeTag.length - 1
if (codeTagRequired) { if (codeTagRequired) {
splitWithCodeTag.splice(index + 1, 0, '```') splitWithCodeTag.splice((index + 1), 0, '\`\`\`')
} }
} }
let inCodeTag = false let inCodeTag = false
let result = '' let result = ''
for (let content of splitWithCodeTag) { for (let content of splitWithCodeTag) {
if (content === '```') { if (content === '\`\`\`') {
inCodeTag = !inCodeTag inCodeTag = !inCodeTag
} else if (inCodeTag) { } else if (inCodeTag) {
content = escapeHtmlCharacters(content) content = escapeHtmlCharacters(content)
@@ -596,9 +468,15 @@ class MarkdownPreview extends React.Component {
getScrollBarStyle () { getScrollBarStyle () {
const { theme } = this.props const { theme } = this.props
return uiThemes.some(item => item.name === theme && item.isDark) switch (theme) {
? scrollBarDarkStyle case 'dark':
: scrollBarStyle case 'solarized-dark':
case 'monokai':
case 'dracula':
return scrollBarDarkStyle
default:
return scrollBarStyle
}
} }
componentDidMount () { componentDidMount () {
@@ -651,7 +529,6 @@ class MarkdownPreview extends React.Component {
'scroll', 'scroll',
this.scrollHandler this.scrollHandler
) )
this.refs.root.contentWindow.addEventListener('resize', this.resizeHandler)
eventEmitter.on('export:save-text', this.saveAsTextHandler) eventEmitter.on('export:save-text', this.saveAsTextHandler)
eventEmitter.on('export:save-md', this.saveAsMdHandler) eventEmitter.on('export:save-md', this.saveAsMdHandler)
eventEmitter.on('export:save-html', this.saveAsHtmlHandler) eventEmitter.on('export:save-html', this.saveAsHtmlHandler)
@@ -690,10 +567,6 @@ class MarkdownPreview extends React.Component {
'scroll', 'scroll',
this.scrollHandler this.scrollHandler
) )
this.refs.root.contentWindow.removeEventListener(
'resize',
this.resizeHandler
)
eventEmitter.off('export:save-text', this.saveAsTextHandler) eventEmitter.off('export:save-text', this.saveAsTextHandler)
eventEmitter.off('export:save-md', this.saveAsMdHandler) eventEmitter.off('export:save-md', this.saveAsMdHandler)
eventEmitter.off('export:save-html', this.saveAsHtmlHandler) eventEmitter.off('export:save-html', this.saveAsHtmlHandler)
@@ -726,8 +599,7 @@ class MarkdownPreview extends React.Component {
prevProps.theme !== this.props.theme || prevProps.theme !== this.props.theme ||
prevProps.scrollPastEnd !== this.props.scrollPastEnd || prevProps.scrollPastEnd !== this.props.scrollPastEnd ||
prevProps.allowCustomCSS !== this.props.allowCustomCSS || prevProps.allowCustomCSS !== this.props.allowCustomCSS ||
prevProps.customCSS !== this.props.customCSS || prevProps.customCSS !== this.props.customCSS
prevProps.RTL !== this.props.RTL
) { ) {
this.applyStyle() this.applyStyle()
needsRewriteIframe = true needsRewriteIframe = true
@@ -739,7 +611,7 @@ class MarkdownPreview extends React.Component {
// Should scroll to top after selecting another note // Should scroll to top after selecting another note
if (prevProps.noteKey !== this.props.noteKey) { if (prevProps.noteKey !== this.props.noteKey) {
this.scrollTo(0, 0) this.getWindow().scrollTo(0, 0)
} }
} }
@@ -751,19 +623,17 @@ class MarkdownPreview extends React.Component {
scrollPastEnd, scrollPastEnd,
theme, theme,
allowCustomCSS, allowCustomCSS,
customCSS, customCSS
RTL
} = this.props } = this.props
let { fontFamily, codeBlockFontFamily } = this.props let { fontFamily, codeBlockFontFamily } = this.props
fontFamily = fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
_.isString(fontFamily) && fontFamily.trim().length > 0
? fontFamily ? fontFamily
.split(',') .split(',')
.map(fontName => fontName.trim()) .map(fontName => fontName.trim())
.concat(defaultFontFamily) .concat(defaultFontFamily)
: defaultFontFamily : defaultFontFamily
codeBlockFontFamily = codeBlockFontFamily = _.isString(codeBlockFontFamily) &&
_.isString(codeBlockFontFamily) && codeBlockFontFamily.trim().length > 0 codeBlockFontFamily.trim().length > 0
? codeBlockFontFamily ? codeBlockFontFamily
.split(',') .split(',')
.map(fontName => fontName.trim()) .map(fontName => fontName.trim())
@@ -779,8 +649,7 @@ class MarkdownPreview extends React.Component {
scrollPastEnd, scrollPastEnd,
theme, theme,
allowCustomCSS, allowCustomCSS,
customCSS, customCSS
RTL
} }
} }
@@ -794,8 +663,7 @@ class MarkdownPreview extends React.Component {
scrollPastEnd, scrollPastEnd,
theme, theme,
allowCustomCSS, allowCustomCSS,
customCSS, customCSS
RTL
} = this.getStyleParams() } = this.getStyleParams()
this.getWindow().document.getElementById( this.getWindow().document.getElementById(
@@ -807,11 +675,12 @@ class MarkdownPreview extends React.Component {
codeBlockFontFamily, codeBlockFontFamily,
lineNumber, lineNumber,
scrollPastEnd, scrollPastEnd,
optimizeOverflowScroll: true,
theme, theme,
allowCustomCSS, allowCustomCSS,
customCSS, customCSS
RTL
}) })
this.getWindow().document.documentElement.style.overflowY = 'hidden'
} }
getCodeThemeLink (name) { getCodeThemeLink (name) {
@@ -880,9 +749,7 @@ class MarkdownPreview extends React.Component {
codeBlockTheme = consts.THEMES.find(theme => theme.name === codeBlockTheme) codeBlockTheme = consts.THEMES.find(theme => theme.name === codeBlockTheme)
const codeBlockThemeClassName = codeBlockTheme const codeBlockThemeClassName = codeBlockTheme ? codeBlockTheme.className : 'cm-s-default'
? codeBlockTheme.className
: 'cm-s-default'
_.forEach( _.forEach(
this.refs.root.contentWindow.document.querySelectorAll('.code code'), this.refs.root.contentWindow.document.querySelectorAll('.code code'),
@@ -968,10 +835,7 @@ class MarkdownPreview extends React.Component {
el => { el => {
try { try {
const format = el.attributes.getNamedItem('data-format').value const format = el.attributes.getNamedItem('data-format').value
const chartConfig = const chartConfig = format === 'yaml' ? yaml.load(el.innerHTML) : JSON.parse(el.innerHTML)
format === 'yaml'
? yaml.load(el.innerHTML)
: JSON.parse(el.innerHTML)
el.innerHTML = '' el.innerHTML = ''
const canvas = document.createElement('canvas') const canvas = document.createElement('canvas')
@@ -994,12 +858,7 @@ class MarkdownPreview extends React.Component {
_.forEach( _.forEach(
this.refs.root.contentWindow.document.querySelectorAll('.mermaid'), this.refs.root.contentWindow.document.querySelectorAll('.mermaid'),
el => { el => {
mermaidRender( mermaidRender(el, htmlTextHelper.decodeEntities(el.innerHTML), theme, mermaidHTMLLabel)
el,
htmlTextHelper.decodeEntities(el.innerHTML),
theme,
mermaidHTMLLabel
)
} }
) )
@@ -1021,14 +880,20 @@ class MarkdownPreview extends React.Component {
autoplay = 0 autoplay = 0
} }
render(<Carousel images={images} autoplay={autoplay} />, el) render(
<Carousel
images={images}
autoplay={autoplay}
/>,
el
)
} }
) )
const markdownPreviewIframe = document.querySelector('.MarkdownPreview') const markdownPreviewIframe = document.querySelector('.MarkdownPreview')
const rect = markdownPreviewIframe.getBoundingClientRect() const rect = markdownPreviewIframe.getBoundingClientRect()
const config = { attributes: true, subtree: true } const config = { attributes: true, subtree: true }
const imgObserver = new MutationObserver(mutationList => { const imgObserver = new MutationObserver((mutationList) => {
for (const mu of mutationList) { for (const mu of mutationList) {
if (mu.target.className === 'carouselContent-enter-done') { if (mu.target.className === 'carouselContent-enter-done') {
this.setImgOnClickEventHelper(mu.target, rect) this.setImgOnClickEventHelper(mu.target, rect)
@@ -1037,18 +902,14 @@ class MarkdownPreview extends React.Component {
} }
}) })
const imgList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll( const imgList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll('img')
'img'
)
for (const img of imgList) { for (const img of imgList) {
const parentEl = img.parentElement const parentEl = img.parentElement
this.setImgOnClickEventHelper(img, rect) this.setImgOnClickEventHelper(img, rect)
imgObserver.observe(parentEl, config) imgObserver.observe(parentEl, config)
} }
const aList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll( const aList = markdownPreviewIframe.contentWindow.document.body.querySelectorAll('a')
'a'
)
for (const a of aList) { for (const a of aList) {
a.removeEventListener('click', this.linkClickHandler) a.removeEventListener('click', this.linkClickHandler)
a.addEventListener('click', this.linkClickHandler) a.addEventListener('click', this.linkClickHandler)
@@ -1060,9 +921,7 @@ class MarkdownPreview extends React.Component {
const widthMagnification = document.body.clientWidth / img.width const widthMagnification = document.body.clientWidth / img.width
const heightMagnification = document.body.clientHeight / img.height const heightMagnification = document.body.clientHeight / img.height
const baseOnWidth = widthMagnification < heightMagnification const baseOnWidth = widthMagnification < heightMagnification
const magnification = baseOnWidth const magnification = baseOnWidth ? widthMagnification : heightMagnification
? widthMagnification
: heightMagnification
const zoomImgWidth = img.width * magnification const zoomImgWidth = img.width * magnification
const zoomImgHeight = img.height * magnification const zoomImgHeight = img.height * magnification
@@ -1093,7 +952,10 @@ class MarkdownPreview extends React.Component {
width: ${zoomImgWidth}; width: ${zoomImgWidth};
height: ${zoomImgHeight}px; height: ${zoomImgHeight}px;
` `
zoomImg.animate([originalImgRect, zoomInImgRect], animationSpeed) zoomImg.animate([
originalImgRect,
zoomInImgRect
], animationSpeed)
const overlay = document.createElement('div') const overlay = document.createElement('div')
overlay.style = ` overlay.style = `
@@ -1114,10 +976,10 @@ class MarkdownPreview extends React.Component {
width: ${img.width}px; width: ${img.width}px;
height: ${img.height}px; height: ${img.height}px;
` `
const zoomOutImgAnimation = zoomImg.animate( const zoomOutImgAnimation = zoomImg.animate([
[zoomInImgRect, originalImgRect], zoomInImgRect,
animationSpeed originalImgRect
) ], animationSpeed)
zoomOutImgAnimation.onfinish = () => overlay.remove() zoomOutImgAnimation.onfinish = () => overlay.remove()
} }
@@ -1126,15 +988,6 @@ 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 () { focus () {
this.refs.root.focus() this.refs.root.focus()
} }
@@ -1143,11 +996,7 @@ class MarkdownPreview extends React.Component {
return this.refs.root.contentWindow return this.refs.root.contentWindow
} }
/** scrollTo (targetRow) {
* @public
* @param {Number} targetRow
*/
scrollToRow(targetRow) {
const blocks = this.getWindow().document.querySelectorAll( const blocks = this.getWindow().document.querySelectorAll(
'body>[data-line]' 'body>[data-line]'
) )
@@ -1157,21 +1006,12 @@ class MarkdownPreview extends React.Component {
const row = parseInt(block.getAttribute('data-line')) const row = parseInt(block.getAttribute('data-line'))
if (row > targetRow || index === blocks.length - 1) { if (row > targetRow || index === blocks.length - 1) {
block = blocks[index - 1] block = blocks[index - 1]
block != null && this.scrollTo(0, block.offsetTop) block != null && this.getWindow().scrollTo(0, block.offsetTop)
break break
} }
} }
} }
/**
* `document.body.scrollTo`
* @param {Number} x
* @param {Number} y
*/
scrollTo(x, y) {
this.getWindow().document.body.scrollTo(x, y)
}
preventImageDroppedHandler (e) { preventImageDroppedHandler (e) {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
@@ -1193,7 +1033,6 @@ class MarkdownPreview extends React.Component {
e.stopPropagation() e.stopPropagation()
const rawHref = e.target.getAttribute('href') const rawHref = e.target.getAttribute('href')
const { dispatch } = this.props
if (!rawHref) return // not checked href because parser will create file://... string for [empty link]() if (!rawHref) return // not checked href because parser will create file://... string for [empty link]()
const parser = document.createElement('a') const parser = document.createElement('a')
@@ -1210,10 +1049,12 @@ class MarkdownPreview extends React.Component {
if (posOfHash > -1) { if (posOfHash > -1) {
const extractedId = linkHash.slice(posOfHash + 1) const extractedId = linkHash.slice(posOfHash + 1)
const targetId = mdurl.encode(extractedId) const targetId = mdurl.encode(extractedId)
const targetElement = this.getWindow().document.getElementById(targetId) const targetElement = this.refs.root.contentWindow.document.getElementById(
targetId
)
if (targetElement != null) { if (targetElement != null) {
this.scrollTo(0, targetElement.offsetTop) this.getWindow().scrollTo(0, targetElement.offsetTop)
} }
return return
} }
@@ -1247,26 +1088,8 @@ class MarkdownPreview extends React.Component {
return return
} }
const regexIsTagLink = /^:tag:([\w]+)$/
if (regexIsTagLink.test(rawHref)) {
const tag = rawHref.match(regexIsTagLink)[1]
dispatch(push(`/tags/${encodeURIComponent(tag)}`))
return
}
// other case // other case
this.openExternal(href) shell.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 () { render () {
@@ -1298,10 +1121,3 @@ MarkdownPreview.propTypes = {
smartArrows: PropTypes.bool, smartArrows: PropTypes.bool,
breaks: PropTypes.bool breaks: PropTypes.bool
} }
export default connect(
null,
null,
null,
{ forwardRef: true }
)(MarkdownPreview)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,9 +21,7 @@ const FolderIcon = ({ className, color, isActive }) => {
/** /**
* @param {boolean} isActive * @param {boolean} isActive
* @param {object} tooltipRef,
* @param {Function} handleButtonClick * @param {Function} handleButtonClick
* @param {Function} handleMouseEnter
* @param {Function} handleContextMenu * @param {Function} handleContextMenu
* @param {string} folderName * @param {string} folderName
* @param {string} folderColor * @param {string} folderColor
@@ -37,9 +35,7 @@ const FolderIcon = ({ className, color, isActive }) => {
const StorageItem = ({ const StorageItem = ({
styles, styles,
isActive, isActive,
tooltipRef,
handleButtonClick, handleButtonClick,
handleMouseEnter,
handleContextMenu, handleContextMenu,
folderName, folderName,
folderColor, folderColor,
@@ -53,15 +49,13 @@ const StorageItem = ({
<button <button
styleName={isActive ? 'folderList-item--active' : 'folderList-item'} styleName={isActive ? 'folderList-item--active' : 'folderList-item'}
onClick={handleButtonClick} onClick={handleButtonClick}
onMouseEnter={handleMouseEnter}
onContextMenu={handleContextMenu} onContextMenu={handleContextMenu}
onDrop={handleDrop} onDrop={handleDrop}
onDragEnter={handleDragEnter} onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave} onDragLeave={handleDragLeave}
> >
{!isFolded && ( {!isFolded &&
<DraggableIcon className={styles['folderList-item-reorder']} /> <DraggableIcon className={styles['folderList-item-reorder']} />}
)}
<span <span
styleName={ styleName={
isFolded ? 'folderList-item-name--folded' : 'folderList-item-name' isFolded ? 'folderList-item-name--folded' : 'folderList-item-name'
@@ -76,23 +70,18 @@ const StorageItem = ({
? _.truncate(folderName, { length: 1, omission: '' }) ? _.truncate(folderName, { length: 1, omission: '' })
: folderName} : folderName}
</span> </span>
{!isFolded && _.isNumber(noteCount) && ( {!isFolded &&
<span styleName='folderList-item-noteCount'>{noteCount}</span> _.isNumber(noteCount) &&
)} <span styleName='folderList-item-noteCount'>{noteCount}</span>}
{isFolded && ( {isFolded &&
<span styleName='folderList-item-tooltip' ref={tooltipRef}> <span styleName='folderList-item-tooltip'>{folderName}</span>}
{folderName}
</span>
)}
</button> </button>
) )
} }
StorageItem.propTypes = { StorageItem.propTypes = {
isActive: PropTypes.bool.isRequired, isActive: PropTypes.bool.isRequired,
tooltipRef: PropTypes.object,
handleButtonClick: PropTypes.func, handleButtonClick: PropTypes.func,
handleMouseEnter: PropTypes.func,
handleContextMenu: PropTypes.func, handleContextMenu: PropTypes.func,
folderName: PropTypes.string.isRequired, folderName: PropTypes.string.isRequired,
folderColor: PropTypes.string, folderColor: PropTypes.string,

View File

@@ -60,7 +60,6 @@
border-bottom-right-radius 2px border-bottom-right-radius 2px
height 34px height 34px
line-height 32px line-height 32px
transition-property opacity
.folderList-item:hover, .folderList-item--active:hover .folderList-item:hover, .folderList-item--active:hover
.folderList-item-tooltip .folderList-item-tooltip
@@ -121,28 +120,59 @@ body[data-theme="dark"]
color $ui-dark-text-color color $ui-dark-text-color
background-color alpha($ui-dark-button--active-backgroundColor, 50%) background-color alpha($ui-dark-button--active-backgroundColor, 50%)
apply-theme(theme) body[data-theme="solarized-dark"]
body[data-theme={theme}]
.folderList-item .folderList-item
&:hover &:hover
background-color get-theme-var(theme, 'button-backgroundColor') background-color $ui-solarized-dark-button-backgroundColor
color get-theme-var(theme, 'text-color') color $ui-solarized-dark-text-color
&:active &:active
color get-theme-var(theme, 'text-color') color $ui-solarized-dark-text-color
background-color get-theme-var(theme, 'button-backgroundColor') background-color $ui-solarized-dark-button-backgroundColor
.folderList-item--active .folderList-item--active
@extend .folderList-item @extend .folderList-item
color get-theme-var(theme, 'text-color') color $ui-solarized-dark-text-color
background-color get-theme-var(theme, 'button-backgroundColor') background-color $ui-solarized-dark-button-backgroundColor
&:active &:active
background-color get-theme-var(theme, 'button-backgroundColor') background-color $ui-solarized-dark-button-backgroundColor
&:hover &:hover
color get-theme-var(theme, 'text-color') color $ui-solarized-dark-text-color
background-color get-theme-var(theme, 'button-backgroundColor') background-color $ui-solarized-dark-button-backgroundColor
for theme in 'solarized-dark' 'dracula' body[data-theme="monokai"]
apply-theme(theme) .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 $themes .folderList-item--active
apply-theme(theme) @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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -124,34 +124,40 @@ hr
border-bottom solid 1px borderColor border-bottom solid 1px borderColor
margin 15px 0 margin 15px 0
h1, h2, h3, h4, h5, h6 h1, h2, h3, h4, h5, h6
margin 1em 0 1.5em
line-height 1.4
font-weight bold font-weight bold
word-wrap break-word word-wrap break-word
padding .2em 0 .2em
h1 h1
font-size 2.55em font-size 2.55em
line-height 1.2 padding-bottom 0.3em
line-height 1.2em
border-bottom solid 1px borderColor border-bottom solid 1px borderColor
margin 1em 0 0.44em
&:first-child &:first-child
margin-top 0 margin-top 0
h2 h2
font-size 1.75em font-size 1.75em
line-height 1.225 padding-bottom 0.3em
line-height 1.225em
border-bottom solid 1px borderColor border-bottom solid 1px borderColor
margin 1em 0 0.57em
&:first-child &:first-child
margin-top 0 margin-top 0
h3 h3
font-size 1.5em font-size 1.5em
line-height 1.43 line-height 1.43em
margin 1em 0 0.66em
h4 h4
font-size 1.25em font-size 1.25em
line-height 1.4 line-height 1.4em
margin 1em 0 0.8em
h5 h5
font-size 1em font-size 1em
line-height 1.1 line-height 1.4em
margin 1em 0 1em
h6 h6
font-size 1em font-size 1em
line-height 1.4em
margin 1em 0 1em
color #777 color #777
p p
line-height 1.6em line-height 1.6em
@@ -357,10 +363,7 @@ admonition_types = {
danger: {color: #c2185b, icon: "block"}, danger: {color: #c2185b, icon: "block"},
caution: {color: #ffa726, icon: "warning"}, caution: {color: #ffa726, icon: "warning"},
error: {color: #d32f2f, icon: "error_outline"}, error: {color: #d32f2f, icon: "error_outline"},
question: {color: #64dd17, icon: "help_outline"}, attention: {color: #455a64, icon: "priority_high"}
quote: {color: #9e9e9e, icon: "format_quote"},
abstract: {color: #00b0ff, icon: "subject"},
attention: {color: #455a64, icon: "priority_high"},
} }
for name, val in admonition_types for name, val in admonition_types
@@ -421,9 +424,6 @@ pre.fence
canvas, svg canvas, svg
max-width 100% !important max-width 100% !important
svg[ratio]
width 100%
.gallery .gallery
width 100% width 100%
height 50vh height 50vh
@@ -444,44 +444,6 @@ pre.fence
color $ui-text-color color $ui-text-color
background-color $ui-tag-backgroundColor 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%) themeDarkBackground = darken(#21252B, 10%)
themeDarkText = #f9f9f9 themeDarkText = #f9f9f9
themeDarkBorder = lighten(themeDarkBackground, 20%) themeDarkBorder = lighten(themeDarkBackground, 20%)
@@ -549,63 +511,137 @@ body[data-theme="dark"]
color $ui-dark-text-color color $ui-dark-text-color
background-color $ui-dark-tag-backgroundColor background-color $ui-dark-tag-backgroundColor
.markdownIt-TOC-wrapper themeSolarizedDarkTableOdd = $ui-solarized-dark-noteDetail-backgroundColor
&, themeSolarizedDarkTableEven = darken($ui-solarized-dark-noteDetail-backgroundColor, 10%)
&:before themeSolarizedDarkTableHead = themeSolarizedDarkTableEven
background-color darken(themeDarkBackground, 5%) themeSolarizedDarkTableBorder = themeDarkBorder
color themeDarkText
apply-theme(theme) body[data-theme="solarized-dark"]
body[data-theme={theme}] color $ui-solarized-dark-text-color
color get-theme-var(theme, 'text-color')
border-color themeDarkBorder border-color themeDarkBorder
background-color get-theme-var(theme, 'noteDetail-backgroundColor') background-color $ui-solarized-dark-noteDetail-backgroundColor
table table
thead thead
tr tr
background-color get-theme-var(theme, 'table-head-backgroundColor') background-color themeSolarizedDarkTableHead
th th
border-color get-theme-var(theme, 'table-borderColor') border-color themeSolarizedDarkTableBorder
&:last-child &:last-child
border-right solid 1px get-theme-var(theme, 'table-borderColor') border-right solid 1px themeSolarizedDarkTableBorder
tbody tbody
tr:nth-child(2n + 1) tr:nth-child(2n + 1)
background-color get-theme-var(theme, 'table-odd-backgroundColor') background-color themeSolarizedDarkTableOdd
tr:nth-child(2n) tr:nth-child(2n)
background-color get-theme-var(theme, 'table-even-backgroundColor') background-color themeSolarizedDarkTableEven
td td
border-color get-theme-var(theme, 'table-borderColor') border-color themeSolarizedDarkTableBorder
&:last-child &:last-child
border-right solid 1px get-theme-var(theme, 'table-borderColor') border-right solid 1px themeSolarizedDarkTableBorder
kbd
background-color get-theme-var(theme, 'kbd-backgroundColor')
color get-theme-var(theme, 'kbd-color')
dl dl
border-color themeDarkBorder border-color themeDarkBorder
background-color get-theme-var(theme, 'table-head-backgroundColor') background-color themeSolarizedDarkTableHead
dt dt
border-color themeDarkBorder border-color themeDarkBorder
dd dd
border-color themeDarkBorder border-color themeDarkBorder
background-color get-theme-var(theme, 'noteDetail-backgroundColor') background-color $ui-solarized-dark-noteDetail-backgroundColor
pre.fence pre.fence
.gallery .gallery
.carousel-main, .carousel-footer .carousel-main, .carousel-footer
background-color get-theme-var(theme, 'noteDetail-backgroundColor') background-color $ui-solarized-dark-noteDetail-backgroundColor
.prev, .next .prev, .next
color get-theme-var(theme, 'button--active-color') color $ui-solarized-dark-button--active-color
background-color get-theme-var(theme, 'button-backgroundColor') background-color $ui-solarized-dark-button-backgroundColor
.markdownIt-TOC-wrapper themeMonokaiTableOdd = $ui-monokai-noteDetail-backgroundColor
&, themeMonokaiTableEven = darken($ui-monokai-noteDetail-backgroundColor, 10%)
&:before themeMonokaiTableHead = themeMonokaiTableEven
background-color darken(get-theme-var(theme, 'noteDetail-backgroundColor'), 15%) themeMonokaiTableBorder = themeDarkBorder
color themeDarkText
for theme in 'solarized-dark' 'dracula' body[data-theme="monokai"]
apply-theme(theme) 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
kbd
background-color themeDarkBackground
for theme in $themes dl
apply-theme(theme) border-color themeDarkBorder
background-color themeMonokaiTableHead
dt
border-color themeDarkBorder
dd
border-color themeDarkBorder
background-color $ui-monokai-noteDetail-backgroundColor
pre.fence
.gallery
.carousel-main, .carousel-footer
background-color $ui-monokai-noteDetail-backgroundColor
.prev, .next
color $ui-monokai-button--active-color
background-color $ui-monokai-button-backgroundColor
themeDraculaTableOdd = $ui-dracula-noteDetail-backgroundColor
themeDraculaTableEven = darken($ui-dracula-noteDetail-backgroundColor, 10%)
themeDraculaTableHead = themeDraculaTableEven
themeDraculaTableBorder = themeDarkBorder
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
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

View File

@@ -1,5 +1,4 @@
import mermaidAPI from 'mermaid' import mermaidAPI from 'mermaid'
import uiThemes from 'browser/lib/ui-themes'
// fixes bad styling in the mermaid dark theme // fixes bad styling in the mermaid dark theme
const darkThemeStyling = ` const darkThemeStyling = `
@@ -23,46 +22,18 @@ function getId() {
function render (element, content, theme, enableHTMLLabel) { function render (element, content, theme, enableHTMLLabel) {
try { try {
const height = element.attributes.getNamedItem('data-height') const height = element.attributes.getNamedItem('data-height')
const isPredefined = height && height.value !== 'undefined' if (height && height.value !== 'undefined') {
if (isPredefined) {
element.style.height = height.value + 'vh' 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({ mermaidAPI.initialize({
theme: isDarkTheme ? 'dark' : 'default', theme: isDarkTheme ? 'dark' : 'default',
themeCSS: isDarkTheme ? darkThemeStyling : '', themeCSS: isDarkTheme ? darkThemeStyling : '',
flowchart: { useMaxWidth: false,
htmlLabels: enableHTMLLabel flowchart: { htmlLabels: enableHTMLLabel }
},
gantt: {
useWidth: element.clientWidth
}
}) })
mermaidAPI.render(getId(), content, (svgGraph) => {
mermaidAPI.render(getId(), content, svgGraph => {
element.innerHTML = 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) { } catch (e) {
element.className = 'mermaid-error' element.className = 'mermaid-error'

View File

View File

@@ -11,10 +11,6 @@ const languages = [
name: 'Chinese (zh-TW)', name: 'Chinese (zh-TW)',
locale: 'zh-TW' locale: 'zh-TW'
}, },
{
name: 'Czech',
locale: 'cs'
},
{ {
name: 'Danish', name: 'Danish',
locale: 'da' locale: 'da'

View File

@@ -11,7 +11,7 @@ export function parse(boostnotercPath = _boostnotercPath) {
return JSON.parse(sander.readFileSync(boostnotercPath).toString()) return JSON.parse(sander.readFileSync(boostnotercPath).toString())
} catch (e) { } catch (e) {
console.warn(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 {} return {}
} }
} }

View File

@@ -9,8 +9,7 @@ class SnippetManager {
id: crypto.randomBytes(16).toString('hex'), id: crypto.randomBytes(16).toString('hex'),
name: 'Dummy text', name: 'Dummy text',
prefix: ['lorem', 'ipsum'], prefix: ['lorem', 'ipsum'],
content: 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.'
'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 = [] this.snippets = []

View File

@@ -76,7 +76,11 @@ export default class TextEditorInterface {
) )
} }
} else { } 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

@@ -6,15 +6,14 @@ const { dialog } = remote
export function confirmDeleteNote (confirmDeletion, permanent) { export function confirmDeleteNote (confirmDeletion, permanent) {
if (confirmDeletion || permanent) { if (confirmDeletion || permanent) {
const alertConfig = { const alertConfig = {
type: 'warning', ype: 'warning',
message: i18n.__('Confirm note deletion'), message: i18n.__('Confirm note deletion'),
detail: i18n.__('This will permanently remove this note.'), detail: i18n.__('This will permanently remove this note.'),
buttons: [i18n.__('Confirm'), i18n.__('Cancel')] buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
} }
const dialogButtonIndex = dialog.showMessageBox( const dialogButtonIndex = dialog.showMessageBox(
remote.getCurrentWindow(), remote.getCurrentWindow(), alertConfig
alertConfig
) )
return dialogButtonIndex === 0 return dialogButtonIndex === 0

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@ export function getTodoStatus(content) {
let numberOfTodo = 0 let numberOfTodo = 0
let numberOfCompletedTodo = 0 let numberOfCompletedTodo = 0
splitted.forEach(line => { splitted.forEach((line) => {
const trimmedLine = line.trim().replace(/^(>\s*)*/, '') const trimmedLine = line.trim().replace(/^(>\s*)*/, '')
if (trimmedLine.match(/^[+\-*] \[(\s|x)] ./i)) { if (trimmedLine.match(/^[+\-*] \[(\s|x)] ./i)) {
numberOfTodo++ numberOfTodo++
@@ -21,5 +21,5 @@ export function getTodoStatus(content) {
export function getTodoPercentageOfCompleted (content) { export function getTodoPercentageOfCompleted (content) {
const state = getTodoStatus(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) { export function decodeEntities (text) {
var entities = [ var entities = [
['apos', "'"], ['apos', '\''],
['amp', '&'], ['amp', '&'],
['lt', '<'], ['lt', '<'],
['gt', '>'], ['gt', '>'],
@@ -26,14 +26,14 @@ export function decodeEntities(text) {
export function encodeEntities (text) { export function encodeEntities (text) {
const entities = [ const entities = [
["'", 'apos'], ['\'', 'apos'],
['<', 'lt'], ['<', 'lt'],
['>', 'gt'], ['>', 'gt'],
['\\?', '#63'], ['\\?', '#63'],
['\\$', '#36'] ['\\$', '#36']
] ]
entities.forEach(entity => { entities.forEach((entity) => {
text = text.replace(new RegExp(entity[0], 'g'), `&${entity[1]};`) text = text.replace(new RegExp(entity[0], 'g'), `&${entity[1]};`)
}) })
return text return text

View File

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

View File

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

View File

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

View File

@@ -2,18 +2,13 @@
module.exports = function frontMatterPlugin (md) { module.exports = function frontMatterPlugin (md) {
function frontmatter (state, startLine, endLine, silent) { function frontmatter (state, startLine, endLine, silent) {
if ( if (startLine !== 0 || state.src.substr(startLine, state.eMarks[0]) !== '---') {
startLine !== 0 ||
state.src.substr(startLine, state.eMarks[0]) !== '---'
) {
return false return false
} }
let line = 0 let line = 0
while (++line < state.lineMax) { while (++line < state.lineMax) {
if ( if (state.src.substring(state.bMarks[line], state.eMarks[line]) === '---') {
state.src.substring(state.bMarks[line], state.eMarks[line]) === '---'
) {
state.line = line + 1 state.line = line + 1
return true 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 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)?/gi const attributesRegex = /([A-Z][A-Z0-9]*)(?:=("|')([^\2]+?)\2)?/ig
function sanitizeInline (html, options) { function sanitizeInline (html, options) {
let match = tagRegex.exec(html) let match = tagRegex.exec(html)
@@ -46,12 +46,7 @@ function sanitizeInline(html, options) {
return '' return ''
} }
const { const { allowedTags, allowedAttributes, selfClosing, allowedSchemesAppliedToAttributes } = options
allowedTags,
allowedAttributes,
selfClosing,
allowedSchemesAppliedToAttributes
} = options
if (match[1] !== undefined) { if (match[1] !== undefined) {
// opening tag // opening tag
@@ -70,17 +65,9 @@ function sanitizeInline(html, options) {
name = match[1].toLowerCase() name = match[1].toLowerCase()
value = match[3] value = match[3]
if ( if (allowedAttributes['*'].indexOf(name) !== -1 || (allowedAttributes[tag] && allowedAttributes[tag].indexOf(name) !== -1)) {
allowedAttributes['*'].indexOf(name) !== -1 ||
(allowedAttributes[tag] && allowedAttributes[tag].indexOf(name) !== -1)
) {
if (allowedSchemesAppliedToAttributes.indexOf(name) !== -1) { if (allowedSchemesAppliedToAttributes.indexOf(name) !== -1) {
if ( if (naughtyHRef(value, options) || (tag === 'iframe' && name === 'src' && naughtyIFrame(value, options))) {
naughtyHRef(value, options) ||
(tag === 'iframe' &&
name === 'src' &&
naughtyIFrame(value, options))
) {
continue continue
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,3 @@
/* eslint-disable camelcase */
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import React from 'react' import React from 'react'
import CSSModules from 'browser/lib/CSSModules' import CSSModules from 'browser/lib/CSSModules'
@@ -32,7 +31,6 @@ import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
import markdownToc from 'browser/lib/markdown-toc-generator' import markdownToc from 'browser/lib/markdown-toc-generator'
import queryString from 'query-string' import queryString from 'query-string'
import { replace } from 'connected-react-router' import { replace } from 'connected-react-router'
import ToggleDirectionButton from 'browser/main/Detail/ToggleDirectionButton'
class MarkdownNoteDetail extends React.Component { class MarkdownNoteDetail extends React.Component {
constructor (props) { constructor (props) {
@@ -40,26 +38,21 @@ class MarkdownNoteDetail extends React.Component {
this.state = { this.state = {
isMovingNote: false, isMovingNote: false,
note: Object.assign( note: Object.assign({
{
title: '', title: '',
content: '', content: '',
linesHighlighted: [] linesHighlighted: []
}, }, props.note),
props.note
),
isLockButtonShown: props.config.editor.type !== 'SPLIT', isLockButtonShown: props.config.editor.type !== 'SPLIT',
isLocked: false, isLocked: false,
editorType: props.config.editor.type, editorType: props.config.editor.type,
switchPreview: props.config.editor.switchPreview, switchPreview: props.config.editor.switchPreview
RTL: false
} }
this.dispatchTimer = null this.dispatchTimer = null
this.toggleLockButton = this.handleToggleLockButton.bind(this) this.toggleLockButton = this.handleToggleLockButton.bind(this)
this.generateToc = this.handleGenerateToc.bind(this) this.generateToc = () => this.handleGenerateToc()
this.handleUpdateContent = this.handleUpdateContent.bind(this)
} }
focus () { focus () {
@@ -68,31 +61,25 @@ class MarkdownNoteDetail extends React.Component {
componentDidMount () { componentDidMount () {
ee.on('topbar:togglelockbutton', this.toggleLockButton) ee.on('topbar:togglelockbutton', this.toggleLockButton)
ee.on('topbar:toggledirectionbutton', () => this.handleSwitchDirection())
ee.on('topbar:togglemodebutton', () => { ee.on('topbar:togglemodebutton', () => {
const reversedType = const reversedType = this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
this.handleSwitchMode(reversedType) this.handleSwitchMode(reversedType)
}) })
ee.on('hotkey:deletenote', this.handleDeleteNote.bind(this)) ee.on('hotkey:deletenote', this.handleDeleteNote.bind(this))
ee.on('code:generate-toc', this.generateToc) ee.on('code:generate-toc', this.generateToc)
} }
UNSAFE_componentWillReceiveProps(nextProps) { componentWillReceiveProps (nextProps) {
const isNewNote = nextProps.note.key !== this.props.note.key const isNewNote = nextProps.note.key !== this.props.note.key
const hasDeletedTags = const hasDeletedTags = nextProps.note.tags.length < this.props.note.tags.length
nextProps.note.tags.length < this.props.note.tags.length
if (!this.state.isMovingNote && (isNewNote || hasDeletedTags)) { if (!this.state.isMovingNote && (isNewNote || hasDeletedTags)) {
if (this.saveQueue != null) this.saveNow() if (this.saveQueue != null) this.saveNow()
this.setState( this.setState({
{
note: Object.assign({linesHighlighted: []}, nextProps.note) note: Object.assign({linesHighlighted: []}, nextProps.note)
}, }, () => {
() => {
this.refs.content.reload() this.refs.content.reload()
if (this.refs.tags) this.refs.tags.reset() if (this.refs.tags) this.refs.tags.reset()
} })
)
} }
// Focus content if using blur or double click // Focus content if using blur or double click
@@ -112,7 +99,6 @@ class MarkdownNoteDetail extends React.Component {
componentWillUnmount () { componentWillUnmount () {
ee.off('topbar:togglelockbutton', this.toggleLockButton) ee.off('topbar:togglelockbutton', this.toggleLockButton)
ee.on('topbar:toggledirectionbutton', this.handleSwitchDirection)
ee.off('code:generate-toc', this.generateToc) ee.off('code:generate-toc', this.generateToc)
if (this.saveQueue != null) this.saveNow() if (this.saveQueue != null) this.saveNow()
} }
@@ -127,11 +113,7 @@ class MarkdownNoteDetail extends React.Component {
const { note } = this.state const { note } = this.state
note.content = this.refs.content.value note.content = this.refs.content.value
let title = findNoteTitle( let title = findNoteTitle(note.content, this.props.config.editor.enableFrontMatterTitle, this.props.config.editor.frontMatterTitleField)
note.content,
this.props.config.editor.enableFrontMatterTitle,
this.props.config.editor.frontMatterTitleField
)
title = striptags(title) title = striptags(title)
title = markdown.strip(title) title = markdown.strip(title)
note.title = title note.title = title
@@ -158,7 +140,9 @@ class MarkdownNoteDetail extends React.Component {
clearTimeout(this.saveQueue) clearTimeout(this.saveQueue)
this.saveQueue = null 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({ dispatch({
type: 'UPDATE_NOTE', type: 'UPDATE_NOTE',
note: note note: note
@@ -176,53 +160,46 @@ class MarkdownNoteDetail extends React.Component {
dataApi dataApi
.moveNote(note.storage, note.key, newStorageKey, newFolderKey) .moveNote(note.storage, note.key, newStorageKey, newFolderKey)
.then(newNote => { .then((newNote) => {
this.setState( this.setState({
{
isMovingNote: true, isMovingNote: true,
note: Object.assign({}, newNote) note: Object.assign({}, newNote)
}, }, () => {
() => {
const { dispatch, location } = this.props const { dispatch, location } = this.props
dispatch({ dispatch({
type: 'MOVE_NOTE', type: 'MOVE_NOTE',
originNote: note, originNote: note,
note: newNote note: newNote
}) })
dispatch( dispatch(replace({
replace({
pathname: location.pathname, pathname: location.pathname,
search: queryString.stringify({ search: queryString.stringify({
key: newNote.key key: newNote.key
}) })
}) }))
)
this.setState({ this.setState({
isMovingNote: false isMovingNote: false
}) })
} })
)
}) })
} }
handleStarButtonClick (e) { handleStarButtonClick (e) {
const { note } = this.state const { note } = this.state
if (!note.isStarred) if (!note.isStarred) AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_STAR')
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_STAR')
note.isStarred = !note.isStarred note.isStarred = !note.isStarred
this.setState( this.setState({
{
note note
}, }, () => {
() => {
this.save() this.save()
} })
)
} }
exportAsFile() {} exportAsFile () {
}
exportAsMd () { exportAsMd () {
ee.emit('export:save-md') ee.emit('export:save-md')
@@ -250,11 +227,7 @@ class MarkdownNoteDetail extends React.Component {
} else if (e.ctrlKey && e.shiftKey) { } else if (e.ctrlKey && e.shiftKey) {
e.preventDefault() e.preventDefault()
this.jumpPrevTab() this.jumpPrevTab()
} else if ( } else if (!e.ctrlKey && !e.shiftKey && e.target === this.refs.description) {
!e.ctrlKey &&
!e.shiftKey &&
e.target === this.refs.description
) {
e.preventDefault() e.preventDefault()
this.focusEditor() this.focusEditor()
} }
@@ -262,8 +235,9 @@ class MarkdownNoteDetail extends React.Component {
// I key // I key
case 73: case 73:
{ {
const isSuper = const isSuper = global.process.platform === 'darwin'
global.process.platform === 'darwin' ? e.metaKey : e.ctrlKey ? e.metaKey
: e.ctrlKey
if (isSuper) { if (isSuper) {
e.preventDefault() e.preventDefault()
this.handleInfoButtonClick(e) this.handleInfoButtonClick(e)
@@ -283,7 +257,7 @@ class MarkdownNoteDetail extends React.Component {
const {note, dispatch} = this.props const {note, dispatch} = this.props
dataApi dataApi
.deleteNote(note.storage, note.key) .deleteNote(note.storage, note.key)
.then(data => { .then((data) => {
const dispatchHandler = () => { const dispatchHandler = () => {
dispatch({ dispatch({
type: 'DELETE_NOTE', type: 'DELETE_NOTE',
@@ -299,14 +273,11 @@ class MarkdownNoteDetail extends React.Component {
if (confirmDeleteNote(confirmDeletion, false)) { if (confirmDeleteNote(confirmDeletion, false)) {
note.isTrashed = true note.isTrashed = true
this.setState( this.setState({
{
note note
}, }, () => {
() => {
this.save() this.save()
} })
)
ee.emit('list:next') ee.emit('list:next')
} }
@@ -318,16 +289,13 @@ class MarkdownNoteDetail extends React.Component {
note.isTrashed = false note.isTrashed = false
this.setState( this.setState({
{
note note
}, }, () => {
() => {
this.save() this.save()
this.refs.content.reload() this.refs.content.reload()
ee.emit('list:next') ee.emit('list:next')
} })
)
} }
handleFullScreenButton (e) { handleFullScreenButton (e) {
@@ -342,9 +310,7 @@ class MarkdownNoteDetail extends React.Component {
} }
getToggleLockButton () { getToggleLockButton () {
return this.state.isLocked return this.state.isLocked ? '../resources/icon/icon-lock.svg' : '../resources/icon/icon-unlock.svg'
? '../resources/icon/icon-lock.svg'
: '../resources/icon/icon-unlock.svg'
} }
handleDeleteKeyDown (e) { handleDeleteKeyDown (e) {
@@ -371,9 +337,7 @@ class MarkdownNoteDetail extends React.Component {
handleInfoButtonClick (e) { handleInfoButtonClick (e) {
const infoPanel = document.querySelector('.infoPanel') const infoPanel = document.querySelector('.infoPanel')
if (infoPanel.style) if (infoPanel.style) infoPanel.style.display = infoPanel.style.display === 'none' ? 'inline' : 'none'
infoPanel.style.display =
infoPanel.style.display === 'none' ? 'inline' : 'none'
} }
print (e) { print (e) {
@@ -382,24 +346,12 @@ class MarkdownNoteDetail extends React.Component {
handleSwitchMode (type) { handleSwitchMode (type) {
// If in split mode, hide the lock button // If in split mode, hide the lock button
this.setState( this.setState({ editorType: type, isLockButtonShown: !(type === 'SPLIT') }, () => {
{ editorType: type, isLockButtonShown: !(type === 'SPLIT') },
() => {
this.focus() this.focus()
const newConfig = Object.assign({}, this.props.config) const newConfig = Object.assign({}, this.props.config)
newConfig.editor.type = type newConfig.editor.type = type
ConfigManager.set(newConfig) ConfigManager.set(newConfig)
} })
)
}
handleSwitchDirection() {
if (!this.props.config.editor.rtlEnabled) {
return
}
// If in split mode, hide the lock button
const direction = this.state.RTL
this.setState({ RTL: !direction })
} }
handleDeleteNote () { handleDeleteNote () {
@@ -410,16 +362,14 @@ class MarkdownNoteDetail extends React.Component {
const { note } = this.state const { note } = this.state
const splitted = note.content.split('\n') const splitted = note.content.split('\n')
const clearTodoContent = splitted const clearTodoContent = splitted.map((line) => {
.map(line => {
const trimmedLine = line.trim() const trimmedLine = line.trim()
if (trimmedLine.match(/\[x\]/i)) { if (trimmedLine.match(/\[x\]/i)) {
return line.replace(/\[x\]/i, '[ ]') return line.replace(/\[x\]/i, '[ ]')
} else { } else {
return line return line
} }
}) }).join('\n')
.join('\n')
note.content = clearTodoContent note.content = clearTodoContent
this.refs.content.setValue(note.content) this.refs.content.setValue(note.content)
@@ -432,8 +382,7 @@ class MarkdownNoteDetail extends React.Component {
const { note } = this.state const { note } = this.state
if (this.state.editorType === 'EDITOR_PREVIEW') { if (this.state.editorType === 'EDITOR_PREVIEW') {
return ( return <MarkdownEditor
<MarkdownEditor
ref='content' ref='content'
styleName='body-noteEditor' styleName='body-noteEditor'
config={config} config={config}
@@ -441,26 +390,21 @@ class MarkdownNoteDetail extends React.Component {
storageKey={note.storage} storageKey={note.storage}
noteKey={note.key} noteKey={note.key}
linesHighlighted={note.linesHighlighted} linesHighlighted={note.linesHighlighted}
onChange={this.handleUpdateContent} onChange={this.handleUpdateContent.bind(this)}
isLocked={this.state.isLocked} isLocked={this.state.isLocked}
ignorePreviewPointerEvents={ignorePreviewPointerEvents} ignorePreviewPointerEvents={ignorePreviewPointerEvents}
RTL={config.editor.rtlEnabled && this.state.RTL}
/> />
)
} else { } else {
return ( return <MarkdownSplitEditor
<MarkdownSplitEditor
ref='content' ref='content'
config={config} config={config}
value={note.content} value={note.content}
storageKey={note.storage} storageKey={note.storage}
noteKey={note.key} noteKey={note.key}
linesHighlighted={note.linesHighlighted} linesHighlighted={note.linesHighlighted}
onChange={this.handleUpdateContent} onChange={this.handleUpdateContent.bind(this)}
ignorePreviewPointerEvents={ignorePreviewPointerEvents} ignorePreviewPointerEvents={ignorePreviewPointerEvents}
RTL={config.editor.rtlEnabled && this.state.RTL}
/> />
)
} }
} }
@@ -472,37 +416,27 @@ class MarkdownNoteDetail extends React.Component {
const options = [] const options = []
data.storageMap.forEach((storage, index) => { data.storageMap.forEach((storage, index) => {
storage.folders.forEach(folder => { storage.folders.forEach((folder) => {
options.push({ options.push({
storage: storage, storage: storage,
folder: folder folder: folder
}) })
}) })
}) })
const currentOption = options.filter((option) => option.storage.key === storageKey && option.folder.key === folderKey)[0]
const currentOption = _.find( const trashTopBar = <div styleName='info'>
options,
option =>
option.storage.key === storageKey && option.folder.key === folderKey
)
// currentOption may be undefined
const storageName = _.get(currentOption, 'storage.name') || ''
const folderName = _.get(currentOption, 'folder.name') || ''
const trashTopBar = (
<div styleName='info'>
<div styleName='info-left'> <div styleName='info-left'>
<RestoreButton onClick={e => this.handleUndoButtonClick(e)} /> <RestoreButton onClick={(e) => this.handleUndoButtonClick(e)} />
</div> </div>
<div styleName='info-right'> <div styleName='info-right'>
<PermanentDeleteButton <PermanentDeleteButton onClick={(e) => this.handleTrashButtonClick(e)} />
onClick={e => this.handleTrashButtonClick(e)} <InfoButton
onClick={(e) => this.handleInfoButtonClick(e)}
/> />
<InfoButton onClick={e => this.handleInfoButtonClick(e)} />
<InfoPanelTrashed <InfoPanelTrashed
storageName={storageName} storageName={currentOption.storage.name}
folderName={folderName} folderName={currentOption.folder.name}
updatedAt={formatDate(note.updatedAt)} updatedAt={formatDate(note.updatedAt)}
createdAt={formatDate(note.createdAt)} createdAt={formatDate(note.createdAt)}
exportAsHtml={this.exportAsHtml} exportAsHtml={this.exportAsHtml}
@@ -512,18 +446,15 @@ class MarkdownNoteDetail extends React.Component {
/> />
</div> </div>
</div> </div>
)
const detailTopBar = ( const detailTopBar = <div styleName='info'>
<div styleName='info'>
<div styleName='info-left'> <div styleName='info-left'>
<div> <div>
<FolderSelect <FolderSelect styleName='info-left-top-folderSelect'
styleName='info-left-top-folderSelect'
value={this.state.note.storage + '-' + this.state.note.folder} value={this.state.note.storage + '-' + this.state.note.folder}
ref='folder' ref='folder'
data={data} data={data}
onChange={e => this.handleFolderChange(e)} onChange={(e) => this.handleFolderChange(e)}
/> />
</div> </div>
@@ -537,59 +468,44 @@ class MarkdownNoteDetail extends React.Component {
onChange={this.handleUpdateTag.bind(this)} onChange={this.handleUpdateTag.bind(this)}
coloredTags={config.coloredTags} coloredTags={config.coloredTags}
/> />
<TodoListPercentage <TodoListPercentage onClearCheckboxClick={(e) => this.handleClearTodo(e)} percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
onClearCheckboxClick={e => this.handleClearTodo(e)}
percentageOfTodo={getTodoPercentageOfCompleted(note.content)}
/>
</div> </div>
<div styleName='info-right'> <div styleName='info-right'>
<ToggleModeButton <ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
onClick={e => this.handleSwitchMode(e)}
editorType={editorType}
/>
{this.props.config.editor.rtlEnabled && (
<ToggleDirectionButton
onClick={e => this.handleSwitchDirection(e)}
isRTL={this.state.RTL}
/>
)}
<StarButton <StarButton
onClick={e => this.handleStarButtonClick(e)} onClick={(e) => this.handleStarButtonClick(e)}
isActive={note.isStarred} isActive={note.isStarred}
/> />
{(() => { {(() => {
const imgSrc = `${this.getToggleLockButton()}` const imgSrc = `${this.getToggleLockButton()}`
const lockButtonComponent = ( const lockButtonComponent =
<button <button styleName='control-lockButton'
styleName='control-lockButton' onFocus={(e) => this.handleFocus(e)}
onFocus={e => this.handleFocus(e)} onMouseDown={(e) => this.handleLockButtonMouseDown(e)}
onMouseDown={e => this.handleLockButtonMouseDown(e)}
> >
<img src={imgSrc} /> <img src={imgSrc} />
{this.state.isLocked ? ( {this.state.isLocked ? <span styleName='tooltip'>Unlock</span> : <span styleName='tooltip'>Lock</span>}
<span styleName='tooltip'>Unlock</span>
) : (
<span styleName='tooltip'>Lock</span>
)}
</button> </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 <InfoPanel
storageName={storageName} storageName={currentOption.storage.name}
folderName={folderName} folderName={currentOption.folder.name}
noteLink={`[${note.title}](:note:${ noteLink={`[${note.title}](:note:${queryString.parse(location.search).key})`}
queryString.parse(location.search).key
})`}
updatedAt={formatDate(note.updatedAt)} updatedAt={formatDate(note.updatedAt)}
createdAt={formatDate(note.createdAt)} createdAt={formatDate(note.createdAt)}
exportAsMd={this.exportAsMd} exportAsMd={this.exportAsMd}
@@ -603,18 +519,19 @@ class MarkdownNoteDetail extends React.Component {
/> />
</div> </div>
</div> </div>
)
return ( return (
<div <div className='NoteDetail'
className='NoteDetail'
style={this.props.style} style={this.props.style}
styleName='root' styleName='root'
onKeyDown={e => this.handleKeyDown(e)} onKeyDown={(e) => this.handleKeyDown(e)}
> >
{location.pathname === '/trashed' ? trashTopBar : detailTopBar} {location.pathname === '/trashed' ? trashTopBar : detailTopBar}
<div styleName='body'>{this.renderEditor()}</div> <div styleName='body'>
{this.renderEditor()}
</div>
<StatusBar <StatusBar
{..._.pick(this.props, ['config', 'location', 'dispatch'])} {..._.pick(this.props, ['config', 'location', 'dispatch'])}
@@ -628,7 +545,9 @@ class MarkdownNoteDetail extends React.Component {
MarkdownNoteDetail.propTypes = { MarkdownNoteDetail.propTypes = {
dispatch: PropTypes.func, dispatch: PropTypes.func,
repositories: PropTypes.array, repositories: PropTypes.array,
note: PropTypes.shape({}), note: PropTypes.shape({
}),
style: PropTypes.shape({ style: PropTypes.shape({
left: PropTypes.number left: PropTypes.number
}), }),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,26 +0,0 @@
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.bool.isRequired
}
export default CSSModules(ToggleDirectionButton, styles)

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,9 +16,7 @@ import { store } from 'browser/main/store'
import i18n from 'browser/lib/i18n' import i18n from 'browser/lib/i18n'
import { getLocales } from 'browser/lib/Languages' import { getLocales } from 'browser/lib/Languages'
import applyShortcuts from 'browser/main/lib/shortcutManager' import applyShortcuts from 'browser/main/lib/shortcutManager'
import { chooseTheme, applyTheme } from 'browser/main/lib/ThemeManager'
import { push } from 'connected-react-router' import { push } from 'connected-react-router'
const path = require('path') const path = require('path')
const electron = require('electron') const electron = require('electron')
const { remote } = electron const { remote } = electron
@@ -93,21 +91,18 @@ class Main extends React.Component {
type: 'SNIPPET_NOTE', type: 'SNIPPET_NOTE',
folder: data.storage.folders[0].key, folder: data.storage.folders[0].key,
title: 'Snippet note example', title: 'Snippet note example',
description: description: 'Snippet note example\nYou can store a series of snippets as a single note, like Gist.',
'Snippet note example\nYou can store a series of snippets as a single note, like Gist.',
snippets: [ snippets: [
{ {
name: 'example.html', name: 'example.html',
mode: 'html', mode: 'html',
content: content: "<html>\n<body>\n<h1 id='hello'>Enjoy Boostnote!</h1>\n</body>\n</html>",
"<html>\n<body>\n<h1 id='hello'>Enjoy Boostnote!</h1>\n</body>\n</html>",
linesHighlighted: [] linesHighlighted: []
}, },
{ {
name: 'example.js', name: 'example.js',
mode: 'javascript', mode: 'javascript',
content: content: "var boostnote = document.getElementById('hello').innerHTML\n\nconsole.log(boostnote)",
"var boostnote = document.getElementById('hello').innerHTML\n\nconsole.log(boostnote)",
linesHighlighted: [] linesHighlighted: []
} }
] ]
@@ -123,8 +118,7 @@ class Main extends React.Component {
type: 'MARKDOWN_NOTE', type: 'MARKDOWN_NOTE',
folder: data.storage.folders[0].key, folder: data.storage.folders[0].key,
title: 'Welcome to Boostnote!', title: 'Welcome to Boostnote!',
content: 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)'
'# 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 => { .then(note => {
store.dispatch({ store.dispatch({
@@ -148,13 +142,13 @@ class Main extends React.Component {
componentDidMount () { componentDidMount () {
const { dispatch, config } = this.props const { dispatch, config } = this.props
this.refreshTheme = setInterval(() => { const supportedThemes = ['dark', 'white', 'solarized-dark', 'monokai', 'dracula']
const conf = ConfigManager.get()
chooseTheme(conf)
}, 5 * 1000)
chooseTheme(config) if (supportedThemes.indexOf(config.ui.theme) !== -1) {
applyTheme(config.ui.theme) document.body.setAttribute('data-theme', config.ui.theme)
} else {
document.body.setAttribute('data-theme', 'default')
}
if (getLocales().indexOf(config.ui.language) !== -1) { if (getLocales().indexOf(config.ui.language) !== -1) {
i18n.setLocale(config.ui.language) i18n.setLocale(config.ui.language)
@@ -179,26 +173,12 @@ class Main extends React.Component {
delete CodeMirror.keyMap.emacs['Ctrl-V'] delete CodeMirror.keyMap.emacs['Ctrl-V']
eventEmitter.on('editor:fullscreen', this.toggleFullScreen) eventEmitter.on('editor:fullscreen', this.toggleFullScreen)
eventEmitter.on( eventEmitter.on('menubar:togglemenubar', this.toggleMenuBarVisible.bind(this))
'menubar:togglemenubar',
this.toggleMenuBarVisible.bind(this)
)
eventEmitter.on('dispatch:push', this.changeRoutePush.bind(this))
} }
componentWillUnmount () { componentWillUnmount () {
eventEmitter.off('editor:fullscreen', this.toggleFullScreen) eventEmitter.off('editor:fullscreen', this.toggleFullScreen)
eventEmitter.off( eventEmitter.off('menubar:togglemenubar', this.toggleMenuBarVisible.bind(this))
'menubar:togglemenubar',
this.toggleMenuBarVisible.bind(this)
)
eventEmitter.off('dispatch:push', this.changeRoutePush.bind(this))
clearInterval(this.refreshTheme)
}
changeRoutePush(event, destination) {
const { dispatch } = this.props
dispatch(push(destination))
} }
toggleMenuBarVisible () { toggleMenuBarVisible () {
@@ -332,16 +312,10 @@ class Main extends React.Component {
onMouseUp={e => this.handleMouseUp(e)} onMouseUp={e => this.handleMouseUp(e)}
> >
<SideNav <SideNav
{..._.pick(this.props, [ {..._.pick(this.props, ['dispatch', 'data', 'config', 'match', 'location'])}
'dispatch',
'data',
'config',
'match',
'location'
])}
width={this.state.navWidth} width={this.state.navWidth}
/> />
{!config.isSideNavFolded && ( {!config.isSideNavFolded &&
<div <div
styleName={ styleName={
this.state.isLeftSliderFocused ? 'slider--active' : 'slider' this.state.isLeftSliderFocused ? 'slider--active' : 'slider'
@@ -351,8 +325,7 @@ class Main extends React.Component {
draggable='false' draggable='false'
> >
<div styleName='slider-hitbox' /> <div styleName='slider-hitbox' />
</div> </div>}
)}
<div <div
styleName={config.isSideNavFolded ? 'body--expanded' : 'body'} styleName={config.isSideNavFolded ? 'body--expanded' : 'body'}
id='main-body' id='main-body'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,26 +0,0 @@
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

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

View File

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

View File

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

View File

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

View File

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

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