mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-13 17:56:25 +00:00
resolved conflict
This commit is contained in:
@@ -19,5 +19,8 @@
|
|||||||
"FileReader": true,
|
"FileReader": true,
|
||||||
"localStorage": true,
|
"localStorage": true,
|
||||||
"fetch": true
|
"fetch": true
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"jest": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- 6
|
- 7
|
||||||
script:
|
script:
|
||||||
- npm run lint && npm run test
|
- 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'
|
- 'if [[ ${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH} = "master" ]]; then npm install -g grunt npm@5.2 && grunt pre-build; fi'
|
||||||
after_success:
|
after_success:
|
||||||
- openssl aes-256-cbc -K $encrypted_440d7f9a3c38_key -iv $encrypted_440d7f9a3c38_iv
|
- openssl aes-256-cbc -K $encrypted_440d7f9a3c38_key -iv $encrypted_440d7f9a3c38_iv
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import _ from 'lodash'
|
|||||||
import CodeMirror from 'codemirror'
|
import CodeMirror from 'codemirror'
|
||||||
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 eventEmitter from 'browser/main/lib/eventEmitter'
|
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
import iconv from 'iconv-lite'
|
import iconv from 'iconv-lite'
|
||||||
import crypto from 'crypto'
|
import crypto from 'crypto'
|
||||||
@@ -17,21 +18,6 @@ const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', '
|
|||||||
const buildCMRulers = (rulers, enableRulers) =>
|
const buildCMRulers = (rulers, enableRulers) =>
|
||||||
enableRulers ? rulers.map(ruler => ({ column: ruler })) : []
|
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 {
|
export default class CodeEditor extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
@@ -52,6 +38,9 @@ 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
|
||||||
|
attachmentManagement.deleteAttachmentsNotPresentInNote(this.editor.getValue(), storageKey, noteKey)
|
||||||
}
|
}
|
||||||
this.pasteHandler = (editor, e) => this.handlePaste(editor, e)
|
this.pasteHandler = (editor, e) => this.handlePaste(editor, e)
|
||||||
this.loadStyleHandler = (e) => {
|
this.loadStyleHandler = (e) => {
|
||||||
@@ -323,7 +312,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setMode (mode) {
|
setMode (mode) {
|
||||||
let syntax = CodeMirror.findModeByName(pass(mode))
|
let syntax = CodeMirror.findModeByName(convertModeName(mode))
|
||||||
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
||||||
|
|
||||||
this.editor.setOption('mode', syntax.mime)
|
this.editor.setOption('mode', syntax.mime)
|
||||||
|
|||||||
@@ -283,6 +283,7 @@ class MarkdownEditor extends React.Component {
|
|||||||
indentSize={editorIndentSize}
|
indentSize={editorIndentSize}
|
||||||
scrollPastEnd={config.preview.scrollPastEnd}
|
scrollPastEnd={config.preview.scrollPastEnd}
|
||||||
smartQuotes={config.preview.smartQuotes}
|
smartQuotes={config.preview.smartQuotes}
|
||||||
|
breaks={config.preview.breaks}
|
||||||
sanitize={config.preview.sanitize}
|
sanitize={config.preview.sanitize}
|
||||||
ref='preview'
|
ref='preview'
|
||||||
onContextMenu={(e) => this.handleContextMenu(e)}
|
onContextMenu={(e) => this.handleContextMenu(e)}
|
||||||
@@ -294,6 +295,7 @@ class MarkdownEditor extends React.Component {
|
|||||||
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}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import flowchart from 'flowchart'
|
|||||||
import SequenceDiagram from 'js-sequence-diagrams'
|
import SequenceDiagram from 'js-sequence-diagrams'
|
||||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||||
import htmlTextHelper from 'browser/lib/htmlTextHelper'
|
import htmlTextHelper from 'browser/lib/htmlTextHelper'
|
||||||
|
import convertModeName from 'browser/lib/convertModeName'
|
||||||
import copy from 'copy-to-clipboard'
|
import copy from 'copy-to-clipboard'
|
||||||
import mdurl from 'mdurl'
|
import mdurl from 'mdurl'
|
||||||
import exportNote from 'browser/main/lib/dataApi/exportNote'
|
import exportNote from 'browser/main/lib/dataApi/exportNote'
|
||||||
@@ -31,7 +32,7 @@ const CSS_FILES = [
|
|||||||
`${appPath}/node_modules/codemirror/lib/codemirror.css`
|
`${appPath}/node_modules/codemirror/lib/codemirror.css`
|
||||||
]
|
]
|
||||||
|
|
||||||
function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd) {
|
function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme) {
|
||||||
return `
|
return `
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Lato';
|
font-family: 'Lato';
|
||||||
@@ -103,6 +104,13 @@ h2 {
|
|||||||
body p {
|
body p {
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
body[data-theme="${theme}"] {
|
||||||
|
color: #000;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +123,6 @@ if (!OSX) {
|
|||||||
defaultFontFamily.unshift('meiryo')
|
defaultFontFamily.unshift('meiryo')
|
||||||
}
|
}
|
||||||
const defaultCodeBlockFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
|
const defaultCodeBlockFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
|
||||||
|
|
||||||
export default class MarkdownPreview extends React.Component {
|
export default class MarkdownPreview extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
@@ -138,10 +145,11 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
initMarkdown () {
|
initMarkdown () {
|
||||||
const { smartQuotes, sanitize } = this.props
|
const { smartQuotes, sanitize, breaks } = this.props
|
||||||
this.markdown = new Markdown({
|
this.markdown = new Markdown({
|
||||||
typographer: smartQuotes,
|
typographer: smartQuotes,
|
||||||
sanitize
|
sanitize,
|
||||||
|
breaks
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,11 +216,13 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
|
|
||||||
handleSaveAsHtml () {
|
handleSaveAsHtml () {
|
||||||
this.exportAsDocument('html', (noteContent, exportTasks) => {
|
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 files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
||||||
|
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(noteContent, this.props.storagePath)
|
||||||
|
|
||||||
files.forEach((file) => {
|
files.forEach((file) => {
|
||||||
file = file.replace('file://', '')
|
file = file.replace('file://', '')
|
||||||
@@ -221,6 +231,13 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
dst: 'css'
|
dst: 'css'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
attachmentsAbsolutePaths.forEach((attachment) => {
|
||||||
|
exportTasks.push({
|
||||||
|
src: attachment,
|
||||||
|
dst: attachmentManagement.DESTINATION_FOLDER
|
||||||
|
})
|
||||||
|
})
|
||||||
|
body = attachmentManagement.removeStorageAndNoteReferences(body, this.props.noteKey)
|
||||||
|
|
||||||
let styles = ''
|
let styles = ''
|
||||||
files.forEach((file) => {
|
files.forEach((file) => {
|
||||||
@@ -324,7 +341,9 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
componentDidUpdate (prevProps) {
|
||||||
if (prevProps.value !== this.props.value) this.rewriteIframe()
|
if (prevProps.value !== this.props.value) this.rewriteIframe()
|
||||||
if (prevProps.smartQuotes !== this.props.smartQuotes || prevProps.sanitize !== this.props.sanitize) {
|
if (prevProps.smartQuotes !== this.props.smartQuotes ||
|
||||||
|
prevProps.sanitize !== this.props.sanitize ||
|
||||||
|
prevProps.breaks !== this.props.breaks) {
|
||||||
this.initMarkdown()
|
this.initMarkdown()
|
||||||
this.rewriteIframe()
|
this.rewriteIframe()
|
||||||
}
|
}
|
||||||
@@ -342,7 +361,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getStyleParams () {
|
getStyleParams () {
|
||||||
const { fontSize, lineNumber, codeBlockTheme, scrollPastEnd } = this.props
|
const { fontSize, lineNumber, codeBlockTheme, scrollPastEnd, theme } = this.props
|
||||||
let { fontFamily, codeBlockFontFamily } = this.props
|
let { fontFamily, codeBlockFontFamily } = this.props
|
||||||
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
|
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
|
||||||
? fontFamily.split(',').map(fontName => fontName.trim()).concat(defaultFontFamily)
|
? fontFamily.split(',').map(fontName => fontName.trim()).concat(defaultFontFamily)
|
||||||
@@ -351,14 +370,14 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
? codeBlockFontFamily.split(',').map(fontName => fontName.trim()).concat(defaultCodeBlockFontFamily)
|
? codeBlockFontFamily.split(',').map(fontName => fontName.trim()).concat(defaultCodeBlockFontFamily)
|
||||||
: defaultCodeBlockFontFamily
|
: defaultCodeBlockFontFamily
|
||||||
|
|
||||||
return {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd}
|
return {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme}
|
||||||
}
|
}
|
||||||
|
|
||||||
applyStyle () {
|
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('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) {
|
GetCodeThemeLink (theme) {
|
||||||
@@ -414,7 +433,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
: 'default'
|
: 'default'
|
||||||
|
|
||||||
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.code code'), (el) => {
|
_.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')
|
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
||||||
CodeMirror.requireMode(syntax.mode, () => {
|
CodeMirror.requireMode(syntax.mode, () => {
|
||||||
const content = htmlTextHelper.decodeEntities(el.innerHTML)
|
const content = htmlTextHelper.decodeEntities(el.innerHTML)
|
||||||
@@ -526,21 +545,36 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
return
|
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
|
// this will match the new uuid v4 hash and the old hash
|
||||||
// e.g.
|
// e.g.
|
||||||
// :note:1c211eb7dcb463de6490 and
|
// :note:1c211eb7dcb463de6490 and
|
||||||
// :note:7dd23275-f2b4-49cb-9e93-3454daf1af9c
|
// :note:7dd23275-f2b4-49cb-9e93-3454daf1af9c
|
||||||
const regexIsNoteLink = /^:note:([a-zA-Z0-9-]{20,36})$/
|
const regexIsNoteLink = /^:note:([a-zA-Z0-9-]{20,36})$/
|
||||||
if (regexIsNoteLink.test(noteHash)) {
|
if (regexIsNoteLink.test(linkHash)) {
|
||||||
eventEmitter.emit('list:jump', noteHash.replace(':note:', ''))
|
eventEmitter.emit('list:jump', linkHash.replace(':note:', ''))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// this will match the old link format storage.key-note.key
|
// this will match the old link format storage.key-note.key
|
||||||
// e.g.
|
// e.g.
|
||||||
// 877f99c3268608328037-1c211eb7dcb463de6490
|
// 877f99c3268608328037-1c211eb7dcb463de6490
|
||||||
const regexIsLegacyNoteLink = /^(.{20})-(.{20})$/
|
const regexIsLegacyNoteLink = /^(.{20})-(.{20})$/
|
||||||
if (regexIsLegacyNoteLink.test(noteHash)) {
|
if (regexIsLegacyNoteLink.test(linkHash)) {
|
||||||
eventEmitter.emit('list:jump', noteHash.split('-')[1])
|
eventEmitter.emit('list:jump', linkHash.split('-')[1])
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -568,5 +602,6 @@ MarkdownPreview.propTypes = {
|
|||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
showCopyNotification: PropTypes.bool,
|
showCopyNotification: PropTypes.bool,
|
||||||
storagePath: PropTypes.string,
|
storagePath: PropTypes.string,
|
||||||
smartQuotes: PropTypes.bool
|
smartQuotes: PropTypes.bool,
|
||||||
|
breaks: PropTypes.bool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
lineNumber={config.preview.lineNumber}
|
lineNumber={config.preview.lineNumber}
|
||||||
scrollPastEnd={config.preview.scrollPastEnd}
|
scrollPastEnd={config.preview.scrollPastEnd}
|
||||||
smartQuotes={config.preview.smartQuotes}
|
smartQuotes={config.preview.smartQuotes}
|
||||||
|
breaks={config.preview.breaks}
|
||||||
sanitize={config.preview.sanitize}
|
sanitize={config.preview.sanitize}
|
||||||
ref='preview'
|
ref='preview'
|
||||||
tabInde='0'
|
tabInde='0'
|
||||||
@@ -139,6 +140,7 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
onScroll={this.handleScroll.bind(this)}
|
onScroll={this.handleScroll.bind(this)}
|
||||||
showCopyNotification={config.ui.showCopyNotification}
|
showCopyNotification={config.ui.showCopyNotification}
|
||||||
storagePath={storage.path}
|
storagePath={storage.path}
|
||||||
|
noteKey={noteKey}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -321,3 +321,76 @@ body[data-theme="solarized-dark"]
|
|||||||
.item-bottom-tagList-empty
|
.item-bottom-tagList-empty
|
||||||
color $ui-inactive-text-color
|
color $ui-inactive-text-color
|
||||||
vertical-align middle
|
vertical-align middle
|
||||||
|
|
||||||
|
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
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ body[data-theme="dark"]
|
|||||||
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
.item-simple-title
|
.item-simple-title
|
||||||
|
.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
|
||||||
@@ -117,6 +118,7 @@ body[data-theme="dark"]
|
|||||||
background-color $ui-dark-button--active-backgroundColor
|
background-color $ui-dark-button--active-backgroundColor
|
||||||
color $ui-dark-text-color
|
color $ui-dark-text-color
|
||||||
.item-simple-title
|
.item-simple-title
|
||||||
|
.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
|
||||||
@@ -165,9 +167,10 @@ body[data-theme="solarized-dark"]
|
|||||||
background-color $ui-solarized-dark-noteList-backgroundColor
|
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||||
&:hover
|
&:hover
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
// background-color alpha($ui-dark-button--active-backgroundColor, 20%)
|
background-color alpha($ui-dark-button--active-backgroundColor, 60%)
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
.item-simple-title
|
.item-simple-title
|
||||||
|
.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
|
||||||
@@ -178,9 +181,10 @@ body[data-theme="solarized-dark"]
|
|||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
&:active
|
&:active
|
||||||
transition 0.15s
|
transition 0.15s
|
||||||
background-color $ui-solarized-dark-button--active-backgroundColor
|
// background-color $ui-solarized-dark-button--active-backgroundColor
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-dark-text-color
|
||||||
.item-simple-title
|
.item-simple-title
|
||||||
|
.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
|
||||||
@@ -192,11 +196,13 @@ body[data-theme="solarized-dark"]
|
|||||||
|
|
||||||
.item-simple--active
|
.item-simple--active
|
||||||
border-color $ui-solarized-dark-borderColor
|
border-color $ui-solarized-dark-borderColor
|
||||||
background-color $ui-solarized-dark-button--active-backgroundColor
|
background-color $ui-solarized-dark-tag-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-icon
|
.item-simple-title-icon
|
||||||
|
color $ui-dark-text-color
|
||||||
.item-simple-bottom-time
|
.item-simple-bottom-time
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
.item-simple-bottom-tagList-item
|
.item-simple-bottom-tagList-item
|
||||||
@@ -207,8 +213,75 @@ body[data-theme="solarized-dark"]
|
|||||||
color #c0392b
|
color #c0392b
|
||||||
.item-simple-bottom-tagList-item
|
.item-simple-bottom-tagList-item
|
||||||
background-color alpha(#fff, 20%)
|
background-color alpha(#fff, 20%)
|
||||||
.item-simple-right
|
.item-simple-title
|
||||||
float right
|
color $ui-dark-text-color
|
||||||
.item-simple-right-storageName
|
border-bottom $ui-dark-borderColor
|
||||||
padding-left 4px
|
.item-simple-right
|
||||||
opacity 0.4
|
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
|
||||||
|
|||||||
@@ -41,3 +41,14 @@ body[data-theme="solarized-dark"]
|
|||||||
background-color $ui-solarized-dark-button-backgroundColor
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
&:hover
|
&:hover
|
||||||
color #5CB85C
|
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
|
||||||
@@ -68,10 +68,9 @@
|
|||||||
.menu-button-label
|
.menu-button-label
|
||||||
position fixed
|
position fixed
|
||||||
display inline-block
|
display inline-block
|
||||||
height 32px
|
height 36px
|
||||||
left 44px
|
left 44px
|
||||||
padding 0 10px
|
padding 0 10px
|
||||||
margin-top -8px
|
|
||||||
margin-left 0
|
margin-left 0
|
||||||
overflow ellipsis
|
overflow ellipsis
|
||||||
z-index 10
|
z-index 10
|
||||||
@@ -223,3 +222,45 @@ body[data-theme="solarized-dark"]
|
|||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
.menu-button-label
|
.menu-button-label
|
||||||
color $ui-solarized-dark-text-color
|
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
|
||||||
@@ -55,10 +55,10 @@ class SnippetTab extends React.Component {
|
|||||||
this.handleRename()
|
this.handleRename()
|
||||||
break
|
break
|
||||||
case 27:
|
case 27:
|
||||||
this.setState({
|
this.setState((prevState, props) => ({
|
||||||
name: this.props.snippet.name,
|
name: props.snippet.name,
|
||||||
isRenaming: false
|
isRenaming: false
|
||||||
})
|
}))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,8 +58,8 @@
|
|||||||
opacity 0
|
opacity 0
|
||||||
border-top-right-radius 2px
|
border-top-right-radius 2px
|
||||||
border-bottom-right-radius 2px
|
border-bottom-right-radius 2px
|
||||||
height 26px
|
height 34px
|
||||||
line-height 26px
|
line-height 32px
|
||||||
|
|
||||||
.folderList-item:hover, .folderList-item--active:hover
|
.folderList-item:hover, .folderList-item--active:hover
|
||||||
.folderList-item-tooltip
|
.folderList-item-tooltip
|
||||||
@@ -138,3 +138,22 @@ body[data-theme="solarized-dark"]
|
|||||||
&:hover
|
&:hover
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
background-color $ui-solarized-dark-button-backgroundColor
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
|
|
||||||
|
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
|
||||||
@@ -49,3 +49,13 @@ body[data-theme="solarized-dark"]
|
|||||||
|
|
||||||
.percentageText
|
.percentageText
|
||||||
color #fdf6e3
|
color #fdf6e3
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.percentageBar
|
||||||
|
background-color #f92672
|
||||||
|
|
||||||
|
.progressBar
|
||||||
|
background-color: #373831
|
||||||
|
|
||||||
|
.percentageText
|
||||||
|
color #fdf6e3
|
||||||
@@ -199,7 +199,6 @@ ol
|
|||||||
&>li>ul, &>li>ol
|
&>li>ul, &>li>ol
|
||||||
margin 0
|
margin 0
|
||||||
code
|
code
|
||||||
color #CC305F
|
|
||||||
padding 0.2em 0.4em
|
padding 0.2em 0.4em
|
||||||
background-color #f7f7f7
|
background-color #f7f7f7
|
||||||
border-radius 3px
|
border-radius 3px
|
||||||
@@ -371,3 +370,30 @@ body[data-theme="solarized-dark"]
|
|||||||
border-color themeSolarizedDarkTableBorder
|
border-color themeSolarizedDarkTableBorder
|
||||||
&:last-child
|
&:last-child
|
||||||
border-right solid 1px themeSolarizedDarkTableBorder
|
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
|
||||||
14
browser/lib/convertModeName.js
Normal file
14
browser/lib/convertModeName.js
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,7 +25,7 @@ class Markdown {
|
|||||||
linkify: true,
|
linkify: true,
|
||||||
html: true,
|
html: true,
|
||||||
xhtmlOut: true,
|
xhtmlOut: true,
|
||||||
breaks: true,
|
breaks: config.preview.breaks,
|
||||||
highlight: function (str, lang) {
|
highlight: function (str, lang) {
|
||||||
const delimiter = ':'
|
const delimiter = ':'
|
||||||
const langInfo = lang.split(delimiter)
|
const langInfo = lang.split(delimiter)
|
||||||
@@ -145,11 +145,13 @@ class Markdown {
|
|||||||
const deflate = require('markdown-it-plantuml/lib/deflate')
|
const deflate = require('markdown-it-plantuml/lib/deflate')
|
||||||
this.md.use(require('markdown-it-plantuml'), '', {
|
this.md.use(require('markdown-it-plantuml'), '', {
|
||||||
generateSource: function (umlCode) {
|
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 s = unescape(encodeURIComponent(umlCode))
|
||||||
const zippedCode = deflate.encode64(
|
const zippedCode = deflate.encode64(
|
||||||
deflate.zip_deflate(`@startuml\n${s}\n@enduml`, 9)
|
deflate.zip_deflate(`@startuml\n${s}\n@enduml`, 9)
|
||||||
)
|
)
|
||||||
return `http://www.plantuml.com/plantuml/svg/${zippedCode}`
|
return `${serverAddress}/${zippedCode}`
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,25 @@ export function escapeHtmlCharacters (text) {
|
|||||||
: html
|
: 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 {
|
export default {
|
||||||
lastFindInArray,
|
lastFindInArray,
|
||||||
escapeHtmlCharacters
|
escapeHtmlCharacters,
|
||||||
|
isObjectEqual
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,3 +30,10 @@ body[data-theme="solarized-dark"]
|
|||||||
border-left 1px solid $ui-solarized-dark-borderColor
|
border-left 1px solid $ui-solarized-dark-borderColor
|
||||||
.empty-message
|
.empty-message
|
||||||
color $ui-solarized-dark-text-color
|
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
|
||||||
|
|||||||
@@ -133,3 +133,29 @@ body[data-theme="dark"]
|
|||||||
color $ui-dark-button--active-color
|
color $ui-dark-button--active-color
|
||||||
.search-optionList-item-name-surfix
|
.search-optionList-item-name-surfix
|
||||||
color $ui-dark-inactive-text-color
|
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
|
||||||
|
|||||||
@@ -215,3 +215,43 @@ body[data-theme="solarized-dark"]
|
|||||||
color $ui-dark-inactive-text-color
|
color $ui-dark-inactive-text-color
|
||||||
&:hover
|
&:hover
|
||||||
color $ui-solarized-ark-text-color
|
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
|
||||||
|
|||||||
@@ -55,10 +55,14 @@ class MarkdownNoteDetail extends React.Component {
|
|||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
ee.on('topbar:togglelockbutton', this.toggleLockButton)
|
ee.on('topbar:togglelockbutton', this.toggleLockButton)
|
||||||
|
ee.on('topbar:togglemodebutton', () => {
|
||||||
|
const reversedType = this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
|
||||||
|
this.handleSwitchMode(reversedType)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
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()
|
if (this.saveQueue != null) this.saveNow()
|
||||||
this.setState({
|
this.setState({
|
||||||
note: Object.assign({}, nextProps.note)
|
note: Object.assign({}, nextProps.note)
|
||||||
|
|||||||
@@ -71,3 +71,8 @@ body[data-theme="solarized-dark"]
|
|||||||
.root
|
.root
|
||||||
border-left 1px solid $ui-solarized-dark-borderColor
|
border-left 1px solid $ui-solarized-dark-borderColor
|
||||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
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
|
||||||
|
|||||||
@@ -98,3 +98,7 @@ body[data-theme="solarized-dark"]
|
|||||||
border-color $ui-solarized-dark-borderColor
|
border-color $ui-solarized-dark-borderColor
|
||||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.info
|
||||||
|
border-color $ui-monokai-borderColor
|
||||||
|
background-color $ui-monokai-noteDetail-backgroundColor
|
||||||
@@ -18,6 +18,7 @@ import context from 'browser/lib/context'
|
|||||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import {findNoteTitle} from 'browser/lib/findNoteTitle'
|
import {findNoteTitle} from 'browser/lib/findNoteTitle'
|
||||||
|
import convertModeName from 'browser/lib/convertModeName'
|
||||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||||
import TrashButton from './TrashButton'
|
import TrashButton from './TrashButton'
|
||||||
import RestoreButton from './RestoreButton'
|
import RestoreButton from './RestoreButton'
|
||||||
@@ -29,21 +30,6 @@ import { formatDate } from 'browser/lib/date-formatter'
|
|||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
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 electron = require('electron')
|
||||||
const { remote } = electron
|
const { remote } = electron
|
||||||
const { Menu, MenuItem, dialog } = remote
|
const { Menu, MenuItem, dialog } = remote
|
||||||
@@ -82,7 +68,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps (nextProps) {
|
||||||
if (nextProps.note.key !== this.props.note.key && !this.isMovingNote) {
|
if (nextProps.note.key !== this.props.note.key && !this.state.isMovingNote) {
|
||||||
if (this.saveQueue != null) this.saveNow()
|
if (this.saveQueue != null) this.saveNow()
|
||||||
const nextNote = Object.assign({
|
const nextNote = Object.assign({
|
||||||
description: ''
|
description: ''
|
||||||
@@ -382,11 +368,11 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
name: mode
|
name: mode
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
|
this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
|
||||||
|
|
||||||
this.setState({
|
this.setState(state => ({
|
||||||
note: this.state.note
|
note: state.note
|
||||||
}, () => {
|
}), () => {
|
||||||
this.save()
|
this.save()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -395,11 +381,11 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
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({note: Object.assign(this.state.note, {snippets: snippets})})
|
this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
|
||||||
|
|
||||||
this.setState({
|
this.setState(state => ({
|
||||||
note: this.state.note
|
note: state.note
|
||||||
}, () => {
|
}), () => {
|
||||||
this.save()
|
this.save()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -413,10 +399,10 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
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
|
||||||
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
|
this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
|
||||||
this.setState({
|
this.setState(state => ({
|
||||||
note: this.state.note
|
note: state.note
|
||||||
}, () => {
|
}), () => {
|
||||||
this.save()
|
this.save()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -611,17 +597,17 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
jumpNextTab () {
|
jumpNextTab () {
|
||||||
this.setState({
|
this.setState(state => ({
|
||||||
snippetIndex: (this.state.snippetIndex + 1) % this.state.note.snippets.length
|
snippetIndex: (state.snippetIndex + 1) % state.note.snippets.length
|
||||||
}, () => {
|
}), () => {
|
||||||
this.focusEditor()
|
this.focusEditor()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
jumpPrevTab () {
|
jumpPrevTab () {
|
||||||
this.setState({
|
this.setState(state => ({
|
||||||
snippetIndex: (this.state.snippetIndex - 1 + this.state.note.snippets.length) % this.state.note.snippets.length
|
snippetIndex: (state.snippetIndex - 1 + state.note.snippets.length) % state.note.snippets.length
|
||||||
}, () => {
|
}), () => {
|
||||||
this.focusEditor()
|
this.focusEditor()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -677,7 +663,7 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
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
|
||||||
|
|
||||||
let syntax = CodeMirror.findModeByName(pass(snippet.mode))
|
let syntax = CodeMirror.findModeByName(convertModeName(snippet.mode))
|
||||||
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
|
||||||
|
|
||||||
return <div styleName='tabView'
|
return <div styleName='tabView'
|
||||||
|
|||||||
@@ -153,3 +153,20 @@ body[data-theme="solarized-dark"]
|
|||||||
.tabList
|
.tabList
|
||||||
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
background-color $ui-solarized-dark-noteDetail-backgroundColor
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.root
|
||||||
|
border-left 1px solid $ui-monokai-borderColor
|
||||||
|
background-color $ui-monokai-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
.body
|
||||||
|
background-color $ui-monokai-noteDetail-backgroundColor
|
||||||
|
|
||||||
|
.body .description textarea
|
||||||
|
background-color $ui-monokai-noteDetail-backgroundColor
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
border 1px solid $ui-monokai-borderColor
|
||||||
|
|
||||||
|
.tabList
|
||||||
|
background-color $ui-monokai-noteDetail-backgroundColor
|
||||||
|
color $ui-monokai-text-color
|
||||||
@@ -44,16 +44,9 @@ class TagSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeLastTag () {
|
removeLastTag () {
|
||||||
let { value } = this.props
|
this.removeTagByCallback((value) => {
|
||||||
|
value.pop()
|
||||||
value = _.isArray(value)
|
})
|
||||||
? value.slice()
|
|
||||||
: []
|
|
||||||
value.pop()
|
|
||||||
value = _.uniq(value)
|
|
||||||
|
|
||||||
this.value = value
|
|
||||||
this.props.onChange()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reset () {
|
reset () {
|
||||||
@@ -96,15 +89,22 @@ class TagSelect extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleTagRemoveButtonClick (tag) {
|
handleTagRemoveButtonClick (tag) {
|
||||||
return (e) => {
|
this.removeTagByCallback((value, tag) => {
|
||||||
let { value } = this.props
|
|
||||||
|
|
||||||
value.splice(value.indexOf(tag), 1)
|
value.splice(value.indexOf(tag), 1)
|
||||||
value = _.uniq(value)
|
}, tag)
|
||||||
|
}
|
||||||
|
|
||||||
this.value = value
|
removeTagByCallback (callback, tag = null) {
|
||||||
this.props.onChange()
|
let { value } = this.props
|
||||||
}
|
|
||||||
|
value = _.isArray(value)
|
||||||
|
? value.slice()
|
||||||
|
: []
|
||||||
|
callback(value, tag)
|
||||||
|
value = _.uniq(value)
|
||||||
|
|
||||||
|
this.value = value
|
||||||
|
this.props.onChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
@@ -118,7 +118,7 @@ class TagSelect extends React.Component {
|
|||||||
>
|
>
|
||||||
<span styleName='tag-label'>#{tag}</span>
|
<span styleName='tag-label'>#{tag}</span>
|
||||||
<button styleName='tag-removeButton'
|
<button styleName='tag-removeButton'
|
||||||
onClick={(e) => this.handleTagRemoveButtonClick(tag)(e)}
|
onClick={(e) => this.handleTagRemoveButtonClick(tag)}
|
||||||
>
|
>
|
||||||
<img className='tag-removeButton-icon' src='../resources/icon/icon-x.svg' width='8px' />
|
<img className='tag-removeButton-icon' src='../resources/icon/icon-x.svg' width='8px' />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -82,3 +82,19 @@ body[data-theme="solarized-dark"]
|
|||||||
border-color none
|
border-color none
|
||||||
background-color transparent
|
background-color transparent
|
||||||
color $ui-solarized-dark-text-color
|
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
|
||||||
|
|||||||
@@ -56,3 +56,10 @@ body[data-theme="solarized-dark"]
|
|||||||
.active
|
.active
|
||||||
background-color #1EC38B
|
background-color #1EC38B
|
||||||
box-shadow 2px 0px 7px #222222
|
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
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { hashHistory } from 'react-router'
|
|||||||
import store from 'browser/main/store'
|
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'
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const electron = require('electron')
|
const electron = require('electron')
|
||||||
const { remote } = electron
|
const { remote } = electron
|
||||||
@@ -144,7 +145,8 @@ class Main extends React.Component {
|
|||||||
const supportedThemes = [
|
const supportedThemes = [
|
||||||
'dark',
|
'dark',
|
||||||
'white',
|
'white',
|
||||||
'solarized-dark'
|
'solarized-dark',
|
||||||
|
'monokai'
|
||||||
]
|
]
|
||||||
|
|
||||||
if (supportedThemes.indexOf(config.ui.theme) !== -1) {
|
if (supportedThemes.indexOf(config.ui.theme) !== -1) {
|
||||||
@@ -158,7 +160,7 @@ class Main extends React.Component {
|
|||||||
} else {
|
} else {
|
||||||
i18n.setLocale('en')
|
i18n.setLocale('en')
|
||||||
}
|
}
|
||||||
|
applyShortcuts()
|
||||||
// Reload all data
|
// Reload all data
|
||||||
dataApi.init()
|
dataApi.init()
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
|
|||||||
@@ -75,3 +75,7 @@ body[data-theme="dark"]
|
|||||||
body[data-theme="solarized-dark"]
|
body[data-theme="solarized-dark"]
|
||||||
.root, .root--expanded
|
.root, .root--expanded
|
||||||
background-color $ui-solarized-dark-noteList-backgroundColor
|
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.root, .root--expanded
|
||||||
|
background-color $ui-monokai-noteList-backgroundColor
|
||||||
|
|||||||
@@ -114,3 +114,27 @@ body[data-theme="solarized-dark"]
|
|||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
&:active
|
&:active
|
||||||
color $ui-solarized-dark-text-color
|
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
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import moment from 'moment'
|
|||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import ee from 'browser/main/lib/eventEmitter'
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
import dataApi from 'browser/main/lib/dataApi'
|
import dataApi from 'browser/main/lib/dataApi'
|
||||||
|
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
|
||||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||||
import NoteItem from 'browser/components/NoteItem'
|
import NoteItem from 'browser/components/NoteItem'
|
||||||
import NoteItemSimple from 'browser/components/NoteItemSimple'
|
import NoteItemSimple from 'browser/components/NoteItemSimple'
|
||||||
@@ -455,12 +456,19 @@ class NoteList extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleDragStart (e, note) {
|
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 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)
|
||||||
this.setState({ selectedNoteKeys: [] })
|
this.selectNextNote()
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNoteContextMenu (e, uniqueKey) {
|
handleNoteContextMenu (e, uniqueKey) {
|
||||||
@@ -655,6 +663,10 @@ class NoteList extends React.Component {
|
|||||||
title: firstNote.title + ' ' + i18n.__('copy'),
|
title: firstNote.title + ' ' + i18n.__('copy'),
|
||||||
content: firstNote.content
|
content: firstNote.content
|
||||||
})
|
})
|
||||||
|
.then((note) => {
|
||||||
|
attachmentManagement.cloneAttachments(firstNote, note)
|
||||||
|
return note
|
||||||
|
})
|
||||||
.then((note) => {
|
.then((note) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'UPDATE_NOTE',
|
type: 'UPDATE_NOTE',
|
||||||
|
|||||||
@@ -117,3 +117,8 @@ body[data-theme="solarized-dark"]
|
|||||||
.root, .root--folded
|
.root, .root--folded
|
||||||
background-color $ui-solarized-dark-backgroundColor
|
background-color $ui-solarized-dark-backgroundColor
|
||||||
border-right 1px solid $ui-solarized-dark-borderColor
|
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
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import i18n from 'browser/lib/i18n'
|
|||||||
|
|
||||||
const { remote } = require('electron')
|
const { remote } = require('electron')
|
||||||
const { Menu, dialog } = remote
|
const { Menu, dialog } = remote
|
||||||
|
const escapeStringRegexp = require('escape-string-regexp')
|
||||||
|
const path = require('path')
|
||||||
|
|
||||||
class StorageItem extends React.Component {
|
class StorageItem extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@@ -201,7 +203,7 @@ class StorageItem extends React.Component {
|
|||||||
createdNoteData.forEach((newNote) => {
|
createdNoteData.forEach((newNote) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'MOVE_NOTE',
|
type: 'MOVE_NOTE',
|
||||||
originNote: noteData.find((note) => note.content === newNote.content),
|
originNote: noteData.find((note) => note.content === newNote.oldContent),
|
||||||
note: newNote
|
note: newNote
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -223,7 +225,8 @@ 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 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)
|
const noteSet = folderNoteMap.get(storage.key + '-' + folder.key)
|
||||||
|
|
||||||
let noteCount = 0
|
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 (
|
return (
|
||||||
<div styleName={isFolded ? 'root--folded' : 'root'}
|
<div styleName={isFolded ? 'root--folded' : 'root'}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
height 36px
|
height 36px
|
||||||
padding-left 25px
|
padding-left 25px
|
||||||
padding-right 15px
|
padding-right 15px
|
||||||
line-height 22px
|
line-height 36px
|
||||||
cursor pointer
|
cursor pointer
|
||||||
font-size 14px
|
font-size 14px
|
||||||
border none
|
border none
|
||||||
|
|||||||
@@ -148,7 +148,9 @@ class SideNav extends React.Component {
|
|||||||
const relatedTags = this.getRelatedTags(this.getActiveTags(location.pathname), data.noteMap)
|
const relatedTags = this.getRelatedTags(this.getActiveTags(location.pathname), data.noteMap)
|
||||||
let tagList = _.sortBy(data.tagNoteMap.map(
|
let tagList = _.sortBy(data.tagNoteMap.map(
|
||||||
(tag, name) => ({ name, size: tag.size, related: relatedTags.has(name) })
|
(tag, name) => ({ name, size: tag.size, related: relatedTags.has(name) })
|
||||||
), ['name'])
|
), ['name']).filter(
|
||||||
|
tag => tag.size > 0
|
||||||
|
)
|
||||||
if (config.sortTagsBy === 'COUNTER') {
|
if (config.sortTagsBy === 'COUNTER') {
|
||||||
tagList = _.sortBy(tagList, item => (0 - item.size))
|
tagList = _.sortBy(tagList, item => (0 - item.size))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,3 +69,14 @@ body[data-theme="dark"]
|
|||||||
navDarkButtonColor()
|
navDarkButtonColor()
|
||||||
border-color $ui-dark-borderColor
|
border-color $ui-dark-borderColor
|
||||||
border-left 1px solid $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
|
||||||
|
|||||||
@@ -234,3 +234,25 @@ body[data-theme="solarized-dark"]
|
|||||||
input
|
input
|
||||||
background-color $ui-solarized-dark-noteList-backgroundColor
|
background-color $ui-solarized-dark-noteList-backgroundColor
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
|
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
|
||||||
|
|||||||
@@ -134,4 +134,10 @@ body[data-theme="solarized-dark"]
|
|||||||
.sortableItemHelper
|
.sortableItemHelper
|
||||||
color: $ui-solarized-dark-text-color
|
color: $ui-solarized-dark-text-color
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.ModalBase
|
||||||
|
.modalBack
|
||||||
|
background-color $ui-monokai-backgroundColor
|
||||||
|
.sortableItemHelper
|
||||||
|
color: $ui-monokai-text-color
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import RcParser from 'browser/lib/RcParser'
|
import RcParser from 'browser/lib/RcParser'
|
||||||
import i18n from 'browser/lib/i18n'
|
import i18n from 'browser/lib/i18n'
|
||||||
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
|
|
||||||
const OSX = global.process.platform === 'darwin'
|
const OSX = global.process.platform === 'darwin'
|
||||||
const win = global.process.platform === 'win32'
|
const win = global.process.platform === 'win32'
|
||||||
@@ -20,7 +21,8 @@ export const DEFAULT_CONFIG = {
|
|||||||
listStyle: 'DEFAULT', // 'DEFAULT', 'SMALL'
|
listStyle: 'DEFAULT', // 'DEFAULT', 'SMALL'
|
||||||
amaEnabled: true,
|
amaEnabled: true,
|
||||||
hotkey: {
|
hotkey: {
|
||||||
toggleMain: OSX ? 'Cmd + Alt + L' : 'Super + Alt + E'
|
toggleMain: OSX ? 'Cmd + Alt + L' : 'Super + Alt + E',
|
||||||
|
toggleMode: OSX ? 'Cmd + M' : 'Ctrl + M'
|
||||||
},
|
},
|
||||||
ui: {
|
ui: {
|
||||||
language: 'en',
|
language: 'en',
|
||||||
@@ -53,8 +55,10 @@ export const DEFAULT_CONFIG = {
|
|||||||
latexInlineClose: '$',
|
latexInlineClose: '$',
|
||||||
latexBlockOpen: '$$',
|
latexBlockOpen: '$$',
|
||||||
latexBlockClose: '$$',
|
latexBlockClose: '$$',
|
||||||
|
plantUMLServerAddress: 'http://www.plantuml.com/plantuml',
|
||||||
scrollPastEnd: false,
|
scrollPastEnd: false,
|
||||||
smartQuotes: true,
|
smartQuotes: true,
|
||||||
|
breaks: true,
|
||||||
sanitize: 'STRICT' // 'STRICT', 'ALLOW_STYLES', 'NONE'
|
sanitize: 'STRICT' // 'STRICT', 'ALLOW_STYLES', 'NONE'
|
||||||
},
|
},
|
||||||
blog: {
|
blog: {
|
||||||
@@ -135,6 +139,8 @@ function set (updates) {
|
|||||||
document.body.setAttribute('data-theme', 'white')
|
document.body.setAttribute('data-theme', 'white')
|
||||||
} else if (newConfig.ui.theme === 'solarized-dark') {
|
} else if (newConfig.ui.theme === 'solarized-dark') {
|
||||||
document.body.setAttribute('data-theme', 'solarized-dark')
|
document.body.setAttribute('data-theme', 'solarized-dark')
|
||||||
|
} else if (newConfig.ui.theme === 'monokai') {
|
||||||
|
document.body.setAttribute('data-theme', 'monokai')
|
||||||
} else {
|
} else {
|
||||||
document.body.setAttribute('data-theme', 'default')
|
document.body.setAttribute('data-theme', 'default')
|
||||||
}
|
}
|
||||||
@@ -163,6 +169,7 @@ function set (updates) {
|
|||||||
ipcRenderer.send('config-renew', {
|
ipcRenderer.send('config-renew', {
|
||||||
config: get()
|
config: get()
|
||||||
})
|
})
|
||||||
|
ee.emit('config-renew')
|
||||||
}
|
}
|
||||||
|
|
||||||
function assignConfigValues (originalConfig, rcConfig) {
|
function assignConfigValues (originalConfig, rcConfig) {
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ const fs = require('fs')
|
|||||||
const path = require('path')
|
const path = require('path')
|
||||||
const findStorage = require('browser/lib/findStorage')
|
const findStorage = require('browser/lib/findStorage')
|
||||||
const mdurl = require('mdurl')
|
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 STORAGE_FOLDER_PLACEHOLDER = ':storage'
|
||||||
const DESTINATION_FOLDER = 'attachments'
|
const DESTINATION_FOLDER = 'attachments'
|
||||||
@@ -39,7 +42,7 @@ function copyAttachment (sourceFilePath, storageKey, noteKey, useRandomName = tr
|
|||||||
|
|
||||||
const targetStorage = findStorage.findStorage(storageKey)
|
const targetStorage = findStorage.findStorage(storageKey)
|
||||||
|
|
||||||
const inputFile = fs.createReadStream(sourceFilePath)
|
const inputFileStream = fs.createReadStream(sourceFilePath)
|
||||||
let destinationName
|
let destinationName
|
||||||
if (useRandomName) {
|
if (useRandomName) {
|
||||||
destinationName = `${uniqueSlug()}${path.extname(sourceFilePath)}`
|
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)
|
const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
|
||||||
createAttachmentDestinationFolder(targetStorage.path, noteKey)
|
createAttachmentDestinationFolder(targetStorage.path, noteKey)
|
||||||
const outputFile = fs.createWriteStream(path.join(destinationDir, destinationName))
|
const outputFile = fs.createWriteStream(path.join(destinationDir, destinationName))
|
||||||
inputFile.pipe(outputFile)
|
inputFileStream.pipe(outputFile)
|
||||||
resolve(destinationName)
|
inputFileStream.on('end', () => {
|
||||||
|
resolve(destinationName)
|
||||||
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return reject(e)
|
return reject(e)
|
||||||
}
|
}
|
||||||
@@ -146,19 +151,176 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem
|
|||||||
base64data = reader.result.replace(/^data:image\/png;base64,/, '')
|
base64data = reader.result.replace(/^data:image\/png;base64,/, '')
|
||||||
base64data += base64data.replace('+', ' ')
|
base64data += base64data.replace('+', ' ')
|
||||||
const binaryData = new Buffer(base64data, 'base64').toString('binary')
|
const binaryData = new Buffer(base64data, 'base64').toString('binary')
|
||||||
fs.writeFile(imagePath, binaryData, 'binary')
|
fs.writeFileSync(imagePath, binaryData, 'binary')
|
||||||
const imageMd = generateAttachmentMarkdown(imageName, imagePath, true)
|
const imageMd = generateAttachmentMarkdown(imageName, imagePath, true)
|
||||||
codeEditor.insertAttachmentMd(imageMd)
|
codeEditor.insertAttachmentMd(imageMd)
|
||||||
}
|
}
|
||||||
reader.readAsDataURL(blob)
|
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 = {
|
module.exports = {
|
||||||
copyAttachment,
|
copyAttachment,
|
||||||
fixLocalURLS,
|
fixLocalURLS,
|
||||||
generateAttachmentMarkdown,
|
generateAttachmentMarkdown,
|
||||||
handleAttachmentDrop,
|
handleAttachmentDrop,
|
||||||
handlePastImageEvent,
|
handlePastImageEvent,
|
||||||
|
getAttachmentsInContent,
|
||||||
|
getAbsolutePathsOfAttachmentsInContent,
|
||||||
|
removeStorageAndNoteReferences,
|
||||||
|
deleteAttachmentFolder,
|
||||||
|
deleteAttachmentsNotPresentInNote,
|
||||||
|
moveAttachments,
|
||||||
|
cloneAttachments,
|
||||||
STORAGE_FOLDER_PLACEHOLDER,
|
STORAGE_FOLDER_PLACEHOLDER,
|
||||||
DESTINATION_FOLDER
|
DESTINATION_FOLDER
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<any>} 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
|
|
||||||
@@ -5,6 +5,7 @@ const resolveStorageNotes = require('./resolveStorageNotes')
|
|||||||
const CSON = require('@rokt33r/season')
|
const CSON = require('@rokt33r/season')
|
||||||
const sander = require('sander')
|
const sander = require('sander')
|
||||||
const { findStorage } = require('browser/lib/findStorage')
|
const { findStorage } = require('browser/lib/findStorage')
|
||||||
|
const deleteSingleNote = require('./deleteNote')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {String} storageKey
|
* @param {String} storageKey
|
||||||
@@ -49,11 +50,7 @@ function deleteFolder (storageKey, folderKey) {
|
|||||||
|
|
||||||
const deleteAllNotes = targetNotes
|
const deleteAllNotes = targetNotes
|
||||||
.map(function deleteNote (note) {
|
.map(function deleteNote (note) {
|
||||||
const notePath = path.join(storage.path, 'notes', note.key + '.cson')
|
return deleteSingleNote(storageKey, note.key)
|
||||||
return sander.unlink(notePath)
|
|
||||||
.catch(function (err) {
|
|
||||||
console.warn('Failed to delete', notePath, err)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
return Promise.all(deleteAllNotes)
|
return Promise.all(deleteAllNotes)
|
||||||
.then(() => storage)
|
.then(() => storage)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
const resolveStorageData = require('./resolveStorageData')
|
const resolveStorageData = require('./resolveStorageData')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const sander = require('sander')
|
const sander = require('sander')
|
||||||
|
const attachmentManagement = require('./attachmentManagement')
|
||||||
const { findStorage } = require('browser/lib/findStorage')
|
const { findStorage } = require('browser/lib/findStorage')
|
||||||
|
|
||||||
function deleteNote (storageKey, noteKey) {
|
function deleteNote (storageKey, noteKey) {
|
||||||
@@ -25,6 +26,10 @@ function deleteNote (storageKey, noteKey) {
|
|||||||
storageKey
|
storageKey
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.then(function deleteAttachments (storageInfo) {
|
||||||
|
attachmentManagement.deleteAttachmentFolder(storageInfo.storageKey, storageInfo.noteKey)
|
||||||
|
return storageInfo
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = deleteNote
|
module.exports = deleteNote
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
import copyFile from 'browser/main/lib/dataApi/copyFile'
|
import copyFile from 'browser/main/lib/dataApi/copyFile'
|
||||||
import { findStorage } from 'browser/lib/findStorage'
|
import { findStorage } from 'browser/lib/findStorage'
|
||||||
import filenamify from 'filenamify'
|
|
||||||
|
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
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
|
* Export note together with images
|
||||||
*
|
*
|
||||||
@@ -29,21 +24,7 @@ function exportNote (storageKey, noteContent, targetPath, outputFormatter) {
|
|||||||
throw new Error('Storage path is not found')
|
throw new Error('Storage path is not found')
|
||||||
}
|
}
|
||||||
|
|
||||||
let exportedData = noteContent.replace(LOCAL_STORED_REGEX, (match, dstFilename, srcFilename) => {
|
let exportedData = noteContent
|
||||||
dstFilename = filenamify(dstFilename, {replacement: '_'})
|
|
||||||
if (!path.extname(dstFilename)) {
|
|
||||||
dstFilename += path.extname(srcFilename)
|
|
||||||
}
|
|
||||||
|
|
||||||
const dstRelativePath = path.join(IMAGES_FOLDER_NAME, dstFilename)
|
|
||||||
|
|
||||||
exportTasks.push({
|
|
||||||
src: path.join(IMAGES_FOLDER_NAME, srcFilename),
|
|
||||||
dst: dstRelativePath
|
|
||||||
})
|
|
||||||
|
|
||||||
return ``
|
|
||||||
})
|
|
||||||
|
|
||||||
if (outputFormatter) {
|
if (outputFormatter) {
|
||||||
exportedData = outputFormatter(exportedData, exportTasks)
|
exportedData = outputFormatter(exportedData, exportTasks)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const CSON = require('@rokt33r/season')
|
|||||||
const keygen = require('browser/lib/keygen')
|
const keygen = require('browser/lib/keygen')
|
||||||
const sander = require('sander')
|
const sander = require('sander')
|
||||||
const { findStorage } = require('browser/lib/findStorage')
|
const { findStorage } = require('browser/lib/findStorage')
|
||||||
const copyImage = require('./copyImage')
|
const attachmentManagement = require('./attachmentManagement')
|
||||||
|
|
||||||
function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
|
function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
|
||||||
let oldStorage, newStorage
|
let oldStorage, newStorage
|
||||||
@@ -64,35 +64,20 @@ function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
|
|||||||
noteData.key = newNoteKey
|
noteData.key = newNoteKey
|
||||||
noteData.storage = newStorageKey
|
noteData.storage = newStorageKey
|
||||||
noteData.updatedAt = new Date()
|
noteData.updatedAt = new Date()
|
||||||
|
noteData.oldContent = noteData.content
|
||||||
|
|
||||||
return noteData
|
return noteData
|
||||||
})
|
})
|
||||||
.then(function moveImages (noteData) {
|
.then(function moveAttachments (noteData) {
|
||||||
if (oldStorage.path === newStorage.path) return 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(moveTasks).then(() => noteData)
|
noteData.content = attachmentManagement.moveAttachments(oldStorage.path, newStorage.path, noteKey, newNoteKey, noteData.content)
|
||||||
|
return noteData
|
||||||
})
|
})
|
||||||
.then(function writeAndReturn (noteData) {
|
.then(function writeAndReturn (noteData) {
|
||||||
CSON.writeFileSync(path.join(newStorage.path, 'notes', noteData.key + '.cson'), _.omit(noteData, ['key', 'storage']))
|
CSON.writeFileSync(path.join(newStorage.path, 'notes', noteData.key + '.cson'), _.omit(noteData, ['key', 'storage', 'oldContent']))
|
||||||
return noteData
|
return noteData
|
||||||
})
|
})
|
||||||
.then(function deleteOldNote (data) {
|
.then(function deleteOldNote (data) {
|
||||||
|
|||||||
7
browser/main/lib/shortcut.js
Normal file
7
browser/main/lib/shortcut.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import ee from 'browser/main/lib/eventEmitter'
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
'toggleMode': () => {
|
||||||
|
ee.emit('topbar:togglemodebutton')
|
||||||
|
}
|
||||||
|
}
|
||||||
40
browser/main/lib/shortcutManager.js
Normal file
40
browser/main/lib/shortcutManager.js
Normal file
@@ -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
|
||||||
@@ -102,3 +102,29 @@ body[data-theme="solarized-dark"]
|
|||||||
|
|
||||||
.control-confirmButton
|
.control-confirmButton
|
||||||
colorSolarizedDarkPrimaryButton()
|
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()
|
||||||
|
|||||||
@@ -81,3 +81,19 @@ body[data-theme="solarized-dark"]
|
|||||||
.description
|
.description
|
||||||
color $ui-solarized-dark-text-color
|
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
|
||||||
|
|||||||
@@ -133,6 +133,11 @@ colorSolarizedDarkControl()
|
|||||||
background-color $ui-solarized-dark-button-backgroundColor
|
background-color $ui-solarized-dark-button-backgroundColor
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
|
colorMonokaiControl()
|
||||||
|
border none
|
||||||
|
background-color $ui-monokai-button-backgroundColor
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
|
|
||||||
body[data-theme="dark"]
|
body[data-theme="dark"]
|
||||||
.root
|
.root
|
||||||
@@ -189,4 +194,29 @@ body[data-theme="solarized-dark"]
|
|||||||
select, .group-section-control-input
|
select, .group-section-control-input
|
||||||
colorSolarizedDarkControl()
|
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()
|
||||||
|
|||||||
@@ -34,3 +34,9 @@ body[data-theme="solarized-dark"]
|
|||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
p
|
p
|
||||||
color $ui-solarized-dark-text-color
|
color $ui-solarized-dark-text-color
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.root
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
p
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
|||||||
@@ -126,3 +126,26 @@ body[data-theme="solarized-dark"]
|
|||||||
|
|
||||||
.folderItem-right-dangerButton
|
.folderItem-right-dangerButton
|
||||||
colorSolarizedDarkPrimaryButton()
|
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()
|
||||||
|
|||||||
@@ -67,7 +67,8 @@ class HotkeyTab extends React.Component {
|
|||||||
handleHotkeyChange (e) {
|
handleHotkeyChange (e) {
|
||||||
const { config } = this.state
|
const { config } = this.state
|
||||||
config.hotkey = {
|
config.hotkey = {
|
||||||
toggleMain: this.refs.toggleMain.value
|
toggleMain: this.refs.toggleMain.value,
|
||||||
|
toggleMode: this.refs.toggleMode.value
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
config
|
config
|
||||||
@@ -115,6 +116,17 @@ class HotkeyTab extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-label'>{i18n.__('Toggle editor mode')}</div>
|
||||||
|
<div styleName='group-section-control'>
|
||||||
|
<input styleName='group-section-control-input'
|
||||||
|
onChange={(e) => this.handleHotkeyChange(e)}
|
||||||
|
ref='toggleMode'
|
||||||
|
value={config.hotkey.toggleMode}
|
||||||
|
type='text'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div styleName='group-control'>
|
<div styleName='group-control'>
|
||||||
<button styleName='group-control-leftButton'
|
<button styleName='group-control-leftButton'
|
||||||
onClick={(e) => this.handleHintToggleButtonClick(e)}
|
onClick={(e) => this.handleHintToggleButtonClick(e)}
|
||||||
|
|||||||
@@ -68,3 +68,10 @@ body[data-theme="solarized-dark"]
|
|||||||
.list
|
.list
|
||||||
a
|
a
|
||||||
color $ui-solarized-dark-active-color
|
color $ui-solarized-dark-active-color
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.root
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
.list
|
||||||
|
a
|
||||||
|
color $ui-monokai-active-color
|
||||||
|
|||||||
@@ -116,3 +116,26 @@ body[data-theme="solarized-dark"]
|
|||||||
&:hover
|
&:hover
|
||||||
color white
|
color white
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.root
|
||||||
|
background-color transparent
|
||||||
|
.top-bar
|
||||||
|
background-color transparent
|
||||||
|
border-color $ui-monokai-borderColor
|
||||||
|
p
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
.nav
|
||||||
|
background-color transparent
|
||||||
|
border-color $ui-monokai-borderColor
|
||||||
|
.nav-button
|
||||||
|
background-color transparent
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
&:hover
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
|
.nav-button--active
|
||||||
|
@extend .nav-button
|
||||||
|
color $ui-monokai-button--active-color
|
||||||
|
background-color $ui-monokai-button--active-backgroundColor
|
||||||
|
&:hover
|
||||||
|
color white
|
||||||
|
|||||||
@@ -199,3 +199,40 @@ body[data-theme="solarized-dark"]
|
|||||||
colorDarkDefaultButton()
|
colorDarkDefaultButton()
|
||||||
border-color $ui-solarized-dark-borderColor
|
border-color $ui-solarized-dark-borderColor
|
||||||
|
|
||||||
|
body[data-theme="monokai"]
|
||||||
|
.root
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
|
.folderList-item
|
||||||
|
border-bottom $ui-monokai-borderColor
|
||||||
|
|
||||||
|
.folderList-empty
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
|
.list-empty
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
.list-control-addStorageButton
|
||||||
|
border-color $ui-monokai-button-backgroundColor
|
||||||
|
background-color $ui-monokai-button-backgroundColor
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
|
.addStorage-header
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
border-color $ui-monokai-borderColor
|
||||||
|
|
||||||
|
.addStorage-body-section-name-input
|
||||||
|
border-color $$ui-monokai-borderColor
|
||||||
|
|
||||||
|
.addStorage-body-section-type-description
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
|
||||||
|
.addStorage-body-section-path-button
|
||||||
|
colorPrimaryButton()
|
||||||
|
.addStorage-body-control
|
||||||
|
border-color $ui-monokai-borderColor
|
||||||
|
|
||||||
|
.addStorage-body-control-createButton
|
||||||
|
colorDarkPrimaryButton()
|
||||||
|
.addStorage-body-control-cancelButton
|
||||||
|
colorDarkDefaultButton()
|
||||||
|
border-color $ui-monokai-borderColor
|
||||||
|
|||||||
@@ -94,8 +94,10 @@ class UiTab extends React.Component {
|
|||||||
latexInlineClose: this.refs.previewLatexInlineClose.value,
|
latexInlineClose: this.refs.previewLatexInlineClose.value,
|
||||||
latexBlockOpen: this.refs.previewLatexBlockOpen.value,
|
latexBlockOpen: this.refs.previewLatexBlockOpen.value,
|
||||||
latexBlockClose: this.refs.previewLatexBlockClose.value,
|
latexBlockClose: this.refs.previewLatexBlockClose.value,
|
||||||
|
plantUMLServerAddress: this.refs.previewPlantUMLServerAddress.value,
|
||||||
scrollPastEnd: this.refs.previewScrollPastEnd.checked,
|
scrollPastEnd: this.refs.previewScrollPastEnd.checked,
|
||||||
smartQuotes: this.refs.previewSmartQuotes.checked,
|
smartQuotes: this.refs.previewSmartQuotes.checked,
|
||||||
|
breaks: this.refs.previewBreaks.checked,
|
||||||
sanitize: this.refs.previewSanitize.value
|
sanitize: this.refs.previewSanitize.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -172,6 +174,7 @@ class UiTab extends React.Component {
|
|||||||
<option value='default'>{i18n.__('Default')}</option>
|
<option value='default'>{i18n.__('Default')}</option>
|
||||||
<option value='white'>{i18n.__('White')}</option>
|
<option value='white'>{i18n.__('White')}</option>
|
||||||
<option value='solarized-dark'>{i18n.__('Solarized Dark')}</option>
|
<option value='solarized-dark'>{i18n.__('Solarized Dark')}</option>
|
||||||
|
<option value='monokai'>{i18n.__('Monokai')}</option>
|
||||||
<option value='dark'>{i18n.__('Dark')}</option>
|
<option value='dark'>{i18n.__('Dark')}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -474,6 +477,16 @@ class UiTab extends React.Component {
|
|||||||
Enable smart quotes
|
Enable smart quotes
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div styleName='group-checkBoxSection'>
|
||||||
|
<label>
|
||||||
|
<input onChange={(e) => this.handleUIChange(e)}
|
||||||
|
checked={this.state.config.preview.breaks}
|
||||||
|
ref='previewBreaks'
|
||||||
|
type='checkbox'
|
||||||
|
/>
|
||||||
|
Render newlines in Markdown paragraphs as <br>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div styleName='group-section'>
|
<div styleName='group-section'>
|
||||||
<div styleName='group-section-label'>
|
<div styleName='group-section-label'>
|
||||||
@@ -543,6 +556,19 @@ class UiTab extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-label'>
|
||||||
|
{i18n.__('PlantUML Server')}
|
||||||
|
</div>
|
||||||
|
<div styleName='group-section-control'>
|
||||||
|
<input styleName='group-section-control-input'
|
||||||
|
ref='previewPlantUMLServerAddress'
|
||||||
|
value={config.preview.plantUMLServerAddress}
|
||||||
|
onChange={(e) => this.handleUIChange(e)}
|
||||||
|
type='text'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div styleName='group-control'>
|
<div styleName='group-control'>
|
||||||
<button styleName='group-control-rightButton'
|
<button styleName='group-control-rightButton'
|
||||||
|
|||||||
@@ -88,9 +88,27 @@ function data (state = defaultDataMap(), action) {
|
|||||||
if (note.isTrashed) {
|
if (note.isTrashed) {
|
||||||
state.trashedSet.add(uniqueKey)
|
state.trashedSet.add(uniqueKey)
|
||||||
state.starredSet.delete(uniqueKey)
|
state.starredSet.delete(uniqueKey)
|
||||||
|
|
||||||
|
note.tags.forEach(tag => {
|
||||||
|
let tagNoteList = state.tagNoteMap.get(tag)
|
||||||
|
if (tagNoteList != null) {
|
||||||
|
tagNoteList = new Set(tagNoteList)
|
||||||
|
tagNoteList.delete(uniqueKey)
|
||||||
|
state.tagNoteMap.set(tag, tagNoteList)
|
||||||
|
}
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
state.trashedSet.delete(uniqueKey)
|
state.trashedSet.delete(uniqueKey)
|
||||||
|
|
||||||
|
note.tags.forEach(tag => {
|
||||||
|
let tagNoteList = state.tagNoteMap.get(tag)
|
||||||
|
if (tagNoteList != null) {
|
||||||
|
tagNoteList = new Set(tagNoteList)
|
||||||
|
tagNoteList.add(uniqueKey)
|
||||||
|
state.tagNoteMap.set(tag, tagNoteList)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
if (note.isStarred) {
|
if (note.isStarred) {
|
||||||
state.starredSet.add(uniqueKey)
|
state.starredSet.add(uniqueKey)
|
||||||
}
|
}
|
||||||
@@ -238,7 +256,7 @@ function data (state = defaultDataMap(), action) {
|
|||||||
let noteSet = state.storageNoteMap.get(note.storage)
|
let noteSet = state.storageNoteMap.get(note.storage)
|
||||||
noteSet = new Set(noteSet)
|
noteSet = new Set(noteSet)
|
||||||
noteSet.add(uniqueKey)
|
noteSet.add(uniqueKey)
|
||||||
state.folderNoteMap.set(folderKey, noteSet)
|
state.storageNoteMap.set(folderKey, noteSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update foldermap if folder changed or post created
|
// Update foldermap if folder changed or post created
|
||||||
|
|||||||
@@ -118,6 +118,16 @@ colorSolarizedDarkPrimaryButton()
|
|||||||
&:active:hover
|
&:active:hover
|
||||||
background-color $dark-primary-button-background--active
|
background-color $dark-primary-button-background--active
|
||||||
|
|
||||||
|
colorMonokaiPrimaryButton()
|
||||||
|
color $ui-monokai-text-color
|
||||||
|
background-color $ui-monokai-button-backgroundColor
|
||||||
|
border none
|
||||||
|
&:hover
|
||||||
|
background-color $dark-primary-button-background--hover
|
||||||
|
&:active
|
||||||
|
&:active:hover
|
||||||
|
background-color $dark-primary-button-background--active
|
||||||
|
|
||||||
|
|
||||||
// Danger button(Brand color)
|
// Danger button(Brand color)
|
||||||
$danger-button-background = #c9302c
|
$danger-button-background = #c9302c
|
||||||
@@ -348,3 +358,29 @@ modalSolarizedDark()
|
|||||||
background-color $ui-solarized-dark-backgroundColor
|
background-color $ui-solarized-dark-backgroundColor
|
||||||
overflow hidden
|
overflow hidden
|
||||||
border-radius $modal-border-radius
|
border-radius $modal-border-radius
|
||||||
|
|
||||||
|
/******* Monokai theme ********/
|
||||||
|
$ui-monokai-backgroundColor = #272822
|
||||||
|
$ui-monokai-noteList-backgroundColor = #272822
|
||||||
|
$ui-monokai-noteDetail-backgroundColor = #272822
|
||||||
|
|
||||||
|
$ui-monokai-text-color = #f8f8f2
|
||||||
|
$ui-monokai-active-color = #f92672
|
||||||
|
|
||||||
|
$ui-monokai-borderColor = #373831
|
||||||
|
|
||||||
|
$ui-monokai-tag-backgroundColor = #f92672
|
||||||
|
|
||||||
|
$ui-monokai-button-backgroundColor = #373831
|
||||||
|
$ui-monokai-button--active-color = white
|
||||||
|
$ui-monokai-button--active-backgroundColor = #f92672
|
||||||
|
$ui-monokai-button--hover-backgroundColor = lighten($ui-dark-backgroundColor, 10%)
|
||||||
|
$ui-monokai-button--focus-borderColor = lighten(#369DCD, 25%)
|
||||||
|
|
||||||
|
modalmonokai()
|
||||||
|
position relative
|
||||||
|
z-index $modal-z-index
|
||||||
|
width 100%
|
||||||
|
background-color $ui-monokai-backgroundColor
|
||||||
|
overflow hidden
|
||||||
|
border-radius $modal-border-radius
|
||||||
@@ -77,6 +77,7 @@
|
|||||||
<script src="../node_modules/codemirror/addon/mode/overlay.js"></script>
|
<script src="../node_modules/codemirror/addon/mode/overlay.js"></script>
|
||||||
<script src="../node_modules/codemirror/addon/mode/loadmode.js"></script>
|
<script src="../node_modules/codemirror/addon/mode/loadmode.js"></script>
|
||||||
<script src="../node_modules/codemirror/addon/mode/simple.js"></script>
|
<script src="../node_modules/codemirror/addon/mode/simple.js"></script>
|
||||||
|
<script src="../node_modules/codemirror/addon/mode/multiplex.js"></script>
|
||||||
<script src="../node_modules/codemirror/keymap/sublime.js"></script>
|
<script src="../node_modules/codemirror/keymap/sublime.js"></script>
|
||||||
<script src="../node_modules/codemirror/keymap/vim.js"></script>
|
<script src="../node_modules/codemirror/keymap/vim.js"></script>
|
||||||
<script src="../node_modules/codemirror/keymap/emacs.js"></script>
|
<script src="../node_modules/codemirror/keymap/emacs.js"></script>
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
|
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
|
||||||
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
|
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
|
||||||
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
|
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
|
||||||
|
"PlantUML Server": "PlantUML Server",
|
||||||
"Community": "Community",
|
"Community": "Community",
|
||||||
"Subscribe to Newsletter": "Subscribe to Newsletter",
|
"Subscribe to Newsletter": "Subscribe to Newsletter",
|
||||||
"GitHub": "GitHub",
|
"GitHub": "GitHub",
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
"LaTeX Inline Close Delimiter": "LaTeX Inline Ende Kennzeichen",
|
"LaTeX Inline Close Delimiter": "LaTeX Inline Ende Kennzeichen",
|
||||||
"LaTeX Block Open Delimiter": "LaTeX Block Beginn Kennzeichen",
|
"LaTeX Block Open Delimiter": "LaTeX Block Beginn Kennzeichen",
|
||||||
"LaTeX Block Close Delimiter": "LaTeX Block Ende Kennzeichen",
|
"LaTeX Block Close Delimiter": "LaTeX Block Ende Kennzeichen",
|
||||||
|
"PlantUML Server": "PlantUML Server",
|
||||||
"Community": "Community",
|
"Community": "Community",
|
||||||
"Subscribe to Newsletter": "Newsletter abonnieren",
|
"Subscribe to Newsletter": "Newsletter abonnieren",
|
||||||
"GitHub": "GitHub",
|
"GitHub": "GitHub",
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
|
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
|
||||||
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
|
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
|
||||||
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
|
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
|
||||||
|
"PlantUML Server": "PlantUML Server",
|
||||||
"Community": "Community",
|
"Community": "Community",
|
||||||
"Subscribe to Newsletter": "Subscribe to Newsletter",
|
"Subscribe to Newsletter": "Subscribe to Newsletter",
|
||||||
"GitHub": "GitHub",
|
"GitHub": "GitHub",
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
"LaTeX Inline Close Delimiter": "Delimitador de cierre LaTeX en línea",
|
"LaTeX Inline Close Delimiter": "Delimitador de cierre LaTeX en línea",
|
||||||
"LaTeX Block Open Delimiter": "Delimitado de apertura bloque LaTeX",
|
"LaTeX Block Open Delimiter": "Delimitado de apertura bloque LaTeX",
|
||||||
"LaTeX Block Close Delimiter": "Delimitador de cierre bloque LaTeX",
|
"LaTeX Block Close Delimiter": "Delimitador de cierre bloque LaTeX",
|
||||||
|
"PlantUML Server": "PlantUML Server",
|
||||||
"Community": "Comunidad",
|
"Community": "Comunidad",
|
||||||
"Subscribe to Newsletter": "Suscribirse al boletín",
|
"Subscribe to Newsletter": "Suscribirse al boletín",
|
||||||
"GitHub": "GitHub",
|
"GitHub": "GitHub",
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
"LaTeX Inline Close Delimiter": "جداکننده پایانی لاتکس خطی",
|
"LaTeX Inline Close Delimiter": "جداکننده پایانی لاتکس خطی",
|
||||||
"LaTeX Block Open Delimiter": "جداکننده آغازین بلوک لاتکس ",
|
"LaTeX Block Open Delimiter": "جداکننده آغازین بلوک لاتکس ",
|
||||||
"LaTeX Block Close Delimiter": "جداکننده آغازین بلوک لاتکس ",
|
"LaTeX Block Close Delimiter": "جداکننده آغازین بلوک لاتکس ",
|
||||||
|
"PlantUML Server": "PlantUML Server",
|
||||||
"Community": "کامینیتی",
|
"Community": "کامینیتی",
|
||||||
"Subscribe to Newsletter": "اشتراک در خبرنامه",
|
"Subscribe to Newsletter": "اشتراک در خبرنامه",
|
||||||
"GitHub": "گیت هاب",
|
"GitHub": "گیت هاب",
|
||||||
|
|||||||
@@ -61,6 +61,7 @@
|
|||||||
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
|
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
|
||||||
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
|
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
|
||||||
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
|
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
|
||||||
|
"PlantUML Server": "PlantUML Server",
|
||||||
"Community": "Communauté",
|
"Community": "Communauté",
|
||||||
"Subscribe to Newsletter": "Souscrire à la newsletter",
|
"Subscribe to Newsletter": "Souscrire à la newsletter",
|
||||||
"GitHub": "GitHub",
|
"GitHub": "GitHub",
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
"LaTeX Inline Close Delimiter": "LaTeX Inline Záró Határolója",
|
"LaTeX Inline Close Delimiter": "LaTeX Inline Záró Határolója",
|
||||||
"LaTeX Block Open Delimiter": "LaTeX Blokk Nyitó Határolója",
|
"LaTeX Block Open Delimiter": "LaTeX Blokk Nyitó Határolója",
|
||||||
"LaTeX Block Close Delimiter": "LaTeX Blokk Záró Határolója",
|
"LaTeX Block Close Delimiter": "LaTeX Blokk Záró Határolója",
|
||||||
|
"PlantUML Server": "PlantUML Server",
|
||||||
"Community": "Közösség",
|
"Community": "Közösség",
|
||||||
"Subscribe to Newsletter": "Feliratkozás a Hírlevélre",
|
"Subscribe to Newsletter": "Feliratkozás a Hírlevélre",
|
||||||
"GitHub": "GitHub",
|
"GitHub": "GitHub",
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
"LaTeX Inline Close Delimiter": "Delimitatore inline per chiusura LaTex",
|
"LaTeX Inline Close Delimiter": "Delimitatore inline per chiusura LaTex",
|
||||||
"LaTeX Block Open Delimiter": "Delimitatore apertura LaTex",
|
"LaTeX Block Open Delimiter": "Delimitatore apertura LaTex",
|
||||||
"LaTeX Block Close Delimiter": "Delimitatore chiusura LaTex",
|
"LaTeX Block Close Delimiter": "Delimitatore chiusura LaTex",
|
||||||
|
"PlantUML Server": "PlantUML Server",
|
||||||
"Community": "Community",
|
"Community": "Community",
|
||||||
"Subscribe to Newsletter": "Iscriviti alla Newsletter",
|
"Subscribe to Newsletter": "Iscriviti alla Newsletter",
|
||||||
"GitHub": "GitHub",
|
"GitHub": "GitHub",
|
||||||
|
|||||||
@@ -62,13 +62,14 @@
|
|||||||
"LaTeX Inline Close Delimiter": "LaTeX 終了デリミタ(インライン)",
|
"LaTeX Inline Close Delimiter": "LaTeX 終了デリミタ(インライン)",
|
||||||
"LaTeX Block Open Delimiter": "LaTeX 開始デリミタ(ブロック)",
|
"LaTeX Block Open Delimiter": "LaTeX 開始デリミタ(ブロック)",
|
||||||
"LaTeX Block Close Delimiter": "LaTeX 終了デリミタ(ブロック)",
|
"LaTeX Block Close Delimiter": "LaTeX 終了デリミタ(ブロック)",
|
||||||
|
"PlantUML Server": "PlantUML サーバー",
|
||||||
"Community": "コミュニティ",
|
"Community": "コミュニティ",
|
||||||
"Subscribe to Newsletter": "ニュースレターを購読する",
|
"Subscribe to Newsletter": "ニュースレターを購読する",
|
||||||
"GitHub": "GitHub",
|
"GitHub": "GitHub",
|
||||||
"Blog": "ブログ",
|
"Blog": "ブログ",
|
||||||
"Facebook Group": "Facebook グループ",
|
"Facebook Group": "Facebook グループ",
|
||||||
"Twitter": "Twitter",
|
"Twitter": "Twitter",
|
||||||
"About": "について",
|
"About": "Boostnote について",
|
||||||
"Boostnote": "Boostnote",
|
"Boostnote": "Boostnote",
|
||||||
"An open source note-taking app made for programmers just like you.": "あなたのようなプログラマー向けのオープンソースメモ書きアプリケーション",
|
"An open source note-taking app made for programmers just like you.": "あなたのようなプログラマー向けのオープンソースメモ書きアプリケーション",
|
||||||
"Website": "ウェブサイト",
|
"Website": "ウェブサイト",
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
"LaTeX Inline Close Delimiter": "LaTeX 인라인 블록 닫기 기호",
|
"LaTeX Inline Close Delimiter": "LaTeX 인라인 블록 닫기 기호",
|
||||||
"LaTeX Block Open Delimiter": "LaTeX 블록 열기 기호",
|
"LaTeX Block Open Delimiter": "LaTeX 블록 열기 기호",
|
||||||
"LaTeX Block Close Delimiter": "LaTeX 블록 닫기 기호",
|
"LaTeX Block Close Delimiter": "LaTeX 블록 닫기 기호",
|
||||||
|
"PlantUML Server": "PlantUML Server",
|
||||||
"Community": "커뮤니티",
|
"Community": "커뮤니티",
|
||||||
"Subscribe to Newsletter": "뉴스레터 구독",
|
"Subscribe to Newsletter": "뉴스레터 구독",
|
||||||
"GitHub": "깃허브",
|
"GitHub": "깃허브",
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
|
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
|
||||||
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
|
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
|
||||||
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
|
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
|
||||||
|
"PlantUML Server": "PlantUML Server",
|
||||||
"Community": "Community",
|
"Community": "Community",
|
||||||
"Subscribe to Newsletter": "Subscribe to Newsletter",
|
"Subscribe to Newsletter": "Subscribe to Newsletter",
|
||||||
"GitHub": "GitHub",
|
"GitHub": "GitHub",
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
|
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
|
||||||
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
|
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
|
||||||
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
|
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
|
||||||
|
"PlantUML Server": "PlantUML Server",
|
||||||
"Community": "Community",
|
"Community": "Community",
|
||||||
"Subscribe to Newsletter": "Subscribe to Newsletter",
|
"Subscribe to Newsletter": "Subscribe to Newsletter",
|
||||||
"GitHub": "GitHub",
|
"GitHub": "GitHub",
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
"LaTeX Inline Close Delimiter": "Delimitador em Linha Fechado do LaTeX",
|
"LaTeX Inline Close Delimiter": "Delimitador em Linha Fechado do LaTeX",
|
||||||
"LaTeX Block Open Delimiter": "Delimitador de Bloco Aberto do LaTeX",
|
"LaTeX Block Open Delimiter": "Delimitador de Bloco Aberto do LaTeX",
|
||||||
"LaTeX Block Close Delimiter": "Delimitador de Bloco Fechado do LaTeX",
|
"LaTeX Block Close Delimiter": "Delimitador de Bloco Fechado do LaTeX",
|
||||||
|
"PlantUML Server": "PlantUML Server",
|
||||||
"Community": "Comunidade",
|
"Community": "Comunidade",
|
||||||
"Subscribe to Newsletter": "Subscrever à Newsletter",
|
"Subscribe to Newsletter": "Subscrever à Newsletter",
|
||||||
"GitHub": "GitHub",
|
"GitHub": "GitHub",
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
|
"LaTeX Inline Close Delimiter": "LaTeX Inline Close Delimiter",
|
||||||
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
|
"LaTeX Block Open Delimiter": "LaTeX Block Open Delimiter",
|
||||||
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
|
"LaTeX Block Close Delimiter": "LaTeX Block Close Delimiter",
|
||||||
|
"PlantUML Server": "PlantUML Server",
|
||||||
"Community": "Community",
|
"Community": "Community",
|
||||||
"Subscribe to Newsletter": "Subscribe to Newsletter",
|
"Subscribe to Newsletter": "Subscribe to Newsletter",
|
||||||
"GitHub": "GitHub",
|
"GitHub": "GitHub",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "boost",
|
"name": "boost",
|
||||||
"productName": "Boostnote",
|
"productName": "Boostnote",
|
||||||
"version": "0.11.4",
|
"version": "0.11.5",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"description": "Boostnote",
|
"description": "Boostnote",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
@@ -53,13 +53,15 @@
|
|||||||
"@rokt33r/season": "^5.3.0",
|
"@rokt33r/season": "^5.3.0",
|
||||||
"aws-sdk": "^2.48.0",
|
"aws-sdk": "^2.48.0",
|
||||||
"aws-sdk-mobile-analytics": "^0.9.2",
|
"aws-sdk-mobile-analytics": "^0.9.2",
|
||||||
"codemirror": "^5.19.0",
|
"codemirror": "^5.37.0",
|
||||||
"codemirror-mode-elixir": "^1.1.1",
|
"codemirror-mode-elixir": "^1.1.1",
|
||||||
"electron-config": "^0.2.1",
|
"electron-config": "^0.2.1",
|
||||||
"electron-gh-releases": "^2.0.2",
|
"electron-gh-releases": "^2.0.2",
|
||||||
|
"escape-string-regexp": "^1.0.5",
|
||||||
"filenamify": "^2.0.0",
|
"filenamify": "^2.0.0",
|
||||||
"flowchart.js": "^1.6.5",
|
"flowchart.js": "^1.6.5",
|
||||||
"font-awesome": "^4.3.0",
|
"font-awesome": "^4.3.0",
|
||||||
|
"fs-extra": "^5.0.0",
|
||||||
"i18n-2": "^0.7.2",
|
"i18n-2": "^0.7.2",
|
||||||
"iconv-lite": "^0.4.19",
|
"iconv-lite": "^0.4.19",
|
||||||
"immutable": "^3.8.1",
|
"immutable": "^3.8.1",
|
||||||
@@ -79,6 +81,8 @@
|
|||||||
"md5": "^2.0.0",
|
"md5": "^2.0.0",
|
||||||
"mdurl": "^1.0.1",
|
"mdurl": "^1.0.1",
|
||||||
"moment": "^2.10.3",
|
"moment": "^2.10.3",
|
||||||
|
"mousetrap": "^1.6.1",
|
||||||
|
"mousetrap-global-bind": "^1.1.0",
|
||||||
"node-ipc": "^8.1.0",
|
"node-ipc": "^8.1.0",
|
||||||
"raphael": "^2.2.7",
|
"raphael": "^2.2.7",
|
||||||
"react": "^15.5.4",
|
"react": "^15.5.4",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
:mega: We've launched a blogging platform with markdown called **[Boostlog](https://boostlog.io/)**.
|
:mega: We've launched [Boostnote Bounty Program](http://bit.ly/2I5Tpik).
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
@@ -17,9 +17,7 @@ exports[`TagListItem renders correctly 1`] = `
|
|||||||
# Test
|
# Test
|
||||||
<span
|
<span
|
||||||
className="tagList-item-count"
|
className="tagList-item-count"
|
||||||
>
|
/>
|
||||||
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ const findStorage = require('browser/lib/findStorage')
|
|||||||
jest.mock('unique-slug')
|
jest.mock('unique-slug')
|
||||||
const uniqueSlug = require('unique-slug')
|
const uniqueSlug = require('unique-slug')
|
||||||
const mdurl = require('mdurl')
|
const mdurl = require('mdurl')
|
||||||
|
const fse = require('fs-extra')
|
||||||
|
jest.mock('sander')
|
||||||
|
const sander = require('sander')
|
||||||
|
|
||||||
const systemUnderTest = require('browser/main/lib/dataApi/attachmentManagement')
|
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 noteKey = 'noteKey'
|
||||||
const dummyUniquePath = 'dummyPath'
|
const dummyUniquePath = 'dummyPath'
|
||||||
const dummyStorage = {path: 'dummyStoragePath'}
|
const dummyStorage = {path: 'dummyStoragePath'}
|
||||||
|
const dummyReadStream = {}
|
||||||
|
|
||||||
|
dummyReadStream.pipe = jest.fn()
|
||||||
|
dummyReadStream.on = jest.fn((event, callback) => { callback() })
|
||||||
fs.existsSync = jest.fn()
|
fs.existsSync = jest.fn()
|
||||||
fs.existsSync.mockReturnValue(true)
|
fs.existsSync.mockReturnValue(true)
|
||||||
fs.createReadStream = jest.fn()
|
fs.createReadStream = jest.fn(() => dummyReadStream)
|
||||||
fs.createReadStream.mockReturnValue({pipe: jest.fn()})
|
|
||||||
fs.createWriteStream = jest.fn()
|
fs.createWriteStream = jest.fn()
|
||||||
|
|
||||||
findStorage.findStorage = 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 noteKey = 'noteKey'
|
||||||
const attachmentFolderPath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER)
|
const attachmentFolderPath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER)
|
||||||
const attachmentFolderNoteKyPath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, noteKey)
|
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 = jest.fn()
|
||||||
fs.existsSync.mockReturnValueOnce(true)
|
fs.existsSync.mockReturnValueOnce(true)
|
||||||
fs.existsSync.mockReturnValueOnce(false)
|
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 () {
|
it('should test that copyAttachment don\'t uses a random file name if not intended ', function () {
|
||||||
const dummyStorage = {path: 'dummyStoragePath'}
|
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 = jest.fn()
|
||||||
fs.existsSync.mockReturnValueOnce(true)
|
fs.existsSync.mockReturnValueOnce(true)
|
||||||
fs.existsSync.mockReturnValueOnce(false)
|
fs.existsSync.mockReturnValueOnce(false)
|
||||||
@@ -142,13 +155,13 @@ it('should replace the all ":storage" path with the actual storage path', functi
|
|||||||
' <body data-theme="default">\n' +
|
' <body data-theme="default">\n' +
|
||||||
' <h2 data-line="0" id="Headline">Headline</h2>\n' +
|
' <h2 data-line="0" id="Headline">Headline</h2>\n' +
|
||||||
' <p data-line="2">\n' +
|
' <p data-line="2">\n' +
|
||||||
' <img src="file:///' + storagePath + '\\' + storageFolder + '\\0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' +
|
' <img src="file:///' + storagePath + path.sep + storageFolder + path.sep + '0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' +
|
||||||
' </p>\n' +
|
' </p>\n' +
|
||||||
' <p data-line="4">\n' +
|
' <p data-line="4">\n' +
|
||||||
' <a href="file:///' + storagePath + '\\' + storageFolder + '\\0.q2i4iw0fyx.pdf">dummyPDF.pdf</a>\n' +
|
' <a href="file:///' + storagePath + path.sep + storageFolder + path.sep + '0.q2i4iw0fyx.pdf">dummyPDF.pdf</a>\n' +
|
||||||
' </p>\n' +
|
' </p>\n' +
|
||||||
' <p data-line="6">\n' +
|
' <p data-line="6">\n' +
|
||||||
' <img src="file:///' + storagePath + '\\' + storageFolder + '\\d6c5ee92.jpg" alt="dummyImage2.jpg">\n' +
|
' <img src="file:///' + storagePath + path.sep + storageFolder + path.sep + 'd6c5ee92.jpg" alt="dummyImage2.jpg">\n' +
|
||||||
' </p>\n' +
|
' </p>\n' +
|
||||||
' </body>\n' +
|
' </body>\n' +
|
||||||
'</html>'
|
'</html>'
|
||||||
@@ -166,3 +179,291 @@ it('should test that generateAttachmentMarkdown works correct both with previews
|
|||||||
actual = systemUnderTest.generateAttachmentMarkdown(fileName, path, false)
|
actual = systemUnderTest.generateAttachmentMarkdown(fileName, path, false)
|
||||||
expect(actual).toEqual(expected)
|
expect(actual).toEqual(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should test that getAttachmentsInContent finds all attachments', function () {
|
||||||
|
const testInput =
|
||||||
|
'<html>\n' +
|
||||||
|
' <head>\n' +
|
||||||
|
' //header\n' +
|
||||||
|
' </head>\n' +
|
||||||
|
' <body data-theme="default">\n' +
|
||||||
|
' <h2 data-line="0" id="Headline">Headline</h2>\n' +
|
||||||
|
' <p data-line="2">\n' +
|
||||||
|
' <img src=":storage' + mdurl.encode(path.sep) + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + mdurl.encode(path.sep) + '0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' +
|
||||||
|
' </p>\n' +
|
||||||
|
' <p data-line="4">\n' +
|
||||||
|
' <a href=":storage' + mdurl.encode(path.sep) + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + mdurl.encode(path.sep) + '0.q2i4iw0fyx.pdf">dummyPDF.pdf</a>\n' +
|
||||||
|
' </p>\n' +
|
||||||
|
' <p data-line="6">\n' +
|
||||||
|
' <img src=":storage' + mdurl.encode(path.sep) + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + mdurl.encode(path.sep) + 'd6c5ee92.jpg" alt="dummyImage2.jpg">\n' +
|
||||||
|
' </p>\n' +
|
||||||
|
' </body>\n' +
|
||||||
|
'</html>'
|
||||||
|
const actual = systemUnderTest.getAttachmentsInContent(testInput)
|
||||||
|
const expected = [':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.6r4zdgc22xp', ':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.q2i4iw0fyx', ':storage' + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + 'd6c5ee92.jpg']
|
||||||
|
expect(actual).toEqual(expect.arrayContaining(expected))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should test that getAbsolutePathsOfAttachmentsInContent returns all absolute paths', function () {
|
||||||
|
const dummyStoragePath = 'dummyStoragePath'
|
||||||
|
const testInput =
|
||||||
|
'<html>\n' +
|
||||||
|
' <head>\n' +
|
||||||
|
' //header\n' +
|
||||||
|
' </head>\n' +
|
||||||
|
' <body data-theme="default">\n' +
|
||||||
|
' <h2 data-line="0" id="Headline">Headline</h2>\n' +
|
||||||
|
' <p data-line="2">\n' +
|
||||||
|
' <img src=":storage' + mdurl.encode(path.sep) + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + mdurl.encode(path.sep) + '0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' +
|
||||||
|
' </p>\n' +
|
||||||
|
' <p data-line="4">\n' +
|
||||||
|
' <a href=":storage' + mdurl.encode(path.sep) + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + mdurl.encode(path.sep) + '0.q2i4iw0fyx.pdf">dummyPDF.pdf</a>\n' +
|
||||||
|
' </p>\n' +
|
||||||
|
' <p data-line="6">\n' +
|
||||||
|
' <img src=":storage' + mdurl.encode(path.sep) + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + mdurl.encode(path.sep) + 'd6c5ee92.jpg" alt="dummyImage2.jpg">\n' +
|
||||||
|
' </p>\n' +
|
||||||
|
' </body>\n' +
|
||||||
|
'</html>'
|
||||||
|
const actual = systemUnderTest.getAbsolutePathsOfAttachmentsInContent(testInput, dummyStoragePath)
|
||||||
|
const expected = [dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.6r4zdgc22xp',
|
||||||
|
dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + '0.q2i4iw0fyx',
|
||||||
|
dummyStoragePath + path.sep + systemUnderTest.DESTINATION_FOLDER + path.sep + '9c9c4ba3-bc1e-441f-9866-c1e9a806e31c' + path.sep + 'd6c5ee92.jpg']
|
||||||
|
expect(actual).toEqual(expect.arrayContaining(expected))
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should remove the all ":storage" and noteKey references', function () {
|
||||||
|
const storageFolder = systemUnderTest.DESTINATION_FOLDER
|
||||||
|
const noteKey = 'noteKey'
|
||||||
|
const testInput =
|
||||||
|
'<html>\n' +
|
||||||
|
' <head>\n' +
|
||||||
|
' //header\n' +
|
||||||
|
' </head>\n' +
|
||||||
|
' <body data-theme="default">\n' +
|
||||||
|
' <h2 data-line="0" id="Headline">Headline</h2>\n' +
|
||||||
|
' <p data-line="2">\n' +
|
||||||
|
' <img src=":storage' + mdurl.encode(path.sep) + noteKey + mdurl.encode(path.sep) + '0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' +
|
||||||
|
' </p>\n' +
|
||||||
|
' <p data-line="4">\n' +
|
||||||
|
' <a href=":storage' + mdurl.encode(path.sep) + noteKey + mdurl.encode(path.sep) + '0.q2i4iw0fyx.pdf">dummyPDF.pdf</a>\n' +
|
||||||
|
' </p>\n' +
|
||||||
|
' <p data-line="6">\n' +
|
||||||
|
' <img src=":storage' + mdurl.encode(path.sep) + noteKey + mdurl.encode(path.sep) + 'd6c5ee92.jpg" alt="dummyImage2.jpg">\n' +
|
||||||
|
' </p>\n' +
|
||||||
|
' </body>\n' +
|
||||||
|
'</html>'
|
||||||
|
const expectedOutput =
|
||||||
|
'<html>\n' +
|
||||||
|
' <head>\n' +
|
||||||
|
' //header\n' +
|
||||||
|
' </head>\n' +
|
||||||
|
' <body data-theme="default">\n' +
|
||||||
|
' <h2 data-line="0" id="Headline">Headline</h2>\n' +
|
||||||
|
' <p data-line="2">\n' +
|
||||||
|
' <img src="' + storageFolder + path.sep + '0.6r4zdgc22xp.png" alt="dummyImage.png" >\n' +
|
||||||
|
' </p>\n' +
|
||||||
|
' <p data-line="4">\n' +
|
||||||
|
' <a href="' + storageFolder + path.sep + '0.q2i4iw0fyx.pdf">dummyPDF.pdf</a>\n' +
|
||||||
|
' </p>\n' +
|
||||||
|
' <p data-line="6">\n' +
|
||||||
|
' <img src="' + storageFolder + path.sep + 'd6c5ee92.jpg" alt="dummyImage2.jpg">\n' +
|
||||||
|
' </p>\n' +
|
||||||
|
' </body>\n' +
|
||||||
|
'</html>'
|
||||||
|
const actual = systemUnderTest.removeStorageAndNoteReferences(testInput, noteKey)
|
||||||
|
expect(actual).toEqual(expectedOutput)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should delete the correct attachment folder if a note is deleted', function () {
|
||||||
|
const dummyStorage = {path: 'dummyStoragePath'}
|
||||||
|
const storageKey = 'storageKey'
|
||||||
|
const noteKey = 'noteKey'
|
||||||
|
findStorage.findStorage = jest.fn(() => dummyStorage)
|
||||||
|
sander.rimrafSync = jest.fn()
|
||||||
|
|
||||||
|
const expectedPathToBeDeleted = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, noteKey)
|
||||||
|
systemUnderTest.deleteAttachmentFolder(storageKey, noteKey)
|
||||||
|
expect(findStorage.findStorage).toHaveBeenCalledWith(storageKey)
|
||||||
|
expect(sander.rimrafSync).toHaveBeenCalledWith(expectedPathToBeDeleted)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should test that deleteAttachmentsNotPresentInNote deletes all unreferenced attachments ', function () {
|
||||||
|
const dummyStorage = {path: 'dummyStoragePath'}
|
||||||
|
const noteKey = 'noteKey'
|
||||||
|
const storageKey = 'storageKey'
|
||||||
|
const markdownContent = ''
|
||||||
|
const dummyFilesInFolder = ['file1.txt', 'file2.pdf', 'file3.jpg']
|
||||||
|
const attachmentFolderPath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, noteKey)
|
||||||
|
|
||||||
|
findStorage.findStorage = jest.fn(() => dummyStorage)
|
||||||
|
fs.existsSync = jest.fn(() => true)
|
||||||
|
fs.readdir = jest.fn((paht, callback) => callback(undefined, dummyFilesInFolder))
|
||||||
|
fs.unlink = jest.fn()
|
||||||
|
|
||||||
|
systemUnderTest.deleteAttachmentsNotPresentInNote(markdownContent, storageKey, noteKey)
|
||||||
|
expect(fs.existsSync).toHaveBeenLastCalledWith(attachmentFolderPath)
|
||||||
|
expect(fs.readdir).toHaveBeenCalledTimes(1)
|
||||||
|
expect(fs.readdir.mock.calls[0][0]).toBe(attachmentFolderPath)
|
||||||
|
|
||||||
|
expect(fs.unlink).toHaveBeenCalledTimes(dummyFilesInFolder.length)
|
||||||
|
const fsUnlinkCallArguments = []
|
||||||
|
for (let i = 0; i < dummyFilesInFolder.length; i++) {
|
||||||
|
fsUnlinkCallArguments.push(fs.unlink.mock.calls[i][0])
|
||||||
|
}
|
||||||
|
|
||||||
|
dummyFilesInFolder.forEach(function (file) {
|
||||||
|
expect(fsUnlinkCallArguments.includes(path.join(attachmentFolderPath, file))).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should test that deleteAttachmentsNotPresentInNote does not delete referenced attachments', function () {
|
||||||
|
const dummyStorage = {path: 'dummyStoragePath'}
|
||||||
|
const noteKey = 'noteKey'
|
||||||
|
const storageKey = 'storageKey'
|
||||||
|
const dummyFilesInFolder = ['file1.txt', 'file2.pdf', 'file3.jpg']
|
||||||
|
const markdownContent = systemUnderTest.generateAttachmentMarkdown('fileLabel', path.join(systemUnderTest.STORAGE_FOLDER_PLACEHOLDER, noteKey, dummyFilesInFolder[0]), false)
|
||||||
|
const attachmentFolderPath = path.join(dummyStorage.path, systemUnderTest.DESTINATION_FOLDER, noteKey)
|
||||||
|
|
||||||
|
findStorage.findStorage = jest.fn(() => dummyStorage)
|
||||||
|
fs.existsSync = jest.fn(() => true)
|
||||||
|
fs.readdir = jest.fn((paht, callback) => callback(undefined, dummyFilesInFolder))
|
||||||
|
fs.unlink = jest.fn()
|
||||||
|
|
||||||
|
systemUnderTest.deleteAttachmentsNotPresentInNote(markdownContent, storageKey, noteKey)
|
||||||
|
|
||||||
|
expect(fs.unlink).toHaveBeenCalledTimes(dummyFilesInFolder.length - 1)
|
||||||
|
const fsUnlinkCallArguments = []
|
||||||
|
for (let i = 0; i < dummyFilesInFolder.length - 1; i++) {
|
||||||
|
fsUnlinkCallArguments.push(fs.unlink.mock.calls[i][0])
|
||||||
|
}
|
||||||
|
expect(fsUnlinkCallArguments.includes(path.join(attachmentFolderPath, dummyFilesInFolder[0]))).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should test that moveAttachments moves attachments only if the source folder existed', function () {
|
||||||
|
fse.existsSync = jest.fn(() => false)
|
||||||
|
fse.moveSync = jest.fn()
|
||||||
|
|
||||||
|
const oldPath = 'oldPath'
|
||||||
|
const newPath = 'newPath'
|
||||||
|
const oldNoteKey = 'oldNoteKey'
|
||||||
|
const newNoteKey = 'newNoteKey'
|
||||||
|
const content = ''
|
||||||
|
|
||||||
|
const expectedSource = path.join(oldPath, systemUnderTest.DESTINATION_FOLDER, oldNoteKey)
|
||||||
|
|
||||||
|
systemUnderTest.moveAttachments(oldPath, newPath, oldNoteKey, newNoteKey, content)
|
||||||
|
expect(fse.existsSync).toHaveBeenCalledWith(expectedSource)
|
||||||
|
expect(fse.moveSync).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should test that moveAttachments moves attachments to the right destination', function () {
|
||||||
|
fse.existsSync = jest.fn(() => true)
|
||||||
|
fse.moveSync = jest.fn()
|
||||||
|
|
||||||
|
const oldPath = 'oldPath'
|
||||||
|
const newPath = 'newPath'
|
||||||
|
const oldNoteKey = 'oldNoteKey'
|
||||||
|
const newNoteKey = 'newNoteKey'
|
||||||
|
const content = ''
|
||||||
|
|
||||||
|
const expectedSource = path.join(oldPath, systemUnderTest.DESTINATION_FOLDER, oldNoteKey)
|
||||||
|
const expectedDestination = path.join(newPath, systemUnderTest.DESTINATION_FOLDER, newNoteKey)
|
||||||
|
|
||||||
|
systemUnderTest.moveAttachments(oldPath, newPath, oldNoteKey, newNoteKey, content)
|
||||||
|
expect(fse.existsSync).toHaveBeenCalledWith(expectedSource)
|
||||||
|
expect(fse.moveSync).toHaveBeenCalledWith(expectedSource, expectedDestination)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should test that moveAttachments returns a correct modified content version', function () {
|
||||||
|
fse.existsSync = jest.fn()
|
||||||
|
fse.moveSync = jest.fn()
|
||||||
|
|
||||||
|
const oldPath = 'oldPath'
|
||||||
|
const newPath = 'newPath'
|
||||||
|
const oldNoteKey = 'oldNoteKey'
|
||||||
|
const newNoteKey = 'newNoteKey'
|
||||||
|
const testInput =
|
||||||
|
'Test input' +
|
||||||
|
' \n' +
|
||||||
|
'[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + oldNoteKey + path.sep + 'pdf.pdf](pdf})'
|
||||||
|
const expectedOutput =
|
||||||
|
'Test input' +
|
||||||
|
' \n' +
|
||||||
|
'[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNoteKey + path.sep + 'pdf.pdf](pdf})'
|
||||||
|
|
||||||
|
const actualContent = systemUnderTest.moveAttachments(oldPath, newPath, oldNoteKey, newNoteKey, testInput)
|
||||||
|
expect(actualContent).toBe(expectedOutput)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should test that cloneAttachments modifies the content of the new note correctly', function () {
|
||||||
|
const oldNote = {key: 'oldNoteKey', content: 'oldNoteContent', storage: 'storageKey', type: 'MARKDOWN_NOTE'}
|
||||||
|
const newNote = {key: 'newNoteKey', content: 'oldNoteContent', storage: 'storageKey', type: 'MARKDOWN_NOTE'}
|
||||||
|
const testInput =
|
||||||
|
'Test input' +
|
||||||
|
' \n' +
|
||||||
|
'[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + oldNote.key + path.sep + 'pdf.pdf](pdf})'
|
||||||
|
newNote.content = testInput
|
||||||
|
|
||||||
|
const expectedOutput =
|
||||||
|
'Test input' +
|
||||||
|
' \n' +
|
||||||
|
'[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + newNote.key + path.sep + 'pdf.pdf](pdf})'
|
||||||
|
systemUnderTest.cloneAttachments(oldNote, newNote)
|
||||||
|
|
||||||
|
expect(newNote.content).toBe(expectedOutput)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should test that cloneAttachments finds all attachments and copies them to the new location', function () {
|
||||||
|
const storagePathOld = 'storagePathOld'
|
||||||
|
const storagePathNew = 'storagePathNew'
|
||||||
|
const dummyStorageOld = {path: storagePathOld}
|
||||||
|
const dummyStorageNew = {path: storagePathNew}
|
||||||
|
const oldNote = {key: 'oldNoteKey', content: 'oldNoteContent', storage: 'storageKeyOldNote', type: 'MARKDOWN_NOTE'}
|
||||||
|
const newNote = {key: 'newNoteKey', content: 'oldNoteContent', storage: 'storageKeyNewNote', type: 'MARKDOWN_NOTE'}
|
||||||
|
const testInput =
|
||||||
|
'Test input' +
|
||||||
|
' \n' +
|
||||||
|
'[' + systemUnderTest.STORAGE_FOLDER_PLACEHOLDER + path.sep + oldNote.key + path.sep + 'pdf.pdf](pdf})'
|
||||||
|
oldNote.content = testInput
|
||||||
|
newNote.content = testInput
|
||||||
|
|
||||||
|
const copyFileSyncResp = {to: jest.fn()}
|
||||||
|
sander.copyFileSync = jest.fn()
|
||||||
|
sander.copyFileSync.mockReturnValue(copyFileSyncResp)
|
||||||
|
findStorage.findStorage = jest.fn()
|
||||||
|
findStorage.findStorage.mockReturnValueOnce(dummyStorageOld)
|
||||||
|
findStorage.findStorage.mockReturnValue(dummyStorageNew)
|
||||||
|
|
||||||
|
const pathAttachmentOneFrom = path.join(storagePathOld, systemUnderTest.DESTINATION_FOLDER, oldNote.key, 'image.jpg')
|
||||||
|
const pathAttachmentOneTo = path.join(storagePathNew, systemUnderTest.DESTINATION_FOLDER, newNote.key, 'image.jpg')
|
||||||
|
|
||||||
|
const pathAttachmentTwoFrom = path.join(storagePathOld, systemUnderTest.DESTINATION_FOLDER, oldNote.key, 'pdf.pdf')
|
||||||
|
const pathAttachmentTwoTo = path.join(storagePathNew, systemUnderTest.DESTINATION_FOLDER, newNote.key, 'pdf.pdf')
|
||||||
|
|
||||||
|
systemUnderTest.cloneAttachments(oldNote, newNote)
|
||||||
|
|
||||||
|
expect(findStorage.findStorage).toHaveBeenCalledWith(oldNote.storage)
|
||||||
|
expect(findStorage.findStorage).toHaveBeenCalledWith(newNote.storage)
|
||||||
|
expect(sander.copyFileSync).toHaveBeenCalledTimes(2)
|
||||||
|
expect(copyFileSyncResp.to).toHaveBeenCalledTimes(2)
|
||||||
|
expect(sander.copyFileSync.mock.calls[0][0]).toBe(pathAttachmentOneFrom)
|
||||||
|
expect(copyFileSyncResp.to.mock.calls[0][0]).toBe(pathAttachmentOneTo)
|
||||||
|
expect(sander.copyFileSync.mock.calls[1][0]).toBe(pathAttachmentTwoFrom)
|
||||||
|
expect(copyFileSyncResp.to.mock.calls[1][0]).toBe(pathAttachmentTwoTo)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should test that cloneAttachments finds all attachments and copies them to the new location', function () {
|
||||||
|
const oldNote = {key: 'oldNoteKey', content: 'oldNoteContent', storage: 'storageKeyOldNote', type: 'SOMETHING_ELSE'}
|
||||||
|
const newNote = {key: 'newNoteKey', content: 'oldNoteContent', storage: 'storageKeyNewNote', type: 'SOMETHING_ELSE'}
|
||||||
|
const testInput = 'Test input'
|
||||||
|
oldNote.content = testInput
|
||||||
|
newNote.content = testInput
|
||||||
|
|
||||||
|
sander.copyFileSync = jest.fn()
|
||||||
|
findStorage.findStorage = jest.fn()
|
||||||
|
|
||||||
|
systemUnderTest.cloneAttachments(oldNote, newNote)
|
||||||
|
|
||||||
|
expect(findStorage.findStorage).not.toHaveBeenCalled()
|
||||||
|
expect(sander.copyFileSync).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
const test = require('ava')
|
const test = require('ava')
|
||||||
const deleteFolder = require('browser/main/lib/dataApi/deleteFolder')
|
const deleteFolder = require('browser/main/lib/dataApi/deleteFolder')
|
||||||
|
const attachmentManagement = require('browser/main/lib/dataApi/attachmentManagement')
|
||||||
|
const createNote = require('browser/main/lib/dataApi/createNote')
|
||||||
|
const fs = require('fs')
|
||||||
|
const faker = require('faker')
|
||||||
|
|
||||||
global.document = require('jsdom').jsdom('<body></body>')
|
global.document = require('jsdom').jsdom('<body></body>')
|
||||||
global.window = document.defaultView
|
global.window = document.defaultView
|
||||||
@@ -24,8 +28,32 @@ test.beforeEach((t) => {
|
|||||||
test.serial('Delete a folder', (t) => {
|
test.serial('Delete a folder', (t) => {
|
||||||
const storageKey = t.context.storage.cache.key
|
const storageKey = t.context.storage.cache.key
|
||||||
const folderKey = t.context.storage.json.folders[0].key
|
const folderKey = t.context.storage.json.folders[0].key
|
||||||
|
let noteKey
|
||||||
|
|
||||||
|
const input1 = {
|
||||||
|
type: 'SNIPPET_NOTE',
|
||||||
|
description: faker.lorem.lines(),
|
||||||
|
snippets: [{
|
||||||
|
name: faker.system.fileName(),
|
||||||
|
mode: 'text',
|
||||||
|
content: faker.lorem.lines()
|
||||||
|
}],
|
||||||
|
tags: faker.lorem.words().split(' '),
|
||||||
|
folder: folderKey
|
||||||
|
}
|
||||||
|
input1.title = input1.description.split('\n').shift()
|
||||||
|
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
|
.then(function prepare () {
|
||||||
|
return createNote(storageKey, input1)
|
||||||
|
.then(function createAttachmentFolder (data) {
|
||||||
|
fs.mkdirSync(path.join(storagePath, attachmentManagement.DESTINATION_FOLDER))
|
||||||
|
fs.mkdirSync(path.join(storagePath, attachmentManagement.DESTINATION_FOLDER, data.key))
|
||||||
|
noteKey = data.key
|
||||||
|
|
||||||
|
return data
|
||||||
|
})
|
||||||
|
})
|
||||||
.then(function doTest () {
|
.then(function doTest () {
|
||||||
return deleteFolder(storageKey, folderKey)
|
return deleteFolder(storageKey, folderKey)
|
||||||
})
|
})
|
||||||
@@ -36,6 +64,9 @@ test.serial('Delete a folder', (t) => {
|
|||||||
t.true(_.find(jsonData.folders, {key: folderKey}) == null)
|
t.true(_.find(jsonData.folders, {key: folderKey}) == null)
|
||||||
const notePaths = sander.readdirSync(data.storage.path, 'notes')
|
const notePaths = sander.readdirSync(data.storage.path, 'notes')
|
||||||
t.is(notePaths.length, t.context.storage.notes.filter((note) => note.folder !== folderKey).length)
|
t.is(notePaths.length, t.context.storage.notes.filter((note) => note.folder !== folderKey).length)
|
||||||
|
|
||||||
|
const attachmentFolderPath = path.join(storagePath, attachmentManagement.DESTINATION_FOLDER, noteKey)
|
||||||
|
t.false(fs.existsSync(attachmentFolderPath))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ const sander = require('sander')
|
|||||||
const os = require('os')
|
const os = require('os')
|
||||||
const CSON = require('@rokt33r/season')
|
const CSON = require('@rokt33r/season')
|
||||||
const faker = require('faker')
|
const faker = require('faker')
|
||||||
|
const fs = require('fs')
|
||||||
|
const attachmentManagement = require('browser/main/lib/dataApi/attachmentManagement')
|
||||||
|
|
||||||
const storagePath = path.join(os.tmpdir(), 'test/delete-note')
|
const storagePath = path.join(os.tmpdir(), 'test/delete-note')
|
||||||
|
|
||||||
@@ -42,6 +44,11 @@ test.serial('Delete a note', (t) => {
|
|||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
.then(function doTest () {
|
.then(function doTest () {
|
||||||
return createNote(storageKey, input1)
|
return createNote(storageKey, input1)
|
||||||
|
.then(function createAttachmentFolder (data) {
|
||||||
|
fs.mkdirSync(path.join(storagePath, attachmentManagement.DESTINATION_FOLDER))
|
||||||
|
fs.mkdirSync(path.join(storagePath, attachmentManagement.DESTINATION_FOLDER, data.key))
|
||||||
|
return data
|
||||||
|
})
|
||||||
.then(function (data) {
|
.then(function (data) {
|
||||||
return deleteNote(storageKey, data.key)
|
return deleteNote(storageKey, data.key)
|
||||||
})
|
})
|
||||||
@@ -52,8 +59,13 @@ test.serial('Delete a note', (t) => {
|
|||||||
t.fail('note cson must be deleted.')
|
t.fail('note cson must be deleted.')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
t.is(err.code, 'ENOENT')
|
t.is(err.code, 'ENOENT')
|
||||||
|
return data
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.then(function assertAttachmentFolderDeleted (data) {
|
||||||
|
const attachmentFolderPath = path.join(storagePath, attachmentManagement.DESTINATION_FOLDER, data.noteKey)
|
||||||
|
t.is(fs.existsSync(attachmentFolderPath), false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test.after(function after () {
|
test.after(function after () {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const TestDummy = require('../fixtures/TestDummy')
|
|||||||
const os = require('os')
|
const os = require('os')
|
||||||
const faker = require('faker')
|
const faker = require('faker')
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
|
const sander = require('sander')
|
||||||
|
|
||||||
const storagePath = path.join(os.tmpdir(), 'test/export-note')
|
const storagePath = path.join(os.tmpdir(), 'test/export-note')
|
||||||
|
|
||||||
@@ -60,3 +61,8 @@ test.serial('Export a folder', (t) => {
|
|||||||
t.false(fs.existsSync(filePath))
|
t.false(fs.existsSync(filePath))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test.after.always(function after () {
|
||||||
|
localStorage.clear()
|
||||||
|
sander.rimrafSync(storagePath)
|
||||||
|
})
|
||||||
|
|||||||
5
tests/fixtures/markdowns.js
vendored
5
tests/fixtures/markdowns.js
vendored
@@ -48,10 +48,13 @@ const checkboxes = `
|
|||||||
|
|
||||||
const smartQuotes = 'This is a "QUOTE".'
|
const smartQuotes = 'This is a "QUOTE".'
|
||||||
|
|
||||||
|
const breaks = 'This is the first line.\nThis is the second line.'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
basic,
|
basic,
|
||||||
codeblock,
|
codeblock,
|
||||||
katex,
|
katex,
|
||||||
checkboxes,
|
checkboxes,
|
||||||
smartQuotes
|
smartQuotes,
|
||||||
|
breaks
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,3 +34,12 @@ test('Markdown.render() should text with quotes correctly', t => {
|
|||||||
const renderedNonSmartQuotes = newmd.render(markdownFixtures.smartQuotes)
|
const renderedNonSmartQuotes = newmd.render(markdownFixtures.smartQuotes)
|
||||||
t.snapshot(renderedNonSmartQuotes)
|
t.snapshot(renderedNonSmartQuotes)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Markdown.render() should render line breaks correctly', t => {
|
||||||
|
const renderedBreaks = md.render(markdownFixtures.breaks)
|
||||||
|
t.snapshot(renderedBreaks)
|
||||||
|
|
||||||
|
const newmd = new Markdown({ breaks: false })
|
||||||
|
const renderedNonBreaks = newmd.render(markdownFixtures.breaks)
|
||||||
|
t.snapshot(renderedNonBreaks)
|
||||||
|
})
|
||||||
|
|||||||
@@ -4,6 +4,20 @@ The actual snapshot is saved in `markdown-test.js.snap`.
|
|||||||
|
|
||||||
Generated by [AVA](https://ava.li).
|
Generated by [AVA](https://ava.li).
|
||||||
|
|
||||||
|
## Markdown.render() should render line breaks correctly
|
||||||
|
|
||||||
|
> Snapshot 1
|
||||||
|
|
||||||
|
`<p data-line="0">This is the first line.<br />␊
|
||||||
|
This is the second line.</p>␊
|
||||||
|
`
|
||||||
|
|
||||||
|
> Snapshot 2
|
||||||
|
|
||||||
|
`<p data-line="0">This is the first line.␊
|
||||||
|
This is the second line.</p>␊
|
||||||
|
`
|
||||||
|
|
||||||
## Markdown.render() should renders KaTeX correctly
|
## Markdown.render() should renders KaTeX correctly
|
||||||
|
|
||||||
> Snapshot 1
|
> Snapshot 1
|
||||||
|
|||||||
Binary file not shown.
32
yarn.lock
32
yarn.lock
@@ -1864,7 +1864,7 @@ codemirror-mode-elixir@^1.1.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
codemirror "^5.20.2"
|
codemirror "^5.20.2"
|
||||||
|
|
||||||
codemirror@^5.18.2, codemirror@^5.19.0:
|
codemirror@^5.18.2:
|
||||||
version "5.26.0"
|
version "5.26.0"
|
||||||
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.26.0.tgz#bcbee86816ed123870c260461c2b5c40b68746e5"
|
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.26.0.tgz#bcbee86816ed123870c260461c2b5c40b68746e5"
|
||||||
|
|
||||||
@@ -1872,6 +1872,10 @@ codemirror@^5.20.2:
|
|||||||
version "5.33.0"
|
version "5.33.0"
|
||||||
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.33.0.tgz#462ad9a6fe8d38b541a9536a3997e1ef93b40c6a"
|
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.33.0.tgz#462ad9a6fe8d38b541a9536a3997e1ef93b40c6a"
|
||||||
|
|
||||||
|
codemirror@^5.37.0:
|
||||||
|
version "5.37.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.37.0.tgz#c349b584e158f590277f26d37c2469a6bc538036"
|
||||||
|
|
||||||
coffee-script@^1.10.0:
|
coffee-script@^1.10.0:
|
||||||
version "1.12.6"
|
version "1.12.6"
|
||||||
resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.12.6.tgz#285a3f7115689065064d6bf9ef4572db66695cbf"
|
resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.12.6.tgz#285a3f7115689065064d6bf9ef4572db66695cbf"
|
||||||
@@ -3644,6 +3648,14 @@ fs-extra@^1.0.0:
|
|||||||
jsonfile "^2.1.0"
|
jsonfile "^2.1.0"
|
||||||
klaw "^1.0.0"
|
klaw "^1.0.0"
|
||||||
|
|
||||||
|
fs-extra@^5.0.0:
|
||||||
|
version "5.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-5.0.0.tgz#414d0110cdd06705734d055652c5411260c31abd"
|
||||||
|
dependencies:
|
||||||
|
graceful-fs "^4.1.2"
|
||||||
|
jsonfile "^4.0.0"
|
||||||
|
universalify "^0.1.0"
|
||||||
|
|
||||||
fs-plus@2.x:
|
fs-plus@2.x:
|
||||||
version "2.10.1"
|
version "2.10.1"
|
||||||
resolved "https://registry.yarnpkg.com/fs-plus/-/fs-plus-2.10.1.tgz#3204781d7840611e6364e7b6fb058c96327c5aa5"
|
resolved "https://registry.yarnpkg.com/fs-plus/-/fs-plus-2.10.1.tgz#3204781d7840611e6364e7b6fb058c96327c5aa5"
|
||||||
@@ -5316,6 +5328,12 @@ jsonfile@^2.0.0, jsonfile@^2.1.0:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
graceful-fs "^4.1.6"
|
graceful-fs "^4.1.6"
|
||||||
|
|
||||||
|
jsonfile@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
|
||||||
|
optionalDependencies:
|
||||||
|
graceful-fs "^4.1.6"
|
||||||
|
|
||||||
jsonify@~0.0.0:
|
jsonify@~0.0.0:
|
||||||
version "0.0.0"
|
version "0.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
|
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
|
||||||
@@ -5967,6 +5985,14 @@ moment@^2.10.3:
|
|||||||
version "2.18.1"
|
version "2.18.1"
|
||||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f"
|
resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f"
|
||||||
|
|
||||||
|
mousetrap-global-bind@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mousetrap-global-bind/-/mousetrap-global-bind-1.1.0.tgz#cd7de9222bd0646fa2e010d54c84a74c26a88edd"
|
||||||
|
|
||||||
|
mousetrap@^1.6.1:
|
||||||
|
version "1.6.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.6.1.tgz#2a085f5c751294c75e7e81f6ec2545b29cbf42d9"
|
||||||
|
|
||||||
ms@0.7.1:
|
ms@0.7.1:
|
||||||
version "0.7.1"
|
version "0.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098"
|
||||||
@@ -8749,6 +8775,10 @@ unique-temp-dir@^1.0.0:
|
|||||||
os-tmpdir "^1.0.1"
|
os-tmpdir "^1.0.1"
|
||||||
uid2 "0.0.3"
|
uid2 "0.0.3"
|
||||||
|
|
||||||
|
universalify@^0.1.0:
|
||||||
|
version "0.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7"
|
||||||
|
|
||||||
unpipe@~1.0.0:
|
unpipe@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
||||||
|
|||||||
Reference in New Issue
Block a user