1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-13 09:46:22 +00:00

Merge branch 'master' into master

This commit is contained in:
Kiryanenko Alexander
2018-10-29 21:25:11 +03:00
committed by GitHub
112 changed files with 4011 additions and 779 deletions

View File

@@ -5,7 +5,7 @@ import CodeMirror from 'codemirror'
import 'codemirror-mode-elixir'
import attachmentManagement from 'browser/main/lib/dataApi/attachmentManagement'
import convertModeName from 'browser/lib/convertModeName'
import { options, TableEditor } from '@susisu/mte-kernel'
import { options, TableEditor, Alignment } from '@susisu/mte-kernel'
import TextEditorInterface from 'browser/lib/TextEditorInterface'
import eventEmitter from 'browser/main/lib/eventEmitter'
import iconv from 'iconv-lite'
@@ -26,7 +26,6 @@ export default class CodeEditor extends React.Component {
constructor (props) {
super(props)
this.state = { isReady: false }
this.scrollHandler = _.debounce(this.handleScroll.bind(this), 100, {
leading: false,
trailing: true
@@ -60,11 +59,10 @@ export default class CodeEditor extends React.Component {
}
this.searchHandler = (e, msg) => this.handleSearch(msg)
this.searchState = null
this.scrollToLineHandeler = this.scrollToLine.bind(this)
this.formatTable = () => this.handleFormatTable()
this.turndownService = new TurndownService()
this.turndownService.use(gfm)
this.editorActivityHandler = () => this.handleEditorActivity()
}
handleSearch (msg) {
@@ -105,9 +103,32 @@ export default class CodeEditor extends React.Component {
this.tableEditor.formatAll(options({textWidthOptions: {}}))
}
handleEditorActivity () {
if (!this.textEditorInterface.transaction) {
this.updateTableEditorState()
}
}
updateTableEditorState () {
const active = this.tableEditor.cursorIsInTable(this.tableEditorOptions)
if (active) {
if (this.extraKeysMode !== 'editor') {
this.extraKeysMode = 'editor'
this.editor.setOption('extraKeys', this.editorKeyMap)
}
} else {
if (this.extraKeysMode !== 'default') {
this.extraKeysMode = 'default'
this.editor.setOption('extraKeys', this.defaultKeyMap)
this.tableEditor.resetSmartCursor()
}
}
}
componentDidMount () {
const { rulers, enableRulers } = this.props
const expandSnippet = this.expandSnippet.bind(this)
eventEmitter.on('line:jump', this.scrollToLineHandeler)
const defaultSnippet = [
{
@@ -125,6 +146,59 @@ export default class CodeEditor extends React.Component {
)
}
this.defaultKeyMap = CodeMirror.normalizeKeyMap({
Tab: function (cm) {
const cursor = cm.getCursor()
const line = cm.getLine(cursor.line)
const cursorPosition = cursor.ch
const charBeforeCursor = line.substr(cursorPosition - 1, 1)
if (cm.somethingSelected()) cm.indentSelection('add')
else {
const tabs = cm.getOption('indentWithTabs')
if (line.trimLeft().match(/^(-|\*|\+) (\[( |x)] )?$/)) {
cm.execCommand('goLineStart')
if (tabs) {
cm.execCommand('insertTab')
} else {
cm.execCommand('insertSoftTab')
}
cm.execCommand('goLineEnd')
} else if (
!charBeforeCursor.match(/\t|\s|\r|\n/) &&
cursor.ch > 1
) {
// text expansion on tab key if the char before is alphabet
const snippets = JSON.parse(
fs.readFileSync(consts.SNIPPET_FILE, 'utf8')
)
if (expandSnippet(line, cursor, cm, snippets) === false) {
if (tabs) {
cm.execCommand('insertTab')
} else {
cm.execCommand('insertSoftTab')
}
}
} else {
if (tabs) {
cm.execCommand('insertTab')
} else {
cm.execCommand('insertSoftTab')
}
}
}
},
'Cmd-T': function (cm) {
// Do nothing
},
Enter: 'boostNewLineAndIndentContinueMarkdownList',
'Ctrl-C': cm => {
if (cm.getOption('keyMap').substr(0, 3) === 'vim') {
document.execCommand('copy')
}
return CodeMirror.Pass
}
})
this.value = this.props.value
this.editor = CodeMirror(this.refs.root, {
rulers: buildCMRulers(rulers, enableRulers),
@@ -147,58 +221,7 @@ export default class CodeEditor extends React.Component {
explode: '[]{}``$$',
override: true
},
extraKeys: {
Tab: function (cm) {
const cursor = cm.getCursor()
const line = cm.getLine(cursor.line)
const cursorPosition = cursor.ch
const charBeforeCursor = line.substr(cursorPosition - 1, 1)
if (cm.somethingSelected()) cm.indentSelection('add')
else {
const tabs = cm.getOption('indentWithTabs')
if (line.trimLeft().match(/^(-|\*|\+) (\[( |x)] )?$/)) {
cm.execCommand('goLineStart')
if (tabs) {
cm.execCommand('insertTab')
} else {
cm.execCommand('insertSoftTab')
}
cm.execCommand('goLineEnd')
} else if (
!charBeforeCursor.match(/\t|\s|\r|\n/) &&
cursor.ch > 1
) {
// text expansion on tab key if the char before is alphabet
const snippets = JSON.parse(
fs.readFileSync(consts.SNIPPET_FILE, 'utf8')
)
if (expandSnippet(line, cursor, cm, snippets) === false) {
if (tabs) {
cm.execCommand('insertTab')
} else {
cm.execCommand('insertSoftTab')
}
}
} else {
if (tabs) {
cm.execCommand('insertTab')
} else {
cm.execCommand('insertSoftTab')
}
}
}
},
'Cmd-T': function (cm) {
// Do nothing
},
Enter: 'boostNewLineAndIndentContinueMarkdownList',
'Ctrl-C': cm => {
if (cm.getOption('keyMap').substr(0, 3) === 'vim') {
document.execCommand('copy')
}
return CodeMirror.Pass
}
}
extraKeys: this.defaultKeyMap
})
this.setMode(this.props.mode)
@@ -220,9 +243,63 @@ export default class CodeEditor extends React.Component {
CodeMirror.Vim.defineEx('wq', 'wq', this.quitEditor)
CodeMirror.Vim.defineEx('qw', 'qw', this.quitEditor)
CodeMirror.Vim.map('ZZ', ':q', 'normal')
this.setState({ isReady: true })
this.tableEditor = new TableEditor(new TextEditorInterface(this.editor))
this.textEditorInterface = new TextEditorInterface(this.editor)
this.tableEditor = new TableEditor(this.textEditorInterface)
eventEmitter.on('code:format-table', this.formatTable)
this.tableEditorOptions = options({
smartCursor: true
})
this.editorKeyMap = CodeMirror.normalizeKeyMap({
'Tab': () => { this.tableEditor.nextCell(this.tableEditorOptions) },
'Shift-Tab': () => { this.tableEditor.previousCell(this.tableEditorOptions) },
'Enter': () => { this.tableEditor.nextRow(this.tableEditorOptions) },
'Ctrl-Enter': () => { this.tableEditor.escape(this.tableEditorOptions) },
'Cmd-Enter': () => { this.tableEditor.escape(this.tableEditorOptions) },
'Shift-Ctrl-Left': () => { this.tableEditor.alignColumn(Alignment.LEFT, this.tableEditorOptions) },
'Shift-Cmd-Left': () => { this.tableEditor.alignColumn(Alignment.LEFT, this.tableEditorOptions) },
'Shift-Ctrl-Right': () => { this.tableEditor.alignColumn(Alignment.RIGHT, this.tableEditorOptions) },
'Shift-Cmd-Right': () => { this.tableEditor.alignColumn(Alignment.RIGHT, this.tableEditorOptions) },
'Shift-Ctrl-Up': () => { this.tableEditor.alignColumn(Alignment.CENTER, this.tableEditorOptions) },
'Shift-Cmd-Up': () => { this.tableEditor.alignColumn(Alignment.CENTER, this.tableEditorOptions) },
'Shift-Ctrl-Down': () => { this.tableEditor.alignColumn(Alignment.NONE, this.tableEditorOptions) },
'Shift-Cmd-Down': () => { this.tableEditor.alignColumn(Alignment.NONE, this.tableEditorOptions) },
'Ctrl-Left': () => { this.tableEditor.moveFocus(0, -1, this.tableEditorOptions) },
'Cmd-Left': () => { this.tableEditor.moveFocus(0, -1, this.tableEditorOptions) },
'Ctrl-Right': () => { this.tableEditor.moveFocus(0, 1, this.tableEditorOptions) },
'Cmd-Right': () => { this.tableEditor.moveFocus(0, 1, this.tableEditorOptions) },
'Ctrl-Up': () => { this.tableEditor.moveFocus(-1, 0, this.tableEditorOptions) },
'Cmd-Up': () => { this.tableEditor.moveFocus(-1, 0, this.tableEditorOptions) },
'Ctrl-Down': () => { this.tableEditor.moveFocus(1, 0, this.tableEditorOptions) },
'Cmd-Down': () => { this.tableEditor.moveFocus(1, 0, this.tableEditorOptions) },
'Ctrl-K Ctrl-I': () => { this.tableEditor.insertRow(this.tableEditorOptions) },
'Cmd-K Cmd-I': () => { this.tableEditor.insertRow(this.tableEditorOptions) },
'Ctrl-L Ctrl-I': () => { this.tableEditor.deleteRow(this.tableEditorOptions) },
'Cmd-L Cmd-I': () => { this.tableEditor.deleteRow(this.tableEditorOptions) },
'Ctrl-K Ctrl-J': () => { this.tableEditor.insertColumn(this.tableEditorOptions) },
'Cmd-K Cmd-J': () => { this.tableEditor.insertColumn(this.tableEditorOptions) },
'Ctrl-L Ctrl-J': () => { this.tableEditor.deleteColumn(this.tableEditorOptions) },
'Cmd-L Cmd-J': () => { this.tableEditor.deleteColumn(this.tableEditorOptions) },
'Alt-Shift-Ctrl-Left': () => { this.tableEditor.moveColumn(-1, this.tableEditorOptions) },
'Alt-Shift-Cmd-Left': () => { this.tableEditor.moveColumn(-1, this.tableEditorOptions) },
'Alt-Shift-Ctrl-Right': () => { this.tableEditor.moveColumn(1, this.tableEditorOptions) },
'Alt-Shift-Cmd-Right': () => { this.tableEditor.moveColumn(1, this.tableEditorOptions) },
'Alt-Shift-Ctrl-Up': () => { this.tableEditor.moveRow(-1, this.tableEditorOptions) },
'Alt-Shift-Cmd-Up': () => { this.tableEditor.moveRow(-1, this.tableEditorOptions) },
'Alt-Shift-Ctrl-Down': () => { this.tableEditor.moveRow(1, this.tableEditorOptions) },
'Alt-Shift-Cmd-Down': () => { this.tableEditor.moveRow(1, this.tableEditorOptions) }
})
if (this.props.enableTableEditor) {
this.editor.on('cursorActivity', this.editorActivityHandler)
this.editor.on('changes', this.editorActivityHandler)
}
this.setState({
clientWidth: this.refs.root.clientWidth
})
}
expandSnippet (line, cursor, cm, snippets) {
@@ -359,6 +436,27 @@ export default class CodeEditor extends React.Component {
this.editor.setOption('scrollPastEnd', this.props.scrollPastEnd)
}
if (prevProps.enableTableEditor !== this.props.enableTableEditor) {
if (this.props.enableTableEditor) {
this.editor.on('cursorActivity', this.editorActivityHandler)
this.editor.on('changes', this.editorActivityHandler)
} else {
this.editor.off('cursorActivity', this.editorActivityHandler)
this.editor.off('changes', this.editorActivityHandler)
}
this.extraKeysMode = 'default'
this.editor.setOption('extraKeys', this.defaultKeyMap)
}
if (this.state.clientWidth !== this.refs.root.clientWidth) {
this.setState({
clientWidth: this.refs.root.clientWidth
})
needRefresh = true
}
if (needRefresh) {
this.editor.refresh()
}
@@ -381,7 +479,13 @@ export default class CodeEditor extends React.Component {
moveCursorTo (row, col) {}
scrollToLine (num) {}
scrollToLine (event, num) {
const cursor = {
line: num,
ch: 1
}
this.editor.setCursor(cursor)
}
focus () {
this.editor.focus()
@@ -532,7 +636,10 @@ export default class CodeEditor extends React.Component {
body,
'text/html'
)
const linkWithTitle = `[${parsedBody.title}](${pastedTxt})`
const escapePipe = (str) => {
return str.replace('|', '\\|')
}
const linkWithTitle = `[${escapePipe(parsedBody.title)}](${pastedTxt})`
resolve(linkWithTitle)
} catch (e) {
reject(e)
@@ -597,8 +704,7 @@ export default class CodeEditor extends React.Component {
style={{
fontFamily,
fontSize: fontSize,
width: width,
opacity: this.state.isReady ? '1' : '0'
width: width
}}
onDrop={e => this.handleDropImage(e)}
/>

View File

@@ -6,7 +6,6 @@ import CodeEditor from 'browser/components/CodeEditor'
import MarkdownPreview from 'browser/components/MarkdownPreview'
import eventEmitter from 'browser/main/lib/eventEmitter'
import { findStorage } from 'browser/lib/findStorage'
import debounceRender from 'react-debounce-render'
class MarkdownEditor extends React.Component {
constructor (props) {
@@ -65,6 +64,10 @@ class MarkdownEditor extends React.Component {
})
}
setValue (value) {
this.refs.code.setValue(value)
}
handleChange (e) {
this.value = this.refs.code.value
this.props.onChange(e)
@@ -251,7 +254,7 @@ class MarkdownEditor extends React.Component {
: 'codeEditor--hide'
}
ref='code'
mode='GitHub Flavored Markdown'
mode='Boost Flavored Markdown'
value={value}
theme={config.editor.theme}
keyMap={config.editor.keyMap}
@@ -266,6 +269,7 @@ class MarkdownEditor extends React.Component {
storageKey={storageKey}
noteKey={noteKey}
fetchUrlTitle={config.editor.fetchUrlTitle}
enableTableEditor={config.editor.enableTableEditor}
onChange={(e) => this.handleChange(e)}
onBlur={(e) => this.handleBlur(e)}
/>
@@ -300,6 +304,7 @@ class MarkdownEditor extends React.Component {
noteKey={noteKey}
customCSS={config.preview.customCSS}
allowCustomCSS={config.preview.allowCustomCSS}
lineThroughCheckbox={config.preview.lineThroughCheckbox}
/>
</div>
)
@@ -313,4 +318,4 @@ MarkdownEditor.propTypes = {
ignorePreviewPointerEvents: PropTypes.bool
}
export default debounceRender(CSSModules(MarkdownEditor, styles))
export default CSSModules(MarkdownEditor, styles)

View File

@@ -16,7 +16,6 @@
.preview
display block
absolute top bottom left right
z-index 100
background-color white
height 100%
width 100%

View File

@@ -17,8 +17,11 @@ import copy from 'copy-to-clipboard'
import mdurl from 'mdurl'
import exportNote from 'browser/main/lib/dataApi/exportNote'
import { escapeHtmlCharacters } from 'browser/lib/utils'
import context from 'browser/lib/context'
import i18n from 'browser/lib/i18n'
import fs from 'fs'
const { remote } = require('electron')
const { remote, shell } = require('electron')
const attachmentManagement = require('../main/lib/dataApi/attachmentManagement')
const { app } = remote
@@ -27,6 +30,8 @@ const fileUrl = require('file-url')
const dialog = remote.dialog
const uri2path = require('file-uri-to-path')
const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
const appPath = fileUrl(
process.env.NODE_ENV === 'production' ? app.getAppPath() : path.resolve()
@@ -75,7 +80,6 @@ function buildStyle (
url('${appPath}/resources/fonts/MaterialIcons-Regular.woff') format('woff'),
url('${appPath}/resources/fonts/MaterialIcons-Regular.ttf') format('truetype');
}
${allowCustomCSS ? customCSS : ''}
${markdownStyle}
body {
@@ -83,6 +87,11 @@ body {
font-size: ${fontSize}px;
${scrollPastEnd && 'padding-bottom: 90vh;'}
}
@media print {
body {
padding-bottom: initial;
}
}
code {
font-family: '${codeBlockFontFamily.join("','")}';
background-color: rgba(0,0,0,0.04);
@@ -139,6 +148,8 @@ body p {
display: none
}
}
${allowCustomCSS ? customCSS : ''}
`
}
@@ -161,7 +172,6 @@ const scrollBarDarkStyle = `
}
`
const { shell } = require('electron')
const OSX = global.process.platform === 'darwin'
const defaultFontFamily = ['helvetica', 'arial', 'sans-serif']
@@ -180,9 +190,7 @@ const defaultCodeBlockFontFamily = [
export default class MarkdownPreview extends React.Component {
constructor (props) {
super(props)
this.state = {
isReady: false
}
this.contextMenuHandler = e => this.handleContextMenu(e)
this.mouseDownHandler = e => this.handleMouseDown(e)
this.mouseUpHandler = e => this.handleMouseUp(e)
@@ -221,8 +229,32 @@ export default class MarkdownPreview extends React.Component {
}
}
handleContextMenu (e) {
this.props.onContextMenu(e)
handleContextMenu (event) {
// If a contextMenu handler was passed to us, use it instead of the self-defined one -> return
if (_.isFunction(this.props.onContextMenu)) {
this.props.onContextMenu(event)
return
}
// No contextMenu was passed to us -> execute our own link-opener
if (event.target.tagName.toLowerCase() === 'a') {
const href = event.target.href
const isLocalFile = href.startsWith('file:')
if (isLocalFile) {
const absPath = uri2path(href)
try {
if (fs.lstatSync(absPath).isFile()) {
context.popup([
{
label: i18n.__('Show in explorer'),
click: (e) => shell.showItemInFolder(absPath)
}
])
}
} catch (e) {
console.log('Error while evaluating if the file is locally available', e)
}
}
}
}
handleDoubleClick (e) {
@@ -399,6 +431,7 @@ export default class MarkdownPreview extends React.Component {
case 'dark':
case 'solarized-dark':
case 'monokai':
case 'dracula':
return scrollBarDarkStyle
default:
return scrollBarStyle
@@ -457,7 +490,6 @@ export default class MarkdownPreview extends React.Component {
eventEmitter.on('export:save-md', this.saveAsMdHandler)
eventEmitter.on('export:save-html', this.saveAsHtmlHandler)
eventEmitter.on('print', this.printHandler)
setTimeout(() => this.setState({ isReady: true }))
}
componentWillUnmount () {
@@ -501,7 +533,8 @@ export default class MarkdownPreview extends React.Component {
prevProps.smartQuotes !== this.props.smartQuotes ||
prevProps.sanitize !== this.props.sanitize ||
prevProps.smartArrows !== this.props.smartArrows ||
prevProps.breaks !== this.props.breaks
prevProps.breaks !== this.props.breaks ||
prevProps.lineThroughCheckbox !== this.props.lineThroughCheckbox
) {
this.initMarkdown()
this.rewriteIframe()
@@ -830,6 +863,15 @@ export default class MarkdownPreview extends React.Component {
return
}
const regexIsLine = /^:line:[0-9]/
if (regexIsLine.test(linkHash)) {
const numberPattern = /\d+/g
const lineNumber = parseInt(linkHash.match(numberPattern)[0])
eventEmitter.emit('line:jump', lineNumber)
return
}
// this will match the old link format storage.key-note.key
// e.g.
// 877f99c3268608328037-1c211eb7dcb463de6490
@@ -850,11 +892,7 @@ export default class MarkdownPreview extends React.Component {
className={
className != null ? 'MarkdownPreview ' + className : 'MarkdownPreview'
}
style={
this.state.isReady
? Object.assign(style, { opacity: '1' })
: Object.assign(style, { opacity: '0' })
}
style={style}
tabIndex={tabIndex}
ref='root'
/>

View File

@@ -20,6 +20,10 @@ class MarkdownSplitEditor extends React.Component {
}
}
setValue (value) {
this.refs.code.setValue(value)
}
handleOnChange () {
this.value = this.refs.code.value
this.props.onChange()
@@ -145,7 +149,7 @@ class MarkdownSplitEditor extends React.Component {
styleName='codeEditor'
ref='code'
width={this.state.codeEditorWidthInPercent + '%'}
mode='GitHub Flavored Markdown'
mode='Boost Flavored Markdown'
value={value}
theme={config.editor.theme}
keyMap={config.editor.keyMap}
@@ -158,6 +162,7 @@ class MarkdownSplitEditor extends React.Component {
rulers={config.editor.rulers}
scrollPastEnd={config.editor.scrollPastEnd}
fetchUrlTitle={config.editor.fetchUrlTitle}
enableTableEditor={config.editor.enableTableEditor}
storageKey={storageKey}
noteKey={noteKey}
onChange={this.handleOnChange.bind(this)}
@@ -191,6 +196,7 @@ class MarkdownSplitEditor extends React.Component {
noteKey={noteKey}
customCSS={config.preview.customCSS}
allowCustomCSS={config.preview.allowCustomCSS}
lineThroughCheckbox={config.preview.lineThroughCheckbox}
/>
</div>
)

View File

@@ -74,24 +74,22 @@ const NoteItem = ({
? note.title
: <span styleName='item-title-empty'>{i18n.__('Empty note')}</span>}
</div>
{['ALL', 'STORAGE'].includes(viewType) &&
<div styleName='item-middle'>
<div styleName='item-middle-time'>{dateDisplay}</div>
<div styleName='item-middle-app-meta'>
<div
title={
viewType === 'ALL'
? storageName
: viewType === 'STORAGE' ? folderName : null
}
styleName='item-middle-app-meta-label'
>
{viewType === 'ALL' && storageName}
{viewType === 'STORAGE' && folderName}
</div>
<div styleName='item-middle'>
<div styleName='item-middle-time'>{dateDisplay}</div>
<div styleName='item-middle-app-meta'>
<div
title={
viewType === 'ALL'
? storageName
: viewType === 'STORAGE' ? folderName : null
}
styleName='item-middle-app-meta-label'
>
{viewType === 'ALL' && storageName}
{viewType === 'STORAGE' && folderName}
</div>
</div>}
</div>
</div>
<div styleName='item-bottom'>
<div styleName='item-bottom-tagList'>
{note.tags.length > 0

View File

@@ -368,13 +368,13 @@ body[data-theme="monokai"]
.item-title
.item-title-icon
.item-bottom-time
color $ui-monokai-text-color
color $ui-monokai-active-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
color #f92672
.item-bottom-tagList-item
background-color alpha(#fff, 20%)
@@ -394,3 +394,76 @@ body[data-theme="monokai"]
.item-bottom-tagList-empty
color $ui-inactive-text-color
vertical-align middle
body[data-theme="dracula"]
.root
border-color $ui-dracula-borderColor
background-color $ui-dracula-noteList-backgroundColor
.item
border-color $ui-dracula-borderColor
background-color $ui-dracula-noteList-backgroundColor
&:hover
transition 0.15s
// background-color alpha($ui-dracula-noteList-backgroundColor, 20%)
color $ui-dracula-text-color
.item-title
.item-title-icon
.item-bottom-time
transition 0.15s
color $ui-dracula-text-color
.item-bottom-tagList-item
transition 0.15s
background-color alpha($ui-dracula-noteList-backgroundColor, 20%)
color $ui-dracula-text-color
&:active
transition 0.15s
background-color $ui-dracula-noteList-backgroundColor
color $ui-dracula-text-color
.item-title
.item-title-icon
.item-bottom-time
transition 0.15s
color $ui-dracula-text-color
.item-bottom-tagList-item
transition 0.15s
background-color alpha($ui-dracula-noteList-backgroundColor, 10%)
color $ui-dracula-text-color
.item-wrapper
border-color alpha($ui-dracula-button-backgroundColor, 60%)
.item--active
border-color $ui-dracula-borderColor
background-color $ui-dracula-button-backgroundColor
.item-wrapper
border-color transparent
.item-title
.item-title-icon
.item-bottom-time
color $ui-dracula-active-color
.item-bottom-tagList-item
background-color alpha(#f8f8f2, 10%)
color $ui-dracula-text-color
&:hover
// background-color alpha($ui-dracula-button--active-backgroundColor, 60%)
color #ff79c6
.item-bottom-tagList-item
background-color alpha(#f8f8f2, 20%)
.item-title
color $ui-inactive-text-color
.item-title-icon
color $ui-inactive-text-color
.item-title-empty
color $ui-inactive-text-color
.item-bottom-tagList-item
background-color alpha($ui-dark-button--active-backgroundColor, 40%)
color $ui-inactive-text-color
.item-bottom-tagList-empty
color $ui-inactive-text-color
vertical-align middle

View File

@@ -240,7 +240,7 @@ body[data-theme="monokai"]
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
color $ui-solarized-dark-text-color
color $ui-monokai-text-color
.item-simple-bottom-tagList-item
transition 0.15s
background-color alpha(#fff, 20%)
@@ -286,3 +286,67 @@ body[data-theme="monokai"]
.item-simple-right-storageName
padding-left 4px
opacity 0.4
body[data-theme="dracula"]
.root
border-color $ui-dracula-borderColor
background-color $ui-dracula-noteList-backgroundColor
.item-simple
border-color $ui-dracula-borderColor
background-color $ui-dracula-noteList-backgroundColor
&:hover
transition 0.15s
background-color alpha($ui-dracula-button-backgroundColor, 60%)
color $ui-dracula-text-color
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
color $ui-dracula-text-color
.item-simple-bottom-tagList-item
transition 0.15s
background-color alpha(#f8f8f2, 20%)
color $ui-dracula-text-color
&:active
transition 0.15s
background-color $ui-dracula-button--active-backgroundColor
color $ui-dracula-text-color
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
transition 0.15s
color $ui-dracula-text-color
.item-simple-bottom-tagList-item
transition 0.15s
background-color alpha(#f8f8f2, 10%)
color $ui-dracula-text-color
.item-simple--active
border-color $ui-dracula-borderColor
background-color $ui-dracula-button--active-backgroundColor
.item-simple-wrapper
border-color transparent
.item-simple-title
.item-simple-title-empty
.item-simple-title-icon
.item-simple-bottom-time
color $ui-dracula-text-color
.item-simple-bottom-tagList-item
background-color alpha(#f8f8f2, 10%)
color $ui-dracula-text-color
&:hover
// background-color alpha($ui-dark-button--active-backgroundColor, 60%)
color #c0392b
.item-simple-bottom-tagList-item
background-color alpha(#f8f8f2, 20%)
.item-simple-title
color $ui-dark-text-color
border-bottom $ui-dark-borderColor
.item-simple-right
float right
.item-simple-right-storageName
padding-left 4px
opacity 0.4

View File

@@ -51,4 +51,15 @@ body[data-theme="monokai"]
border none
background-color $ui-monokai-button-backgroundColor
&:hover
color #5CB85C
color #5CB85C
body[data-theme="dracula"]
.notification-area
background-color none
.notification-link
color $ui-dracula-text-color
border none
background-color $ui-dracula-button-backgroundColor
&:hover
color #ff79c6

View File

@@ -263,4 +263,46 @@ body[data-theme="monokai"]
background-color $ui-monokai-button-backgroundColor
color $ui-monokai-text-color
.menu-button-label
color $ui-monokai-text-color
color $ui-monokai-text-color
body[data-theme="dracula"]
.menu-button
&:active
background-color $ui-dracula-noteList-backgroundColor
color $ui-dracula-text-color
&:hover
background-color $ui-dracula-button-backgroundColor
color $ui-dracula-text-color
.menu-button--active
color $ui-dracula-text-color
background-color $ui-dracula-button-backgroundColor
.menu-button-label
color $ui-dracula-text-color
&:hover
background-color $ui-dracula-button-backgroundColor
color $ui-dracula-text-color
.menu-button-label
color $ui-dracula-text-color
.menu-button-star--active
color $ui-dracula-text-color
background-color $ui-dracula-button-backgroundColor
.menu-button-label
color $ui-dracula-text-color
&:hover
background-color $ui-dracula-button-backgroundColor
color $ui-dracula-text-color
.menu-button-label
color $ui-dracula-text-color
.menu-button-trash--active
color $ui-dracula-text-color
background-color $ui-dracula-button-backgroundColor
.menu-button-label
color $ui-dracula-text-color
&:hover
background-color $ui-dracula-button-backgroundColor
color $ui-dracula-text-color
.menu-button-label
color $ui-dracula-text-color

View File

@@ -136,4 +136,92 @@ body[data-theme="solarized-dark"]
color $ui-solarized-dark-text-color
.deleteButton
color alpha($ui-solarized-dark-text-color, 30%)
color alpha($ui-solarized-dark-text-color, 30%)
body[data-theme="monokai"]
.root
color $ui-monokai-text-color
border-color $ui-dark-borderColor
&:hover
background-color $ui-monokai-noteDetail-backgroundColor
.deleteButton
color $ui-monokai-text-color
&:hover
background-color darken($ui-monokai-noteDetail-backgroundColor, 15%)
&:active
color $ui-monokai-text-color
background-color $ui-dark-button--active-backgroundColor
.root--active
color $ui-monokai-text-color
border-color $ui-monokai-borderColor
&:hover
background-color $ui-monokai-noteDetail-backgroundColor
.deleteButton
color $ui-monokai-text-color
&:hover
background-color darken($ui-monokai-noteDetail-backgroundColor, 15%)
&:active
color $ui-monokai-text-color
background-color $ui-dark-button--active-backgroundColor
.button
border none
color $ui-monokai-text-color
background-color transparent
transition color background-color 0.15s
border-left 4px solid transparent
&:hover
color $ui-monokai-text-color
background-color $ui-monokai-noteDetail-backgroundColor
.input
background-color $ui-monokai-noteDetail-backgroundColor
color $ui-monokai-text-color
.deleteButton
color alpha($ui-monokai-text-color, 30%)
body[data-theme="dracula"]
.root
color $ui-dracula-text-color
border-color $ui-dark-borderColor
&:hover
background-color $ui-dracula-noteDetail-backgroundColor
.deleteButton
color $ui-dracula-text-color
&:hover
background-color darken($ui-dracula-noteDetail-backgroundColor, 15%)
&:active
color $ui-dracula-text-color
background-color $ui-dark-button--active-backgroundColor
.root--active
color $ui-dracula-text-color
border-color $ui-dracula-borderColor
&:hover
background-color $ui-dracula-noteDetail-backgroundColor
.deleteButton
color $ui-dracula-text-color
&:hover
background-color darken($ui-dracula-noteDetail-backgroundColor, 15%)
&:active
color $ui-dracula-text-color
background-color $ui-dark-button--active-backgroundColor
.button
border none
color $ui-dracula-text-color
background-color transparent
transition color background-color 0.15s
border-left 4px solid transparent
&:hover
color $ui-dracula-text-color
background-color $ui-dracula-noteDetail-backgroundColor
.input
background-color $ui-dracula-noteDetail-backgroundColor
color $ui-dracula-text-color
.deleteButton
color alpha($ui-dracula-text-color, 30%)

View File

@@ -156,4 +156,23 @@ body[data-theme="monokai"]
background-color $ui-monokai-button-backgroundColor
&:hover
color $ui-monokai-text-color
background-color $ui-monokai-button-backgroundColor
background-color $ui-monokai-button-backgroundColor
body[data-theme="dracula"]
.folderList-item
&:hover
background-color $ui-dracula-button-backgroundColor
color $ui-dracula-text-color
&:active
color $ui-dracula-text-color
background-color $ui-dracula-button-backgroundColor
.folderList-item--active
@extend .folderList-item
color $ui-dracula-text-color
background-color $ui-dracula-button-backgroundColor
&:active
background-color $ui-dracula-button-backgroundColor
&:hover
color $ui-dracula-text-color
background-color $ui-dracula-button-backgroundColor

View File

@@ -14,8 +14,8 @@ import CSSModules from 'browser/lib/CSSModules'
* @param {bool} isRelated
*/
const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, isActive, isRelated, count}) => (
<div styleName='tagList-itemContainer'>
const TagListItem = ({name, handleClickTagListItem, handleClickNarrowToTag, handleContextMenu, isActive, isRelated, count}) => (
<div styleName='tagList-itemContainer' onContextMenu={e => handleContextMenu(e, name)}>
{isRelated
? <button styleName={isActive ? 'tagList-itemNarrow-active' : 'tagList-itemNarrow'} onClick={() => handleClickNarrowToTag(name)}>
<i className={isActive ? 'fa fa-minus-circle' : 'fa fa-plus-circle'} />

View File

@@ -12,7 +12,7 @@ import styles from './TodoListPercentage.styl'
*/
const TodoListPercentage = ({
percentageOfTodo
percentageOfTodo, onClearCheckboxClick
}) => (
<div styleName='percentageBar' style={{display: isNaN(percentageOfTodo) ? 'none' : ''}}>
<div styleName='progressBar' style={{width: `${percentageOfTodo}%`}}>
@@ -20,11 +20,15 @@ const TodoListPercentage = ({
<p styleName='percentageText'>{percentageOfTodo}%</p>
</div>
</div>
<div styleName='todoClear'>
<p styleName='todoClearText' onClick={(e) => onClearCheckboxClick(e)}>clear</p>
</div>
</div>
)
TodoListPercentage.propTypes = {
percentageOfTodo: PropTypes.number.isRequired
percentageOfTodo: PropTypes.number.isRequired,
onClearCheckboxClick: PropTypes.func.isRequired
}
export default CSSModules(TodoListPercentage, styles)

View File

@@ -1,4 +1,5 @@
.percentageBar
display: flex
position absolute
top 72px
right 0px
@@ -30,6 +31,20 @@
color #f4f4f4
font-weight 600
.todoClear
display flex
justify-content: flex-end
position absolute
z-index 120
width 100%
height 100%
padding 2px 10px
.todoClearText
color #f4f4f4
cursor pointer
font-weight 500
body[data-theme="dark"]
.percentageBar
background-color #444444
@@ -39,7 +54,10 @@ body[data-theme="dark"]
.percentageText
color $ui-dark-text-color
.todoClearText
color $ui-dark-text-color
body[data-theme="solarized-dark"]
.percentageBar
background-color #002b36
@@ -50,12 +68,28 @@ body[data-theme="solarized-dark"]
.percentageText
color #fdf6e3
.todoClearText
color #fdf6e3
body[data-theme="monokai"]
.percentageBar
background-color #f92672
background-color: $ui-monokai-borderColor
.progressBar
background-color: #373831
background-color $ui-monokai-active-color
.percentageText
color #fdf6e3
color $ui-monokai-text-color
body[data-theme="dracula"]
.percentageBar
background-color $ui-dracula-borderColor
.progressBar
background-color: $ui-dracula-active-color
.percentageText
color $ui-dracula-text-color
.percentageText
color $ui-dracula-text-color

View File

@@ -80,6 +80,9 @@ li
&.checked
text-decoration line-through
opacity 0.5
&.taskListItem.checked
text-decoration line-through
opacity 0.5
div.math-rendered
text-align center
.math-failed
@@ -257,6 +260,7 @@ table
display block
width 100%
margin 0 0 1em
overflow-x auto
thead
tr
background-color tableHeadBgColor
@@ -360,7 +364,7 @@ for name, val in admonition_types
.admonition.{name}
@extend $admonition
border-left-color: val[color]
.admonition.{name}>.admonition-title
@extend $admonition-title
border-bottom-color: .1rem solid rgba(val[color], 0.2)
@@ -475,5 +479,34 @@ body[data-theme="monokai"]
border-color themeMonokaiTableBorder
&:last-child
border-right solid 1px themeMonokaiTableBorder
kbd
background-color themeDarkBackground
themeDraculaTableOdd = $ui-dracula-noteDetail-backgroundColor
themeDraculaTableEven = darken($ui-dracula-noteDetail-backgroundColor, 10%)
themeDraculaTableHead = themeDraculaTableEven
themeDraculaTableBorder = themeDarkBorder
body[data-theme="dracula"]
color $ui-dracula-text-color
border-color themeDarkBorder
background-color $ui-dracula-noteDetail-backgroundColor
table
thead
tr
background-color themeDraculaTableHead
th
border-color themeDraculaTableBorder
&:last-child
border-right solid 1px themeDraculaTableBorder
tbody
tr:nth-child(2n + 1)
background-color themeDraculaTableOdd
tr:nth-child(2n)
background-color themeDraculaTableEven
td
border-color themeDraculaTableBorder
&:last-child
border-right solid 1px themeDraculaTableBorder
kbd
background-color themeDarkBackground

View File

@@ -2,8 +2,8 @@ import mermaidAPI from 'mermaid'
// fixes bad styling in the mermaid dark theme
const darkThemeStyling = `
.loopText tspan {
fill: white;
.loopText tspan {
fill: white;
}`
function getRandomInt (min, max) {
@@ -21,7 +21,7 @@ function getId () {
function render (element, content, theme) {
try {
let isDarkTheme = theme === 'dark' || theme === 'solarized-dark' || theme === 'monokai'
let isDarkTheme = theme === 'dark' || theme === 'solarized-dark' || theme === 'monokai' || theme === 'dracula'
mermaidAPI.initialize({
theme: isDarkTheme ? 'dark' : 'default',
themeCSS: isDarkTheme ? darkThemeStyling : ''

View File

@@ -1,53 +1,113 @@
import { Point } from '@susisu/mte-kernel'
export default class TextEditorInterface {
constructor (editor) {
this.editor = editor
}
getCursorPosition () {
const pos = this.editor.getCursor()
return new Point(pos.line, pos.ch)
}
setCursorPosition (pos) {
this.editor.setCursor({line: pos.row, ch: pos.column})
}
setSelectionRange (range) {
this.editor.setSelection({
anchor: {line: range.start.row, ch: range.start.column},
head: {line: range.end.row, ch: range.end.column}
})
}
getLastRow () {
return this.editor.lastLine()
}
acceptsTableEdit (row) {
return true
}
getLine (row) {
return this.editor.getLine(row)
}
insertLine (row, line) {
this.editor.replaceRange(line, {line: row, ch: 0})
}
deleteLine (row) {
this.editor.replaceRange('', {line: row, ch: 0}, {line: row, ch: this.editor.getLine(row).length})
}
replaceLines (startRow, endRow, lines) {
endRow-- // because endRow is a first line after a table.
const endRowCh = this.editor.getLine(endRow).length
this.editor.replaceRange(lines, {line: startRow, ch: 0}, {line: endRow, ch: endRowCh})
}
transact (func) {
func()
}
}
import { Point } from '@susisu/mte-kernel'
export default class TextEditorInterface {
constructor (editor) {
this.editor = editor
this.doc = editor.getDoc()
this.transaction = false
}
getCursorPosition () {
const { line, ch } = this.doc.getCursor()
return new Point(line, ch)
}
setCursorPosition (pos) {
this.doc.setCursor({
line: pos.row,
ch: pos.column
})
}
setSelectionRange (range) {
this.doc.setSelection(
{ line: range.start.row, ch: range.start.column },
{ line: range.end.row, ch: range.end.column }
)
}
getLastRow () {
return this.doc.lineCount() - 1
}
acceptsTableEdit () {
return true
}
getLine (row) {
return this.doc.getLine(row)
}
insertLine (row, line) {
const lastRow = this.getLastRow()
if (row > lastRow) {
const lastLine = this.getLine(lastRow)
this.doc.replaceRange(
'\n' + line,
{ line: lastRow, ch: lastLine.length },
{ line: lastRow, ch: lastLine.length }
)
} else {
this.doc.replaceRange(
line + '\n',
{ line: row, ch: 0 },
{ line: row, ch: 0 }
)
}
}
deleteLine (row) {
const lastRow = this.getLastRow()
if (row >= lastRow) {
if (lastRow > 0) {
const preLastLine = this.getLine(lastRow - 1)
const lastLine = this.getLine(lastRow)
this.doc.replaceRange(
'',
{ line: lastRow - 1, ch: preLastLine.length },
{ line: lastRow, ch: lastLine.length }
)
} else {
const lastLine = this.getLine(lastRow)
this.doc.replaceRange(
'',
{ line: lastRow, ch: 0 },
{ line: lastRow, ch: lastLine.length }
)
}
} else {
this.doc.replaceRange(
'',
{ line: row, ch: 0 },
{ line: row + 1, ch: 0 }
)
}
}
replaceLines (startRow, endRow, lines) {
const lastRow = this.getLastRow()
if (endRow > lastRow) {
const lastLine = this.getLine(lastRow)
this.doc.replaceRange(
lines.join('\n'),
{ line: startRow, ch: 0 },
{ line: lastRow, ch: lastLine.length }
)
} else {
this.doc.replaceRange(
lines.join('\n') + '\n',
{ line: startRow, ch: 0 },
{ line: endRow, ch: 0 }
)
}
}
transact (func) {
this.transaction = true
func()
this.transaction = false
if (this.onDidFinishTransaction) {
this.onDidFinishTransaction.call(undefined)
}
}
}

View File

@@ -1,19 +1,37 @@
export function findNoteTitle (value) {
export function findNoteTitle (value, enableFrontMatterTitle, frontMatterTitleField = 'title') {
const splitted = value.split('\n')
let title = null
let isInsideCodeBlock = false
splitted.some((line, index) => {
const trimmedLine = line.trim()
const trimmedNextLine = splitted[index + 1] === undefined ? '' : splitted[index + 1].trim()
if (trimmedLine.match('```')) {
isInsideCodeBlock = !isInsideCodeBlock
if (splitted[0] === '---') {
let line = 0
while (++line < splitted.length) {
if (enableFrontMatterTitle && splitted[line].startsWith(frontMatterTitleField + ':')) {
title = splitted[line].substring(frontMatterTitleField.length + 1).trim()
break
}
if (splitted[line] === '---') {
splitted.splice(0, line + 1)
break
}
}
if (isInsideCodeBlock === false && (trimmedLine.match(/^# +/) || trimmedNextLine.match(/^=+$/))) {
title = trimmedLine
return true
}
})
}
if (title === null) {
splitted.some((line, index) => {
const trimmedLine = line.trim()
const trimmedNextLine = splitted[index + 1] === undefined ? '' : splitted[index + 1].trim()
if (trimmedLine.match('```')) {
isInsideCodeBlock = !isInsideCodeBlock
}
if (isInsideCodeBlock === false && (trimmedLine.match(/^# +/) || trimmedNextLine.match(/^=+$/))) {
title = trimmedLine
return true
}
})
}
if (title === null) {
title = ''

View File

@@ -0,0 +1,24 @@
'use strict'
module.exports = function frontMatterPlugin (md) {
function frontmatter (state, startLine, endLine, silent) {
if (startLine !== 0 || state.src.substr(startLine, state.eMarks[0]) !== '---') {
return false
}
let line = 0
while (++line < state.lineMax) {
if (state.src.substring(state.bMarks[line], state.eMarks[line]) === '---') {
state.line = line + 1
return true
}
}
return false
}
md.block.ruler.before('table', 'frontmatter', frontmatter, {
alt: [ 'paragraph', 'reference', 'blockquote', 'list' ]
})
}

View File

@@ -0,0 +1,100 @@
/**
* @fileoverview Markdown table of contents generator
*/
import toc from 'markdown-toc'
import diacritics from 'diacritics-map'
import stripColor from 'strip-color'
const EOL = require('os').EOL
/**
* @caseSensitiveSlugify Custom slugify function
* Same implementation that the original used by markdown-toc (node_modules/markdown-toc/lib/utils.js),
* but keeps original case to properly handle https://github.com/BoostIO/Boostnote/issues/2067
*/
function caseSensitiveSlugify (str) {
function replaceDiacritics (str) {
return str.replace(/[À-ž]/g, function (ch) {
return diacritics[ch] || ch
})
}
function getTitle (str) {
if (/^\[[^\]]+\]\(/.test(str)) {
var m = /^\[([^\]]+)\]/.exec(str)
if (m) return m[1]
}
return str
}
str = getTitle(str)
str = stripColor(str)
// str = str.toLowerCase() //let's be case sensitive
// `.split()` is often (but not always) faster than `.replace()`
str = str.split(' ').join('-')
str = str.split(/\t/).join('--')
str = str.split(/<\/?[^>]+>/).join('')
str = str.split(/[|$&`~=\\\/@+*!?({[\]})<>=.,;:'"^]/).join('')
str = str.split(/[。?!,、;:“”【】()〔〕[]﹃﹄“ ”‘’﹁﹂—…-~《》〈〉「」]/).join('')
str = replaceDiacritics(str)
return str
}
const TOC_MARKER_START = '<!-- toc -->'
const TOC_MARKER_END = '<!-- tocstop -->'
/**
* Takes care of proper updating given editor with TOC.
* If TOC doesn't exit in the editor, it's inserted at current caret position.
* Otherwise,TOC is updated in place.
* @param editor CodeMirror editor to be updated with TOC
*/
export function generateInEditor (editor) {
const tocRegex = new RegExp(`${TOC_MARKER_START}[\\s\\S]*?${TOC_MARKER_END}`)
function tocExistsInEditor () {
return tocRegex.test(editor.getValue())
}
function updateExistingToc () {
const toc = generate(editor.getValue())
const search = editor.getSearchCursor(tocRegex)
while (search.findNext()) {
search.replace(toc)
}
}
function addTocAtCursorPosition () {
const toc = generate(editor.getRange(editor.getCursor(), {line: Infinity}))
editor.replaceRange(wrapTocWithEol(toc, editor), editor.getCursor())
}
if (tocExistsInEditor()) {
updateExistingToc()
} else {
addTocAtCursorPosition()
}
}
/**
* Generates MD TOC based on MD document passed as string.
* @param markdownText MD document
* @returns generatedTOC String containing generated TOC
*/
export function generate (markdownText) {
const generatedToc = toc(markdownText, {slugify: caseSensitiveSlugify})
return TOC_MARKER_START + EOL + EOL + generatedToc.content + EOL + EOL + TOC_MARKER_END
}
function wrapTocWithEol (toc, editor) {
const leftWrap = editor.getCursor().ch === 0 ? '' : EOL
const rightWrap = editor.getLine(editor.getCursor().line).length === editor.getCursor().ch ? '' : EOL
return leftWrap + toc + rightWrap
}
export default {
generate,
generateInEditor
}

View File

@@ -7,6 +7,7 @@ import _ from 'lodash'
import ConfigManager from 'browser/main/lib/ConfigManager'
import katex from 'katex'
import { lastFindInArray } from './utils'
import ee from 'browser/main/lib/eventEmitter'
function createGutter (str, firstLineNumber) {
if (Number.isNaN(firstLineNumber)) firstLineNumber = 1
@@ -148,7 +149,9 @@ class Markdown {
}
})
this.md.use(require('markdown-it-kbd'))
this.md.use(require('markdown-it-admonition'))
this.md.use(require('markdown-it-admonition'), {types: ['note', 'hint', 'attention', 'caution', 'danger', 'error']})
this.md.use(require('./markdown-it-frontmatter'))
const deflate = require('markdown-it-plantuml/lib/deflate')
this.md.use(require('markdown-it-plantuml'), '', {
@@ -221,7 +224,11 @@ class Markdown {
if (!liToken.attrs) {
liToken.attrs = []
}
liToken.attrs.push(['class', 'taskListItem'])
if (config.preview.lineThroughCheckbox) {
liToken.attrs.push(['class', `taskListItem${match[1] !== ' ' ? ' checked' : ''}`])
} else {
liToken.attrs.push(['class', 'taskListItem'])
}
}
content = `<label class='taskListItem${match[1] !== ' ' ? ' checked' : ''}' for='checkbox-${startLine + 1}'><input type='checkbox'${match[1] !== ' ' ? ' checked' : ''} id='checkbox-${startLine + 1}'/> ${content.substring(4, content.length)}</label>`
}

62
browser/lib/newNote.js Normal file
View File

@@ -0,0 +1,62 @@
import { hashHistory } from 'react-router'
import dataApi from 'browser/main/lib/dataApi'
import ee from 'browser/main/lib/eventEmitter'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
export function createMarkdownNote (storage, folder, dispatch, location) {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_MARKDOWN')
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
return dataApi
.createNote(storage, {
type: 'MARKDOWN_NOTE',
folder: folder,
title: '',
content: ''
})
.then(note => {
const noteHash = note.key
dispatch({
type: 'UPDATE_NOTE',
note: note
})
hashHistory.push({
pathname: location.pathname,
query: { key: noteHash }
})
ee.emit('list:jump', noteHash)
ee.emit('detail:focus')
})
}
export function createSnippetNote (storage, folder, dispatch, location, config) {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_SNIPPET')
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
return dataApi
.createNote(storage, {
type: 'SNIPPET_NOTE',
folder: folder,
title: '',
description: '',
snippets: [
{
name: '',
mode: config.editor.snippetDefaultLanguage || 'text',
content: ''
}
]
})
.then(note => {
const noteHash = note.key
dispatch({
type: 'UPDATE_NOTE',
note: note
})
hashHistory.push({
pathname: location.pathname,
query: { key: noteHash }
})
ee.emit('list:jump', noteHash)
ee.emit('detail:focus')
})
}

View File

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

View File

@@ -36,7 +36,7 @@
height 34px
width 20px
line-height 34px
.search-input
vertical-align middle
position relative
@@ -71,7 +71,7 @@
overflow ellipsis
cursor pointer
&:hover
background-color $ui-button--hover-backgroundColor
background-color $ui-button--hover-backgroundColor
.search-optionList-item--active
@extend .search-optionList-item
@@ -159,3 +159,29 @@ body[data-theme="monokai"]
color $ui-monokai-button--active-color
.search-optionList-item-name-surfix
color $ui-monokai-inactive-text-color
body[data-theme="dracula"]
.root
color $ui-dracula-text-color
&:hover
color #f8f8f2
background-color $ui-dark-button--hover-backgroundColor
border-color $ui-dracula-borderColor
.search-optionList
color #f8f8f2
border-color $ui-dracula-borderColor
background-color $ui-dracula-button-backgroundColor
.search-optionList-item
&:hover
background-color lighten($ui-dracula-button--hover-backgroundColor, 15%)
.search-optionList-item--active
background-color $ui-dracula-button--active-backgroundColor
color $ui-dracula-button--active-color
&:hover
background-color $ui-dark-button--hover-backgroundColor
color $ui-dracula-button--active-color
.search-optionList-item-name-surfix
color $ui-dracula-inactive-text-color

View File

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

View File

@@ -29,6 +29,7 @@ import { formatDate } from 'browser/lib/date-formatter'
import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus'
import striptags from 'striptags'
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
import markdownToc from 'browser/lib/markdown-toc-generator'
class MarkdownNoteDetail extends React.Component {
constructor (props) {
@@ -47,6 +48,7 @@ class MarkdownNoteDetail extends React.Component {
this.dispatchTimer = null
this.toggleLockButton = this.handleToggleLockButton.bind(this)
this.generateToc = () => this.handleGenerateToc()
}
focus () {
@@ -59,10 +61,13 @@ class MarkdownNoteDetail extends React.Component {
const reversedType = this.state.editorType === 'SPLIT' ? 'EDITOR_PREVIEW' : 'SPLIT'
this.handleSwitchMode(reversedType)
})
ee.on('code:generate-toc', this.generateToc)
}
componentWillReceiveProps (nextProps) {
if (nextProps.note.key !== this.props.note.key && !this.state.isMovingNote) {
const isNewNote = nextProps.note.key !== this.props.note.key
const hasDeletedTags = nextProps.note.tags.length < this.props.note.tags.length
if (!this.state.isMovingNote && (isNewNote || hasDeletedTags)) {
if (this.saveQueue != null) this.saveNow()
this.setState({
note: Object.assign({}, nextProps.note)
@@ -75,6 +80,7 @@ class MarkdownNoteDetail extends React.Component {
componentWillUnmount () {
ee.off('topbar:togglelockbutton', this.toggleLockButton)
ee.off('code:generate-toc', this.generateToc)
if (this.saveQueue != null) this.saveNow()
}
@@ -87,7 +93,7 @@ class MarkdownNoteDetail extends React.Component {
handleUpdateContent () {
const { note } = this.state
note.content = this.refs.content.value
note.title = markdown.strip(striptags(findNoteTitle(note.content)))
note.title = markdown.strip(striptags(findNoteTitle(note.content, this.props.config.editor.enableFrontMatterTitle, this.props.config.editor.frontMatterTitleField)))
this.updateNote(note)
}
@@ -262,6 +268,11 @@ class MarkdownNoteDetail extends React.Component {
}
}
handleGenerateToc () {
const editor = this.refs.content.refs.code.editor
markdownToc.generateInEditor(editor)
}
handleFocus (e) {
this.focus()
}
@@ -284,9 +295,29 @@ class MarkdownNoteDetail extends React.Component {
})
}
handleClearTodo () {
const { note } = this.state
const splitted = note.content.split('\n')
const clearTodoContent = splitted.map((line) => {
const trimmedLine = line.trim()
if (trimmedLine.match(/\[x\]/i)) {
return line.replace(/\[x\]/i, '[ ]')
} else {
return line
}
}).join('\n')
note.content = clearTodoContent
this.refs.content.setValue(note.content)
this.updateNote(note)
}
renderEditor () {
const { config, ignorePreviewPointerEvents } = this.props
const { note } = this.state
if (this.state.editorType === 'EDITOR_PREVIEW') {
return <MarkdownEditor
ref='content'
@@ -363,9 +394,10 @@ class MarkdownNoteDetail extends React.Component {
<TagSelect
ref='tags'
value={this.state.note.tags}
data={data}
onChange={this.handleUpdateTag.bind(this)}
/>
<TodoListPercentage percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
<TodoListPercentage onClearCheckboxClick={(e) => this.handleClearTodo(e)} percentageOfTodo={getTodoPercentageOfCompleted(note.content)} />
</div>
<div styleName='info-right'>
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />

View File

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

View File

@@ -13,6 +13,7 @@ $info-margin-under-border = 30px
display flex
align-items center
padding 0 20px
z-index 99
.info-left
padding 0 10px
@@ -97,8 +98,13 @@ body[data-theme="solarized-dark"]
.info
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
background-color $ui-monokai-noteDetail-backgroundColor
body[data-theme="dracula"]
.info
border-color $ui-dracula-borderColor
background-color $ui-dracula-noteDetail-backgroundColor

View File

@@ -29,6 +29,7 @@ import InfoPanelTrashed from './InfoPanelTrashed'
import { formatDate } from 'browser/lib/date-formatter'
import i18n from 'browser/lib/i18n'
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
import markdownToc from 'browser/lib/markdown-toc-generator'
const electron = require('electron')
const { remote } = electron
@@ -52,6 +53,7 @@ class SnippetNoteDetail extends React.Component {
}
this.scrollToNextTabThreshold = 0.7
this.generateToc = () => this.handleGenerateToc()
}
componentDidMount () {
@@ -65,6 +67,7 @@ class SnippetNoteDetail extends React.Component {
enableLeftArrow: allTabs.offsetLeft !== 0
})
}
ee.on('code:generate-toc', this.generateToc)
}
componentWillReceiveProps (nextProps) {
@@ -91,6 +94,16 @@ class SnippetNoteDetail extends React.Component {
componentWillUnmount () {
if (this.saveQueue != null) this.saveNow()
ee.off('code:generate-toc', this.generateToc)
}
handleGenerateToc () {
const { note, snippetIndex } = this.state
const currentMode = note.snippets[snippetIndex].mode
if (currentMode.includes('Markdown')) {
const currentEditor = this.refs[`code-${snippetIndex}`].refs.code.editor
markdownToc.generateInEditor(currentEditor)
}
}
handleChange (e) {
@@ -99,7 +112,7 @@ class SnippetNoteDetail extends React.Component {
if (this.refs.tags) note.tags = this.refs.tags.value
note.description = this.refs.description.value
note.updatedAt = new Date()
note.title = findNoteTitle(note.description)
note.title = findNoteTitle(note.description, false)
this.setState({
note
@@ -441,7 +454,7 @@ class SnippetNoteDetail extends React.Component {
const isSuper = global.process.platform === 'darwin'
? e.metaKey
: e.ctrlKey
if (isSuper && !e.shiftKey) {
if (isSuper && !e.shiftKey && !e.altKey) {
e.preventDefault()
this.addSnippet()
}
@@ -573,11 +586,12 @@ class SnippetNoteDetail extends React.Component {
}
addSnippet () {
const { config } = this.props
const { note } = this.state
note.snippets = note.snippets.concat([{
name: '',
mode: 'Plain Text',
mode: config.editor.snippetDefaultLanguage || 'text',
content: ''
}])
const snippetIndex = note.snippets.length - 1
@@ -691,6 +705,7 @@ class SnippetNoteDetail extends React.Component {
keyMap={config.editor.keyMap}
scrollPastEnd={config.editor.scrollPastEnd}
fetchUrlTitle={config.editor.fetchUrlTitle}
enableTableEditor={config.editor.enableTableEditor}
onChange={(e) => this.handleCodeChange(index)(e)}
ref={'code-' + index}
/>
@@ -744,6 +759,7 @@ class SnippetNoteDetail extends React.Component {
<TagSelect
ref='tags'
value={this.state.note.tags}
data={data}
onChange={(e) => this.handleChange(e)}
/>
</div>

View File

@@ -169,4 +169,21 @@ body[data-theme="monokai"]
.tabList
background-color $ui-monokai-noteDetail-backgroundColor
color $ui-monokai-text-color
color $ui-monokai-text-color
body[data-theme="dracula"]
.root
border-left 1px solid $ui-dracula-borderColor
background-color $ui-dracula-noteDetail-backgroundColor
.body
background-color $ui-dracula-noteDetail-backgroundColor
.body .description textarea
background-color $ui-dracula-noteDetail-backgroundColor
color $ui-dracula-text-color
border 1px solid $ui-dracula-borderColor
.tabList
background-color $ui-dracula-noteDetail-backgroundColor
color $ui-dracula-text-color

View File

@@ -6,71 +6,33 @@ import _ from 'lodash'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import i18n from 'browser/lib/i18n'
import ee from 'browser/main/lib/eventEmitter'
import Autosuggest from 'react-autosuggest'
class TagSelect extends React.Component {
constructor (props) {
super(props)
this.state = {
newTag: ''
newTag: '',
suggestions: []
}
this.addtagHandler = this.handleAddTag.bind(this)
this.handleAddTag = this.handleAddTag.bind(this)
this.onInputBlur = this.onInputBlur.bind(this)
this.onInputChange = this.onInputChange.bind(this)
this.onInputKeyDown = this.onInputKeyDown.bind(this)
this.onSuggestionsClearRequested = this.onSuggestionsClearRequested.bind(this)
this.onSuggestionsFetchRequested = this.onSuggestionsFetchRequested.bind(this)
this.onSuggestionSelected = this.onSuggestionSelected.bind(this)
}
componentDidMount () {
this.value = this.props.value
ee.on('editor:add-tag', this.addtagHandler)
}
componentDidUpdate () {
this.value = this.props.value
}
componentWillUnmount () {
ee.off('editor:add-tag', this.addtagHandler)
}
handleAddTag () {
this.refs.newTag.focus()
}
handleNewTagInputKeyDown (e) {
switch (e.keyCode) {
case 9:
e.preventDefault()
this.submitTag()
break
case 13:
this.submitTag()
break
case 8:
if (this.refs.newTag.value.length === 0) {
this.removeLastTag()
}
}
}
handleNewTagBlur (e) {
this.submitTag()
}
removeLastTag () {
this.removeTagByCallback((value) => {
value.pop()
})
}
reset () {
this.setState({
newTag: ''
})
}
submitTag () {
addNewTag (newTag) {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_TAG')
let { value } = this.props
let newTag = this.refs.newTag.value.trim().replace(/ +/g, '_')
newTag = newTag.charAt(0) === '#' ? newTag.substring(1) : newTag
newTag = newTag.trim().replace(/ +/g, '_')
if (newTag.charAt(0) === '#') {
newTag.substring(1)
}
if (newTag.length <= 0) {
this.setState({
@@ -79,6 +41,7 @@ class TagSelect extends React.Component {
return
}
let { value } = this.props
value = _.isArray(value)
? value.slice()
: []
@@ -93,10 +56,41 @@ class TagSelect extends React.Component {
})
}
handleNewTagInputChange (e) {
this.setState({
newTag: this.refs.newTag.value
})
buildSuggestions () {
this.suggestions = _.sortBy(this.props.data.tagNoteMap.map(
(tag, name) => ({
name,
nameLC: name.toLowerCase(),
size: tag.size
})
).filter(
tag => tag.size > 0
), ['name'])
}
componentDidMount () {
this.value = this.props.value
this.buildSuggestions()
ee.on('editor:add-tag', this.handleAddTag)
}
componentDidUpdate () {
this.value = this.props.value
}
componentWillUnmount () {
ee.off('editor:add-tag', this.handleAddTag)
}
handleAddTag () {
this.refs.newTag.input.focus()
}
handleTagLabelClick (tag) {
const { router } = this.context
router.push(`/tags/${tag}`)
}
handleTagRemoveButtonClick (tag) {
@@ -105,6 +99,60 @@ class TagSelect extends React.Component {
}, tag)
}
onInputBlur (e) {
this.submitNewTag()
}
onInputChange (e, { newValue, method }) {
this.setState({
newTag: newValue
})
}
onInputKeyDown (e) {
switch (e.keyCode) {
case 9:
e.preventDefault()
this.submitNewTag()
break
case 13:
this.submitNewTag()
break
case 8:
if (this.state.newTag.length === 0) {
this.removeLastTag()
}
}
}
onSuggestionsClearRequested () {
this.setState({
suggestions: []
})
}
onSuggestionsFetchRequested ({ value }) {
const valueLC = value.toLowerCase()
const suggestions = _.filter(
this.suggestions,
tag => !_.includes(this.value, tag.name) && tag.nameLC.indexOf(valueLC) !== -1
)
this.setState({
suggestions
})
}
onSuggestionSelected (event, { suggestion, suggestionValue }) {
this.addNewTag(suggestionValue)
}
removeLastTag () {
this.removeTagByCallback((value) => {
value.pop()
})
}
removeTagByCallback (callback, tag = null) {
let { value } = this.props
@@ -118,6 +166,18 @@ class TagSelect extends React.Component {
this.props.onChange()
}
reset () {
this.buildSuggestions()
this.setState({
newTag: ''
})
}
submitNewTag () {
this.addNewTag(this.refs.newTag.input.value)
}
render () {
const { value, className } = this.props
@@ -127,7 +187,7 @@ class TagSelect extends React.Component {
<span styleName='tag'
key={tag}
>
<span styleName='tag-label'>#{tag}</span>
<span styleName='tag-label' onClick={(e) => this.handleTagLabelClick(tag)}>#{tag}</span>
<button styleName='tag-removeButton'
onClick={(e) => this.handleTagRemoveButtonClick(tag)}
>
@@ -138,6 +198,8 @@ class TagSelect extends React.Component {
})
: []
const { newTag, suggestions } = this.state
return (
<div className={_.isString(className)
? 'TagSelect ' + className
@@ -146,24 +208,39 @@ class TagSelect extends React.Component {
styleName='root'
>
{tagList}
<input styleName='newTag'
<Autosuggest
ref='newTag'
value={this.state.newTag}
placeholder={i18n.__('Add tag...')}
onChange={(e) => this.handleNewTagInputChange(e)}
onKeyDown={(e) => this.handleNewTagInputKeyDown(e)}
onBlur={(e) => this.handleNewTagBlur(e)}
suggestions={suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
onSuggestionSelected={this.onSuggestionSelected}
getSuggestionValue={suggestion => suggestion.name}
renderSuggestion={suggestion => (
<div>
{suggestion.name}
</div>
)}
inputProps={{
placeholder: i18n.__('Add tag...'),
value: newTag,
onChange: this.onInputChange,
onKeyDown: this.onInputKeyDown,
onBlur: this.onInputBlur
}}
/>
</div>
)
}
}
TagSelect.contextTypes = {
router: PropTypes.shape({})
}
TagSelect.propTypes = {
className: PropTypes.string,
value: PropTypes.arrayOf(PropTypes.string),
onChange: PropTypes.func
}
export default CSSModules(TagSelect, styles)

View File

@@ -39,16 +39,9 @@
.tag-label
font-size 13px
color: $ui-text-color
color $ui-text-color
padding 4px 16px 4px 8px
.newTag
box-sizing border-box
border none
background-color transparent
outline none
padding 0 4px
font-size 13px
cursor pointer
body[data-theme="dark"]
.tag
@@ -62,11 +55,6 @@ body[data-theme="dark"]
.tag-label
color $ui-dark-text-color
.newTag
border-color none
background-color transparent
color $ui-dark-text-color
body[data-theme="solarized-dark"]
.tag
background-color $ui-solarized-dark-tag-backgroundColor
@@ -78,14 +66,9 @@ body[data-theme="solarized-dark"]
.tag-label
color $ui-solarized-dark-text-color
.newTag
border-color none
background-color transparent
color $ui-solarized-dark-text-color
body[data-theme="monokai"]
.tag
background-color $ui-monokai-button-backgroundColor
background-color $ui-monokai-tag-backgroundColor
.tag-removeButton
border-color $ui-button--focus-borderColor
@@ -93,8 +76,14 @@ body[data-theme="monokai"]
.tag-label
color $ui-monokai-text-color
.newTag
border-color none
body[data-theme="dracula"]
.tag
background-color $ui-dracula-tag-backgroundColor
.tag-removeButton
border-color $ui-dracula-button--focus-borderColor
background-color transparent
color $ui-monokai-text-color
.tag-label
color $ui-dracula-borderColor

View File

@@ -59,7 +59,14 @@ body[data-theme="solarized-dark"]
body[data-theme="monokai"]
.control-toggleModeButton
background-color #272822
background-color #373831
.active
background-color #1EC38B
background-color #f92672
box-shadow 2px 0px 7px #222222
body[data-theme="dracula"]
.control-toggleModeButton
background-color #44475a
.active
background-color #bd93f9
box-shadow 2px 0px 7px #222222

View File

@@ -56,7 +56,7 @@ class Main extends React.Component {
init () {
dataApi
.addStorage({
name: 'My Storage',
name: 'My Storage Location',
path: path.join(remote.app.getPath('home'), 'Boostnote')
})
.then(data => {
@@ -141,7 +141,7 @@ class Main extends React.Component {
componentDidMount () {
const { dispatch, config } = this.props
const supportedThemes = ['dark', 'white', 'solarized-dark', 'monokai']
const supportedThemes = ['dark', 'white', 'solarized-dark', 'monokai', 'dracula']
if (supportedThemes.indexOf(config.ui.theme) !== -1) {
document.body.setAttribute('data-theme', config.ui.theme)
@@ -297,7 +297,7 @@ class Main extends React.Component {
onMouseUp={e => this.handleMouseUp(e)}
>
<SideNav
{..._.pick(this.props, ['dispatch', 'data', 'config', 'location'])}
{..._.pick(this.props, ['dispatch', 'data', 'config', 'params', 'location'])}
width={this.state.navWidth}
/>
{!config.isSideNavFolded &&

View File

@@ -79,3 +79,7 @@ body[data-theme="solarized-dark"]
body[data-theme="monokai"]
.root, .root--expanded
background-color $ui-monokai-noteList-backgroundColor
body[data-theme="dracula"]
.root, .root--expanded
background-color $ui-dracula-noteList-backgroundColor

View File

@@ -7,6 +7,7 @@ import modal from 'browser/main/lib/modal'
import NewNoteModal from 'browser/main/modals/NewNoteModal'
import eventEmitter from 'browser/main/lib/eventEmitter'
import i18n from 'browser/lib/i18n'
import { createMarkdownNote, createSnippetNote } from 'browser/lib/newNote'
const { remote } = require('electron')
const { dialog } = remote
@@ -34,15 +35,22 @@ class NewNoteButton extends React.Component {
}
handleNewNoteButtonClick (e) {
const { location, dispatch } = this.props
const { location, dispatch, config } = this.props
const { storage, folder } = this.resolveTargetFolder()
modal.open(NewNoteModal, {
storage: storage.key,
folder: folder.key,
dispatch,
location
})
if (config.ui.defaultNote === 'MARKDOWN_NOTE') {
createMarkdownNote(storage.key, folder.key, dispatch, location)
} else if (config.ui.defaultNote === 'SNIPPET_NOTE') {
createSnippetNote(storage.key, folder.key, dispatch, location, config)
} else {
modal.open(NewNoteModal, {
storage: storage.key,
folder: folder.key,
dispatch,
location,
config
})
}
}
resolveTargetFolder () {

View File

@@ -84,7 +84,7 @@ body[data-theme="dark"]
color $ui-dark-inactive-text-color
&:hover
color $ui-dark-text-color
.control-button--active
color $ui-dark-text-color
&:active
@@ -109,7 +109,7 @@ body[data-theme="solarized-dark"]
color $ui-solarized-dark-inactive-text-color
&:hover
color $ui-solarized-dark-text-color
.control-button--active
color $ui-solarized-dark-text-color
&:active
@@ -138,3 +138,27 @@ body[data-theme="monokai"]
color $ui-monokai-text-color
&:active
color $ui-monokai-text-color
body[data-theme="dracula"]
.root
border-color $ui-dracula-borderColor
background-color $ui-dracula-noteList-backgroundColor
.control
background-color $ui-dracula-noteList-backgroundColor
border-color $ui-dracula-borderColor
.control-sortBy-select
&:hover
transition 0.2s
color $ui-dracula-text-color
.control-button
color $ui-dracula-inactive-text-color
&:hover
color $ui-dracula-text-color
.control-button--active
color $ui-dracula-text-color
&:active
color $ui-dracula-text-color

View File

@@ -80,6 +80,7 @@ class NoteList extends React.Component {
this.getViewType = this.getViewType.bind(this)
this.restoreNote = this.restoreNote.bind(this)
this.copyNoteLink = this.copyNoteLink.bind(this)
this.navigate = this.navigate.bind(this)
// TODO: not Selected noteKeys but SelectedNote(for reusing)
this.state = {
@@ -98,6 +99,7 @@ class NoteList extends React.Component {
ee.on('list:isMarkdownNote', this.alertIfSnippetHandler)
ee.on('import:file', this.importFromFileHandler)
ee.on('list:jump', this.jumpNoteByHash)
ee.on('list:navigate', this.navigate)
}
componentWillReceiveProps (nextProps) {
@@ -517,7 +519,7 @@ class NoteList extends React.Component {
click: this.cloneNote.bind(this)
}, {
label: copyNoteLink,
click: this.copyNoteLink(note)
click: this.copyNoteLink.bind(this, note)
})
if (note.type === 'MARKDOWN_NOTE') {
if (note.blog && note.blog.blogLink && note.blog.blogId) {
@@ -687,6 +689,16 @@ class NoteList extends React.Component {
return copy(noteLink)
}
navigate (sender, pathname) {
const { router } = this.context
router.push({
pathname,
query: {
// key: noteKey
}
})
}
save (note) {
const { dispatch } = this.props
dataApi

View File

@@ -19,7 +19,7 @@
text-align center
.top-menu-label
margin-left 5px
overflow ellipsis
@@ -122,3 +122,8 @@ body[data-theme="monokai"]
.root, .root--folded
background-color $ui-monokai-backgroundColor
border-right 1px solid $ui-monokai-borderColor
body[data-theme="dracula"]
.root, .root--folded
background-color $ui-dracula-backgroundColor
border-right 1px solid $ui-dracula-borderColor

View File

@@ -38,6 +38,22 @@ class StorageItem extends React.Component {
{
type: 'separator'
},
{
label: i18n.__('Export Storage'),
submenu: [
{
label: i18n.__('Export as txt'),
click: (e) => this.handleExportStorageClick(e, 'txt')
},
{
label: i18n.__('Export as md'),
click: (e) => this.handleExportStorageClick(e, 'md')
}
]
},
{
type: 'separator'
},
{
label: i18n.__('Unlink Storage'),
click: (e) => this.handleUnlinkStorageClick(e)
@@ -68,6 +84,30 @@ class StorageItem extends React.Component {
}
}
handleExportStorageClick (e, fileType) {
const options = {
properties: ['openDirectory', 'createDirectory'],
buttonLabel: i18n.__('Select directory'),
title: i18n.__('Select a folder to export the files to'),
multiSelections: false
}
dialog.showOpenDialog(remote.getCurrentWindow(), options,
(paths) => {
if (paths && paths.length === 1) {
const { storage, dispatch } = this.props
dataApi
.exportStorage(storage.key, fileType, paths[0])
.then(data => {
dispatch({
type: 'EXPORT_STORAGE',
storage: data.storage,
fileType: data.fileType
})
})
}
})
}
handleToggleButtonClick (e) {
const { storage, dispatch } = this.props
const isOpen = !this.state.isOpen

View File

@@ -18,6 +18,7 @@ import TagButton from './TagButton'
import {SortableContainer} from 'react-sortable-hoc'
import i18n from 'browser/lib/i18n'
import context from 'browser/lib/context'
import { remote } from 'electron'
class SideNav extends React.Component {
// TODO: should not use electron stuff v0.7
@@ -30,6 +31,52 @@ class SideNav extends React.Component {
EventEmitter.off('side:preferences', this.handleMenuButtonClick)
}
deleteTag (tag) {
const selectedButton = remote.dialog.showMessageBox(remote.getCurrentWindow(), {
ype: 'warning',
message: i18n.__('Confirm tag deletion'),
detail: i18n.__('This will permanently remove this tag.'),
buttons: [i18n.__('Confirm'), i18n.__('Cancel')]
})
if (selectedButton === 0) {
const { data, dispatch, location, params } = this.props
const notes = data.noteMap
.map(note => note)
.filter(note => note.tags.indexOf(tag) !== -1)
.map(note => {
note = Object.assign({}, note)
note.tags = note.tags.slice()
note.tags.splice(note.tags.indexOf(tag), 1)
return note
})
Promise
.all(notes.map(note => dataApi.updateNote(note.storage, note.key, note)))
.then(updatedNotes => {
updatedNotes.forEach(note => {
dispatch({
type: 'UPDATE_NOTE',
note
})
})
if (location.pathname.match('/tags')) {
const tags = params.tagname.split(' ')
const index = tags.indexOf(tag)
if (index !== -1) {
tags.splice(index, 1)
this.context.router.push(`/tags/${tags.map(tag => encodeURIComponent(tag)).join(' ')}`)
}
}
})
}
}
handleMenuButtonClick (e) {
openModal(PreferencesModal)
}
@@ -44,6 +91,17 @@ class SideNav extends React.Component {
router.push('/starred')
}
handleTagContextMenu (e, tag) {
const menu = []
menu.push({
label: i18n.__('Delete Tag'),
click: this.deleteTag.bind(this, tag)
})
context.popup(menu)
}
handleToggleButtonClick (e) {
const { dispatch, config } = this.props
@@ -165,6 +223,7 @@ class SideNav extends React.Component {
name={tag.name}
handleClickTagListItem={this.handleClickTagListItem.bind(this)}
handleClickNarrowToTag={this.handleClickNarrowToTag.bind(this)}
handleContextMenu={this.handleTagContextMenu.bind(this)}
isActive={this.getTagActive(location.pathname, tag.name)}
isRelated={tag.related}
key={tag.name}
@@ -198,12 +257,12 @@ class SideNav extends React.Component {
const tags = pathSegments[pathSegments.length - 1]
return (tags === 'alltags')
? []
: tags.split(' ')
: tags.split(' ').map(tag => decodeURIComponent(tag))
}
handleClickTagListItem (name) {
const { router } = this.context
router.push(`/tags/${name}`)
router.push(`/tags/${encodeURIComponent(name)}`)
}
handleSortTagsByChange (e) {
@@ -230,7 +289,7 @@ class SideNav extends React.Component {
} else {
listOfTags.push(tag)
}
router.push(`/tags/${listOfTags.join(' ')}`)
router.push(`/tags/${listOfTags.map(tag => encodeURIComponent(tag)).join(' ')}`)
}
emptyTrash (entries) {

View File

@@ -34,7 +34,7 @@
color $ui-active-color
span
margin-left 5px
.update
navButtonColor()
height 24px
@@ -80,3 +80,14 @@ body[data-theme="monokai"]
color $ui-monokai-active-color
&:active
color $ui-monokai-active-color
body[data-theme="dracula"]
navButtonColor()
.zoom
border-color $ui-dark-borderColor
color $ui-dracula-text-color
&:hover
transition 0.15s
color $ui-dracula-active-color
&:active
color $ui-dracula-active-color

View File

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

View File

@@ -18,6 +18,9 @@ body
::-webkit-scrollbar
width 12px
::-webkit-scrollbar-corner
background-color: transparent;
::-webkit-scrollbar-thumb
background-color rgba(0, 0, 0, 0.15)
@@ -132,6 +135,15 @@ body[data-theme="dark"]
.CodeMirror-foldgutter-folded:after
content: "\25B8"
.CodeMirror-hover
padding 2px 4px 0 4px
position absolute
z-index 99
.CodeMirror-hyperlink
cursor pointer
.sortableItemHelper
z-index modalZIndex + 5
@@ -153,6 +165,17 @@ body[data-theme="monokai"]
.sortableItemHelper
color: $ui-monokai-text-color
body[data-theme="dracula"]
::-webkit-scrollbar-thumb
background-color rgba(0, 0, 0, 0.3)
.ModalBase
.modalBack
background-color $ui-dracula-backgroundColor
.sortableItemHelper
color: $ui-dracula-text-color
body[data-theme="default"]
.SideNav ::-webkit-scrollbar-thumb
background-color rgba(255, 255, 255, 0.3)
@import '../styles/Detail/TagSelect.styl'

View File

@@ -24,7 +24,7 @@ export const DEFAULT_CONFIG = {
amaEnabled: true,
hotkey: {
toggleMain: OSX ? 'Command + Alt + L' : 'Super + Alt + E',
toggleMode: OSX ? 'Command + M' : 'Ctrl + M'
toggleMode: OSX ? 'Command + Option + M' : 'Ctrl + M'
},
ui: {
language: 'en',
@@ -46,7 +46,10 @@ export const DEFAULT_CONFIG = {
switchPreview: 'BLUR', // Available value: RIGHTCLICK, BLUR
scrollPastEnd: false,
type: 'SPLIT',
fetchUrlTitle: true
fetchUrlTitle: true,
enableTableEditor: false,
enableFrontMatterTitle: true,
frontMatterTitleField: 'title'
},
preview: {
fontSize: '14',
@@ -64,7 +67,8 @@ export const DEFAULT_CONFIG = {
smartArrows: false,
allowCustomCSS: false,
customCSS: '',
sanitize: 'STRICT' // 'STRICT', 'ALLOW_STYLES', 'NONE'
sanitize: 'STRICT', // 'STRICT', 'ALLOW_STYLES', 'NONE'
lineThroughCheckbox: true
},
blog: {
type: 'wordpress', // Available value: wordpress, add more types in the future plz
@@ -146,6 +150,8 @@ function set (updates) {
document.body.setAttribute('data-theme', 'solarized-dark')
} else if (newConfig.ui.theme === 'monokai') {
document.body.setAttribute('data-theme', 'monokai')
} else if (newConfig.ui.theme === 'dracula') {
document.body.setAttribute('data-theme', 'dracula')
} else {
document.body.setAttribute('data-theme', 'default')
}
@@ -194,6 +200,7 @@ function rewriteHotkey (config) {
const keys = [...Object.keys(config.hotkey)]
keys.forEach(key => {
config.hotkey[key] = config.hotkey[key].replace(/Cmd/g, 'Command')
config.hotkey[key] = config.hotkey[key].replace(/Opt/g, 'Alt')
})
return config
}

View File

@@ -11,6 +11,118 @@ import i18n from 'browser/lib/i18n'
const STORAGE_FOLDER_PLACEHOLDER = ':storage'
const DESTINATION_FOLDER = 'attachments'
const PATH_SEPARATORS = escapeStringRegexp(path.posix.sep) + escapeStringRegexp(path.win32.sep)
/**
* @description
* Create a Image element to get the real size of image.
* @param {File} file the File object dropped.
* @returns {Promise<Image>} Image element created
*/
function getImage (file) {
return new Promise((resolve) => {
const reader = new FileReader()
const img = new Image()
img.onload = () => resolve(img)
reader.onload = e => {
img.src = e.target.result
}
reader.readAsDataURL(file)
})
}
/**
* @description
* Get the orientation info from iamges's EXIF data.
* case 1: The 0th row is at the visual top of the image, and the 0th column is the visual left-hand side.
* case 2: The 0th row is at the visual top of the image, and the 0th column is the visual right-hand side.
* case 3: The 0th row is at the visual bottom of the image, and the 0th column is the visual right-hand side.
* case 4: The 0th row is at the visual bottom of the image, and the 0th column is the visual left-hand side.
* case 5: The 0th row is the visual left-hand side of the image, and the 0th column is the visual top.
* case 6: The 0th row is the visual right-hand side of the image, and the 0th column is the visual top.
* case 7: The 0th row is the visual right-hand side of the image, and the 0th column is the visual bottom.
* case 8: The 0th row is the visual left-hand side of the image, and the 0th column is the visual bottom.
* Other: reserved
* ref: http://sylvana.net/jpegcrop/exif_orientation.html
* @param {File} file the File object dropped.
* @returns {Promise<Number>} Orientation info
*/
function getOrientation (file) {
const getData = arrayBuffer => {
const view = new DataView(arrayBuffer)
// Not start with SOI(Start of image) Marker return fail value
if (view.getUint16(0, false) !== 0xFFD8) return -2
const length = view.byteLength
let offset = 2
while (offset < length) {
const marker = view.getUint16(offset, false)
offset += 2
// Loop and seed for APP1 Marker
if (marker === 0xFFE1) {
// return fail value if it isn't EXIF data
if (view.getUint32(offset += 2, false) !== 0x45786966) {
return -1
}
// Read TIFF header,
// First 2bytes defines byte align of TIFF data.
// If it is 0x4949="II", it means "Intel" type byte align.
// If it is 0x4d4d="MM", it means "Motorola" type byte align
const little = view.getUint16(offset += 6, false) === 0x4949
offset += view.getUint32(offset + 4, little)
const tags = view.getUint16(offset, little) // Get TAG number
offset += 2
for (let i = 0; i < tags; i++) {
// Loop to find Orientation TAG and return the value
if (view.getUint16(offset + (i * 12), little) === 0x0112) {
return view.getUint16(offset + (i * 12) + 8, little)
}
}
} else if ((marker & 0xFF00) !== 0xFF00) { // If not start with 0xFF, not a Marker
break
} else {
offset += view.getUint16(offset, false)
}
}
return -1
}
return new Promise((resolve) => {
const reader = new FileReader()
reader.onload = event => resolve(getData(event.target.result))
reader.readAsArrayBuffer(file.slice(0, 64 * 1024))
})
}
/**
* @description
* Rotate image file to correct direction.
* Create a canvas and draw the image with correct direction, then export to base64 format.
* @param {*} file the File object dropped.
* @return {String} Base64 encoded image.
*/
function fixRotate (file) {
return Promise.all([getImage(file), getOrientation(file)])
.then(([img, orientation]) => {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
if (orientation > 4 && orientation < 9) {
canvas.width = img.height
canvas.height = img.width
} else {
canvas.width = img.width
canvas.height = img.height
}
switch (orientation) {
case 2: ctx.transform(-1, 0, 0, 1, img.width, 0); break
case 3: ctx.transform(-1, 0, 0, -1, img.width, img.height); break
case 4: ctx.transform(1, 0, 0, -1, 0, img.height); break
case 5: ctx.transform(0, 1, 1, 0, 0, 0); break
case 6: ctx.transform(0, 1, -1, 0, img.height, 0); break
case 7: ctx.transform(0, -1, -1, 0, img.height, img.width); break
case 8: ctx.transform(0, -1, 1, 0, 0, img.width); break
default: break
}
ctx.drawImage(img, 0, 0)
return canvas.toDataURL()
})
}
/**
* @description
@@ -38,26 +150,34 @@ function copyAttachment (sourceFilePath, storageKey, noteKey, useRandomName = tr
}
try {
if (!fs.existsSync(sourceFilePath)) {
reject('source file does not exist')
const isBase64 = typeof sourceFilePath === 'object' && sourceFilePath.type === 'base64'
if (!fs.existsSync(sourceFilePath) && !isBase64) {
return reject('source file does not exist')
}
const targetStorage = findStorage.findStorage(storageKey)
const inputFileStream = fs.createReadStream(sourceFilePath)
let destinationName
if (useRandomName) {
destinationName = `${uniqueSlug()}${path.extname(sourceFilePath)}`
destinationName = `${uniqueSlug()}${path.extname(sourceFilePath.sourceFilePath || sourceFilePath)}`
} else {
destinationName = path.basename(sourceFilePath)
destinationName = path.basename(sourceFilePath.sourceFilePath || sourceFilePath)
}
const destinationDir = path.join(targetStorage.path, DESTINATION_FOLDER, noteKey)
createAttachmentDestinationFolder(targetStorage.path, noteKey)
const outputFile = fs.createWriteStream(path.join(destinationDir, destinationName))
inputFileStream.pipe(outputFile)
inputFileStream.on('end', () => {
resolve(destinationName)
})
if (isBase64) {
const base64Data = sourceFilePath.data.replace(/^data:image\/\w+;base64,/, '')
const dataBuffer = new Buffer(base64Data, 'base64')
outputFile.write(dataBuffer, () => {
resolve(destinationName)
})
} else {
const inputFileStream = fs.createReadStream(sourceFilePath)
inputFileStream.pipe(outputFile)
inputFileStream.on('end', () => {
resolve(destinationName)
})
}
} catch (e) {
return reject(e)
}
@@ -137,10 +257,17 @@ function handleAttachmentDrop (codeEditor, storageKey, noteKey, dropEvent) {
const filePath = file.path
const originalFileName = path.basename(filePath)
const fileType = file['type']
copyAttachment(filePath, storageKey, noteKey).then((fileName) => {
const showPreview = fileType.startsWith('image')
const imageMd = generateAttachmentMarkdown(originalFileName, path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName), showPreview)
const isImage = fileType.startsWith('image')
let promise
if (isImage) {
promise = fixRotate(file).then(base64data => {
return copyAttachment({type: 'base64', data: base64data, sourceFilePath: filePath}, storageKey, noteKey)
})
} else {
promise = copyAttachment(filePath, storageKey, noteKey)
}
promise.then((fileName) => {
const imageMd = generateAttachmentMarkdown(originalFileName, path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName), isImage)
codeEditor.insertAttachmentMd(imageMd)
})
}

View File

@@ -0,0 +1,63 @@
import { findStorage } from 'browser/lib/findStorage'
import resolveStorageData from './resolveStorageData'
import resolveStorageNotes from './resolveStorageNotes'
import filenamify from 'filenamify'
import * as path from 'path'
import * as fs from 'fs'
/**
* @param {String} storageKey
* @param {String} fileType
* @param {String} exportDir
*
* @return {Object}
* ```
* {
* storage: Object,
* fileType: String,
* exportDir: String
* }
* ```
*/
function exportStorage (storageKey, fileType, exportDir) {
let targetStorage
try {
targetStorage = findStorage(storageKey)
} catch (e) {
return Promise.reject(e)
}
return resolveStorageData(targetStorage)
.then(storage => (
resolveStorageNotes(storage).then(notes => ({storage, notes}))
))
.then(function exportNotes (data) {
const { storage, notes } = data
const folderNamesMapping = {}
storage.folders.forEach(folder => {
const folderExportedDir = path.join(exportDir, filenamify(folder.name, {replacement: '_'}))
folderNamesMapping[folder.key] = folderExportedDir
// make sure directory exists
try {
fs.mkdirSync(folderExportedDir)
} catch (e) {}
})
notes
.filter(note => !note.isTrashed && note.type === 'MARKDOWN_NOTE')
.forEach(markdownNote => {
const folderExportedDir = folderNamesMapping[markdownNote.folder]
const snippetName = `${filenamify(markdownNote.title, {replacement: '_'})}.${fileType}`
const notePath = path.join(folderExportedDir, snippetName)
fs.writeFileSync(notePath, markdownNote.content)
})
return {
storage,
fileType,
exportDir
}
})
}
module.exports = exportStorage

View File

@@ -9,6 +9,7 @@ const dataApi = {
deleteFolder: require('./deleteFolder'),
reorderFolder: require('./reorderFolder'),
exportFolder: require('./exportFolder'),
exportStorage: require('./exportStorage'),
createNote: require('./createNote'),
updateNote: require('./updateNote'),
deleteNote: require('./deleteNote'),

View File

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

View File

@@ -1,12 +1,9 @@
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './NewNoteModal.styl'
import dataApi from 'browser/main/lib/dataApi'
import { hashHistory } from 'react-router'
import ee from 'browser/main/lib/eventEmitter'
import ModalEscButton from 'browser/components/ModalEscButton'
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
import i18n from 'browser/lib/i18n'
import { createMarkdownNote, createSnippetNote } from 'browser/lib/newNote'
class NewNoteModal extends React.Component {
constructor (props) {
@@ -24,31 +21,10 @@ class NewNoteModal extends React.Component {
}
handleMarkdownNoteButtonClick (e) {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_MARKDOWN')
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
const { storage, folder, dispatch, location } = this.props
dataApi
.createNote(storage, {
type: 'MARKDOWN_NOTE',
folder: folder,
title: '',
content: ''
})
.then(note => {
const noteHash = note.key
dispatch({
type: 'UPDATE_NOTE',
note: note
})
hashHistory.push({
pathname: location.pathname,
query: { key: noteHash }
})
ee.emit('list:jump', noteHash)
ee.emit('detail:focus')
setTimeout(this.props.close, 200)
})
createMarkdownNote(storage, folder, dispatch, location).then(() => {
setTimeout(this.props.close, 200)
})
}
handleMarkdownNoteButtonKeyDown (e) {
@@ -59,38 +35,10 @@ class NewNoteModal extends React.Component {
}
handleSnippetNoteButtonClick (e) {
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_SNIPPET')
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('ADD_ALLNOTE')
const { storage, folder, dispatch, location } = this.props
dataApi
.createNote(storage, {
type: 'SNIPPET_NOTE',
folder: folder,
title: '',
description: '',
snippets: [
{
name: '',
mode: 'text',
content: ''
}
]
})
.then(note => {
const noteHash = note.key
dispatch({
type: 'UPDATE_NOTE',
note: note
})
hashHistory.push({
pathname: location.pathname,
query: { key: noteHash }
})
ee.emit('list:jump', noteHash)
ee.emit('detail:focus')
setTimeout(this.props.close, 200)
})
const { storage, folder, dispatch, location, config } = this.props
createSnippetNote(storage, folder, dispatch, location, config).then(() => {
setTimeout(this.props.close, 200)
})
}
handleSnippetNoteButtonKeyDown (e) {

View File

@@ -97,3 +97,20 @@ body[data-theme="monokai"]
.description
color $ui-monokai-text-color
body[data-theme="dracula"]
.root
background-color transparent
.header
color $ui-dracula-text-color
.control-button
border-color $ui-dracula-borderColor
color $ui-dracula-text-color
background-color transparent
&:focus
colorDraculaPrimaryButton()
.description
color $ui-dracula-text-color

View File

@@ -43,7 +43,7 @@ class Blog extends React.Component {
this.handleSettingError = (err) => {
this.setState({BlogAlert: {
type: 'error',
message: err.message != null ? err.message : i18n.__('Error occurs!')
message: err.message != null ? err.message : i18n.__('An error occurred!')
}})
}
this.oldBlog = this.state.config.blog
@@ -70,7 +70,7 @@ class Blog extends React.Component {
this.props.haveToSave({
tab: 'Blog',
type: 'warning',
message: i18n.__('You have to save!')
message: i18n.__('Unsaved Changes!')
})
}
}

View File

@@ -24,7 +24,7 @@
line-height 30px
.group-section-label
width 150px
width 200px
text-align left
margin-right 10px
font-size 14px
@@ -68,9 +68,9 @@
:global
.alert
display inline-block
position absolute
top 60px
right 15px
position fixed
top 130px
right 100px
font-size 14px
.success
color #1EC38B
@@ -127,7 +127,7 @@ colorDarkControl()
border-color $ui-dark-borderColor
background-color $ui-dark-backgroundColor
color $ui-dark-text-color
colorSolarizedDarkControl()
border none
background-color $ui-solarized-dark-button-backgroundColor
@@ -138,6 +138,10 @@ colorMonokaiControl()
background-color $ui-monokai-button-backgroundColor
color $ui-monokai-text-color
colorDraculaControl()
border none
background-color $ui-dracula-button-backgroundColor
color $ui-dracula-text-color
body[data-theme="dark"]
.root
@@ -165,27 +169,27 @@ body[data-theme="dark"]
.group-section-control
select, .group-section-control-input
colorDarkControl()
body[data-theme="solarized-dark"]
.root
color $ui-solarized-dark-text-color
.group-header
color $ui-solarized-dark-text-color
border-color $ui-solarized-dark-borderColor
border-color $ui-solarized-dark-borderColor
.group-header2
color $ui-solarized-dark-text-color
.group-section-control-input
border-color $ui-solarized-dark-borderColor
border-color $ui-solarized-dark-borderColor
.group-control
border-color $ui-solarized-dark-borderColor
border-color $ui-solarized-dark-borderColor
.group-control-leftButton
colorDarkDefaultButton()
border-color $ui-solarized-dark-borderColor
border-color $ui-solarized-dark-borderColor
.group-control-rightButton
colorSolarizedDarkPrimaryButton()
.group-hint
@@ -200,19 +204,19 @@ body[data-theme="monokai"]
.group-header
color $ui-monokai-text-color
border-color $ui-monokai-borderColor
border-color $ui-monokai-borderColor
.group-header2
color $ui-monokai-text-color
.group-section-control-input
border-color $ui-monokai-borderColor
border-color $ui-monokai-borderColor
.group-control
border-color $ui-monokai-borderColor
border-color $ui-monokai-borderColor
.group-control-leftButton
colorDarkDefaultButton()
border-color $ui-monokai-borderColor
border-color $ui-monokai-borderColor
.group-control-rightButton
colorMonokaiPrimaryButton()
.group-hint
@@ -220,3 +224,30 @@ body[data-theme="monokai"]
.group-section-control
select, .group-section-control-input
colorMonokaiControl()
body[data-theme="dracula"]
.root
color $ui-dracula-text-color
.group-header
color $ui-dracula-text-color
border-color $ui-dracula-borderColor
.group-header2
color $ui-dracula-text-color
.group-section-control-input
border-color $ui-dracula-borderColor
.group-control
border-color $ui-dracula-borderColor
.group-control-leftButton
colorDarkDefaultButton()
border-color $ui-dracula-borderColor
.group-control-rightButton
colorDraculaPrimaryButton()
.group-hint
colorDraculaControl()
.group-section-control
select, .group-section-control-input
colorDraculaControl()

View File

@@ -23,21 +23,29 @@ class Crowdfunding extends React.Component {
return (
<div styleName='root'>
<div styleName='header'>{i18n.__('Crowdfunding')}</div>
<p>{i18n.__('Dear everyone,')}</p>
<br />
<p>{i18n.__('Thank you for using Boostnote!')}</p>
<p>{i18n.__('Boostnote is used in about 200 different countries and regions by an awesome community of developers.')}</p>
<br />
<p>{i18n.__('To continue supporting this growth, and to satisfy community expectations,')}</p>
<p>{i18n.__('we would like to invest more time and resources in this project.')}</p>
<p>{i18n.__('We launched IssueHunt which is an issue-based crowdfunding / sourcing platform for open source projects.')}</p>
<p>{i18n.__('Anyone can put a bounty on not only a bug but also on OSS feature requests listed on IssueHunt. Collected funds will be distributed to project owners and contributors.')}</p>
<br />
<p>{i18n.__('If you like this project and see its potential, you can help by supporting us on OpenCollective!')}</p>
<p>{i18n.__('### Sustainable Open Source Ecosystem')}</p>
<p>{i18n.__('We discussed about open-source ecosystem and IssueHunt concept with the Boostnote team repeatedly. We actually also discussed with Matz who father of Ruby.')}</p>
<p>{i18n.__('The original reason why we made IssueHunt was to reward our contributors of Boostnote project. Weve got tons of Github stars and hundred of contributors in two years.')}</p>
<p>{i18n.__('We thought that it will be nice if we can pay reward for our contributors.')}</p>
<br />
<p>{i18n.__('Thanks,')}</p>
<p>{i18n.__('Boostnote maintainers')}</p>
<p>{i18n.__('### We believe Meritocracy')}</p>
<p>{i18n.__('We think developers who has skill and did great things must be rewarded properly.')}</p>
<p>{i18n.__('OSS projects are used in everywhere on the internet, but no matter how they great, most of owners of those projects need to have another job to sustain their living.')}</p>
<p>{i18n.__('It sometimes looks like exploitation.')}</p>
<p>{i18n.__('Weve realized IssueHunt could enhance sustainability of open-source ecosystem.')}</p>
<br />
<p>{i18n.__('As same as issues of Boostnote are already funded on IssueHunt, your open-source projects can be also started funding from now.')}</p>
<br />
<p>{i18n.__('Thank you,')}</p>
<p>{i18n.__('The Boostnote Team')}</p>
<br />
<button styleName='cf-link'>
<a href='https://opencollective.com/boostnoteio' onClick={(e) => this.handleLinkClick(e)}>{i18n.__('Support via OpenCollective')}</a>
<a href='http://bit.ly/issuehunt-from-boostnote-app' onClick={(e) => this.handleLinkClick(e)}>{i18n.__('See IssueHunt')}</a>
</button>
</div>
)

View File

@@ -29,7 +29,7 @@ p
body[data-theme="dark"]
p
color $ui-dark-text-color
body[data-theme="solarized-dark"]
.root
color $ui-solarized-dark-text-color
@@ -41,3 +41,9 @@ body[data-theme="monokai"]
color $ui-monokai-text-color
p
color $ui-monokai-text-color
body[data-theme="dracula"]
.root
color $ui-dracula-text-color
p
color $ui-dracula-text-color

View File

@@ -2,6 +2,7 @@
height 35px
box-sizing border-box
padding 2.5px 15px
display flex
&:hover
background-color darken(white, 3%)
@@ -18,7 +19,10 @@
border-left solid 2px transparent
padding 0 10px
line-height 30px
float left
flex 1
white-space nowrap
text-overflow ellipsis
overflow hidden
.folderItem-left-danger
color $danger-color
font-weight bold
@@ -52,7 +56,8 @@
outline none
.folderItem-right
float right
-webkit-box-flex: 1
white-space nowrap
.folderItem-right-button
vertical-align middle
@@ -149,3 +154,26 @@ body[data-theme="monokai"]
.folderItem-right-dangerButton
colorMonokaiPrimaryButton()
body[data-theme="dracula"]
.folderItem
&:hover
background-color $ui-dracula-button-backgroundColor
.folderItem-left-danger
color $danger-color
.folderItem-left-key
color $ui-dark-inactive-text-color
.folderItem-left-colorButton
colorDraculaPrimaryButton()
.folderItem-right-button
colorDraculaPrimaryButton()
.folderItem-right-confirmButton
colorDraculaPrimaryButton()
.folderItem-right-dangerButton
colorDraculaPrimaryButton()

View File

@@ -30,7 +30,7 @@ class HotkeyTab extends React.Component {
this.handleSettingError = (err) => {
this.setState({keymapAlert: {
type: 'error',
message: err.message != null ? err.message : i18n.__('Error occurs!')
message: err.message != null ? err.message : i18n.__('An error occurred!')
}})
}
this.oldHotkey = this.state.config.hotkey
@@ -79,7 +79,7 @@ class HotkeyTab extends React.Component {
this.props.haveToSave({
tab: 'Hotkey',
type: 'warning',
message: i18n.__('You have to save!')
message: i18n.__('Unsaved Changes!')
})
}
}
@@ -117,7 +117,7 @@ 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-label'>{i18n.__('Toggle Editor Mode')}</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
onChange={(e) => this.handleHotkeyChange(e)}

View File

@@ -84,7 +84,7 @@ class InfoTab extends React.Component {
>{i18n.__('GitHub')}</a>
</li>
<li>
<a href='https://boostlog.io/@junp1234'
<a href='https://medium.com/boostnote'
onClick={(e) => this.handleLinkClick(e)}
>{i18n.__('Blog')}</a>
</li>

View File

@@ -60,7 +60,7 @@
body[data-theme="dark"]
.root
color alpha($tab--dark-text-color, 80%)
body[data-theme="solarized-dark"]
.root
@@ -75,3 +75,10 @@ body[data-theme="monokai"]
.list
a
color $ui-monokai-active-color
body[data-theme="dracula"]
.root
color $ui-dracula-text-color
.list
a
color $ui-dracula-active-color

View File

@@ -90,7 +90,7 @@ body[data-theme="dark"]
background-color $dark-primary-button-background--active
&:hover
color white
body[data-theme="solarized-dark"]
.root
@@ -139,3 +139,27 @@ body[data-theme="monokai"]
background-color $ui-monokai-button--active-backgroundColor
&:hover
color white
body[data-theme="dracula"]
.root
background-color transparent
.top-bar
background-color transparent
border-color $ui-dracula-borderColor
p
color $ui-dracula-text-color
.nav
background-color transparent
border-color $ui-dracula-borderColor
.nav-button
background-color transparent
color $ui-dracula-text-color
&:hover
color $ui-dracula-text-color
.nav-button--active
@extend .nav-button
color $ui-dracula-button--active-color
background-color $ui-dracula-button--active-backgroundColor
&:hover
color #f8f8f2

View File

@@ -55,7 +55,11 @@ class SnippetList extends React.Component {
defineSnippetStyleName (snippet) {
const { currentSnippet } = this.props
if (currentSnippet == null) return
if (currentSnippet == null) {
return 'snippet-item'
}
if (currentSnippet.id === snippet.id) {
return 'snippet-item-selected'
} else {

View File

@@ -6,6 +6,9 @@ import i18n from 'browser/lib/i18n'
import dataApi from 'browser/main/lib/dataApi'
import SnippetList from './SnippetList'
import eventEmitter from 'browser/main/lib/eventEmitter'
import copy from 'copy-to-clipboard'
const path = require('path')
class SnippetTab extends React.Component {
constructor (props) {
@@ -16,6 +19,17 @@ class SnippetTab extends React.Component {
this.changeDelay = null
}
notify (title, options) {
if (global.process.platform === 'win32') {
options.icon = path.join(
'file://',
global.__dirname,
'../../resources/app.png'
)
}
return new window.Notification(title, options)
}
handleSnippetNameOrPrefixChange () {
clearTimeout(this.changeDelay)
this.changeDelay = setTimeout(() => {
@@ -27,12 +41,14 @@ class SnippetTab extends React.Component {
handleSnippetSelect (snippet) {
const { currentSnippet } = this.state
if (currentSnippet === null || currentSnippet.id !== snippet.id) {
dataApi.fetchSnippet(snippet.id).then(changedSnippet => {
// notify the snippet editor to load the content of the new snippet
this.snippetEditor.onSnippetChanged(changedSnippet)
this.setState({currentSnippet: changedSnippet})
})
if (snippet !== null) {
if (currentSnippet === null || currentSnippet.id !== snippet.id) {
dataApi.fetchSnippet(snippet.id).then(changedSnippet => {
// notify the snippet editor to load the content of the new snippet
this.snippetEditor.onSnippetChanged(changedSnippet)
this.setState({currentSnippet: changedSnippet})
})
}
}
}
@@ -54,6 +70,17 @@ class SnippetTab extends React.Component {
}
}
handleCopySnippet (e) {
const showCopyNotification = this.props.config.ui.showCopyNotification
copy(this.state.currentSnippet.content)
if (showCopyNotification) {
this.notify('Saved to Clipboard!', {
body: 'Paste it wherever you want!',
silent: true
})
}
}
render () {
const { config, storageKey } = this.props
const { currentSnippet } = this.state
@@ -70,6 +97,13 @@ class SnippetTab extends React.Component {
onSnippetDeleted={this.handleDeleteSnippet.bind(this)}
currentSnippet={currentSnippet} />
<div styleName='snippet-detail' style={{visibility: currentSnippet ? 'visible' : 'hidden'}}>
<div styleName='group-section'>
<div styleName='group-section-control'>
<button styleName='group-control-rightButton'
onClick={e => this.handleCopySnippet(e)}>{i18n.__('Copy')}
</button>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Snippet name')}</div>
<div styleName='group-section-control'>

View File

@@ -196,3 +196,19 @@ body[data-theme="monokai"]
color white
.group-control-button
colorMonokaiPrimaryButton()
body[data-theme="dracula"]
.snippets
background $ui-dracula-backgroundColor
.snippet-item
color #f8f8f2
&::after
background $ui-dracula-borderColor
&:hover
background darken($ui-dracula-backgroundColor, 5)
.snippet-item-selected
background darken($ui-dracula-backgroundColor, 5)
.snippet-detail
color #f8f8f2
.group-control-button
colorDraculaPrimaryButton()

View File

@@ -9,13 +9,17 @@
box-sizing border-box
border-bottom $default-border
margin-bottom 5px
display flex
.header-label
float left
cursor pointer
&:hover
.header-label-editButton
opacity 1
flex 1
white-space nowrap
text-overflow ellipsis
overflow hidden
.header-label-path
color $ui-inactive-text-color
@@ -38,8 +42,8 @@
outline none
.header-control
float right
-webkit-box-flex: 1
white-space nowrap
.header-control-button
width 30px
height 25px

View File

@@ -70,7 +70,7 @@ class StoragesTab extends React.Component {
})
return (
<div styleName='list'>
<div styleName='header'>{i18n.__('Storages')}</div>
<div styleName='header'>{i18n.__('Storage Locations')}</div>
{storageList.length > 0
? storageList
: <div styleName='list-empty'>{i18n.__('No storage found.')}</div>

View File

@@ -158,7 +158,7 @@ body[data-theme="dark"]
.addStorage-body-control-cancelButton
colorDarkDefaultButton()
border-color $ui-dark-borderColor
body[data-theme="solarized-dark"]
@@ -236,3 +236,41 @@ body[data-theme="monokai"]
.addStorage-body-control-cancelButton
colorDarkDefaultButton()
border-color $ui-monokai-borderColor
body[data-theme="dracula"]
.root
color $ui-dracula-text-color
.folderList-item
border-bottom $ui-dracula-borderColor
.folderList-empty
color $ui-dracula-text-color
.list-empty
color $ui-dracula-text-color
.list-control-addStorageButton
border-color $ui-dracula-button-backgroundColor
background-color $ui-dracula-button-backgroundColor
color $ui-dracula-text-color
.addStorage-header
color $ui-dracula-text-color
border-color $ui-dracula-borderColor
.addStorage-body-section-name-input
border-color $$ui-dracula-borderColor
.addStorage-body-section-type-description
color $ui-dracula-text-color
.addStorage-body-section-path-button
colorPrimaryButton()
.addStorage-body-control
border-color $ui-dracula-borderColor
.addStorage-body-control-createButton
colorDarkPrimaryButton()
.addStorage-body-control-cancelButton
colorDarkDefaultButton()
border-color $ui-dracula-borderColor

View File

@@ -40,7 +40,7 @@ class UiTab extends React.Component {
this.handleSettingError = (err) => {
this.setState({UiAlert: {
type: 'error',
message: err.message != null ? err.message : i18n.__('Error occurs!')
message: err.message != null ? err.message : i18n.__('An error occurred!')
}})
}
ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
@@ -67,6 +67,7 @@ class UiTab extends React.Component {
ui: {
theme: this.refs.uiTheme.value,
language: this.refs.uiLanguage.value,
defaultNote: this.refs.defaultNote.value,
showCopyNotification: this.refs.showCopyNotification.checked,
confirmDeletion: this.refs.confirmDeletion.checked,
showOnlyRelatedTags: this.refs.showOnlyRelatedTags.checked,
@@ -85,8 +86,12 @@ class UiTab extends React.Component {
displayLineNumbers: this.refs.editorDisplayLineNumbers.checked,
switchPreview: this.refs.editorSwitchPreview.value,
keyMap: this.refs.editorKeyMap.value,
snippetDefaultLanguage: this.refs.editorSnippetDefaultLanguage.value,
scrollPastEnd: this.refs.scrollPastEnd.checked,
fetchUrlTitle: this.refs.editorFetchUrlTitle.checked
fetchUrlTitle: this.refs.editorFetchUrlTitle.checked,
enableTableEditor: this.refs.enableTableEditor.checked,
enableFrontMatterTitle: this.refs.enableFrontMatterTitle.checked,
frontMatterTitleField: this.refs.frontMatterTitleField.value
},
preview: {
fontSize: this.refs.previewFontSize.value,
@@ -104,6 +109,7 @@ class UiTab extends React.Component {
smartArrows: this.refs.previewSmartArrows.checked,
sanitize: this.refs.previewSanitize.value,
allowCustomCSS: this.refs.previewAllowCustomCSS.checked,
lineThroughCheckbox: this.refs.lineThroughCheckbox.checked,
customCSS: this.customCSSCM.getCodeMirror().getValue()
}
}
@@ -122,7 +128,7 @@ class UiTab extends React.Component {
this.props.haveToSave({
tab: 'UI',
type: 'warning',
message: i18n.__('You have to save!')
message: i18n.__('Unsaved Changes!')
})
}
})
@@ -172,7 +178,9 @@ class UiTab extends React.Component {
<div styleName='group-header'>{i18n.__('Interface')}</div>
<div styleName='group-section'>
{i18n.__('Interface Theme')}
<div styleName='group-section-label'>
{i18n.__('Interface Theme')}
</div>
<div styleName='group-section-control'>
<select value={config.ui.theme}
onChange={(e) => this.handleUIChange(e)}
@@ -182,13 +190,16 @@ class UiTab extends React.Component {
<option value='white'>{i18n.__('White')}</option>
<option value='solarized-dark'>{i18n.__('Solarized Dark')}</option>
<option value='monokai'>{i18n.__('Monokai')}</option>
<option value='dracula'>{i18n.__('Dracula')}</option>
<option value='dark'>{i18n.__('Dark')}</option>
</select>
</div>
</div>
<div styleName='group-section'>
{i18n.__('Language')}
<div styleName='group-section-label'>
{i18n.__('Language')}
</div>
<div styleName='group-section-control'>
<select value={config.ui.language}
onChange={(e) => this.handleUIChange(e)}
@@ -201,6 +212,22 @@ class UiTab extends React.Component {
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Default New Note')}
</div>
<div styleName='group-section-control'>
<select value={config.ui.defaultNote}
onChange={(e) => this.handleUIChange(e)}
ref='defaultNote'
>
<option value='ALWAYS_ASK'>{i18n.__('Always Ask')}</option>
<option value='MARKDOWN_NOTE'>{i18n.__('Markdown Note')}</option>
<option value='SNIPPET_NOTE'>{i18n.__('Snippet Note')}</option>
</select>
</div>
</div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
@@ -387,6 +414,47 @@ class UiTab extends React.Component {
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Snippet Default Language')}
</div>
<div styleName='group-section-control'>
<select value={config.editor.snippetDefaultLanguage}
ref='editorSnippetDefaultLanguage'
onChange={(e) => this.handleUIChange(e)}
>
{
_.sortBy(CodeMirror.modeInfo.map(mode => mode.name)).map(name => (<option key={name} value={name}>{name}</option>))
}
</select>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Front matter title field')}
</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
ref='frontMatterTitleField'
value={config.editor.frontMatterTitleField}
onChange={(e) => this.handleUIChange(e)}
type='text'
/>
</div>
</div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.editor.enableFrontMatterTitle}
ref='enableFrontMatterTitle'
type='checkbox'
/>&nbsp;
{i18n.__('Extract title from front matter')}
</label>
</div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
@@ -420,6 +488,17 @@ class UiTab extends React.Component {
</label>
</div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.editor.enableTableEditor}
ref='enableTableEditor'
type='checkbox'
/>&nbsp;
{i18n.__('Enable smart table editor')}
</label>
</div>
<div styleName='group-header2'>{i18n.__('Preview')}</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
@@ -448,7 +527,7 @@ class UiTab extends React.Component {
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Code block Theme')}</div>
<div styleName='group-section-label'>{i18n.__('Code Block Theme')}</div>
<div styleName='group-section-control'>
<select value={config.preview.codeBlockTheme}
ref='previewCodeBlockTheme'
@@ -462,6 +541,16 @@ class UiTab extends React.Component {
</select>
</div>
</div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}
checked={this.state.config.preview.lineThroughCheckbox}
ref='lineThroughCheckbox'
type='checkbox'
/>&nbsp;
{i18n.__('Allow line through checkbox')}
</label>
</div>
<div styleName='group-checkBoxSection'>
<label>
<input onChange={(e) => this.handleUIChange(e)}

View File

@@ -216,16 +216,10 @@ function data (state = defaultDataMap(), action) {
return state
}
case 'UPDATE_FOLDER':
state = Object.assign({}, state)
state.storageMap = new Map(state.storageMap)
state.storageMap.set(action.storage.key, action.storage)
return state
case 'REORDER_FOLDER':
state = Object.assign({}, state)
state.storageMap = new Map(state.storageMap)
state.storageMap.set(action.storage.key, action.storage)
return state
case 'EXPORT_FOLDER':
case 'RENAME_STORAGE':
case 'EXPORT_STORAGE':
state = Object.assign({}, state)
state.storageMap = new Map(state.storageMap)
state.storageMap.set(action.storage.key, action.storage)
@@ -355,11 +349,6 @@ function data (state = defaultDataMap(), action) {
})
}
return state
case 'RENAME_STORAGE':
state = Object.assign({}, state)
state.storageMap = new Map(state.storageMap)
state.storageMap.set(action.storage.key, action.storage)
return state
case 'EXPAND_STORAGE':
state = Object.assign({}, state)
state.storageMap = new Map(state.storageMap)

View File

@@ -0,0 +1,126 @@
.TagSelect
.react-autosuggest__input
box-sizing border-box
border none
background-color transparent
outline none
padding 0 4px
font-size 13px
ul
position fixed
z-index 999
box-sizing border-box
list-style none
padding 0
margin 0
border-radius 4px
margin .5rem 0 0
background-color $ui-noteList-backgroundColor
border 1px solid rgba(0,0,0,.3)
box-shadow .05em .2em .6em rgba(0,0,0,.2)
text-shadow none
&:empty,
&[hidden]
display none
&:before
content ""
position absolute
top -7px
left 14px
width 0 height 0
padding 6px
background-color $ui-noteList-backgroundColor
border inherit
border-right 0
border-bottom 0
-webkit-transform rotate(45deg)
transform rotate(45deg)
li
position relative
padding 6px 18px 6px 10px
cursor pointer
li[aria-selected="true"]
background-color alpha($ui-button--active-backgroundColor, 40%)
color $ui-text-color
body[data-theme="dark"]
.TagSelect
.react-autosuggest__input
color $ui-dark-text-color
ul
border-color $ui-dark-borderColor
background-color $ui-dark-noteList-backgroundColor
color $ui-dark-text-color
&:before
background-color $ui-dark-noteList-backgroundColor
li[aria-selected="true"]
background-color $ui-dark-button--active-backgroundColor
color $ui-dark-text-color
body[data-theme="monokai"]
.TagSelect
.react-autosuggest__input
color $ui-monokai-text-color
ul
border-color $ui-monokai-borderColor
background-color $ui-monokai-noteList-backgroundColor
color $ui-monokai-text-color
&:before
background-color $ui-dark-noteList-backgroundColor
li[aria-selected="true"]
background-color $ui-monokai-button-backgroundColor
color $ui-monokai-text-color
body[data-theme="dracula"]
.TagSelect
.react-autosuggest__input
color $ui-dracula-text-color
ul
border-color $ui-dracula-borderColor
background-color $ui-dracula-noteList-backgroundColor
color $ui-dracula-text-color
&:before
background-color $ui-dark-noteList-backgroundColor
li[aria-selected="true"]
background-color $ui-dracula-button-backgroundColor
color $ui-dracula-text-color
body[data-theme="solarized-dark"]
.TagSelect
.react-autosuggest__input
color $ui-solarized-dark-text-color
ul
border-color $ui-solarized-dark-borderColor
background-color $ui-solarized-dark-noteList-backgroundColor
color $ui-solarized-dark-text-color
&:before
background-color $ui-solarized-dark-noteList-backgroundColor
li[aria-selected="true"]
background-color $ui-dark-button--active-backgroundColor
color $ui-solarized-dark-text-color
body[data-theme="white"]
.TagSelect
ul
background-color $ui-white-noteList-backgroundColor
li[aria-selected="true"]
background-color $ui-button--active-backgroundColor

View File

@@ -5,7 +5,7 @@ $danger-color = #c9302c
$danger-lighten-color = lighten(#c9302c, 5%)
// Layouts
$statusBar-height = 22px
$statusBar-height = 28px
$sideNav-width = 200px
$sideNav--folded-width = 44px
$topBar-height = 60px
@@ -128,6 +128,16 @@ colorMonokaiPrimaryButton()
&:active:hover
background-color $dark-primary-button-background--active
colorDraculaPrimaryButton()
color $ui-dracula-text-color
background-color $ui-dracula-button-backgroundColor
border none
&:hover
background-color $ui-dracula-button--active-backgroundColor
&:active
&:active:hover
background-color $ui-dracula-button--active-backgroundColor
// Danger button(Brand color)
$danger-button-background = #c9302c
@@ -369,7 +379,7 @@ $ui-monokai-active-color = #f92672
$ui-monokai-borderColor = #373831
$ui-monokai-tag-backgroundColor = #f92672
$ui-monokai-tag-backgroundColor = #373831
$ui-monokai-button-backgroundColor = #373831
$ui-monokai-button--active-color = white
@@ -377,10 +387,36 @@ $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()
modalMonokai()
position relative
z-index $modal-z-index
width 100%
background-color $ui-monokai-backgroundColor
overflow hidden
border-radius $modal-border-radius
/******* Dracula theme ********/
$ui-dracula-backgroundColor = #282a36
$ui-dracula-noteList-backgroundColor = #282a36
$ui-dracula-noteDetail-backgroundColor = #282a36
$ui-dracula-text-color = #f8f8f2
$ui-dracula-active-color = #bd93f9
$ui-dracula-borderColor = #44475a
$ui-dracula-tag-backgroundColor = #8be9fd
$ui-dracula-button-backgroundColor = #44475a
$ui-dracula-button--active-color = #f8f8f2
$ui-dracula-button--active-backgroundColor = #bd93f9
$ui-dracula-button--hover-backgroundColor = lighten($ui-dracula-backgroundColor, 10%)
$ui-dracula-button--focus-borderColor = lighten(#44475a, 25%)
modalDracula()
position relative
z-index $modal-z-index
width 100%
background-color $ui-dracula-backgroundColor
overflow hidden
border-radius $modal-border-radius