diff --git a/.eslintrc b/.eslintrc
index a460b507..1709c9d8 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -19,5 +19,8 @@
"FileReader": true,
"localStorage": true,
"fetch": true
+ },
+ "env": {
+ "jest": true
}
}
diff --git a/.travis.yml b/.travis.yml
index c68d1063..d9267f77 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,8 +1,9 @@
language: node_js
node_js:
- - 6
+ - 7
script:
- npm run lint && npm run test
+ - yarn jest
- 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" ]]; then npm install -g grunt npm@5.2 && grunt pre-build; fi'
after_success:
- openssl aes-256-cbc -K $encrypted_440d7f9a3c38_key -iv $encrypted_440d7f9a3c38_iv
diff --git a/browser/components/CodeEditor.js b/browser/components/CodeEditor.js
index 492ba7b3..98e25f1a 100644
--- a/browser/components/CodeEditor.js
+++ b/browser/components/CodeEditor.js
@@ -4,6 +4,7 @@ import _ from 'lodash'
import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir'
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
+import convertModeName from 'browser/lib/convertModeName'
import eventEmitter from 'browser/main/lib/eventEmitter'
import iconv from 'iconv-lite'
import crypto from 'crypto'
@@ -17,21 +18,6 @@ const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', '
const buildCMRulers = (rulers, enableRulers) =>
enableRulers ? rulers.map(ruler => ({ column: ruler })) : []
-function pass (name) {
- switch (name) {
- case 'ejs':
- return 'Embedded Javascript'
- case 'html_ruby':
- return 'Embedded Ruby'
- case 'objectivec':
- return 'Objective C'
- case 'text':
- return 'Plain Text'
- default:
- return name
- }
-}
-
export default class CodeEditor extends React.Component {
constructor (props) {
super(props)
@@ -52,6 +38,9 @@ export default class CodeEditor extends React.Component {
el = el.parentNode
}
this.props.onBlur != null && this.props.onBlur(e)
+
+ const {storageKey, noteKey} = this.props
+ attachmentManagement.deleteAttachmentsNotPresentInNote(this.editor.getValue(), storageKey, noteKey)
}
this.pasteHandler = (editor, e) => this.handlePaste(editor, e)
this.loadStyleHandler = (e) => {
@@ -323,7 +312,7 @@ export default class CodeEditor extends React.Component {
}
setMode (mode) {
- let syntax = CodeMirror.findModeByName(pass(mode))
+ let syntax = CodeMirror.findModeByName(convertModeName(mode))
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
this.editor.setOption('mode', syntax.mime)
diff --git a/browser/components/MarkdownEditor.js b/browser/components/MarkdownEditor.js
index 313c6f90..2bd5d951 100644
--- a/browser/components/MarkdownEditor.js
+++ b/browser/components/MarkdownEditor.js
@@ -283,6 +283,7 @@ class MarkdownEditor extends React.Component {
indentSize={editorIndentSize}
scrollPastEnd={config.preview.scrollPastEnd}
smartQuotes={config.preview.smartQuotes}
+ breaks={config.preview.breaks}
sanitize={config.preview.sanitize}
ref='preview'
onContextMenu={(e) => this.handleContextMenu(e)}
@@ -294,6 +295,7 @@ class MarkdownEditor extends React.Component {
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
showCopyNotification={config.ui.showCopyNotification}
storagePath={storage.path}
+ noteKey={noteKey}
/>
)
diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js
index 55d85bea..991cd9d0 100755
--- a/browser/components/MarkdownPreview.js
+++ b/browser/components/MarkdownPreview.js
@@ -10,6 +10,7 @@ import flowchart from 'flowchart'
import SequenceDiagram from 'js-sequence-diagrams'
import eventEmitter from 'browser/main/lib/eventEmitter'
import htmlTextHelper from 'browser/lib/htmlTextHelper'
+import convertModeName from 'browser/lib/convertModeName'
import copy from 'copy-to-clipboard'
import mdurl from 'mdurl'
import exportNote from 'browser/main/lib/dataApi/exportNote'
@@ -31,7 +32,7 @@ const CSS_FILES = [
`${appPath}/node_modules/codemirror/lib/codemirror.css`
]
-function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd) {
+function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme) {
return `
@font-face {
font-family: 'Lato';
@@ -103,6 +104,13 @@ h2 {
body p {
white-space: normal;
}
+
+@media print {
+ body[data-theme="${theme}"] {
+ color: #000;
+ background-color: #fff;
+ }
+}
`
}
@@ -115,7 +123,6 @@ if (!OSX) {
defaultFontFamily.unshift('meiryo')
}
const defaultCodeBlockFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
-
export default class MarkdownPreview extends React.Component {
constructor (props) {
super(props)
@@ -138,10 +145,11 @@ export default class MarkdownPreview extends React.Component {
}
initMarkdown () {
- const { smartQuotes, sanitize } = this.props
+ const { smartQuotes, sanitize, breaks } = this.props
this.markdown = new Markdown({
typographer: smartQuotes,
- sanitize
+ sanitize,
+ breaks
})
}
@@ -208,11 +216,13 @@ export default class MarkdownPreview extends React.Component {
handleSaveAsHtml () {
this.exportAsDocument('html', (noteContent, exportTasks) => {
- const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme} = this.getStyleParams()
+ const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme} = this.getStyleParams()
+
+ const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme)
+ let body = this.markdown.render(escapeHtmlCharacters(noteContent))
- const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, lineNumber)
- const body = this.markdown.render(escapeHtmlCharacters(noteContent))
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
+ const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(noteContent, this.props.storagePath)
files.forEach((file) => {
file = file.replace('file://', '')
@@ -221,6 +231,13 @@ export default class MarkdownPreview extends React.Component {
dst: 'css'
})
})
+ attachmentsAbsolutePaths.forEach((attachment) => {
+ exportTasks.push({
+ src: attachment,
+ dst: attachmentManagement.DESTINATION_FOLDER
+ })
+ })
+ body = attachmentManagement.removeStorageAndNoteReferences(body, this.props.noteKey)
let styles = ''
files.forEach((file) => {
@@ -324,7 +341,9 @@ export default class MarkdownPreview extends React.Component {
componentDidUpdate (prevProps) {
if (prevProps.value !== this.props.value) this.rewriteIframe()
- if (prevProps.smartQuotes !== this.props.smartQuotes || prevProps.sanitize !== this.props.sanitize) {
+ if (prevProps.smartQuotes !== this.props.smartQuotes ||
+ prevProps.sanitize !== this.props.sanitize ||
+ prevProps.breaks !== this.props.breaks) {
this.initMarkdown()
this.rewriteIframe()
}
@@ -342,7 +361,7 @@ export default class MarkdownPreview extends React.Component {
}
getStyleParams () {
- const { fontSize, lineNumber, codeBlockTheme, scrollPastEnd } = this.props
+ const { fontSize, lineNumber, codeBlockTheme, scrollPastEnd, theme } = this.props
let { fontFamily, codeBlockFontFamily } = this.props
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
? fontFamily.split(',').map(fontName => fontName.trim()).concat(defaultFontFamily)
@@ -351,14 +370,14 @@ export default class MarkdownPreview extends React.Component {
? codeBlockFontFamily.split(',').map(fontName => fontName.trim()).concat(defaultCodeBlockFontFamily)
: defaultCodeBlockFontFamily
- return {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd}
+ return {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme}
}
applyStyle () {
- const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd} = this.getStyleParams()
+ const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme} = this.getStyleParams()
this.getWindow().document.getElementById('codeTheme').href = this.GetCodeThemeLink(codeBlockTheme)
- this.getWindow().document.getElementById('style').innerHTML = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd)
+ this.getWindow().document.getElementById('style').innerHTML = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme)
}
GetCodeThemeLink (theme) {
@@ -414,7 +433,7 @@ export default class MarkdownPreview extends React.Component {
: 'default'
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.code code'), (el) => {
- let syntax = CodeMirror.findModeByName(el.className)
+ let syntax = CodeMirror.findModeByName(convertModeName(el.className))
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
CodeMirror.requireMode(syntax.mode, () => {
const content = htmlTextHelper.decodeEntities(el.innerHTML)
@@ -526,21 +545,36 @@ export default class MarkdownPreview extends React.Component {
return
}
- const noteHash = e.target.href.split('/').pop()
+ const linkHash = href.split('/').pop()
+
+ const regexNoteInternalLink = /main.html#(.+)/
+ if (regexNoteInternalLink.test(linkHash)) {
+ const targetId = mdurl.encode(linkHash.match(regexNoteInternalLink)[1])
+ const targetElement = this.refs.root.contentWindow.document.getElementById(targetId)
+
+ if (targetElement != null) {
+ this.getWindow().scrollTo(0, targetElement.offsetTop)
+ }
+ return
+ }
+
// this will match the new uuid v4 hash and the old hash
// e.g.
// :note:1c211eb7dcb463de6490 and
// :note:7dd23275-f2b4-49cb-9e93-3454daf1af9c
const regexIsNoteLink = /^:note:([a-zA-Z0-9-]{20,36})$/
- if (regexIsNoteLink.test(noteHash)) {
- eventEmitter.emit('list:jump', noteHash.replace(':note:', ''))
+ if (regexIsNoteLink.test(linkHash)) {
+ eventEmitter.emit('list:jump', linkHash.replace(':note:', ''))
+ return
}
+
// this will match the old link format storage.key-note.key
// e.g.
// 877f99c3268608328037-1c211eb7dcb463de6490
const regexIsLegacyNoteLink = /^(.{20})-(.{20})$/
- if (regexIsLegacyNoteLink.test(noteHash)) {
- eventEmitter.emit('list:jump', noteHash.split('-')[1])
+ if (regexIsLegacyNoteLink.test(linkHash)) {
+ eventEmitter.emit('list:jump', linkHash.split('-')[1])
+ return
}
}
@@ -568,5 +602,6 @@ MarkdownPreview.propTypes = {
value: PropTypes.string,
showCopyNotification: PropTypes.bool,
storagePath: PropTypes.string,
- smartQuotes: PropTypes.bool
+ smartQuotes: PropTypes.bool,
+ breaks: PropTypes.bool
}
diff --git a/browser/components/MarkdownSplitEditor.js b/browser/components/MarkdownSplitEditor.js
index d82d4da3..2bee5c24 100644
--- a/browser/components/MarkdownSplitEditor.js
+++ b/browser/components/MarkdownSplitEditor.js
@@ -131,6 +131,7 @@ class MarkdownSplitEditor extends React.Component {
lineNumber={config.preview.lineNumber}
scrollPastEnd={config.preview.scrollPastEnd}
smartQuotes={config.preview.smartQuotes}
+ breaks={config.preview.breaks}
sanitize={config.preview.sanitize}
ref='preview'
tabInde='0'
@@ -139,6 +140,7 @@ class MarkdownSplitEditor extends React.Component {
onScroll={this.handleScroll.bind(this)}
showCopyNotification={config.ui.showCopyNotification}
storagePath={storage.path}
+ noteKey={noteKey}
/>
)
diff --git a/browser/components/NoteItem.styl b/browser/components/NoteItem.styl
index 4067a6cd..017ef6d0 100644
--- a/browser/components/NoteItem.styl
+++ b/browser/components/NoteItem.styl
@@ -321,3 +321,76 @@ body[data-theme="solarized-dark"]
.item-bottom-tagList-empty
color $ui-inactive-text-color
vertical-align middle
+
+body[data-theme="monokai"]
+ .root
+ border-color $ui-monokai-borderColor
+ background-color $ui-monokai-noteList-backgroundColor
+
+ .item
+ border-color $ui-monokai-borderColor
+ background-color $ui-monokai-noteList-backgroundColor
+ &:hover
+ transition 0.15s
+ // background-color alpha($ui-monokai-noteList-backgroundColor, 20%)
+ color $ui-monokai-text-color
+ .item-title
+ .item-title-icon
+ .item-bottom-time
+ transition 0.15s
+ color $ui-monokai-text-color
+ .item-bottom-tagList-item
+ transition 0.15s
+ background-color alpha($ui-monokai-noteList-backgroundColor, 20%)
+ color $ui-monokai-text-color
+ &:active
+ transition 0.15s
+ background-color $ui-monokai-noteList-backgroundColor
+ color $ui-monokai-text-color
+ .item-title
+ .item-title-icon
+ .item-bottom-time
+ transition 0.15s
+ color $ui-monokai-text-color
+ .item-bottom-tagList-item
+ transition 0.15s
+ background-color alpha($ui-monokai-noteList-backgroundColor, 10%)
+ color $ui-monokai-text-color
+
+ .item-wrapper
+ border-color alpha($ui-monokai-button-backgroundColor, 60%)
+
+ .item--active
+ border-color $ui-monokai-borderColor
+ background-color $ui-monokai-button-backgroundColor
+ .item-wrapper
+ border-color transparent
+ .item-title
+ .item-title-icon
+ .item-bottom-time
+ color $ui-monokai-text-color
+ .item-bottom-tagList-item
+ background-color alpha(white, 10%)
+ color $ui-monokai-text-color
+ &:hover
+ // background-color alpha($ui-monokai-button--active-backgroundColor, 60%)
+ color #c0392b
+ .item-bottom-tagList-item
+ background-color alpha(#fff, 20%)
+
+ .item-title
+ color $ui-inactive-text-color
+
+ .item-title-icon
+ color $ui-inactive-text-color
+
+ .item-title-empty
+ color $ui-inactive-text-color
+
+ .item-bottom-tagList-item
+ background-color alpha($ui-dark-button--active-backgroundColor, 40%)
+ color $ui-inactive-text-color
+
+ .item-bottom-tagList-empty
+ color $ui-inactive-text-color
+ vertical-align middle
diff --git a/browser/components/NoteItemSimple.styl b/browser/components/NoteItemSimple.styl
index 3097b82c..661751bc 100644
--- a/browser/components/NoteItemSimple.styl
+++ b/browser/components/NoteItemSimple.styl
@@ -104,6 +104,7 @@ body[data-theme="dark"]
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
color $ui-dark-text-color
.item-simple-title
+ .item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
@@ -117,6 +118,7 @@ body[data-theme="dark"]
background-color $ui-dark-button--active-backgroundColor
color $ui-dark-text-color
.item-simple-title
+ .item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
@@ -165,9 +167,10 @@ body[data-theme="solarized-dark"]
background-color $ui-solarized-dark-noteList-backgroundColor
&:hover
transition 0.15s
- // background-color alpha($ui-dark-button--active-backgroundColor, 20%)
+ background-color alpha($ui-dark-button--active-backgroundColor, 60%)
color $ui-solarized-dark-text-color
.item-simple-title
+ .item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
@@ -178,9 +181,10 @@ body[data-theme="solarized-dark"]
color $ui-solarized-dark-text-color
&:active
transition 0.15s
- background-color $ui-solarized-dark-button--active-backgroundColor
- color $ui-solarized-dark-text-color
+ // background-color $ui-solarized-dark-button--active-backgroundColor
+ color $ui-dark-text-color
.item-simple-title
+ .item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
@@ -192,11 +196,13 @@ body[data-theme="solarized-dark"]
.item-simple--active
border-color $ui-solarized-dark-borderColor
- background-color $ui-solarized-dark-button--active-backgroundColor
+ background-color $ui-solarized-dark-tag-backgroundColor
.item-simple-wrapper
border-color transparent
.item-simple-title
+ .item-simple-title-empty
.item-simple-title-icon
+ color $ui-dark-text-color
.item-simple-bottom-time
color $ui-solarized-dark-text-color
.item-simple-bottom-tagList-item
@@ -207,8 +213,75 @@ body[data-theme="solarized-dark"]
color #c0392b
.item-simple-bottom-tagList-item
background-color alpha(#fff, 20%)
-.item-simple-right
- float right
- .item-simple-right-storageName
- padding-left 4px
- opacity 0.4
+ .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
+
+body[data-theme="monokai"]
+ .root
+ border-color $ui-monokai-borderColor
+ background-color $ui-monokai-noteList-backgroundColor
+
+ .item-simple
+ border-color $ui-monokai-borderColor
+ background-color $ui-monokai-noteList-backgroundColor
+ &:hover
+ transition 0.15s
+ background-color alpha($ui-monokai-button-backgroundColor, 60%)
+ color $ui-monokai-text-color
+ .item-simple-title
+ .item-simple-title-empty
+ .item-simple-title-icon
+ .item-simple-bottom-time
+ transition 0.15s
+ color $ui-solarized-dark-text-color
+ .item-simple-bottom-tagList-item
+ transition 0.15s
+ background-color alpha(#fff, 20%)
+ color $ui-monokai-text-color
+ &:active
+ transition 0.15s
+ background-color $ui-monokai-button--active-backgroundColor
+ color $ui-monokai-text-color
+ .item-simple-title
+ .item-simple-title-empty
+ .item-simple-title-icon
+ .item-simple-bottom-time
+ transition 0.15s
+ color $ui-monokai-text-color
+ .item-simple-bottom-tagList-item
+ transition 0.15s
+ background-color alpha(white, 10%)
+ color $ui-monokai-text-color
+
+ .item-simple--active
+ border-color $ui-monokai-borderColor
+ background-color $ui-monokai-button--active-backgroundColor
+ .item-simple-wrapper
+ border-color transparent
+ .item-simple-title
+ .item-simple-title-empty
+ .item-simple-title-icon
+ .item-simple-bottom-time
+ color $ui-monokai-text-color
+ .item-simple-bottom-tagList-item
+ background-color alpha(white, 10%)
+ color $ui-monokai-text-color
+ &:hover
+ // background-color alpha($ui-dark-button--active-backgroundColor, 60%)
+ color #c0392b
+ .item-simple-bottom-tagList-item
+ background-color alpha(#fff, 20%)
+ .item-simple-title
+ 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
diff --git a/browser/components/RealtimeNotification.styl b/browser/components/RealtimeNotification.styl
index 0f77acbb..0365d8c9 100644
--- a/browser/components/RealtimeNotification.styl
+++ b/browser/components/RealtimeNotification.styl
@@ -41,3 +41,14 @@ body[data-theme="solarized-dark"]
background-color $ui-solarized-dark-button-backgroundColor
&:hover
color #5CB85C
+
+body[data-theme="monokai"]
+ .notification-area
+ background-color none
+
+ .notification-link
+ color $ui-monokai-text-color
+ border none
+ background-color $ui-monokai-button-backgroundColor
+ &:hover
+ color #5CB85C
\ No newline at end of file
diff --git a/browser/components/SideNavFilter.styl b/browser/components/SideNavFilter.styl
index 8a9a350d..c9dbd861 100644
--- a/browser/components/SideNavFilter.styl
+++ b/browser/components/SideNavFilter.styl
@@ -18,7 +18,7 @@
.iconWrap
width 20px
text-align center
-
+
.counters
float right
color $ui-inactive-text-color
@@ -68,10 +68,9 @@
.menu-button-label
position fixed
display inline-block
- height 32px
+ height 36px
left 44px
padding 0 10px
- margin-top -8px
margin-left 0
overflow ellipsis
z-index 10
@@ -222,4 +221,46 @@ body[data-theme="solarized-dark"]
background-color $ui-solarized-dark-button-backgroundColor
color $ui-solarized-dark-text-color
.menu-button-label
- color $ui-solarized-dark-text-color
\ No newline at end of file
+ color $ui-solarized-dark-text-color
+
+body[data-theme="monokai"]
+ .menu-button
+ &:active
+ background-color $ui-monokai-noteList-backgroundColor
+ color $ui-monokai-text-color
+ &:hover
+ background-color $ui-monokai-button-backgroundColor
+ color $ui-monokai-text-color
+
+ .menu-button--active
+ color $ui-monokai-text-color
+ background-color $ui-monokai-button-backgroundColor
+ .menu-button-label
+ color $ui-monokai-text-color
+ &:hover
+ background-color $ui-monokai-button-backgroundColor
+ color $ui-monokai-text-color
+ .menu-button-label
+ color $ui-monokai-text-color
+
+ .menu-button-star--active
+ color $ui-monokai-text-color
+ background-color $ui-monokai-button-backgroundColor
+ .menu-button-label
+ color $ui-monokai-text-color
+ &:hover
+ background-color $ui-monokai-button-backgroundColor
+ color $ui-monokai-text-color
+ .menu-button-label
+ color $ui-monokai-text-color
+
+ .menu-button-trash--active
+ color $ui-monokai-text-color
+ background-color $ui-monokai-button-backgroundColor
+ .menu-button-label
+ color $ui-monokai-text-color
+ &:hover
+ background-color $ui-monokai-button-backgroundColor
+ color $ui-monokai-text-color
+ .menu-button-label
+ color $ui-monokai-text-color
\ No newline at end of file
diff --git a/browser/components/SnippetTab.js b/browser/components/SnippetTab.js
index 89f5a5bc..c030351f 100644
--- a/browser/components/SnippetTab.js
+++ b/browser/components/SnippetTab.js
@@ -55,10 +55,10 @@ class SnippetTab extends React.Component {
this.handleRename()
break
case 27:
- this.setState({
- name: this.props.snippet.name,
+ this.setState((prevState, props) => ({
+ name: props.snippet.name,
isRenaming: false
- })
+ }))
break
}
}
diff --git a/browser/components/StorageItem.styl b/browser/components/StorageItem.styl
index 842f8d66..0a1b4525 100644
--- a/browser/components/StorageItem.styl
+++ b/browser/components/StorageItem.styl
@@ -58,8 +58,8 @@
opacity 0
border-top-right-radius 2px
border-bottom-right-radius 2px
- height 26px
- line-height 26px
+ height 34px
+ line-height 32px
.folderList-item:hover, .folderList-item--active:hover
.folderList-item-tooltip
@@ -138,3 +138,22 @@ body[data-theme="solarized-dark"]
&:hover
color $ui-solarized-dark-text-color
background-color $ui-solarized-dark-button-backgroundColor
+
+body[data-theme="monokai"]
+ .folderList-item
+ &:hover
+ background-color $ui-monokai-button-backgroundColor
+ color $ui-monokai-text-color
+ &:active
+ color $ui-monokai-text-color
+ background-color $ui-monokai-button-backgroundColor
+
+ .folderList-item--active
+ @extend .folderList-item
+ color $ui-monokai-text-color
+ background-color $ui-monokai-button-backgroundColor
+ &:active
+ background-color $ui-monokai-button-backgroundColor
+ &:hover
+ color $ui-monokai-text-color
+ background-color $ui-monokai-button-backgroundColor
\ No newline at end of file
diff --git a/browser/components/TodoListPercentage.styl b/browser/components/TodoListPercentage.styl
index 329663f9..6116cd58 100644
--- a/browser/components/TodoListPercentage.styl
+++ b/browser/components/TodoListPercentage.styl
@@ -47,5 +47,15 @@ body[data-theme="solarized-dark"]
.progressBar
background-color: #2aa198
+ .percentageText
+ color #fdf6e3
+
+body[data-theme="monokai"]
+ .percentageBar
+ background-color #f92672
+
+ .progressBar
+ background-color: #373831
+
.percentageText
color #fdf6e3
\ No newline at end of file
diff --git a/browser/components/markdown.styl b/browser/components/markdown.styl
index cc6d7d92..32dbda73 100644
--- a/browser/components/markdown.styl
+++ b/browser/components/markdown.styl
@@ -199,7 +199,6 @@ ol
&>li>ul, &>li>ol
margin 0
code
- color #CC305F
padding 0.2em 0.4em
background-color #f7f7f7
border-radius 3px
@@ -371,3 +370,30 @@ body[data-theme="solarized-dark"]
border-color themeSolarizedDarkTableBorder
&:last-child
border-right solid 1px themeSolarizedDarkTableBorder
+
+themeMonokaiTableOdd = $ui-monokai-noteDetail-backgroundColor
+themeMonokaiTableEven = darken($ui-monokai-noteDetail-backgroundColor, 10%)
+themeMonokaiTableHead = themeMonokaiTableEven
+themeMonokaiTableBorder = themeDarkBorder
+
+body[data-theme="monokai"]
+ color $ui-monokai-text-color
+ border-color themeDarkBorder
+ background-color $ui-monokai-noteDetail-backgroundColor
+ table
+ thead
+ tr
+ background-color themeMonokaiTableHead
+ th
+ border-color themeMonokaiTableBorder
+ &:last-child
+ border-right solid 1px themeMonokaiTableBorder
+ tbody
+ tr:nth-child(2n + 1)
+ background-color themeMonokaiTableOdd
+ tr:nth-child(2n)
+ background-color themeMonokaiTableEven
+ td
+ border-color themeMonokaiTableBorder
+ &:last-child
+ border-right solid 1px themeMonokaiTableBorder
\ No newline at end of file
diff --git a/browser/lib/convertModeName.js b/browser/lib/convertModeName.js
new file mode 100644
index 00000000..b0431059
--- /dev/null
+++ b/browser/lib/convertModeName.js
@@ -0,0 +1,14 @@
+export default function convertModeName (name) {
+ switch (name) {
+ case 'ejs':
+ return 'Embedded Javascript'
+ case 'html_ruby':
+ return 'Embedded Ruby'
+ case 'objectivec':
+ return 'Objective C'
+ case 'text':
+ return 'Plain Text'
+ default:
+ return name
+ }
+}
diff --git a/browser/lib/markdown.js b/browser/lib/markdown.js
index 1ef488a7..b9e1a3eb 100644
--- a/browser/lib/markdown.js
+++ b/browser/lib/markdown.js
@@ -25,7 +25,7 @@ class Markdown {
linkify: true,
html: true,
xhtmlOut: true,
- breaks: true,
+ breaks: config.preview.breaks,
highlight: function (str, lang) {
const delimiter = ':'
const langInfo = lang.split(delimiter)
@@ -145,11 +145,13 @@ class Markdown {
const deflate = require('markdown-it-plantuml/lib/deflate')
this.md.use(require('markdown-it-plantuml'), '', {
generateSource: function (umlCode) {
+ const stripTrailingSlash = (url) => url.endsWith('/') ? url.slice(0, -1) : url
+ const serverAddress = stripTrailingSlash(config.preview.plantUMLServerAddress) + '/svg'
const s = unescape(encodeURIComponent(umlCode))
const zippedCode = deflate.encode64(
deflate.zip_deflate(`@startuml\n${s}\n@enduml`, 9)
)
- return `http://www.plantuml.com/plantuml/svg/${zippedCode}`
+ return `${serverAddress}/${zippedCode}`
}
})
diff --git a/browser/lib/utils.js b/browser/lib/utils.js
index f67ca377..441cfbc7 100644
--- a/browser/lib/utils.js
+++ b/browser/lib/utils.js
@@ -54,7 +54,25 @@ export function escapeHtmlCharacters (text) {
: html
}
+export function isObjectEqual (a, b) {
+ const aProps = Object.getOwnPropertyNames(a)
+ const bProps = Object.getOwnPropertyNames(b)
+
+ if (aProps.length !== bProps.length) {
+ return false
+ }
+
+ for (var i = 0; i < aProps.length; i++) {
+ const propName = aProps[i]
+ if (a[propName] !== b[propName]) {
+ return false
+ }
+ }
+ return true
+}
+
export default {
lastFindInArray,
- escapeHtmlCharacters
+ escapeHtmlCharacters,
+ isObjectEqual
}
diff --git a/browser/main/Detail/Detail.styl b/browser/main/Detail/Detail.styl
index d4c4100c..49a634f3 100644
--- a/browser/main/Detail/Detail.styl
+++ b/browser/main/Detail/Detail.styl
@@ -30,3 +30,10 @@ body[data-theme="solarized-dark"]
border-left 1px solid $ui-solarized-dark-borderColor
.empty-message
color $ui-solarized-dark-text-color
+
+body[data-theme="monokai"]
+ .root
+ background-color $ui-monokai-noteDetail-backgroundColor
+ border-left 1px solid $ui-monokai-borderColor
+ .empty-message
+ color $ui-monokai-text-color
diff --git a/browser/main/Detail/FolderSelect.styl b/browser/main/Detail/FolderSelect.styl
index 31930fe6..cfdc2734 100644
--- a/browser/main/Detail/FolderSelect.styl
+++ b/browser/main/Detail/FolderSelect.styl
@@ -133,3 +133,29 @@ body[data-theme="dark"]
color $ui-dark-button--active-color
.search-optionList-item-name-surfix
color $ui-dark-inactive-text-color
+
+body[data-theme="monokai"]
+ .root
+ color $ui-dark-text-color
+ &:hover
+ color white
+ background-color $ui-monokai-button--hover-backgroundColor
+ border-color $ui-monokai-borderColor
+
+ .search-optionList
+ color white
+ border-color $ui-monokai-borderColor
+ background-color $ui-monokai-button-backgroundColor
+
+ .search-optionList-item
+ &:hover
+ background-color lighten($ui-monokai-button--hover-backgroundColor, 15%)
+
+ .search-optionList-item--active
+ background-color $ui-monokai-button--active-backgroundColor
+ color $ui-monokai-button--active-color
+ &:hover
+ background-color $ui-monokai-button--active-backgroundColor
+ color $ui-monokai-button--active-color
+ .search-optionList-item-name-surfix
+ color $ui-monokai-inactive-text-color
diff --git a/browser/main/Detail/InfoPanel.styl b/browser/main/Detail/InfoPanel.styl
index d90dea49..480441bd 100644
--- a/browser/main/Detail/InfoPanel.styl
+++ b/browser/main/Detail/InfoPanel.styl
@@ -215,3 +215,43 @@ body[data-theme="solarized-dark"]
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
diff --git a/browser/main/Detail/MarkdownNoteDetail.js b/browser/main/Detail/MarkdownNoteDetail.js
index 72f832e3..a8fc938b 100755
--- a/browser/main/Detail/MarkdownNoteDetail.js
+++ b/browser/main/Detail/MarkdownNoteDetail.js
@@ -55,10 +55,14 @@ class MarkdownNoteDetail extends React.Component {
componentDidMount () {
ee.on('topbar:togglelockbutton', this.toggleLockButton)
+ ee.on('topbar:togglemodebutton', () => {
+ const reversedType = this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
+ this.handleSwitchMode(reversedType)
+ })
}
componentWillReceiveProps (nextProps) {
- if (nextProps.note.key !== this.props.note.key && !this.isMovingNote) {
+ if (nextProps.note.key !== this.props.note.key && !this.state.isMovingNote) {
if (this.saveQueue != null) this.saveNow()
this.setState({
note: Object.assign({}, nextProps.note)
diff --git a/browser/main/Detail/MarkdownNoteDetail.styl b/browser/main/Detail/MarkdownNoteDetail.styl
index ad20f0f2..b27dc80e 100644
--- a/browser/main/Detail/MarkdownNoteDetail.styl
+++ b/browser/main/Detail/MarkdownNoteDetail.styl
@@ -71,3 +71,8 @@ body[data-theme="solarized-dark"]
.root
border-left 1px solid $ui-solarized-dark-borderColor
background-color $ui-solarized-dark-noteDetail-backgroundColor
+
+body[data-theme="monokai"]
+ .root
+ border-left 1px solid $ui-monokai-borderColor
+ background-color $ui-monokai-noteDetail-backgroundColor
diff --git a/browser/main/Detail/NoteDetailInfo.styl b/browser/main/Detail/NoteDetailInfo.styl
index bc3c9462..8d454203 100644
--- a/browser/main/Detail/NoteDetailInfo.styl
+++ b/browser/main/Detail/NoteDetailInfo.styl
@@ -98,3 +98,7 @@ body[data-theme="solarized-dark"]
border-color $ui-solarized-dark-borderColor
background-color $ui-solarized-dark-noteDetail-backgroundColor
+body[data-theme="monokai"]
+ .info
+ border-color $ui-monokai-borderColor
+ background-color $ui-monokai-noteDetail-backgroundColor
\ No newline at end of file
diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js
index 411027d5..c65f1425 100644
--- a/browser/main/Detail/SnippetNoteDetail.js
+++ b/browser/main/Detail/SnippetNoteDetail.js
@@ -18,6 +18,7 @@ import context from 'browser/lib/context'
import ConfigManager from 'browser/main/lib/ConfigManager'
import _ from 'lodash'
import {findNoteTitle} from 'browser/lib/findNoteTitle'
+import convertModeName from 'browser/lib/convertModeName'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import TrashButton from './TrashButton'
import RestoreButton from './RestoreButton'
@@ -29,21 +30,6 @@ import { formatDate } from 'browser/lib/date-formatter'
import i18n from 'browser/lib/i18n'
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
-function pass (name) {
- switch (name) {
- case 'ejs':
- return 'Embedded Javascript'
- case 'html_ruby':
- return 'Embedded Ruby'
- case 'objectivec':
- return 'Objective C'
- case 'text':
- return 'Plain Text'
- default:
- return name
- }
-}
-
const electron = require('electron')
const { remote } = electron
const { Menu, MenuItem, dialog } = remote
@@ -82,7 +68,7 @@ class SnippetNoteDetail extends React.Component {
}
componentWillReceiveProps (nextProps) {
- if (nextProps.note.key !== this.props.note.key && !this.isMovingNote) {
+ if (nextProps.note.key !== this.props.note.key && !this.state.isMovingNote) {
if (this.saveQueue != null) this.saveNow()
const nextNote = Object.assign({
description: ''
@@ -382,11 +368,11 @@ class SnippetNoteDetail extends React.Component {
name: mode
})
}
- this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
+ this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
- this.setState({
- note: this.state.note
- }, () => {
+ this.setState(state => ({
+ note: state.note
+ }), () => {
this.save()
})
}
@@ -395,11 +381,11 @@ class SnippetNoteDetail extends React.Component {
return (e) => {
const snippets = this.state.note.snippets.slice()
snippets[index].mode = name
- this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
+ this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
- this.setState({
- note: this.state.note
- }, () => {
+ this.setState(state => ({
+ note: state.note
+ }), () => {
this.save()
})
@@ -413,10 +399,10 @@ class SnippetNoteDetail extends React.Component {
return (e) => {
const snippets = this.state.note.snippets.slice()
snippets[index].content = this.refs['code-' + index].value
- this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
- this.setState({
- note: this.state.note
- }, () => {
+ this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
+ this.setState(state => ({
+ note: state.note
+ }), () => {
this.save()
})
}
@@ -611,17 +597,17 @@ class SnippetNoteDetail extends React.Component {
}
jumpNextTab () {
- this.setState({
- snippetIndex: (this.state.snippetIndex + 1) % this.state.note.snippets.length
- }, () => {
+ this.setState(state => ({
+ snippetIndex: (state.snippetIndex + 1) % state.note.snippets.length
+ }), () => {
this.focusEditor()
})
}
jumpPrevTab () {
- this.setState({
- snippetIndex: (this.state.snippetIndex - 1 + this.state.note.snippets.length) % this.state.note.snippets.length
- }, () => {
+ this.setState(state => ({
+ snippetIndex: (state.snippetIndex - 1 + state.note.snippets.length) % state.note.snippets.length
+ }), () => {
this.focusEditor()
})
}
@@ -677,7 +663,7 @@ class SnippetNoteDetail extends React.Component {
const viewList = note.snippets.map((snippet, index) => {
const isActive = this.state.snippetIndex === index
- let syntax = CodeMirror.findModeByName(pass(snippet.mode))
+ let syntax = CodeMirror.findModeByName(convertModeName(snippet.mode))
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
return
{
+ value.pop()
+ })
}
reset () {
@@ -96,15 +89,22 @@ class TagSelect extends React.Component {
}
handleTagRemoveButtonClick (tag) {
- return (e) => {
- let { value } = this.props
-
+ this.removeTagByCallback((value, tag) => {
value.splice(value.indexOf(tag), 1)
- value = _.uniq(value)
+ }, tag)
+ }
- this.value = value
- this.props.onChange()
- }
+ removeTagByCallback (callback, tag = null) {
+ let { value } = this.props
+
+ value = _.isArray(value)
+ ? value.slice()
+ : []
+ callback(value, tag)
+ value = _.uniq(value)
+
+ this.value = value
+ this.props.onChange()
}
render () {
@@ -118,7 +118,7 @@ class TagSelect extends React.Component {
>
#{tag}
diff --git a/browser/main/Detail/TagSelect.styl b/browser/main/Detail/TagSelect.styl
index 18d4d2e0..0ff4c6a3 100644
--- a/browser/main/Detail/TagSelect.styl
+++ b/browser/main/Detail/TagSelect.styl
@@ -81,4 +81,20 @@ body[data-theme="solarized-dark"]
.newTag
border-color none
background-color transparent
- color $ui-solarized-dark-text-color
\ No newline at end of file
+ color $ui-solarized-dark-text-color
+
+body[data-theme="monokai"]
+ .tag
+ background-color $ui-monokai-button-backgroundColor
+
+ .tag-removeButton
+ border-color $ui-button--focus-borderColor
+ background-color transparent
+
+ .tag-label
+ color $ui-monokai-text-color
+
+ .newTag
+ border-color none
+ background-color transparent
+ color $ui-monokai-text-color
diff --git a/browser/main/Detail/ToggleModeButton.styl b/browser/main/Detail/ToggleModeButton.styl
index 185a780c..2e7ab5fa 100644
--- a/browser/main/Detail/ToggleModeButton.styl
+++ b/browser/main/Detail/ToggleModeButton.styl
@@ -56,3 +56,10 @@ body[data-theme="solarized-dark"]
.active
background-color #1EC38B
box-shadow 2px 0px 7px #222222
+
+body[data-theme="monokai"]
+ .control-toggleModeButton
+ background-color #272822
+ .active
+ background-color #1EC38B
+ box-shadow 2px 0px 7px #222222
diff --git a/browser/main/Main.js b/browser/main/Main.js
index 9f1c06e7..69b16bc7 100644
--- a/browser/main/Main.js
+++ b/browser/main/Main.js
@@ -16,6 +16,7 @@ import { hashHistory } from 'react-router'
import store from 'browser/main/store'
import i18n from 'browser/lib/i18n'
import { getLocales } from 'browser/lib/Languages'
+import applyShortcuts from 'browser/main/lib/shortcutManager'
const path = require('path')
const electron = require('electron')
const { remote } = electron
@@ -144,7 +145,8 @@ class Main extends React.Component {
const supportedThemes = [
'dark',
'white',
- 'solarized-dark'
+ 'solarized-dark',
+ 'monokai'
]
if (supportedThemes.indexOf(config.ui.theme) !== -1) {
@@ -158,7 +160,7 @@ class Main extends React.Component {
} else {
i18n.setLocale('en')
}
-
+ applyShortcuts()
// Reload all data
dataApi.init()
.then((data) => {
diff --git a/browser/main/NewNoteButton/NewNoteButton.styl b/browser/main/NewNoteButton/NewNoteButton.styl
index 81ff7e8d..e8e4b5f0 100644
--- a/browser/main/NewNoteButton/NewNoteButton.styl
+++ b/browser/main/NewNoteButton/NewNoteButton.styl
@@ -74,4 +74,8 @@ body[data-theme="dark"]
body[data-theme="solarized-dark"]
.root, .root--expanded
- background-color $ui-solarized-dark-noteList-backgroundColor
\ No newline at end of file
+ background-color $ui-solarized-dark-noteList-backgroundColor
+
+body[data-theme="monokai"]
+ .root, .root--expanded
+ background-color $ui-monokai-noteList-backgroundColor
diff --git a/browser/main/NoteList/NoteList.styl b/browser/main/NoteList/NoteList.styl
index 312f5143..ea261208 100644
--- a/browser/main/NoteList/NoteList.styl
+++ b/browser/main/NoteList/NoteList.styl
@@ -113,4 +113,28 @@ body[data-theme="solarized-dark"]
.control-button--active
color $ui-solarized-dark-text-color
&:active
- color $ui-solarized-dark-text-color
\ No newline at end of file
+ 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
diff --git a/browser/main/NoteList/index.js b/browser/main/NoteList/index.js
index e8c09f65..d6b7f846 100644
--- a/browser/main/NoteList/index.js
+++ b/browser/main/NoteList/index.js
@@ -7,6 +7,7 @@ import moment from 'moment'
import _ from 'lodash'
import ee from 'browser/main/lib/eventEmitter'
import dataApi from 'browser/main/lib/dataApi'
+import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
import ConfigManager from 'browser/main/lib/ConfigManager'
import NoteItem from 'browser/components/NoteItem'
import NoteItemSimple from 'browser/components/NoteItemSimple'
@@ -455,12 +456,19 @@ class NoteList extends React.Component {
}
handleDragStart (e, note) {
- const { selectedNoteKeys } = this.state
+ let { selectedNoteKeys } = this.state
+ const noteKey = getNoteKey(note)
+
+ if (!selectedNoteKeys.includes(noteKey)) {
+ selectedNoteKeys = []
+ selectedNoteKeys.push(noteKey)
+ }
+
const notes = this.notes.map((note) => Object.assign({}, note))
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
const noteData = JSON.stringify(selectedNotes)
e.dataTransfer.setData('note', noteData)
- this.setState({ selectedNoteKeys: [] })
+ this.selectNextNote()
}
handleNoteContextMenu (e, uniqueKey) {
@@ -655,6 +663,10 @@ class NoteList extends React.Component {
title: firstNote.title + ' ' + i18n.__('copy'),
content: firstNote.content
})
+ .then((note) => {
+ attachmentManagement.cloneAttachments(firstNote, note)
+ return note
+ })
.then((note) => {
dispatch({
type: 'UPDATE_NOTE',
diff --git a/browser/main/SideNav/SideNav.styl b/browser/main/SideNav/SideNav.styl
index 666ae0cd..ecab70d0 100644
--- a/browser/main/SideNav/SideNav.styl
+++ b/browser/main/SideNav/SideNav.styl
@@ -117,3 +117,8 @@ body[data-theme="solarized-dark"]
.root, .root--folded
background-color $ui-solarized-dark-backgroundColor
border-right 1px solid $ui-solarized-dark-borderColor
+
+body[data-theme="monokai"]
+ .root, .root--folded
+ background-color $ui-monokai-backgroundColor
+ border-right 1px solid $ui-monokai-borderColor
diff --git a/browser/main/SideNav/StorageItem.js b/browser/main/SideNav/StorageItem.js
index a4d74c30..93e9157f 100644
--- a/browser/main/SideNav/StorageItem.js
+++ b/browser/main/SideNav/StorageItem.js
@@ -14,6 +14,8 @@ import i18n from 'browser/lib/i18n'
const { remote } = require('electron')
const { Menu, dialog } = remote
+const escapeStringRegexp = require('escape-string-regexp')
+const path = require('path')
class StorageItem extends React.Component {
constructor (props) {
@@ -201,7 +203,7 @@ class StorageItem extends React.Component {
createdNoteData.forEach((newNote) => {
dispatch({
type: 'MOVE_NOTE',
- originNote: noteData.find((note) => note.content === newNote.content),
+ originNote: noteData.find((note) => note.content === newNote.oldContent),
note: newNote
})
})
@@ -223,7 +225,8 @@ class StorageItem extends React.Component {
const { folderNoteMap, trashedSet } = data
const SortableStorageItemChild = SortableElement(StorageItemChild)
const folderList = storage.folders.map((folder, index) => {
- const isActive = !!(location.pathname.match(new RegExp('\/storages\/' + storage.key + '\/folders\/' + folder.key)))
+ let folderRegex = new RegExp(escapeStringRegexp(path.sep) + 'storages' + escapeStringRegexp(path.sep) + storage.key + escapeStringRegexp(path.sep) + 'folders' + escapeStringRegexp(path.sep) + folder.key)
+ const isActive = !!(location.pathname.match(folderRegex))
const noteSet = folderNoteMap.get(storage.key + '-' + folder.key)
let noteCount = 0
@@ -253,7 +256,7 @@ class StorageItem extends React.Component {
)
})
- const isActive = location.pathname.match(new RegExp('\/storages\/' + storage.key + '$'))
+ const isActive = location.pathname.match(new RegExp(escapeStringRegexp(path.sep) + 'storages' + escapeStringRegexp(path.sep) + storage.key + '$'))
return (
({ name, size: tag.size, related: relatedTags.has(name) })
- ), ['name'])
+ ), ['name']).filter(
+ tag => tag.size > 0
+ )
if (config.sortTagsBy === 'COUNTER') {
tagList = _.sortBy(tagList, item => (0 - item.size))
}
diff --git a/browser/main/StatusBar/StatusBar.styl b/browser/main/StatusBar/StatusBar.styl
index 9f189fec..52cc4b02 100644
--- a/browser/main/StatusBar/StatusBar.styl
+++ b/browser/main/StatusBar/StatusBar.styl
@@ -69,3 +69,14 @@ body[data-theme="dark"]
navDarkButtonColor()
border-color $ui-dark-borderColor
border-left 1px solid $ui-dark-borderColor
+
+body[data-theme="monokai"]
+ navButtonColor()
+ .zoom
+ border-color $ui-dark-borderColor
+ color $ui-monokai-text-color
+ &:hover
+ transition 0.15s
+ color $ui-monokai-active-color
+ &:active
+ color $ui-monokai-active-color
diff --git a/browser/main/TopBar/TopBar.styl b/browser/main/TopBar/TopBar.styl
index 0956571f..7654f66f 100644
--- a/browser/main/TopBar/TopBar.styl
+++ b/browser/main/TopBar/TopBar.styl
@@ -234,3 +234,25 @@ body[data-theme="solarized-dark"]
input
background-color $ui-solarized-dark-noteList-backgroundColor
color $ui-solarized-dark-text-color
+
+body[data-theme="monokai"]
+ .root, .root--expanded
+ background-color $ui-monokai-noteList-backgroundColor
+
+ .control
+ border-color $ui-monokai-borderColor
+ .control-search
+ background-color $ui-monokai-noteList-backgroundColor
+
+ .control-search-icon
+ absolute top bottom left
+ line-height 32px
+ width 35px
+ color $ui-monokai-inactive-text-color
+ background-color $ui-monokai-noteList-backgroundColor
+
+ .control-search-input
+ background-color $ui-monokai-noteList-backgroundColor
+ input
+ background-color $ui-monokai-noteList-backgroundColor
+ color $ui-monokai-text-color
diff --git a/browser/main/global.styl b/browser/main/global.styl
index 613c7611..7025163f 100644
--- a/browser/main/global.styl
+++ b/browser/main/global.styl
@@ -134,4 +134,10 @@ body[data-theme="solarized-dark"]
.sortableItemHelper
color: $ui-solarized-dark-text-color
+body[data-theme="monokai"]
+ .ModalBase
+ .modalBack
+ background-color $ui-monokai-backgroundColor
+ .sortableItemHelper
+ color: $ui-monokai-text-color
diff --git a/browser/main/lib/ConfigManager.js b/browser/main/lib/ConfigManager.js
index 3e1a2162..228692d6 100644
--- a/browser/main/lib/ConfigManager.js
+++ b/browser/main/lib/ConfigManager.js
@@ -1,6 +1,7 @@
import _ from 'lodash'
import RcParser from 'browser/lib/RcParser'
import i18n from 'browser/lib/i18n'
+import ee from 'browser/main/lib/eventEmitter'
const OSX = global.process.platform === 'darwin'
const win = global.process.platform === 'win32'
@@ -20,7 +21,8 @@ export const DEFAULT_CONFIG = {
listStyle: 'DEFAULT', // 'DEFAULT', 'SMALL'
amaEnabled: true,
hotkey: {
- toggleMain: OSX ? 'Cmd + Alt + L' : 'Super + Alt + E'
+ toggleMain: OSX ? 'Cmd + Alt + L' : 'Super + Alt + E',
+ toggleMode: OSX ? 'Cmd + M' : 'Ctrl + M'
},
ui: {
language: 'en',
@@ -53,8 +55,10 @@ export const DEFAULT_CONFIG = {
latexInlineClose: '$',
latexBlockOpen: '$$',
latexBlockClose: '$$',
+ plantUMLServerAddress: 'http://www.plantuml.com/plantuml',
scrollPastEnd: false,
smartQuotes: true,
+ breaks: true,
sanitize: 'STRICT' // 'STRICT', 'ALLOW_STYLES', 'NONE'
},
blog: {
@@ -135,6 +139,8 @@ function set (updates) {
document.body.setAttribute('data-theme', 'white')
} else if (newConfig.ui.theme === 'solarized-dark') {
document.body.setAttribute('data-theme', 'solarized-dark')
+ } else if (newConfig.ui.theme === 'monokai') {
+ document.body.setAttribute('data-theme', 'monokai')
} else {
document.body.setAttribute('data-theme', 'default')
}
@@ -163,6 +169,7 @@ function set (updates) {
ipcRenderer.send('config-renew', {
config: get()
})
+ ee.emit('config-renew')
}
function assignConfigValues (originalConfig, rcConfig) {
diff --git a/browser/main/lib/dataApi/attachmentManagement.js b/browser/main/lib/dataApi/attachmentManagement.js
index ac0986e1..893e03d1 100644
--- a/browser/main/lib/dataApi/attachmentManagement.js
+++ b/browser/main/lib/dataApi/attachmentManagement.js
@@ -3,6 +3,9 @@ const fs = require('fs')
const path = require('path')
const findStorage = require('browser/lib/findStorage')
const mdurl = require('mdurl')
+const fse = require('fs-extra')
+const escapeStringRegexp = require('escape-string-regexp')
+const sander = require('sander')
const STORAGE_FOLDER_PLACEHOLDER = ':storage'
const DESTINATION_FOLDER = 'attachments'
@@ -39,7 +42,7 @@ function copyAttachment (sourceFilePath, storageKey, noteKey, useRandomName = tr
const targetStorage = findStorage.findStorage(storageKey)
- const inputFile = fs.createReadStream(sourceFilePath)
+ const inputFileStream = fs.createReadStream(sourceFilePath)
let destinationName
if (useRandomName) {
destinationName = `${uniqueSlug()}${path.extname(sourceFilePath)}`
@@ -49,8 +52,10 @@ function copyAttachment (sourceFilePath, storageKey, noteKey, useRandomName = tr
const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
createAttachmentDestinationFolder(targetStorage.path, noteKey)
const outputFile = fs.createWriteStream(path.join(destinationDir, destinationName))
- inputFile.pipe(outputFile)
- resolve(destinationName)
+ inputFileStream.pipe(outputFile)
+ inputFileStream.on('end', () => {
+ resolve(destinationName)
+ })
} catch (e) {
return reject(e)
}
@@ -146,19 +151,176 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem
base64data = reader.result.replace(/^data:image\/png;base64,/, '')
base64data += base64data.replace('+', ' ')
const binaryData = new Buffer(base64data, 'base64').toString('binary')
- fs.writeFile(imagePath, binaryData, 'binary')
+ fs.writeFileSync(imagePath, binaryData, 'binary')
const imageMd = generateAttachmentMarkdown(imageName, imagePath, true)
codeEditor.insertAttachmentMd(imageMd)
}
reader.readAsDataURL(blob)
}
+/**
+ * @description Returns all attachment paths of the given markdown
+ * @param {String} markdownContent content in which the attachment paths should be found
+ * @returns {String[]} Array of the relative paths (starting with :storage) of the attachments of the given markdown
+ */
+function getAttachmentsInContent (markdownContent) {
+ const preparedInput = markdownContent.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep)
+ const regexp = new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + '([a-zA-Z0-9]|-)+' + escapeStringRegexp(path.sep) + '[a-zA-Z0-9]+(\\.[a-zA-Z0-9]+)?', 'g')
+ return preparedInput.match(regexp)
+}
+
+/**
+ * @description Returns an array of the absolute paths of the attachments referenced in the given markdown code
+ * @param {String} markdownContent content in which the attachment paths should be found
+ * @param {String} storagePath path of the current storage
+ * @returns {String[]} Absolute paths of the referenced attachments
+ */
+function getAbsolutePathsOfAttachmentsInContent (markdownContent, storagePath) {
+ const temp = getAttachmentsInContent(markdownContent) || []
+ const result = []
+ for (const relativePath of temp) {
+ result.push(relativePath.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER, 'g'), path.join(storagePath, DESTINATION_FOLDER)))
+ }
+ return result
+}
+
+/**
+ * @description Moves the attachments of the current note to the new location.
+ * Returns a modified version of the given content so that the links to the attachments point to the new note key.
+ * @param {String} oldPath Source of the note to be moved
+ * @param {String} newPath Destination of the note to be moved
+ * @param {String} noteKey Old note key
+ * @param {String} newNoteKey New note key
+ * @param {String} noteContent Content of the note to be moved
+ * @returns {String} Modified version of noteContent in which the paths of the attachments are fixed
+ */
+function moveAttachments (oldPath, newPath, noteKey, newNoteKey, noteContent) {
+ const src = path.join(oldPath, DESTINATION_FOLDER, noteKey)
+ const dest = path.join(newPath, DESTINATION_FOLDER, newNoteKey)
+ if (fse.existsSync(src)) {
+ fse.moveSync(src, dest)
+ }
+ return replaceNoteKeyWithNewNoteKey(noteContent, noteKey, newNoteKey)
+}
+
+/**
+ * Modifies the given content so that in all attachment references the oldNoteKey is replaced by the new one
+ * @param noteContent content that should be modified
+ * @param oldNoteKey note key to be replaced
+ * @param newNoteKey note key serving as a replacement
+ * @returns {String} modified note content
+ */
+function replaceNoteKeyWithNewNoteKey (noteContent, oldNoteKey, newNoteKey) {
+ if (noteContent) {
+ return noteContent.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + oldNoteKey, 'g'), path.join(STORAGE_FOLDER_PLACEHOLDER, newNoteKey))
+ }
+ return noteContent
+}
+
+/**
+ * @description Deletes all :storage and noteKey references from the given input.
+ * @param input Input in which the references should be deleted
+ * @param noteKey Key of the current note
+ * @returns {String} Input without the references
+ */
+function removeStorageAndNoteReferences (input, noteKey) {
+ return input.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep).replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + noteKey, 'g'), DESTINATION_FOLDER)
+}
+
+/**
+ * @description Deletes the attachment folder specified by the given storageKey and noteKey
+ * @param storageKey Key of the storage of the note to be deleted
+ * @param noteKey Key of the note to be deleted
+ */
+function deleteAttachmentFolder (storageKey, noteKey) {
+ const storagePath = findStorage.findStorage(storageKey)
+ const noteAttachmentPath = path.join(storagePath.path, DESTINATION_FOLDER, noteKey)
+ sander.rimrafSync(noteAttachmentPath)
+}
+
+/**
+ * @description Deletes all attachments stored in the attachment folder of the give not that are not referenced in the markdownContent
+ * @param markdownContent Content of the note. All unreferenced notes will be deleted
+ * @param storageKey StorageKey of the current note. Is used to determine the belonging attachment folder.
+ * @param noteKey NoteKey of the current note. Is used to determine the belonging attachment folder.
+ */
+function deleteAttachmentsNotPresentInNote (markdownContent, storageKey, noteKey) {
+ const targetStorage = findStorage.findStorage(storageKey)
+ const attachmentFolder = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
+ const attachmentsInNote = getAttachmentsInContent(markdownContent)
+ const attachmentsInNoteOnlyFileNames = []
+ if (attachmentsInNote) {
+ for (let i = 0; i < attachmentsInNote.length; i++) {
+ attachmentsInNoteOnlyFileNames.push(attachmentsInNote[i].replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + noteKey + escapeStringRegexp(path.sep), 'g'), ''))
+ }
+ }
+
+ if (fs.existsSync(attachmentFolder)) {
+ fs.readdir(attachmentFolder, (err, files) => {
+ if (err) {
+ console.error("Error reading directory '" + attachmentFolder + "'. Error:")
+ console.error(err)
+ return
+ }
+ files.forEach(file => {
+ if (!attachmentsInNoteOnlyFileNames.includes(file)) {
+ const absolutePathOfFile = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey, file)
+ fs.unlink(absolutePathOfFile, (err) => {
+ if (err) {
+ console.error("Could not delete '%s'", absolutePathOfFile)
+ console.error(err)
+ return
+ }
+ console.info("File '" + absolutePathOfFile + "' deleted because it was not included in the content of the note")
+ })
+ }
+ })
+ })
+ } else {
+ console.info("Attachment folder ('" + attachmentFolder + "') did not exist..")
+ }
+}
+
+/**
+ * Clones the attachments of a given note.
+ * Copies the attachments to their new destination and updates the content of the new note so that the attachment-links again point to the correct destination.
+ * @param oldNote Note that is being cloned
+ * @param newNote Clone of the note
+ */
+function cloneAttachments (oldNote, newNote) {
+ if (newNote.type === 'MARKDOWN_NOTE') {
+ const oldStorage = findStorage.findStorage(oldNote.storage)
+ const newStorage = findStorage.findStorage(newNote.storage)
+ const attachmentsPaths = getAbsolutePathsOfAttachmentsInContent(oldNote.content, oldStorage.path) || []
+
+ const destinationFolder = path.join(newStorage.path, DESTINATION_FOLDER, newNote.key)
+ if (!sander.existsSync(destinationFolder)) {
+ sander.mkdirSync(destinationFolder)
+ }
+
+ for (const attachment of attachmentsPaths) {
+ const destination = path.join(newStorage.path, DESTINATION_FOLDER, newNote.key, path.basename(attachment))
+ sander.copyFileSync(attachment).to(destination)
+ }
+ newNote.content = replaceNoteKeyWithNewNoteKey(newNote.content, oldNote.key, newNote.key)
+ } else {
+ console.debug('Cloning of the attachment was skipped since it only works for MARKDOWN_NOTEs')
+ }
+}
+
module.exports = {
copyAttachment,
fixLocalURLS,
generateAttachmentMarkdown,
handleAttachmentDrop,
handlePastImageEvent,
+ getAttachmentsInContent,
+ getAbsolutePathsOfAttachmentsInContent,
+ removeStorageAndNoteReferences,
+ deleteAttachmentFolder,
+ deleteAttachmentsNotPresentInNote,
+ moveAttachments,
+ cloneAttachments,
STORAGE_FOLDER_PLACEHOLDER,
DESTINATION_FOLDER
}
diff --git a/browser/main/lib/dataApi/copyImage.js b/browser/main/lib/dataApi/copyImage.js
deleted file mode 100644
index 24053bdd..00000000
--- a/browser/main/lib/dataApi/copyImage.js
+++ /dev/null
@@ -1,38 +0,0 @@
-const fs = require('fs')
-const path = require('path')
-const { findStorage } = require('browser/lib/findStorage')
-
-// TODO: ehhc: delete this
-
-/**
- * @description Copy an image and return the path.
- * @param {String} filePath
- * @param {String} storageKey
- * @param {Boolean} rename create new filename or leave the old one
- * @return {Promise
} an image path
- */
-function copyImage (filePath, storageKey, rename = true) {
- return new Promise((resolve, reject) => {
- try {
- const targetStorage = findStorage(storageKey)
-
- const inputImage = fs.createReadStream(filePath)
- const imageExt = path.extname(filePath)
- const imageName = rename ? Math.random().toString(36).slice(-16) : path.basename(filePath, imageExt)
- const basename = `${imageName}${imageExt}`
- const imageDir = path.join(targetStorage.path, 'images')
- if (!fs.existsSync(imageDir)) fs.mkdirSync(imageDir)
- const outputImage = fs.createWriteStream(path.join(imageDir, basename))
- outputImage.on('error', reject)
- inputImage.on('error', reject)
- inputImage.on('end', () => {
- resolve(basename)
- })
- inputImage.pipe(outputImage)
- } catch (e) {
- return reject(e)
- }
- })
-}
-
-module.exports = copyImage
diff --git a/browser/main/lib/dataApi/deleteFolder.js b/browser/main/lib/dataApi/deleteFolder.js
index 908677e1..0c7486f5 100644
--- a/browser/main/lib/dataApi/deleteFolder.js
+++ b/browser/main/lib/dataApi/deleteFolder.js
@@ -5,6 +5,7 @@ const resolveStorageNotes = require('./resolveStorageNotes')
const CSON = require('@rokt33r/season')
const sander = require('sander')
const { findStorage } = require('browser/lib/findStorage')
+const deleteSingleNote = require('./deleteNote')
/**
* @param {String} storageKey
@@ -49,11 +50,7 @@ function deleteFolder (storageKey, folderKey) {
const deleteAllNotes = targetNotes
.map(function deleteNote (note) {
- const notePath = path.join(storage.path, 'notes', note.key + '.cson')
- return sander.unlink(notePath)
- .catch(function (err) {
- console.warn('Failed to delete', notePath, err)
- })
+ return deleteSingleNote(storageKey, note.key)
})
return Promise.all(deleteAllNotes)
.then(() => storage)
diff --git a/browser/main/lib/dataApi/deleteNote.js b/browser/main/lib/dataApi/deleteNote.js
index 49498a30..46ec2b55 100644
--- a/browser/main/lib/dataApi/deleteNote.js
+++ b/browser/main/lib/dataApi/deleteNote.js
@@ -1,6 +1,7 @@
const resolveStorageData = require('./resolveStorageData')
const path = require('path')
const sander = require('sander')
+const attachmentManagement = require('./attachmentManagement')
const { findStorage } = require('browser/lib/findStorage')
function deleteNote (storageKey, noteKey) {
@@ -25,6 +26,10 @@ function deleteNote (storageKey, noteKey) {
storageKey
}
})
+ .then(function deleteAttachments (storageInfo) {
+ attachmentManagement.deleteAttachmentFolder(storageInfo.storageKey, storageInfo.noteKey)
+ return storageInfo
+ })
}
module.exports = deleteNote
diff --git a/browser/main/lib/dataApi/exportNote.js b/browser/main/lib/dataApi/exportNote.js
index 71f7d017..e4fec5f4 100755
--- a/browser/main/lib/dataApi/exportNote.js
+++ b/browser/main/lib/dataApi/exportNote.js
@@ -1,14 +1,9 @@
import copyFile from 'browser/main/lib/dataApi/copyFile'
import { findStorage } from 'browser/lib/findStorage'
-import filenamify from 'filenamify'
const fs = require('fs')
const path = require('path')
-const LOCAL_STORED_REGEX = /!\[(.*?)]\(\s*?\/:storage\/(.*\.\S*?)\)/gi
-// TODO: ehhc: check this -> attachmentManagement
-const IMAGES_FOLDER_NAME = 'images'
-
/**
* Export note together with images
*
@@ -29,21 +24,7 @@ function exportNote (storageKey, noteContent, targetPath, outputFormatter) {
throw new Error('Storage path is not found')
}
- let exportedData = noteContent.replace(LOCAL_STORED_REGEX, (match, dstFilename, srcFilename) => {
- dstFilename = filenamify(dstFilename, {replacement: '_'})
- if (!path.extname(dstFilename)) {
- dstFilename += path.extname(srcFilename)
- }
-
- const dstRelativePath = path.join(IMAGES_FOLDER_NAME, dstFilename)
-
- exportTasks.push({
- src: path.join(IMAGES_FOLDER_NAME, srcFilename),
- dst: dstRelativePath
- })
-
- return ``
- })
+ let exportedData = noteContent
if (outputFormatter) {
exportedData = outputFormatter(exportedData, exportTasks)
diff --git a/browser/main/lib/dataApi/moveNote.js b/browser/main/lib/dataApi/moveNote.js
index cffb5c53..2d306cdf 100644
--- a/browser/main/lib/dataApi/moveNote.js
+++ b/browser/main/lib/dataApi/moveNote.js
@@ -6,7 +6,7 @@ const CSON = require('@rokt33r/season')
const keygen = require('browser/lib/keygen')
const sander = require('sander')
const { findStorage } = require('browser/lib/findStorage')
-const copyImage = require('./copyImage')
+const attachmentManagement = require('./attachmentManagement')
function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
let oldStorage, newStorage
@@ -64,35 +64,20 @@ function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
noteData.key = newNoteKey
noteData.storage = newStorageKey
noteData.updatedAt = new Date()
+ noteData.oldContent = noteData.content
return noteData
})
- .then(function moveImages (noteData) {
- if (oldStorage.path === newStorage.path) return noteData
-
- const searchImagesRegex = /!\[.*?]\(\s*?\/:storage\/(.*\.\S*?)\)/gi
- let match = searchImagesRegex.exec(noteData.content)
-
- const moveTasks = []
- while (match != null) {
- const [, filename] = match
- const oldPath = path.join(oldStorage.path, 'images', filename)
- // TODO: ehhc: attachmentManagement
- moveTasks.push(
- copyImage(oldPath, noteData.storage, false)
- .then(() => {
- fs.unlinkSync(oldPath)
- })
- )
-
- // find next occurence
- match = searchImagesRegex.exec(noteData.content)
+ .then(function moveAttachments (noteData) {
+ if (oldStorage.path === newStorage.path) {
+ return noteData
}
- return Promise.all(moveTasks).then(() => noteData)
+ noteData.content = attachmentManagement.moveAttachments(oldStorage.path, newStorage.path, noteKey, newNoteKey, noteData.content)
+ return noteData
})
.then(function writeAndReturn (noteData) {
- CSON.writeFileSync(path.join(newStorage.path, 'notes', noteData.key + '.cson'), _.omit(noteData, ['key', 'storage']))
+ CSON.writeFileSync(path.join(newStorage.path, 'notes', noteData.key + '.cson'), _.omit(noteData, ['key', 'storage', 'oldContent']))
return noteData
})
.then(function deleteOldNote (data) {
diff --git a/browser/main/lib/shortcut.js b/browser/main/lib/shortcut.js
new file mode 100644
index 00000000..a6f33196
--- /dev/null
+++ b/browser/main/lib/shortcut.js
@@ -0,0 +1,7 @@
+import ee from 'browser/main/lib/eventEmitter'
+
+module.exports = {
+ 'toggleMode': () => {
+ ee.emit('topbar:togglemodebutton')
+ }
+}
diff --git a/browser/main/lib/shortcutManager.js b/browser/main/lib/shortcutManager.js
new file mode 100644
index 00000000..2b937aea
--- /dev/null
+++ b/browser/main/lib/shortcutManager.js
@@ -0,0 +1,40 @@
+import Mousetrap from 'mousetrap'
+import CM from 'browser/main/lib/ConfigManager'
+import ee from 'browser/main/lib/eventEmitter'
+import { isObjectEqual } from 'browser/lib/utils'
+require('mousetrap-global-bind')
+const functions = require('./shortcut')
+
+let shortcuts = CM.get().hotkey
+
+ee.on('config-renew', function () {
+ // only update if hotkey changed !
+ const newHotkey = CM.get().hotkey
+ if (!isObjectEqual(newHotkey, shortcuts)) {
+ updateShortcut(newHotkey)
+ }
+})
+
+function updateShortcut (newHotkey) {
+ Mousetrap.reset()
+ shortcuts = newHotkey
+ applyShortcuts(newHotkey)
+}
+
+function formatShortcut (shortcut) {
+ return shortcut.toLowerCase().replace(/ /g, '')
+}
+
+function applyShortcuts (shortcuts) {
+ for (const shortcut in shortcuts) {
+ const toggler = formatShortcut(shortcuts[shortcut])
+ // only bind if the function for that shortcut exists
+ if (functions[shortcut]) {
+ Mousetrap.bindGlobal(toggler, functions[shortcut])
+ }
+ }
+}
+
+applyShortcuts(CM.get().hotkey)
+
+module.exports = applyShortcuts
diff --git a/browser/main/modals/CreateFolderModal.styl b/browser/main/modals/CreateFolderModal.styl
index 45f2e852..1b96e123 100644
--- a/browser/main/modals/CreateFolderModal.styl
+++ b/browser/main/modals/CreateFolderModal.styl
@@ -102,3 +102,29 @@ body[data-theme="solarized-dark"]
.control-confirmButton
colorSolarizedDarkPrimaryButton()
+
+body[data-theme="monokai"]
+ .root
+ modalMonokai()
+ width 500px
+ height 270px
+ overflow hidden
+ position relative
+
+ .header
+ background-color transparent
+ border-color $ui-dark-borderColor
+ color $ui-monokai-text-color
+
+ .control-folder-label
+ color $ui-monokai-text-color
+
+ .control-folder-input
+ border 1px solid $ui-input--create-folder-modal
+ color white
+
+ .description
+ color $ui-inactive-text-color
+
+ .control-confirmButton
+ colorMonokaiPrimaryButton()
diff --git a/browser/main/modals/NewNoteModal.styl b/browser/main/modals/NewNoteModal.styl
index 748ab88c..db14133f 100644
--- a/browser/main/modals/NewNoteModal.styl
+++ b/browser/main/modals/NewNoteModal.styl
@@ -81,3 +81,19 @@ body[data-theme="solarized-dark"]
.description
color $ui-solarized-dark-text-color
+body[data-theme="monokai"]
+ .root
+ background-color transparent
+
+ .header
+ color $ui-monokai-text-color
+
+ .control-button
+ border-color $ui-monokai-borderColor
+ color $ui-monokai-text-color
+ background-color transparent
+ &:focus
+ colorDarkPrimaryButton()
+
+ .description
+ color $ui-monokai-text-color
diff --git a/browser/main/modals/PreferencesModal/ConfigTab.styl b/browser/main/modals/PreferencesModal/ConfigTab.styl
index f6f7ace9..0e5f81fb 100644
--- a/browser/main/modals/PreferencesModal/ConfigTab.styl
+++ b/browser/main/modals/PreferencesModal/ConfigTab.styl
@@ -133,6 +133,11 @@ colorSolarizedDarkControl()
background-color $ui-solarized-dark-button-backgroundColor
color $ui-solarized-dark-text-color
+colorMonokaiControl()
+ border none
+ background-color $ui-monokai-button-backgroundColor
+ color $ui-monokai-text-color
+
body[data-theme="dark"]
.root
@@ -189,4 +194,29 @@ body[data-theme="solarized-dark"]
select, .group-section-control-input
colorSolarizedDarkControl()
+body[data-theme="monokai"]
+ .root
+ color $ui-monokai-text-color
+ .group-header
+ color $ui-monokai-text-color
+ border-color $ui-monokai-borderColor
+
+ .group-header2
+ color $ui-monokai-text-color
+
+ .group-section-control-input
+ border-color $ui-monokai-borderColor
+
+ .group-control
+ border-color $ui-monokai-borderColor
+ .group-control-leftButton
+ colorDarkDefaultButton()
+ border-color $ui-monokai-borderColor
+ .group-control-rightButton
+ colorMonokaiPrimaryButton()
+ .group-hint
+ colorMonokaiControl()
+ .group-section-control
+ select, .group-section-control-input
+ colorMonokaiControl()
diff --git a/browser/main/modals/PreferencesModal/Crowdfunding.styl b/browser/main/modals/PreferencesModal/Crowdfunding.styl
index 930c33f0..3d4af539 100644
--- a/browser/main/modals/PreferencesModal/Crowdfunding.styl
+++ b/browser/main/modals/PreferencesModal/Crowdfunding.styl
@@ -33,4 +33,10 @@ body[data-theme="solarized-dark"]
.root
color $ui-solarized-dark-text-color
p
- color $ui-solarized-dark-text-color
\ No newline at end of file
+ color $ui-solarized-dark-text-color
+
+body[data-theme="monokai"]
+ .root
+ color $ui-monokai-text-color
+ p
+ color $ui-monokai-text-color
diff --git a/browser/main/modals/PreferencesModal/FolderItem.styl b/browser/main/modals/PreferencesModal/FolderItem.styl
index acc4cbfb..8bcf2b02 100644
--- a/browser/main/modals/PreferencesModal/FolderItem.styl
+++ b/browser/main/modals/PreferencesModal/FolderItem.styl
@@ -126,3 +126,26 @@ body[data-theme="solarized-dark"]
.folderItem-right-dangerButton
colorSolarizedDarkPrimaryButton()
+
+body[data-theme="monokai"]
+ .folderItem
+ &:hover
+ background-color $ui-monokai-button-backgroundColor
+
+ .folderItem-left-danger
+ color $danger-color
+
+ .folderItem-left-key
+ color $ui-dark-inactive-text-color
+
+ .folderItem-left-colorButton
+ colorMonokaiPrimaryButton()
+
+ .folderItem-right-button
+ colorMonokaiPrimaryButton()
+
+ .folderItem-right-confirmButton
+ colorMonokaiPrimaryButton()
+
+ .folderItem-right-dangerButton
+ colorMonokaiPrimaryButton()
diff --git a/browser/main/modals/PreferencesModal/HotkeyTab.js b/browser/main/modals/PreferencesModal/HotkeyTab.js
index 8cbf772f..671e1516 100644
--- a/browser/main/modals/PreferencesModal/HotkeyTab.js
+++ b/browser/main/modals/PreferencesModal/HotkeyTab.js
@@ -67,7 +67,8 @@ class HotkeyTab extends React.Component {
handleHotkeyChange (e) {
const { config } = this.state
config.hotkey = {
- toggleMain: this.refs.toggleMain.value
+ toggleMain: this.refs.toggleMain.value,
+ toggleMode: this.refs.toggleMode.value
}
this.setState({
config
@@ -115,6 +116,17 @@ class HotkeyTab extends React.Component {
/>
+
+
{i18n.__('Toggle editor mode')}
+
+ this.handleHotkeyChange(e)}
+ ref='toggleMode'
+ value={config.hotkey.toggleMode}
+ type='text'
+ />
+
+
@@ -474,6 +477,16 @@ class UiTab extends React.Component {
Enable smart quotes
+
+
+
@@ -543,6 +556,19 @@ class UiTab extends React.Component {
/>
+
+
+ {i18n.__('PlantUML Server')}
+
+
+ this.handleUIChange(e)}
+ type='text'
+ />
+
+
diff --git a/tests/dataApi/attachmentManagement.test.js b/tests/dataApi/attachmentManagement.test.js
index c90e1961..b61d8bf9 100644
--- a/tests/dataApi/attachmentManagement.test.js
+++ b/tests/dataApi/attachmentManagement.test.js
@@ -7,6 +7,9 @@ const findStorage = require('browser/lib/findStorage')
jest.mock('unique-slug')
const uniqueSlug = require('unique-slug')
const mdurl = require('mdurl')
+const fse = require('fs-extra')
+jest.mock('sander')
+const sander = require('sander')
const systemUnderTest = require('browser/main/lib/dataApi/attachmentManagement')
@@ -48,11 +51,13 @@ it('should test that copyAttachment works correctly assuming correct working of
const noteKey = 'noteKey'
const dummyUniquePath = 'dummyPath'
const dummyStorage = {path: 'dummyStoragePath'}
+ const dummyReadStream = {}
+ dummyReadStream.pipe = jest.fn()
+ dummyReadStream.on = jest.fn((event, callback) => { callback() })
fs.existsSync = jest.fn()
fs.existsSync.mockReturnValue(true)
- fs.createReadStream = jest.fn()
- fs.createReadStream.mockReturnValue({pipe: jest.fn()})
+ fs.createReadStream = jest.fn(() => dummyReadStream)
fs.createWriteStream = jest.fn()
findStorage.findStorage = jest.fn()
@@ -75,7 +80,11 @@ it('should test that copyAttachment creates a new folder if the attachment folde
const noteKey = 'noteKey'
const attachmentFolderPath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER)
const attachmentFolderNoteKyPath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, noteKey)
+ const dummyReadStream = {}
+ dummyReadStream.pipe = jest.fn()
+ dummyReadStream.on = jest.fn()
+ fs.createReadStream = jest.fn(() => dummyReadStream)
fs.existsSync = jest.fn()
fs.existsSync.mockReturnValueOnce(true)
fs.existsSync.mockReturnValueOnce(false)
@@ -97,7 +106,11 @@ it('should test that copyAttachment creates a new folder if the attachment folde
it('should test that copyAttachment don\'t uses a random file name if not intended ', function () {
const dummyStorage = {path: 'dummyStoragePath'}
+ const dummyReadStream = {}
+ dummyReadStream.pipe = jest.fn()
+ dummyReadStream.on = jest.fn()
+ fs.createReadStream = jest.fn(() => dummyReadStream)
fs.existsSync = jest.fn()
fs.existsSync.mockReturnValueOnce(true)
fs.existsSync.mockReturnValueOnce(false)
@@ -142,13 +155,13 @@ it('should replace the all ":storage" path with the actual storage path', functi
' \n' +
' Headline
\n' +
' \n' +
- '
\n' +
+ '
\n' +
'
\n' +
' \n' +
- ' dummyPDF.pdf\n' +
+ ' dummyPDF.pdf\n' +
'
\n' +
' \n' +
- '
\n' +
+ '
\n' +
'
\n' +
' \n' +
'