1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-30 18:11:47 +00:00

resolved conflict

This commit is contained in:
Nguyễn Việt Hưng
2018-05-25 18:42:02 +07:00
87 changed files with 1586 additions and 235 deletions

View File

@@ -4,6 +4,7 @@ import _ from 'lodash'
import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir'
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
import convertModeName from 'browser/lib/convertModeName'
import eventEmitter from 'browser/main/lib/eventEmitter'
import iconv from 'iconv-lite'
import crypto from 'crypto'
@@ -17,21 +18,6 @@ const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', '
const buildCMRulers = (rulers, enableRulers) =>
enableRulers ? rulers.map(ruler => ({ column: ruler })) : []
function pass (name) {
switch (name) {
case 'ejs':
return 'Embedded Javascript'
case 'html_ruby':
return 'Embedded Ruby'
case 'objectivec':
return 'Objective C'
case 'text':
return 'Plain Text'
default:
return name
}
}
export default class CodeEditor extends React.Component {
constructor (props) {
super(props)
@@ -52,6 +38,9 @@ export default class CodeEditor extends React.Component {
el = el.parentNode
}
this.props.onBlur != null && this.props.onBlur(e)
const {storageKey, noteKey} = this.props
attachmentManagement.deleteAttachmentsNotPresentInNote(this.editor.getValue(), storageKey, noteKey)
}
this.pasteHandler = (editor, e) => this.handlePaste(editor, e)
this.loadStyleHandler = (e) => {
@@ -323,7 +312,7 @@ export default class CodeEditor extends React.Component {
}
setMode (mode) {
let syntax = CodeMirror.findModeByName(pass(mode))
let syntax = CodeMirror.findModeByName(convertModeName(mode))
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
this.editor.setOption('mode', syntax.mime)

View File

@@ -283,6 +283,7 @@ class MarkdownEditor extends React.Component {
indentSize={editorIndentSize}
scrollPastEnd={config.preview.scrollPastEnd}
smartQuotes={config.preview.smartQuotes}
breaks={config.preview.breaks}
sanitize={config.preview.sanitize}
ref='preview'
onContextMenu={(e) => this.handleContextMenu(e)}
@@ -294,6 +295,7 @@ class MarkdownEditor extends React.Component {
onCheckboxClick={(e) => this.handleCheckboxClick(e)}
showCopyNotification={config.ui.showCopyNotification}
storagePath={storage.path}
noteKey={noteKey}
/>
</div>
)

View File

@@ -10,6 +10,7 @@ import flowchart from 'flowchart'
import SequenceDiagram from 'js-sequence-diagrams'
import eventEmitter from 'browser/main/lib/eventEmitter'
import htmlTextHelper from 'browser/lib/htmlTextHelper'
import convertModeName from 'browser/lib/convertModeName'
import copy from 'copy-to-clipboard'
import mdurl from 'mdurl'
import exportNote from 'browser/main/lib/dataApi/exportNote'
@@ -31,7 +32,7 @@ const CSS_FILES = [
`${appPath}/node_modules/codemirror/lib/codemirror.css`
]
function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd) {
function buildStyle (fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme) {
return `
@font-face {
font-family: 'Lato';
@@ -103,6 +104,13 @@ h2 {
body p {
white-space: normal;
}
@media print {
body[data-theme="${theme}"] {
color: #000;
background-color: #fff;
}
}
`
}
@@ -115,7 +123,6 @@ if (!OSX) {
defaultFontFamily.unshift('meiryo')
}
const defaultCodeBlockFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
export default class MarkdownPreview extends React.Component {
constructor (props) {
super(props)
@@ -138,10 +145,11 @@ export default class MarkdownPreview extends React.Component {
}
initMarkdown () {
const { smartQuotes, sanitize } = this.props
const { smartQuotes, sanitize, breaks } = this.props
this.markdown = new Markdown({
typographer: smartQuotes,
sanitize
sanitize,
breaks
})
}
@@ -208,11 +216,13 @@ export default class MarkdownPreview extends React.Component {
handleSaveAsHtml () {
this.exportAsDocument('html', (noteContent, exportTasks) => {
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme} = this.getStyleParams()
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme} = this.getStyleParams()
const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme)
let body = this.markdown.render(escapeHtmlCharacters(noteContent))
const inlineStyles = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, lineNumber)
const body = this.markdown.render(escapeHtmlCharacters(noteContent))
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(noteContent, this.props.storagePath)
files.forEach((file) => {
file = file.replace('file://', '')
@@ -221,6 +231,13 @@ export default class MarkdownPreview extends React.Component {
dst: 'css'
})
})
attachmentsAbsolutePaths.forEach((attachment) => {
exportTasks.push({
src: attachment,
dst: attachmentManagement.DESTINATION_FOLDER
})
})
body = attachmentManagement.removeStorageAndNoteReferences(body, this.props.noteKey)
let styles = ''
files.forEach((file) => {
@@ -324,7 +341,9 @@ export default class MarkdownPreview extends React.Component {
componentDidUpdate (prevProps) {
if (prevProps.value !== this.props.value) this.rewriteIframe()
if (prevProps.smartQuotes !== this.props.smartQuotes || prevProps.sanitize !== this.props.sanitize) {
if (prevProps.smartQuotes !== this.props.smartQuotes ||
prevProps.sanitize !== this.props.sanitize ||
prevProps.breaks !== this.props.breaks) {
this.initMarkdown()
this.rewriteIframe()
}
@@ -342,7 +361,7 @@ export default class MarkdownPreview extends React.Component {
}
getStyleParams () {
const { fontSize, lineNumber, codeBlockTheme, scrollPastEnd } = this.props
const { fontSize, lineNumber, codeBlockTheme, scrollPastEnd, theme } = this.props
let { fontFamily, codeBlockFontFamily } = this.props
fontFamily = _.isString(fontFamily) && fontFamily.trim().length > 0
? fontFamily.split(',').map(fontName => fontName.trim()).concat(defaultFontFamily)
@@ -351,14 +370,14 @@ export default class MarkdownPreview extends React.Component {
? codeBlockFontFamily.split(',').map(fontName => fontName.trim()).concat(defaultCodeBlockFontFamily)
: defaultCodeBlockFontFamily
return {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd}
return {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme}
}
applyStyle () {
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd} = this.getStyleParams()
const {fontFamily, fontSize, codeBlockFontFamily, lineNumber, codeBlockTheme, scrollPastEnd, theme} = this.getStyleParams()
this.getWindow().document.getElementById('codeTheme').href = this.GetCodeThemeLink(codeBlockTheme)
this.getWindow().document.getElementById('style').innerHTML = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd)
this.getWindow().document.getElementById('style').innerHTML = buildStyle(fontFamily, fontSize, codeBlockFontFamily, lineNumber, scrollPastEnd, theme)
}
GetCodeThemeLink (theme) {
@@ -414,7 +433,7 @@ export default class MarkdownPreview extends React.Component {
: 'default'
_.forEach(this.refs.root.contentWindow.document.querySelectorAll('.code code'), (el) => {
let syntax = CodeMirror.findModeByName(el.className)
let syntax = CodeMirror.findModeByName(convertModeName(el.className))
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
CodeMirror.requireMode(syntax.mode, () => {
const content = htmlTextHelper.decodeEntities(el.innerHTML)
@@ -526,21 +545,36 @@ export default class MarkdownPreview extends React.Component {
return
}
const noteHash = e.target.href.split('/').pop()
const linkHash = href.split('/').pop()
const regexNoteInternalLink = /main.html#(.+)/
if (regexNoteInternalLink.test(linkHash)) {
const targetId = mdurl.encode(linkHash.match(regexNoteInternalLink)[1])
const targetElement = this.refs.root.contentWindow.document.getElementById(targetId)
if (targetElement != null) {
this.getWindow().scrollTo(0, targetElement.offsetTop)
}
return
}
// this will match the new uuid v4 hash and the old hash
// e.g.
// :note:1c211eb7dcb463de6490 and
// :note:7dd23275-f2b4-49cb-9e93-3454daf1af9c
const regexIsNoteLink = /^:note:([a-zA-Z0-9-]{20,36})$/
if (regexIsNoteLink.test(noteHash)) {
eventEmitter.emit('list:jump', noteHash.replace(':note:', ''))
if (regexIsNoteLink.test(linkHash)) {
eventEmitter.emit('list:jump', linkHash.replace(':note:', ''))
return
}
// this will match the old link format storage.key-note.key
// e.g.
// 877f99c3268608328037-1c211eb7dcb463de6490
const regexIsLegacyNoteLink = /^(.{20})-(.{20})$/
if (regexIsLegacyNoteLink.test(noteHash)) {
eventEmitter.emit('list:jump', noteHash.split('-')[1])
if (regexIsLegacyNoteLink.test(linkHash)) {
eventEmitter.emit('list:jump', linkHash.split('-')[1])
return
}
}
@@ -568,5 +602,6 @@ MarkdownPreview.propTypes = {
value: PropTypes.string,
showCopyNotification: PropTypes.bool,
storagePath: PropTypes.string,
smartQuotes: PropTypes.bool
smartQuotes: PropTypes.bool,
breaks: PropTypes.bool
}

View File

@@ -131,6 +131,7 @@ class MarkdownSplitEditor extends React.Component {
lineNumber={config.preview.lineNumber}
scrollPastEnd={config.preview.scrollPastEnd}
smartQuotes={config.preview.smartQuotes}
breaks={config.preview.breaks}
sanitize={config.preview.sanitize}
ref='preview'
tabInde='0'
@@ -139,6 +140,7 @@ class MarkdownSplitEditor extends React.Component {
onScroll={this.handleScroll.bind(this)}
showCopyNotification={config.ui.showCopyNotification}
storagePath={storage.path}
noteKey={noteKey}
/>
</div>
)

View File

@@ -321,3 +321,76 @@ body[data-theme="solarized-dark"]
.item-bottom-tagList-empty
color $ui-inactive-text-color
vertical-align middle
body[data-theme="monokai"]
.root
border-color $ui-monokai-borderColor
background-color $ui-monokai-noteList-backgroundColor
.item
border-color $ui-monokai-borderColor
background-color $ui-monokai-noteList-backgroundColor
&:hover
transition 0.15s
// background-color alpha($ui-monokai-noteList-backgroundColor, 20%)
color $ui-monokai-text-color
.item-title
.item-title-icon
.item-bottom-time
transition 0.15s
color $ui-monokai-text-color
.item-bottom-tagList-item
transition 0.15s
background-color alpha($ui-monokai-noteList-backgroundColor, 20%)
color $ui-monokai-text-color
&:active
transition 0.15s
background-color $ui-monokai-noteList-backgroundColor
color $ui-monokai-text-color
.item-title
.item-title-icon
.item-bottom-time
transition 0.15s
color $ui-monokai-text-color
.item-bottom-tagList-item
transition 0.15s
background-color alpha($ui-monokai-noteList-backgroundColor, 10%)
color $ui-monokai-text-color
.item-wrapper
border-color alpha($ui-monokai-button-backgroundColor, 60%)
.item--active
border-color $ui-monokai-borderColor
background-color $ui-monokai-button-backgroundColor
.item-wrapper
border-color transparent
.item-title
.item-title-icon
.item-bottom-time
color $ui-monokai-text-color
.item-bottom-tagList-item
background-color alpha(white, 10%)
color $ui-monokai-text-color
&:hover
// background-color alpha($ui-monokai-button--active-backgroundColor, 60%)
color #c0392b
.item-bottom-tagList-item
background-color alpha(#fff, 20%)
.item-title
color $ui-inactive-text-color
.item-title-icon
color $ui-inactive-text-color
.item-title-empty
color $ui-inactive-text-color
.item-bottom-tagList-item
background-color alpha($ui-dark-button--active-backgroundColor, 40%)
color $ui-inactive-text-color
.item-bottom-tagList-empty
color $ui-inactive-text-color
vertical-align middle

View File

@@ -104,6 +104,7 @@ body[data-theme="dark"]
background-color alpha($ui-dark-button--active-backgroundColor, 20%)
color $ui-dark-text-color
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
@@ -117,6 +118,7 @@ body[data-theme="dark"]
background-color $ui-dark-button--active-backgroundColor
color $ui-dark-text-color
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
@@ -165,9 +167,10 @@ body[data-theme="solarized-dark"]
background-color $ui-solarized-dark-noteList-backgroundColor
&:hover
transition 0.15s
// background-color alpha($ui-dark-button--active-backgroundColor, 20%)
background-color alpha($ui-dark-button--active-backgroundColor, 60%)
color $ui-solarized-dark-text-color
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
@@ -178,9 +181,10 @@ body[data-theme="solarized-dark"]
color $ui-solarized-dark-text-color
&:active
transition 0.15s
background-color $ui-solarized-dark-button--active-backgroundColor
color $ui-solarized-dark-text-color
// background-color $ui-solarized-dark-button--active-backgroundColor
color $ui-dark-text-color
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
@@ -192,11 +196,13 @@ body[data-theme="solarized-dark"]
.item-simple--active
border-color $ui-solarized-dark-borderColor
background-color $ui-solarized-dark-button--active-backgroundColor
background-color $ui-solarized-dark-tag-backgroundColor
.item-simple-wrapper
border-color transparent
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
color $ui-dark-text-color
.item-simple-bottom-time
color $ui-solarized-dark-text-color
.item-simple-bottom-tagList-item
@@ -207,8 +213,75 @@ body[data-theme="solarized-dark"]
color #c0392b
.item-simple-bottom-tagList-item
background-color alpha(#fff, 20%)
.item-simple-right
float right
.item-simple-right-storageName
padding-left 4px
opacity 0.4
.item-simple-title
color $ui-dark-text-color
border-bottom $ui-dark-borderColor
.item-simple-right
float right
.item-simple-right-storageName
padding-left 4px
opacity 0.4
body[data-theme="monokai"]
.root
border-color $ui-monokai-borderColor
background-color $ui-monokai-noteList-backgroundColor
.item-simple
border-color $ui-monokai-borderColor
background-color $ui-monokai-noteList-backgroundColor
&:hover
transition 0.15s
background-color alpha($ui-monokai-button-backgroundColor, 60%)
color $ui-monokai-text-color
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
color $ui-solarized-dark-text-color
.item-simple-bottom-tagList-item
transition 0.15s
background-color alpha(#fff, 20%)
color $ui-monokai-text-color
&:active
transition 0.15s
background-color $ui-monokai-button--active-backgroundColor
color $ui-monokai-text-color
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
color $ui-monokai-text-color
.item-simple-bottom-tagList-item
transition 0.15s
background-color alpha(white, 10%)
color $ui-monokai-text-color
.item-simple--active
border-color $ui-monokai-borderColor
background-color $ui-monokai-button--active-backgroundColor
.item-simple-wrapper
border-color transparent
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
color $ui-monokai-text-color
.item-simple-bottom-tagList-item
background-color alpha(white, 10%)
color $ui-monokai-text-color
&:hover
// background-color alpha($ui-dark-button--active-backgroundColor, 60%)
color #c0392b
.item-simple-bottom-tagList-item
background-color alpha(#fff, 20%)
.item-simple-title
color $ui-dark-text-color
border-bottom $ui-dark-borderColor
.item-simple-right
float right
.item-simple-right-storageName
padding-left 4px
opacity 0.4

View File

@@ -41,3 +41,14 @@ body[data-theme="solarized-dark"]
background-color $ui-solarized-dark-button-backgroundColor
&:hover
color #5CB85C
body[data-theme="monokai"]
.notification-area
background-color none
.notification-link
color $ui-monokai-text-color
border none
background-color $ui-monokai-button-backgroundColor
&:hover
color #5CB85C

View File

@@ -18,7 +18,7 @@
.iconWrap
width 20px
text-align center
.counters
float right
color $ui-inactive-text-color
@@ -68,10 +68,9 @@
.menu-button-label
position fixed
display inline-block
height 32px
height 36px
left 44px
padding 0 10px
margin-top -8px
margin-left 0
overflow ellipsis
z-index 10
@@ -222,4 +221,46 @@ body[data-theme="solarized-dark"]
background-color $ui-solarized-dark-button-backgroundColor
color $ui-solarized-dark-text-color
.menu-button-label
color $ui-solarized-dark-text-color
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

View File

@@ -55,10 +55,10 @@ class SnippetTab extends React.Component {
this.handleRename()
break
case 27:
this.setState({
name: this.props.snippet.name,
this.setState((prevState, props) => ({
name: props.snippet.name,
isRenaming: false
})
}))
break
}
}

View File

@@ -58,8 +58,8 @@
opacity 0
border-top-right-radius 2px
border-bottom-right-radius 2px
height 26px
line-height 26px
height 34px
line-height 32px
.folderList-item:hover, .folderList-item--active:hover
.folderList-item-tooltip
@@ -138,3 +138,22 @@ body[data-theme="solarized-dark"]
&:hover
color $ui-solarized-dark-text-color
background-color $ui-solarized-dark-button-backgroundColor
body[data-theme="monokai"]
.folderList-item
&:hover
background-color $ui-monokai-button-backgroundColor
color $ui-monokai-text-color
&:active
color $ui-monokai-text-color
background-color $ui-monokai-button-backgroundColor
.folderList-item--active
@extend .folderList-item
color $ui-monokai-text-color
background-color $ui-monokai-button-backgroundColor
&:active
background-color $ui-monokai-button-backgroundColor
&:hover
color $ui-monokai-text-color
background-color $ui-monokai-button-backgroundColor

View File

@@ -47,5 +47,15 @@ body[data-theme="solarized-dark"]
.progressBar
background-color: #2aa198
.percentageText
color #fdf6e3
body[data-theme="monokai"]
.percentageBar
background-color #f92672
.progressBar
background-color: #373831
.percentageText
color #fdf6e3

View File

@@ -199,7 +199,6 @@ ol
&>li>ul, &>li>ol
margin 0
code
color #CC305F
padding 0.2em 0.4em
background-color #f7f7f7
border-radius 3px
@@ -371,3 +370,30 @@ body[data-theme="solarized-dark"]
border-color themeSolarizedDarkTableBorder
&:last-child
border-right solid 1px themeSolarizedDarkTableBorder
themeMonokaiTableOdd = $ui-monokai-noteDetail-backgroundColor
themeMonokaiTableEven = darken($ui-monokai-noteDetail-backgroundColor, 10%)
themeMonokaiTableHead = themeMonokaiTableEven
themeMonokaiTableBorder = themeDarkBorder
body[data-theme="monokai"]
color $ui-monokai-text-color
border-color themeDarkBorder
background-color $ui-monokai-noteDetail-backgroundColor
table
thead
tr
background-color themeMonokaiTableHead
th
border-color themeMonokaiTableBorder
&:last-child
border-right solid 1px themeMonokaiTableBorder
tbody
tr:nth-child(2n + 1)
background-color themeMonokaiTableOdd
tr:nth-child(2n)
background-color themeMonokaiTableEven
td
border-color themeMonokaiTableBorder
&:last-child
border-right solid 1px themeMonokaiTableBorder

View 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
}
}

View File

@@ -25,7 +25,7 @@ class Markdown {
linkify: true,
html: true,
xhtmlOut: true,
breaks: true,
breaks: config.preview.breaks,
highlight: function (str, lang) {
const delimiter = ':'
const langInfo = lang.split(delimiter)
@@ -145,11 +145,13 @@ class Markdown {
const deflate = require('markdown-it-plantuml/lib/deflate')
this.md.use(require('markdown-it-plantuml'), '', {
generateSource: function (umlCode) {
const stripTrailingSlash = (url) => url.endsWith('/') ? url.slice(0, -1) : url
const serverAddress = stripTrailingSlash(config.preview.plantUMLServerAddress) + '/svg'
const s = unescape(encodeURIComponent(umlCode))
const zippedCode = deflate.encode64(
deflate.zip_deflate(`@startuml\n${s}\n@enduml`, 9)
)
return `http://www.plantuml.com/plantuml/svg/${zippedCode}`
return `${serverAddress}/${zippedCode}`
}
})

View File

@@ -54,7 +54,25 @@ export function escapeHtmlCharacters (text) {
: html
}
export function isObjectEqual (a, b) {
const aProps = Object.getOwnPropertyNames(a)
const bProps = Object.getOwnPropertyNames(b)
if (aProps.length !== bProps.length) {
return false
}
for (var i = 0; i < aProps.length; i++) {
const propName = aProps[i]
if (a[propName] !== b[propName]) {
return false
}
}
return true
}
export default {
lastFindInArray,
escapeHtmlCharacters
escapeHtmlCharacters,
isObjectEqual
}

View File

@@ -30,3 +30,10 @@ body[data-theme="solarized-dark"]
border-left 1px solid $ui-solarized-dark-borderColor
.empty-message
color $ui-solarized-dark-text-color
body[data-theme="monokai"]
.root
background-color $ui-monokai-noteDetail-backgroundColor
border-left 1px solid $ui-monokai-borderColor
.empty-message
color $ui-monokai-text-color

View File

@@ -133,3 +133,29 @@ body[data-theme="dark"]
color $ui-dark-button--active-color
.search-optionList-item-name-surfix
color $ui-dark-inactive-text-color
body[data-theme="monokai"]
.root
color $ui-dark-text-color
&:hover
color white
background-color $ui-monokai-button--hover-backgroundColor
border-color $ui-monokai-borderColor
.search-optionList
color white
border-color $ui-monokai-borderColor
background-color $ui-monokai-button-backgroundColor
.search-optionList-item
&:hover
background-color lighten($ui-monokai-button--hover-backgroundColor, 15%)
.search-optionList-item--active
background-color $ui-monokai-button--active-backgroundColor
color $ui-monokai-button--active-color
&:hover
background-color $ui-monokai-button--active-backgroundColor
color $ui-monokai-button--active-color
.search-optionList-item-name-surfix
color $ui-monokai-inactive-text-color

View File

@@ -215,3 +215,43 @@ body[data-theme="solarized-dark"]
color $ui-dark-inactive-text-color
&:hover
color $ui-solarized-ark-text-color
body[data-theme="monokai"]
.control-infoButton-panel
background-color $ui-monokai-noteList-backgroundColor
.control-infoButton-panel-trash
background-color $ui-monokai-noteList-backgroundColor
.modification-date
color $ui-monokai-text-color
.modification-date-desc
color $ui-inactive-text-color
.infoPanel-defaul-count
color $ui-monokai-text-color
.infoPanel-sub-count
color $ui-inactive-text-color
.infoPanel-default
color $ui-monokai-text-color
.infoPanel-sub
color $ui-inactive-text-color
.infoPanel-noteLink
background-color alpha($ui-monokai-borderColor, 20%)
color $ui-monokai-text-color
[id=export-wrap]
button
color $ui-dark-inactive-text-color
&:hover
background-color alpha($ui-monokai-borderColor, 20%)
color $ui-monokai-text-color
p
color $ui-dark-inactive-text-color
&:hover
color $ui-monokai-text-color

View File

@@ -55,10 +55,14 @@ class MarkdownNoteDetail extends React.Component {
componentDidMount () {
ee.on('topbar:togglelockbutton', this.toggleLockButton)
ee.on('topbar:togglemodebutton', () => {
const reversedType = this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
this.handleSwitchMode(reversedType)
})
}
componentWillReceiveProps (nextProps) {
if (nextProps.note.key !== this.props.note.key && !this.isMovingNote) {
if (nextProps.note.key !== this.props.note.key && !this.state.isMovingNote) {
if (this.saveQueue != null) this.saveNow()
this.setState({
note: Object.assign({}, nextProps.note)

View File

@@ -71,3 +71,8 @@ body[data-theme="solarized-dark"]
.root
border-left 1px solid $ui-solarized-dark-borderColor
background-color $ui-solarized-dark-noteDetail-backgroundColor
body[data-theme="monokai"]
.root
border-left 1px solid $ui-monokai-borderColor
background-color $ui-monokai-noteDetail-backgroundColor

View File

@@ -98,3 +98,7 @@ body[data-theme="solarized-dark"]
border-color $ui-solarized-dark-borderColor
background-color $ui-solarized-dark-noteDetail-backgroundColor
body[data-theme="monokai"]
.info
border-color $ui-monokai-borderColor
background-color $ui-monokai-noteDetail-backgroundColor

View File

@@ -18,6 +18,7 @@ import context from 'browser/lib/context'
import ConfigManager from 'browser/main/lib/ConfigManager'
import _ from 'lodash'
import {findNoteTitle} from 'browser/lib/findNoteTitle'
import convertModeName from 'browser/lib/convertModeName'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import TrashButton from './TrashButton'
import RestoreButton from './RestoreButton'
@@ -29,21 +30,6 @@ import { formatDate } from 'browser/lib/date-formatter'
import i18n from 'browser/lib/i18n'
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
function pass (name) {
switch (name) {
case 'ejs':
return 'Embedded Javascript'
case 'html_ruby':
return 'Embedded Ruby'
case 'objectivec':
return 'Objective C'
case 'text':
return 'Plain Text'
default:
return name
}
}
const electron = require('electron')
const { remote } = electron
const { Menu, MenuItem, dialog } = remote
@@ -82,7 +68,7 @@ class SnippetNoteDetail extends React.Component {
}
componentWillReceiveProps (nextProps) {
if (nextProps.note.key !== this.props.note.key && !this.isMovingNote) {
if (nextProps.note.key !== this.props.note.key && !this.state.isMovingNote) {
if (this.saveQueue != null) this.saveNow()
const nextNote = Object.assign({
description: ''
@@ -382,11 +368,11 @@ class SnippetNoteDetail extends React.Component {
name: mode
})
}
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
this.setState({
note: this.state.note
}, () => {
this.setState(state => ({
note: state.note
}), () => {
this.save()
})
}
@@ -395,11 +381,11 @@ class SnippetNoteDetail extends React.Component {
return (e) => {
const snippets = this.state.note.snippets.slice()
snippets[index].mode = name
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
this.setState({
note: this.state.note
}, () => {
this.setState(state => ({
note: state.note
}), () => {
this.save()
})
@@ -413,10 +399,10 @@ class SnippetNoteDetail extends React.Component {
return (e) => {
const snippets = this.state.note.snippets.slice()
snippets[index].content = this.refs['code-' + index].value
this.setState({note: Object.assign(this.state.note, {snippets: snippets})})
this.setState({
note: this.state.note
}, () => {
this.setState(state => ({note: Object.assign(state.note, {snippets: snippets})}))
this.setState(state => ({
note: state.note
}), () => {
this.save()
})
}
@@ -611,17 +597,17 @@ class SnippetNoteDetail extends React.Component {
}
jumpNextTab () {
this.setState({
snippetIndex: (this.state.snippetIndex + 1) % this.state.note.snippets.length
}, () => {
this.setState(state => ({
snippetIndex: (state.snippetIndex + 1) % state.note.snippets.length
}), () => {
this.focusEditor()
})
}
jumpPrevTab () {
this.setState({
snippetIndex: (this.state.snippetIndex - 1 + this.state.note.snippets.length) % this.state.note.snippets.length
}, () => {
this.setState(state => ({
snippetIndex: (state.snippetIndex - 1 + state.note.snippets.length) % state.note.snippets.length
}), () => {
this.focusEditor()
})
}
@@ -677,7 +663,7 @@ class SnippetNoteDetail extends React.Component {
const viewList = note.snippets.map((snippet, index) => {
const isActive = this.state.snippetIndex === index
let syntax = CodeMirror.findModeByName(pass(snippet.mode))
let syntax = CodeMirror.findModeByName(convertModeName(snippet.mode))
if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
return <div styleName='tabView'

View File

@@ -152,4 +152,21 @@ body[data-theme="solarized-dark"]
.tabList
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

View File

@@ -44,16 +44,9 @@ class TagSelect extends React.Component {
}
removeLastTag () {
let { value } = this.props
value = _.isArray(value)
? value.slice()
: []
value.pop()
value = _.uniq(value)
this.value = value
this.props.onChange()
this.removeTagByCallback((value) => {
value.pop()
})
}
reset () {
@@ -96,15 +89,22 @@ class TagSelect extends React.Component {
}
handleTagRemoveButtonClick (tag) {
return (e) => {
let { value } = this.props
this.removeTagByCallback((value, tag) => {
value.splice(value.indexOf(tag), 1)
value = _.uniq(value)
}, tag)
}
this.value = value
this.props.onChange()
}
removeTagByCallback (callback, tag = null) {
let { value } = this.props
value = _.isArray(value)
? value.slice()
: []
callback(value, tag)
value = _.uniq(value)
this.value = value
this.props.onChange()
}
render () {
@@ -118,7 +118,7 @@ class TagSelect extends React.Component {
>
<span styleName='tag-label'>#{tag}</span>
<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' />
</button>

View File

@@ -81,4 +81,20 @@ body[data-theme="solarized-dark"]
.newTag
border-color none
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

View File

@@ -56,3 +56,10 @@ body[data-theme="solarized-dark"]
.active
background-color #1EC38B
box-shadow 2px 0px 7px #222222
body[data-theme="monokai"]
.control-toggleModeButton
background-color #272822
.active
background-color #1EC38B
box-shadow 2px 0px 7px #222222

View File

@@ -16,6 +16,7 @@ import { hashHistory } from 'react-router'
import store from 'browser/main/store'
import i18n from 'browser/lib/i18n'
import { getLocales } from 'browser/lib/Languages'
import applyShortcuts from 'browser/main/lib/shortcutManager'
const path = require('path')
const electron = require('electron')
const { remote } = electron
@@ -144,7 +145,8 @@ class Main extends React.Component {
const supportedThemes = [
'dark',
'white',
'solarized-dark'
'solarized-dark',
'monokai'
]
if (supportedThemes.indexOf(config.ui.theme) !== -1) {
@@ -158,7 +160,7 @@ class Main extends React.Component {
} else {
i18n.setLocale('en')
}
applyShortcuts()
// Reload all data
dataApi.init()
.then((data) => {

View File

@@ -74,4 +74,8 @@ body[data-theme="dark"]
body[data-theme="solarized-dark"]
.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

View File

@@ -113,4 +113,28 @@ body[data-theme="solarized-dark"]
.control-button--active
color $ui-solarized-dark-text-color
&:active
color $ui-solarized-dark-text-color
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

View File

@@ -7,6 +7,7 @@ import moment from 'moment'
import _ from 'lodash'
import ee from 'browser/main/lib/eventEmitter'
import dataApi from 'browser/main/lib/dataApi'
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
import ConfigManager from 'browser/main/lib/ConfigManager'
import NoteItem from 'browser/components/NoteItem'
import NoteItemSimple from 'browser/components/NoteItemSimple'
@@ -455,12 +456,19 @@ class NoteList extends React.Component {
}
handleDragStart (e, note) {
const { selectedNoteKeys } = this.state
let { selectedNoteKeys } = this.state
const noteKey = getNoteKey(note)
if (!selectedNoteKeys.includes(noteKey)) {
selectedNoteKeys = []
selectedNoteKeys.push(noteKey)
}
const notes = this.notes.map((note) => Object.assign({}, note))
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
const noteData = JSON.stringify(selectedNotes)
e.dataTransfer.setData('note', noteData)
this.setState({ selectedNoteKeys: [] })
this.selectNextNote()
}
handleNoteContextMenu (e, uniqueKey) {
@@ -655,6 +663,10 @@ class NoteList extends React.Component {
title: firstNote.title + ' ' + i18n.__('copy'),
content: firstNote.content
})
.then((note) => {
attachmentManagement.cloneAttachments(firstNote, note)
return note
})
.then((note) => {
dispatch({
type: 'UPDATE_NOTE',

View File

@@ -117,3 +117,8 @@ body[data-theme="solarized-dark"]
.root, .root--folded
background-color $ui-solarized-dark-backgroundColor
border-right 1px solid $ui-solarized-dark-borderColor
body[data-theme="monokai"]
.root, .root--folded
background-color $ui-monokai-backgroundColor
border-right 1px solid $ui-monokai-borderColor

View File

@@ -14,6 +14,8 @@ import i18n from 'browser/lib/i18n'
const { remote } = require('electron')
const { Menu, dialog } = remote
const escapeStringRegexp = require('escape-string-regexp')
const path = require('path')
class StorageItem extends React.Component {
constructor (props) {
@@ -201,7 +203,7 @@ class StorageItem extends React.Component {
createdNoteData.forEach((newNote) => {
dispatch({
type: 'MOVE_NOTE',
originNote: noteData.find((note) => note.content === newNote.content),
originNote: noteData.find((note) => note.content === newNote.oldContent),
note: newNote
})
})
@@ -223,7 +225,8 @@ class StorageItem extends React.Component {
const { folderNoteMap, trashedSet } = data
const SortableStorageItemChild = SortableElement(StorageItemChild)
const folderList = storage.folders.map((folder, index) => {
const isActive = !!(location.pathname.match(new RegExp('\/storages\/' + storage.key + '\/folders\/' + folder.key)))
let folderRegex = new RegExp(escapeStringRegexp(path.sep) + 'storages' + escapeStringRegexp(path.sep) + storage.key + escapeStringRegexp(path.sep) + 'folders' + escapeStringRegexp(path.sep) + folder.key)
const isActive = !!(location.pathname.match(folderRegex))
const noteSet = folderNoteMap.get(storage.key + '-' + folder.key)
let noteCount = 0
@@ -253,7 +256,7 @@ class StorageItem extends React.Component {
)
})
const isActive = location.pathname.match(new RegExp('\/storages\/' + storage.key + '$'))
const isActive = location.pathname.match(new RegExp(escapeStringRegexp(path.sep) + 'storages' + escapeStringRegexp(path.sep) + storage.key + '$'))
return (
<div styleName={isFolded ? 'root--folded' : 'root'}

View File

@@ -44,7 +44,7 @@
height 36px
padding-left 25px
padding-right 15px
line-height 22px
line-height 36px
cursor pointer
font-size 14px
border none
@@ -147,7 +147,7 @@ body[data-theme="dark"]
background-color $ui-dark-button--active-backgroundColor
&:active
color $ui-dark-text-color
background-color $ui-dark-button--active-backgroundColor
background-color $ui-dark-button--active-backgroundColor
.header--active
.header-addFolderButton
@@ -180,7 +180,7 @@ body[data-theme="dark"]
&:active, &:active:hover
color $ui-dark-text-color
background-color $ui-dark-button--active-backgroundColor

View File

@@ -148,7 +148,9 @@ class SideNav extends React.Component {
const relatedTags = this.getRelatedTags(this.getActiveTags(location.pathname), data.noteMap)
let tagList = _.sortBy(data.tagNoteMap.map(
(tag, name) => ({ name, size: tag.size, related: relatedTags.has(name) })
), ['name'])
), ['name']).filter(
tag => tag.size > 0
)
if (config.sortTagsBy === 'COUNTER') {
tagList = _.sortBy(tagList, item => (0 - item.size))
}

View File

@@ -69,3 +69,14 @@ body[data-theme="dark"]
navDarkButtonColor()
border-color $ui-dark-borderColor
border-left 1px solid $ui-dark-borderColor
body[data-theme="monokai"]
navButtonColor()
.zoom
border-color $ui-dark-borderColor
color $ui-monokai-text-color
&:hover
transition 0.15s
color $ui-monokai-active-color
&:active
color $ui-monokai-active-color

View File

@@ -234,3 +234,25 @@ body[data-theme="solarized-dark"]
input
background-color $ui-solarized-dark-noteList-backgroundColor
color $ui-solarized-dark-text-color
body[data-theme="monokai"]
.root, .root--expanded
background-color $ui-monokai-noteList-backgroundColor
.control
border-color $ui-monokai-borderColor
.control-search
background-color $ui-monokai-noteList-backgroundColor
.control-search-icon
absolute top bottom left
line-height 32px
width 35px
color $ui-monokai-inactive-text-color
background-color $ui-monokai-noteList-backgroundColor
.control-search-input
background-color $ui-monokai-noteList-backgroundColor
input
background-color $ui-monokai-noteList-backgroundColor
color $ui-monokai-text-color

View File

@@ -134,4 +134,10 @@ body[data-theme="solarized-dark"]
.sortableItemHelper
color: $ui-solarized-dark-text-color
body[data-theme="monokai"]
.ModalBase
.modalBack
background-color $ui-monokai-backgroundColor
.sortableItemHelper
color: $ui-monokai-text-color

View File

@@ -1,6 +1,7 @@
import _ from 'lodash'
import RcParser from 'browser/lib/RcParser'
import i18n from 'browser/lib/i18n'
import ee from 'browser/main/lib/eventEmitter'
const OSX = global.process.platform === 'darwin'
const win = global.process.platform === 'win32'
@@ -20,7 +21,8 @@ export const DEFAULT_CONFIG = {
listStyle: 'DEFAULT', // 'DEFAULT', 'SMALL'
amaEnabled: true,
hotkey: {
toggleMain: OSX ? 'Cmd + Alt + L' : 'Super + Alt + E'
toggleMain: OSX ? 'Cmd + Alt + L' : 'Super + Alt + E',
toggleMode: OSX ? 'Cmd + M' : 'Ctrl + M'
},
ui: {
language: 'en',
@@ -53,8 +55,10 @@ export const DEFAULT_CONFIG = {
latexInlineClose: '$',
latexBlockOpen: '$$',
latexBlockClose: '$$',
plantUMLServerAddress: 'http://www.plantuml.com/plantuml',
scrollPastEnd: false,
smartQuotes: true,
breaks: true,
sanitize: 'STRICT' // 'STRICT', 'ALLOW_STYLES', 'NONE'
},
blog: {
@@ -135,6 +139,8 @@ function set (updates) {
document.body.setAttribute('data-theme', 'white')
} else if (newConfig.ui.theme === 'solarized-dark') {
document.body.setAttribute('data-theme', 'solarized-dark')
} else if (newConfig.ui.theme === 'monokai') {
document.body.setAttribute('data-theme', 'monokai')
} else {
document.body.setAttribute('data-theme', 'default')
}
@@ -163,6 +169,7 @@ function set (updates) {
ipcRenderer.send('config-renew', {
config: get()
})
ee.emit('config-renew')
}
function assignConfigValues (originalConfig, rcConfig) {

View File

@@ -3,6 +3,9 @@ const fs = require('fs')
const path = require('path')
const findStorage = require('browser/lib/findStorage')
const mdurl = require('mdurl')
const fse = require('fs-extra')
const escapeStringRegexp = require('escape-string-regexp')
const sander = require('sander')
const STORAGE_FOLDER_PLACEHOLDER = ':storage'
const DESTINATION_FOLDER = 'attachments'
@@ -39,7 +42,7 @@ function copyAttachment (sourceFilePath, storageKey, noteKey, useRandomName = tr
const targetStorage = findStorage.findStorage(storageKey)
const inputFile = fs.createReadStream(sourceFilePath)
const inputFileStream = fs.createReadStream(sourceFilePath)
let destinationName
if (useRandomName) {
destinationName = `${uniqueSlug()}${path.extname(sourceFilePath)}`
@@ -49,8 +52,10 @@ function copyAttachment (sourceFilePath, storageKey, noteKey, useRandomName = tr
const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
createAttachmentDestinationFolder(targetStorage.path, noteKey)
const outputFile = fs.createWriteStream(path.join(destinationDir, destinationName))
inputFile.pipe(outputFile)
resolve(destinationName)
inputFileStream.pipe(outputFile)
inputFileStream.on('end', () => {
resolve(destinationName)
})
} catch (e) {
return reject(e)
}
@@ -146,19 +151,176 @@ function handlePastImageEvent (codeEditor, storageKey, noteKey, dataTransferItem
base64data = reader.result.replace(/^data:image\/png;base64,/, '')
base64data += base64data.replace('+', ' ')
const binaryData = new Buffer(base64data, 'base64').toString('binary')
fs.writeFile(imagePath, binaryData, 'binary')
fs.writeFileSync(imagePath, binaryData, 'binary')
const imageMd = generateAttachmentMarkdown(imageName, imagePath, true)
codeEditor.insertAttachmentMd(imageMd)
}
reader.readAsDataURL(blob)
}
/**
* @description Returns all attachment paths of the given markdown
* @param {String} markdownContent content in which the attachment paths should be found
* @returns {String[]} Array of the relative paths (starting with :storage) of the attachments of the given markdown
*/
function getAttachmentsInContent (markdownContent) {
const preparedInput = markdownContent.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep)
const regexp = new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + '([a-zA-Z0-9]|-)+' + escapeStringRegexp(path.sep) + '[a-zA-Z0-9]+(\\.[a-zA-Z0-9]+)?', 'g')
return preparedInput.match(regexp)
}
/**
* @description Returns an array of the absolute paths of the attachments referenced in the given markdown code
* @param {String} markdownContent content in which the attachment paths should be found
* @param {String} storagePath path of the current storage
* @returns {String[]} Absolute paths of the referenced attachments
*/
function getAbsolutePathsOfAttachmentsInContent (markdownContent, storagePath) {
const temp = getAttachmentsInContent(markdownContent) || []
const result = []
for (const relativePath of temp) {
result.push(relativePath.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER, 'g'), path.join(storagePath, DESTINATION_FOLDER)))
}
return result
}
/**
* @description Moves the attachments of the current note to the new location.
* Returns a modified version of the given content so that the links to the attachments point to the new note key.
* @param {String} oldPath Source of the note to be moved
* @param {String} newPath Destination of the note to be moved
* @param {String} noteKey Old note key
* @param {String} newNoteKey New note key
* @param {String} noteContent Content of the note to be moved
* @returns {String} Modified version of noteContent in which the paths of the attachments are fixed
*/
function moveAttachments (oldPath, newPath, noteKey, newNoteKey, noteContent) {
const src = path.join(oldPath, DESTINATION_FOLDER, noteKey)
const dest = path.join(newPath, DESTINATION_FOLDER, newNoteKey)
if (fse.existsSync(src)) {
fse.moveSync(src, dest)
}
return replaceNoteKeyWithNewNoteKey(noteContent, noteKey, newNoteKey)
}
/**
* Modifies the given content so that in all attachment references the oldNoteKey is replaced by the new one
* @param noteContent content that should be modified
* @param oldNoteKey note key to be replaced
* @param newNoteKey note key serving as a replacement
* @returns {String} modified note content
*/
function replaceNoteKeyWithNewNoteKey (noteContent, oldNoteKey, newNoteKey) {
if (noteContent) {
return noteContent.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + oldNoteKey, 'g'), path.join(STORAGE_FOLDER_PLACEHOLDER, newNoteKey))
}
return noteContent
}
/**
* @description Deletes all :storage and noteKey references from the given input.
* @param input Input in which the references should be deleted
* @param noteKey Key of the current note
* @returns {String} Input without the references
*/
function removeStorageAndNoteReferences (input, noteKey) {
return input.replace(new RegExp(mdurl.encode(path.sep), 'g'), path.sep).replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + noteKey, 'g'), DESTINATION_FOLDER)
}
/**
* @description Deletes the attachment folder specified by the given storageKey and noteKey
* @param storageKey Key of the storage of the note to be deleted
* @param noteKey Key of the note to be deleted
*/
function deleteAttachmentFolder (storageKey, noteKey) {
const storagePath = findStorage.findStorage(storageKey)
const noteAttachmentPath = path.join(storagePath.path, DESTINATION_FOLDER, noteKey)
sander.rimrafSync(noteAttachmentPath)
}
/**
* @description Deletes all attachments stored in the attachment folder of the give not that are not referenced in the markdownContent
* @param markdownContent Content of the note. All unreferenced notes will be deleted
* @param storageKey StorageKey of the current note. Is used to determine the belonging attachment folder.
* @param noteKey NoteKey of the current note. Is used to determine the belonging attachment folder.
*/
function deleteAttachmentsNotPresentInNote (markdownContent, storageKey, noteKey) {
const targetStorage = findStorage.findStorage(storageKey)
const attachmentFolder = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
const attachmentsInNote = getAttachmentsInContent(markdownContent)
const attachmentsInNoteOnlyFileNames = []
if (attachmentsInNote) {
for (let i = 0; i < attachmentsInNote.length; i++) {
attachmentsInNoteOnlyFileNames.push(attachmentsInNote[i].replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER + escapeStringRegexp(path.sep) + noteKey + escapeStringRegexp(path.sep), 'g'), ''))
}
}
if (fs.existsSync(attachmentFolder)) {
fs.readdir(attachmentFolder, (err, files) => {
if (err) {
console.error("Error reading directory '" + attachmentFolder + "'. Error:")
console.error(err)
return
}
files.forEach(file => {
if (!attachmentsInNoteOnlyFileNames.includes(file)) {
const absolutePathOfFile = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey, file)
fs.unlink(absolutePathOfFile, (err) => {
if (err) {
console.error("Could not delete '%s'", absolutePathOfFile)
console.error(err)
return
}
console.info("File '" + absolutePathOfFile + "' deleted because it was not included in the content of the note")
})
}
})
})
} else {
console.info("Attachment folder ('" + attachmentFolder + "') did not exist..")
}
}
/**
* Clones the attachments of a given note.
* Copies the attachments to their new destination and updates the content of the new note so that the attachment-links again point to the correct destination.
* @param oldNote Note that is being cloned
* @param newNote Clone of the note
*/
function cloneAttachments (oldNote, newNote) {
if (newNote.type === 'MARKDOWN_NOTE') {
const oldStorage = findStorage.findStorage(oldNote.storage)
const newStorage = findStorage.findStorage(newNote.storage)
const attachmentsPaths = getAbsolutePathsOfAttachmentsInContent(oldNote.content, oldStorage.path) || []
const destinationFolder = path.join(newStorage.path, DESTINATION_FOLDER, newNote.key)
if (!sander.existsSync(destinationFolder)) {
sander.mkdirSync(destinationFolder)
}
for (const attachment of attachmentsPaths) {
const destination = path.join(newStorage.path, DESTINATION_FOLDER, newNote.key, path.basename(attachment))
sander.copyFileSync(attachment).to(destination)
}
newNote.content = replaceNoteKeyWithNewNoteKey(newNote.content, oldNote.key, newNote.key)
} else {
console.debug('Cloning of the attachment was skipped since it only works for MARKDOWN_NOTEs')
}
}
module.exports = {
copyAttachment,
fixLocalURLS,
generateAttachmentMarkdown,
handleAttachmentDrop,
handlePastImageEvent,
getAttachmentsInContent,
getAbsolutePathsOfAttachmentsInContent,
removeStorageAndNoteReferences,
deleteAttachmentFolder,
deleteAttachmentsNotPresentInNote,
moveAttachments,
cloneAttachments,
STORAGE_FOLDER_PLACEHOLDER,
DESTINATION_FOLDER
}

View File

@@ -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

View File

@@ -5,6 +5,7 @@ const resolveStorageNotes = require('./resolveStorageNotes')
const CSON = require('@rokt33r/season')
const sander = require('sander')
const { findStorage } = require('browser/lib/findStorage')
const deleteSingleNote = require('./deleteNote')
/**
* @param {String} storageKey
@@ -49,11 +50,7 @@ function deleteFolder (storageKey, folderKey) {
const deleteAllNotes = targetNotes
.map(function deleteNote (note) {
const notePath = path.join(storage.path, 'notes', note.key + '.cson')
return sander.unlink(notePath)
.catch(function (err) {
console.warn('Failed to delete', notePath, err)
})
return deleteSingleNote(storageKey, note.key)
})
return Promise.all(deleteAllNotes)
.then(() => storage)

View File

@@ -1,6 +1,7 @@
const resolveStorageData = require('./resolveStorageData')
const path = require('path')
const sander = require('sander')
const attachmentManagement = require('./attachmentManagement')
const { findStorage } = require('browser/lib/findStorage')
function deleteNote (storageKey, noteKey) {
@@ -25,6 +26,10 @@ function deleteNote (storageKey, noteKey) {
storageKey
}
})
.then(function deleteAttachments (storageInfo) {
attachmentManagement.deleteAttachmentFolder(storageInfo.storageKey, storageInfo.noteKey)
return storageInfo
})
}
module.exports = deleteNote

View File

@@ -1,14 +1,9 @@
import copyFile from 'browser/main/lib/dataApi/copyFile'
import { findStorage } from 'browser/lib/findStorage'
import filenamify from 'filenamify'
const fs = require('fs')
const path = require('path')
const LOCAL_STORED_REGEX = /!\[(.*?)]\(\s*?\/:storage\/(.*\.\S*?)\)/gi
// TODO: ehhc: check this -> attachmentManagement
const IMAGES_FOLDER_NAME = 'images'
/**
* Export note together with images
*
@@ -29,21 +24,7 @@ function exportNote (storageKey, noteContent, targetPath, outputFormatter) {
throw new Error('Storage path is not found')
}
let exportedData = noteContent.replace(LOCAL_STORED_REGEX, (match, dstFilename, srcFilename) => {
dstFilename = filenamify(dstFilename, {replacement: '_'})
if (!path.extname(dstFilename)) {
dstFilename += path.extname(srcFilename)
}
const dstRelativePath = path.join(IMAGES_FOLDER_NAME, dstFilename)
exportTasks.push({
src: path.join(IMAGES_FOLDER_NAME, srcFilename),
dst: dstRelativePath
})
return `![${dstFilename}](${dstRelativePath})`
})
let exportedData = noteContent
if (outputFormatter) {
exportedData = outputFormatter(exportedData, exportTasks)

View File

@@ -6,7 +6,7 @@ const CSON = require('@rokt33r/season')
const keygen = require('browser/lib/keygen')
const sander = require('sander')
const { findStorage } = require('browser/lib/findStorage')
const copyImage = require('./copyImage')
const attachmentManagement = require('./attachmentManagement')
function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
let oldStorage, newStorage
@@ -64,35 +64,20 @@ function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
noteData.key = newNoteKey
noteData.storage = newStorageKey
noteData.updatedAt = new Date()
noteData.oldContent = noteData.content
return noteData
})
.then(function moveImages (noteData) {
if (oldStorage.path === newStorage.path) return noteData
const searchImagesRegex = /!\[.*?]\(\s*?\/:storage\/(.*\.\S*?)\)/gi
let match = searchImagesRegex.exec(noteData.content)
const moveTasks = []
while (match != null) {
const [, filename] = match
const oldPath = path.join(oldStorage.path, 'images', filename)
// TODO: ehhc: attachmentManagement
moveTasks.push(
copyImage(oldPath, noteData.storage, false)
.then(() => {
fs.unlinkSync(oldPath)
})
)
// find next occurence
match = searchImagesRegex.exec(noteData.content)
.then(function moveAttachments (noteData) {
if (oldStorage.path === newStorage.path) {
return noteData
}
return Promise.all(moveTasks).then(() => noteData)
noteData.content = attachmentManagement.moveAttachments(oldStorage.path, newStorage.path, noteKey, newNoteKey, noteData.content)
return noteData
})
.then(function writeAndReturn (noteData) {
CSON.writeFileSync(path.join(newStorage.path, 'notes', noteData.key + '.cson'), _.omit(noteData, ['key', 'storage']))
CSON.writeFileSync(path.join(newStorage.path, 'notes', noteData.key + '.cson'), _.omit(noteData, ['key', 'storage', 'oldContent']))
return noteData
})
.then(function deleteOldNote (data) {

View File

@@ -0,0 +1,7 @@
import ee from 'browser/main/lib/eventEmitter'
module.exports = {
'toggleMode': () => {
ee.emit('topbar:togglemodebutton')
}
}

View 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

View File

@@ -102,3 +102,29 @@ body[data-theme="solarized-dark"]
.control-confirmButton
colorSolarizedDarkPrimaryButton()
body[data-theme="monokai"]
.root
modalMonokai()
width 500px
height 270px
overflow hidden
position relative
.header
background-color transparent
border-color $ui-dark-borderColor
color $ui-monokai-text-color
.control-folder-label
color $ui-monokai-text-color
.control-folder-input
border 1px solid $ui-input--create-folder-modal
color white
.description
color $ui-inactive-text-color
.control-confirmButton
colorMonokaiPrimaryButton()

View File

@@ -81,3 +81,19 @@ body[data-theme="solarized-dark"]
.description
color $ui-solarized-dark-text-color
body[data-theme="monokai"]
.root
background-color transparent
.header
color $ui-monokai-text-color
.control-button
border-color $ui-monokai-borderColor
color $ui-monokai-text-color
background-color transparent
&:focus
colorDarkPrimaryButton()
.description
color $ui-monokai-text-color

View File

@@ -133,6 +133,11 @@ colorSolarizedDarkControl()
background-color $ui-solarized-dark-button-backgroundColor
color $ui-solarized-dark-text-color
colorMonokaiControl()
border none
background-color $ui-monokai-button-backgroundColor
color $ui-monokai-text-color
body[data-theme="dark"]
.root
@@ -189,4 +194,29 @@ body[data-theme="solarized-dark"]
select, .group-section-control-input
colorSolarizedDarkControl()
body[data-theme="monokai"]
.root
color $ui-monokai-text-color
.group-header
color $ui-monokai-text-color
border-color $ui-monokai-borderColor
.group-header2
color $ui-monokai-text-color
.group-section-control-input
border-color $ui-monokai-borderColor
.group-control
border-color $ui-monokai-borderColor
.group-control-leftButton
colorDarkDefaultButton()
border-color $ui-monokai-borderColor
.group-control-rightButton
colorMonokaiPrimaryButton()
.group-hint
colorMonokaiControl()
.group-section-control
select, .group-section-control-input
colorMonokaiControl()

View File

@@ -33,4 +33,10 @@ body[data-theme="solarized-dark"]
.root
color $ui-solarized-dark-text-color
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

View File

@@ -126,3 +126,26 @@ body[data-theme="solarized-dark"]
.folderItem-right-dangerButton
colorSolarizedDarkPrimaryButton()
body[data-theme="monokai"]
.folderItem
&:hover
background-color $ui-monokai-button-backgroundColor
.folderItem-left-danger
color $danger-color
.folderItem-left-key
color $ui-dark-inactive-text-color
.folderItem-left-colorButton
colorMonokaiPrimaryButton()
.folderItem-right-button
colorMonokaiPrimaryButton()
.folderItem-right-confirmButton
colorMonokaiPrimaryButton()
.folderItem-right-dangerButton
colorMonokaiPrimaryButton()

View File

@@ -67,7 +67,8 @@ class HotkeyTab extends React.Component {
handleHotkeyChange (e) {
const { config } = this.state
config.hotkey = {
toggleMain: this.refs.toggleMain.value
toggleMain: this.refs.toggleMain.value,
toggleMode: this.refs.toggleMode.value
}
this.setState({
config
@@ -115,6 +116,17 @@ class HotkeyTab extends React.Component {
/>
</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'>
<button styleName='group-control-leftButton'
onClick={(e) => this.handleHintToggleButtonClick(e)}

View File

@@ -68,3 +68,10 @@ body[data-theme="solarized-dark"]
.list
a
color $ui-solarized-dark-active-color
body[data-theme="monokai"]
.root
color $ui-monokai-text-color
.list
a
color $ui-monokai-active-color

View File

@@ -116,3 +116,26 @@ body[data-theme="solarized-dark"]
&:hover
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

View File

@@ -199,3 +199,40 @@ body[data-theme="solarized-dark"]
colorDarkDefaultButton()
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

View File

@@ -94,8 +94,10 @@ class UiTab extends React.Component {
latexInlineClose: this.refs.previewLatexInlineClose.value,
latexBlockOpen: this.refs.previewLatexBlockOpen.value,
latexBlockClose: this.refs.previewLatexBlockClose.value,
plantUMLServerAddress: this.refs.previewPlantUMLServerAddress.value,
scrollPastEnd: this.refs.previewScrollPastEnd.checked,
smartQuotes: this.refs.previewSmartQuotes.checked,
breaks: this.refs.previewBreaks.checked,
sanitize: this.refs.previewSanitize.value
}
}
@@ -172,6 +174,7 @@ class UiTab extends React.Component {
<option value='default'>{i18n.__('Default')}</option>
<option value='white'>{i18n.__('White')}</option>
<option value='solarized-dark'>{i18n.__('Solarized Dark')}</option>
<option value='monokai'>{i18n.__('Monokai')}</option>
<option value='dark'>{i18n.__('Dark')}</option>
</select>
</div>
@@ -474,6 +477,16 @@ class UiTab extends React.Component {
Enable smart quotes
</label>
</div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.preview.breaks}
ref='previewBreaks'
type='checkbox'
/>&nbsp;
Render newlines in Markdown paragraphs as &lt;br&gt;
</label>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
@@ -543,6 +556,19 @@ class UiTab extends React.Component {
/>
</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'>
<button styleName='group-control-rightButton'

View File

@@ -88,9 +88,27 @@ function data (state = defaultDataMap(), action) {
if (note.isTrashed) {
state.trashedSet.add(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 {
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) {
state.starredSet.add(uniqueKey)
}
@@ -238,7 +256,7 @@ function data (state = defaultDataMap(), action) {
let noteSet = state.storageNoteMap.get(note.storage)
noteSet = new Set(noteSet)
noteSet.add(uniqueKey)
state.folderNoteMap.set(folderKey, noteSet)
state.storageNoteMap.set(folderKey, noteSet)
}
// Update foldermap if folder changed or post created

View File

@@ -118,6 +118,16 @@ colorSolarizedDarkPrimaryButton()
&:active:hover
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-background = #c9302c
@@ -348,3 +358,29 @@ modalSolarizedDark()
background-color $ui-solarized-dark-backgroundColor
overflow hidden
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