mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-14 02:06:29 +00:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35938c09e8 | ||
|
|
9eaa90c691 | ||
|
|
049835d426 | ||
|
|
af91c40406 | ||
|
|
4940ad6825 | ||
|
|
d02b740300 | ||
|
|
9cb443dc2f | ||
|
|
473b80710d | ||
|
|
2247c0835d | ||
|
|
b7b715ba3d | ||
|
|
6c43fb2325 | ||
|
|
a6fe3c27d4 | ||
|
|
d47ff96b13 | ||
|
|
a0def654bd | ||
|
|
4873b40e49 | ||
|
|
0a758f20a7 | ||
|
|
5e58d457a3 | ||
|
|
0f745361ad | ||
|
|
bf6cae9a0e | ||
|
|
ab640a7676 | ||
|
|
820171e19e | ||
|
|
f1e9d0ab81 | ||
|
|
0646484c83 | ||
|
|
a27b79c213 | ||
|
|
773a9b4b7f | ||
|
|
07b838ef7b | ||
|
|
85217a7171 | ||
|
|
886d7b7227 | ||
|
|
6987b762dd | ||
|
|
f32ac81f84 | ||
|
|
87ea66bb92 | ||
|
|
ff6fd62932 | ||
|
|
76728448ff | ||
|
|
3b7225e0fa | ||
|
|
d6280f4397 | ||
|
|
8df867046f | ||
|
|
331c822816 | ||
|
|
6219173945 | ||
|
|
6207e02e7f | ||
|
|
537ba537dc | ||
|
|
3e919241e6 | ||
|
|
2324327e7e | ||
|
|
b8374494ea | ||
|
|
a480ca7b55 | ||
|
|
f39b7594ab | ||
|
|
f79734391e | ||
|
|
e54f516418 |
32
.boostnoterc.sample
Normal file
32
.boostnoterc.sample
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"editor": {
|
||||
"fontFamily": "Monaco, Consolas",
|
||||
"fontSize": "14",
|
||||
"indentSize": "2",
|
||||
"indentType": "space",
|
||||
"keyMap": "vim",
|
||||
"switchPreview": "BLUR",
|
||||
"theme": "monokai"
|
||||
},
|
||||
"hotkey": {
|
||||
"toggleFinder": "Cmd + Alt + S",
|
||||
"toggleMain": "Cmd + Alt + L"
|
||||
},
|
||||
"isSideNavFolded": false,
|
||||
"listStyle": "DEFAULT",
|
||||
"listWidth": 174,
|
||||
"navWidth": 200,
|
||||
"preview": {
|
||||
"codeBlockTheme": "dracula",
|
||||
"fontFamily": "Lato",
|
||||
"fontSize": "14",
|
||||
"lineNumber": true,
|
||||
},
|
||||
"sortBy": "UPDATED_AT",
|
||||
"ui": {
|
||||
"defaultNote": "ALWAYS_ASK",
|
||||
"disableDirectWrite": false,
|
||||
"theme": "default"
|
||||
},
|
||||
"zoom": 1
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
Please paste some **screenshots** with opening the developer tool if you report a bug.
|
||||
Please paste some **screenshots** with the **developer tool** open if you report a bug.
|
||||
|
||||
@@ -4,6 +4,7 @@ import styles from './MarkdownEditor.styl'
|
||||
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'
|
||||
const _ = require('lodash')
|
||||
|
||||
class MarkdownEditor extends React.Component {
|
||||
@@ -80,7 +81,6 @@ class MarkdownEditor extends React.Component {
|
||||
if (newStatus === 'CODE') {
|
||||
this.refs.code.focus()
|
||||
} else {
|
||||
this.refs.code.blur()
|
||||
this.refs.preview.focus()
|
||||
}
|
||||
eventEmitter.emit('topbar:togglelockbutton', this.state.status)
|
||||
@@ -163,15 +163,18 @@ class MarkdownEditor extends React.Component {
|
||||
}
|
||||
|
||||
handleKeyDown (e) {
|
||||
let { config } = this.props
|
||||
if (this.state.status !== 'CODE') return false
|
||||
const keyPressed = this.state.keyPressed
|
||||
keyPressed.add(e.keyCode)
|
||||
this.setState({ keyPressed })
|
||||
let isNoteHandlerKey = (el) => { return keyPressed.has(el) }
|
||||
// These conditions are for ctrl-e and ctrl-w
|
||||
if (keyPressed.size === this.escapeFromEditor.length &&
|
||||
!this.state.isLocked && this.state.status === 'CODE' &&
|
||||
this.escapeFromEditor.every(isNoteHandlerKey)) {
|
||||
document.activeElement.blur()
|
||||
this.handleContextMenu()
|
||||
if (config.editor.switchPreview === 'BLUR') document.activeElement.blur()
|
||||
}
|
||||
if (keyPressed.size === this.supportMdSelectionBold.length && this.supportMdSelectionBold.every(isNoteHandlerKey)) {
|
||||
this.addMdAroundWord('**')
|
||||
@@ -214,10 +217,7 @@ class MarkdownEditor extends React.Component {
|
||||
let previewStyle = {}
|
||||
if (this.props.ignorePreviewPointerEvents) previewStyle.pointerEvents = 'none'
|
||||
|
||||
const cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
||||
if (!_.isArray(cachedStorageList)) throw new Error('Target storage doesn\'t exist.')
|
||||
const storage = _.find(cachedStorageList, {key: storageKey})
|
||||
if (storage === undefined) throw new Error('Target storage doesn\'t exist.')
|
||||
const storage = findStorage(storageKey)
|
||||
|
||||
return (
|
||||
<div className={className == null
|
||||
|
||||
@@ -108,6 +108,7 @@ export default class MarkdownPreview extends React.Component {
|
||||
this.checkboxClickHandler = (e) => this.handleCheckboxClick(e)
|
||||
this.saveAsTextHandler = () => this.handleSaveAsText()
|
||||
this.saveAsMdHandler = () => this.handleSaveAsMd()
|
||||
this.printHandler = () => this.handlePrint()
|
||||
|
||||
this.linkClickHandler = this.handlelinkClick.bind(this)
|
||||
}
|
||||
@@ -162,6 +163,10 @@ export default class MarkdownPreview extends React.Component {
|
||||
this.exportAsDocument('md')
|
||||
}
|
||||
|
||||
handlePrint () {
|
||||
this.refs.root.contentWindow.print()
|
||||
}
|
||||
|
||||
exportAsDocument (fileType) {
|
||||
const options = {
|
||||
filters: [
|
||||
@@ -198,6 +203,7 @@ export default class MarkdownPreview extends React.Component {
|
||||
this.refs.root.contentWindow.document.addEventListener('dragover', this.preventImageDroppedHandler)
|
||||
eventEmitter.on('export:save-text', this.saveAsTextHandler)
|
||||
eventEmitter.on('export:save-md', this.saveAsMdHandler)
|
||||
eventEmitter.on('print', this.printHandler)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
@@ -208,6 +214,7 @@ export default class MarkdownPreview extends React.Component {
|
||||
this.refs.root.contentWindow.document.removeEventListener('dragover', this.preventImageDroppedHandler)
|
||||
eventEmitter.off('export:save-text', this.saveAsTextHandler)
|
||||
eventEmitter.off('export:save-md', this.saveAsMdHandler)
|
||||
eventEmitter.off('print', this.printHandler)
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
|
||||
@@ -64,7 +64,7 @@ $list-width = 250px
|
||||
|
||||
.result-nav-storageList
|
||||
absolute bottom left right
|
||||
top 80px + 32px + 10px + 10px
|
||||
top 110px + 32px + 10px + 10px
|
||||
overflow-y auto
|
||||
|
||||
.result-list
|
||||
|
||||
@@ -5,6 +5,7 @@ import MarkdownPreview from 'browser/components/MarkdownPreview'
|
||||
import MarkdownEditor from 'browser/components/MarkdownEditor'
|
||||
import CodeEditor from 'browser/components/CodeEditor'
|
||||
import CodeMirror from 'codemirror'
|
||||
import { findStorage } from 'browser/lib/findStorage'
|
||||
|
||||
const electron = require('electron')
|
||||
const { clipboard } = electron
|
||||
@@ -106,10 +107,7 @@ class NoteDetail extends React.Component {
|
||||
let editorIndentSize = parseInt(config.editor.indentSize, 10)
|
||||
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
||||
|
||||
const cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
||||
if (!_.isArray(cachedStorageList)) throw new Error('Target storage doesn\'t exist.')
|
||||
const storage = _.find(cachedStorageList, {key: note.storage})
|
||||
if (storage === undefined) throw new Error('Target storage doesn\'t exist.')
|
||||
const storage = findStorage(note.storage)
|
||||
|
||||
if (note.type === 'SNIPPET_NOTE') {
|
||||
let tabList = note.snippets.map((snippet, index) => {
|
||||
@@ -148,6 +146,7 @@ class NoteDetail extends React.Component {
|
||||
config={config}
|
||||
value={snippet.content}
|
||||
ref={'code-' + index}
|
||||
storageKey={note.storage}
|
||||
/>
|
||||
: <CodeEditor styleName='tabView-content'
|
||||
mode={snippet.mode}
|
||||
|
||||
14
browser/lib/findStorage.js
Normal file
14
browser/lib/findStorage.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const _ = require('lodash')
|
||||
|
||||
export function findStorage (storageKey) {
|
||||
const cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
||||
if (!_.isArray(cachedStorageList)) throw new Error('Target storage doesn\'t exist.')
|
||||
const storage = _.find(cachedStorageList, {key: storageKey})
|
||||
if (storage === undefined) throw new Error('Target storage doesn\'t exist.')
|
||||
|
||||
return storage
|
||||
}
|
||||
|
||||
export default {
|
||||
findStorage
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import _ from 'lodash'
|
||||
|
||||
export default function searchFromNotes (data, search) {
|
||||
let notes = data.noteMap.map((note) => note)
|
||||
export default function searchFromNotes (notes, search) {
|
||||
if (search.trim().length === 0) return []
|
||||
let searchBlocks = search.split(' ')
|
||||
searchBlocks.forEach((block) => {
|
||||
|
||||
@@ -24,7 +24,7 @@ const InfoPanel = ({
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
Created at
|
||||
Created
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
{createdAt}
|
||||
@@ -32,7 +32,7 @@ const InfoPanel = ({
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
Updated at
|
||||
Updated
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
{updatedAt}
|
||||
|
||||
@@ -18,6 +18,16 @@
|
||||
background-color $ui-noteList-backgroundColor
|
||||
border 1px solid $border-color
|
||||
|
||||
.control-infoButton-panel-trash
|
||||
z-index 200
|
||||
margin-top 45px
|
||||
margin-left -230px
|
||||
position absolute
|
||||
padding 20px 20px 0 20px
|
||||
width 320px
|
||||
background-color $ui-noteList-backgroundColor
|
||||
border 1px solid $border-color
|
||||
|
||||
.group-section
|
||||
display flex
|
||||
line-height 30px
|
||||
@@ -40,6 +50,19 @@
|
||||
width 160px
|
||||
height 25px
|
||||
|
||||
.group-section-control text
|
||||
color #EA4447
|
||||
font-weight 600
|
||||
font-size 14px
|
||||
width 70px
|
||||
height 25px
|
||||
background-color rgba(226,33,113,0.1)
|
||||
border none
|
||||
outline none
|
||||
border-radius 2px
|
||||
margin-right 5px
|
||||
padding 2px 5px
|
||||
|
||||
[id=export-wrap]
|
||||
height 90px
|
||||
display flex
|
||||
@@ -75,6 +98,10 @@ body[data-theme="dark"]
|
||||
background-color $ui-dark-noteList-backgroundColor
|
||||
border 1px solid $ui-dark-borderColor
|
||||
|
||||
.control-infoButton-panel-trash
|
||||
background-color $ui-dark-noteList-backgroundColor
|
||||
border 1px solid $ui-dark-borderColor
|
||||
|
||||
.group-section-label
|
||||
color $ui-inactive-text-color
|
||||
|
||||
|
||||
70
browser/main/Detail/InfoPanelTrashed.js
Normal file
70
browser/main/Detail/InfoPanelTrashed.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './InfoPanel.styl'
|
||||
|
||||
const InfoPanelTrashed = ({
|
||||
storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt
|
||||
}) => (
|
||||
<div className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
Storage
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
{storageName}
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
Folder
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
<text>Trash</text>{folderName}
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
Created
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
{createdAt}
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
Updated
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
{updatedAt}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id='export-wrap'>
|
||||
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
|
||||
<i className='fa fa-file-code-o fa-fw' />
|
||||
<p>.md</p>
|
||||
</button>
|
||||
|
||||
<button styleName='export--enable' onClick={(e) => exportAsTxt(e)}>
|
||||
<i className='fa fa-file-text-o fa-fw' />
|
||||
<p>.txt</p>
|
||||
</button>
|
||||
|
||||
<button styleName='export--unable'>
|
||||
<i className='fa fa-file-pdf-o fa-fw' />
|
||||
<p>.pdf</p>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
InfoPanelTrashed.propTypes = {
|
||||
storageName: PropTypes.string.isRequired,
|
||||
folderName: PropTypes.string.isRequired,
|
||||
updatedAt: PropTypes.string.isRequired,
|
||||
createdAt: PropTypes.string.isRequired,
|
||||
exportAsMd: PropTypes.func.isRequired,
|
||||
exportAsTxt: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default CSSModules(InfoPanelTrashed, styles)
|
||||
@@ -17,6 +17,7 @@ import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import TrashButton from './TrashButton'
|
||||
import InfoButton from './InfoButton'
|
||||
import InfoPanel from './InfoPanel'
|
||||
import InfoPanelTrashed from './InfoPanelTrashed'
|
||||
import { formatDate } from 'browser/lib/date-formatter'
|
||||
|
||||
const electron = require('electron')
|
||||
@@ -190,8 +191,8 @@ class MarkdownNoteDetail extends React.Component {
|
||||
if (isTrashed) {
|
||||
let dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'warning',
|
||||
message: 'Delete a note',
|
||||
detail: 'This work cannot be undone.',
|
||||
message: 'Confirm note deletion',
|
||||
detail: 'This will permanently remove this note.',
|
||||
buttons: ['Confirm', 'Cancel']
|
||||
})
|
||||
if (dialogueButtonIndex === 1) return
|
||||
@@ -297,6 +298,17 @@ class MarkdownNoteDetail extends React.Component {
|
||||
</div>
|
||||
<div styleName='info-right'>
|
||||
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||
<InfoButton
|
||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
||||
/>
|
||||
<InfoPanelTrashed
|
||||
storageName={currentOption.storage.name}
|
||||
folderName={currentOption.folder.name}
|
||||
updatedAt={formatDate(note.updatedAt)}
|
||||
createdAt={formatDate(note.createdAt)}
|
||||
exportAsMd={this.exportAsMd}
|
||||
exportAsTxt={this.exportAsTxt}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import TrashButton from './TrashButton'
|
||||
import InfoButton from './InfoButton'
|
||||
import InfoPanel from './InfoPanel'
|
||||
import InfoPanelTrashed from './InfoPanelTrashed'
|
||||
import { formatDate } from 'browser/lib/date-formatter'
|
||||
|
||||
function pass (name) {
|
||||
@@ -176,8 +177,8 @@ class SnippetNoteDetail extends React.Component {
|
||||
if (isTrashed) {
|
||||
let dialogueButtonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'warning',
|
||||
message: 'Delete a note',
|
||||
detail: 'This work cannot be undone.',
|
||||
message: 'Confirm note deletion',
|
||||
detail: 'This will permanently remove this note.',
|
||||
buttons: ['Confirm', 'Cancel']
|
||||
})
|
||||
if (dialogueButtonIndex === 1) return
|
||||
@@ -519,6 +520,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
onChange={(e) => this.handleCodeChange(index)(e)}
|
||||
ref={'code-' + index}
|
||||
ignorePreviewPointerEvents={this.props.ignorePreviewPointerEvents}
|
||||
storageKey={storageKey}
|
||||
/>
|
||||
: <CodeEditor styleName='tabView-content'
|
||||
mode={snippet.mode}
|
||||
@@ -556,6 +558,17 @@ class SnippetNoteDetail extends React.Component {
|
||||
</div>
|
||||
<div styleName='info-right'>
|
||||
<TrashButton onClick={(e) => this.handleTrashButtonClick(e)} />
|
||||
<InfoButton
|
||||
onClick={(e) => this.handleInfoButtonClick(e)}
|
||||
/>
|
||||
<InfoPanelTrashed
|
||||
storageName={currentOption.storage.name}
|
||||
folderName={currentOption.folder.name}
|
||||
updatedAt={formatDate(note.updatedAt)}
|
||||
createdAt={formatDate(note.createdAt)}
|
||||
exportAsMd={this.showWarning}
|
||||
exportAsTxt={this.showWarning}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ class Detail extends React.Component {
|
||||
tabIndex='0'
|
||||
>
|
||||
<div styleName='empty'>
|
||||
<div styleName='empty-message'>{OSX ? 'Command(⌘)' : 'Ctrl(^)'} + N<br />to create a new post</div>
|
||||
<div styleName='empty-message'>{OSX ? 'Command(⌘)' : 'Ctrl(^)'} + N<br />to create a new note</div>
|
||||
</div>
|
||||
<StatusBar
|
||||
{..._.pick(this.props, ['config', 'location', 'dispatch'])}
|
||||
|
||||
@@ -246,7 +246,7 @@ class NoteList extends React.Component {
|
||||
if (searchInputText === '') {
|
||||
router.push('/home')
|
||||
}
|
||||
return searchFromNotes(this.props.data, searchInputText)
|
||||
return searchFromNotes(this.notes, searchInputText)
|
||||
}
|
||||
|
||||
if (location.pathname.match(/\/trashed/)) {
|
||||
@@ -446,9 +446,9 @@ class NoteList extends React.Component {
|
||||
value={config.sortBy}
|
||||
onChange={(e) => this.handleSortByChange(e)}
|
||||
>
|
||||
<option value='UPDATED_AT'>Updated Time</option>
|
||||
<option value='CREATED_AT'>Created Time</option>
|
||||
<option value='ALPHABETICAL'>Alphabetical</option>
|
||||
<option value='UPDATED_AT'>Last Updated</option>
|
||||
<option value='CREATED_AT'>Creation Time</option>
|
||||
<option value='ALPHABETICAL'>Alphabetically</option>
|
||||
</select>
|
||||
</div>
|
||||
<div styleName='control-button-area'>
|
||||
|
||||
@@ -114,7 +114,7 @@ class StorageItem extends React.Component {
|
||||
let index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'warning',
|
||||
message: 'Delete Folder',
|
||||
detail: 'This work will deletes all notes in the folder and can not be undone.',
|
||||
detail: 'This will delete all notes in the folder and can not be undone.',
|
||||
buttons: ['Confirm', 'Cancel']
|
||||
})
|
||||
|
||||
@@ -180,14 +180,20 @@ class StorageItem extends React.Component {
|
||||
|
||||
render () {
|
||||
let { storage, location, isFolded, data, dispatch } = this.props
|
||||
let { folderNoteMap } = data
|
||||
let { folderNoteMap, trashedSet } = data
|
||||
let folderList = storage.folders.map((folder) => {
|
||||
let isActive = !!(location.pathname.match(new RegExp('\/storages\/' + storage.key + '\/folders\/' + folder.key)))
|
||||
let noteSet = folderNoteMap.get(storage.key + '-' + folder.key)
|
||||
|
||||
let noteCount = noteSet != null
|
||||
? noteSet.size
|
||||
: 0
|
||||
let noteCount = 0
|
||||
if (noteSet) {
|
||||
let trashedNoteCount = 0
|
||||
const noteKeys = noteSet.map(noteKey => { return noteKey })
|
||||
trashedSet.toJS().forEach(trashedKey => {
|
||||
if (noteKeys.some(noteKey => { return noteKey === trashedKey })) trashedNoteCount++
|
||||
})
|
||||
noteCount = noteSet.size - trashedNoteCount
|
||||
}
|
||||
return (
|
||||
<StorageItemChild
|
||||
key={folder.key}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import _ from 'lodash'
|
||||
import RcParser from 'browser/main/lib/RcParser'
|
||||
|
||||
const OSX = global.process.platform === 'darwin'
|
||||
const win = global.process.platform === 'win32'
|
||||
const electron = require('electron')
|
||||
const { ipcRenderer } = electron
|
||||
const consts = require('browser/lib/consts')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
|
||||
let isInitialized = false
|
||||
|
||||
@@ -60,11 +63,13 @@ function get () {
|
||||
let config = window.localStorage.getItem('config')
|
||||
|
||||
try {
|
||||
const boostnotercConfig = RcParser.parse()
|
||||
|
||||
config = Object.assign({}, DEFAULT_CONFIG, JSON.parse(config))
|
||||
config.hotkey = Object.assign({}, DEFAULT_CONFIG.hotkey, config.hotkey)
|
||||
config.ui = Object.assign({}, DEFAULT_CONFIG.ui, config.ui)
|
||||
config.editor = Object.assign({}, DEFAULT_CONFIG.editor, config.editor)
|
||||
config.preview = Object.assign({}, DEFAULT_CONFIG.preview, config.preview)
|
||||
|
||||
config = Object.assign({}, DEFAULT_CONFIG, boostnotercConfig)
|
||||
config = assignConfigValues(config, boostnotercConfig, config)
|
||||
|
||||
if (!validate(config)) throw new Error('INVALID CONFIG')
|
||||
} catch (err) {
|
||||
console.warn('Boostnote resets the malformed configuration.')
|
||||
@@ -126,6 +131,15 @@ function set (updates) {
|
||||
})
|
||||
}
|
||||
|
||||
function assignConfigValues (config, rcConfig, originalConfig) {
|
||||
config = Object.assign({}, DEFAULT_CONFIG, rcConfig, originalConfig)
|
||||
config.hotkey = Object.assign({}, DEFAULT_CONFIG.hotkey, rcConfig.hotkey, originalConfig.hotkey)
|
||||
config.ui = Object.assign({}, DEFAULT_CONFIG.ui, rcConfig.ui, originalConfig.ui)
|
||||
config.editor = Object.assign({}, DEFAULT_CONFIG.editor, rcConfig.editor, originalConfig.editor)
|
||||
config.preview = Object.assign({}, DEFAULT_CONFIG.preview, rcConfig.preview, originalConfig.preview)
|
||||
return config
|
||||
}
|
||||
|
||||
export default {
|
||||
get,
|
||||
set,
|
||||
|
||||
15
browser/main/lib/RcParser.js
Normal file
15
browser/main/lib/RcParser.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import path from 'path'
|
||||
import sander from 'sander'
|
||||
|
||||
function parse () {
|
||||
const BOOSTNOTERC = '.boostnoterc'
|
||||
const homePath = global.process.env.HOME || global.process.env.USERPROFILE
|
||||
const boostnotercPath = path.join(homePath, BOOSTNOTERC)
|
||||
|
||||
if (!sander.existsSync(boostnotercPath)) return {}
|
||||
return JSON.parse(sander.readFileSync(boostnotercPath).toString())
|
||||
}
|
||||
|
||||
export default {
|
||||
parse
|
||||
}
|
||||
@@ -2,6 +2,7 @@ const fs = require('fs')
|
||||
const path = require('path')
|
||||
const _ = require('lodash')
|
||||
const sander = require('sander')
|
||||
const { findStorage } = require('browser/lib/findStorage')
|
||||
|
||||
/**
|
||||
* @description To copy an image and return the path.
|
||||
@@ -12,11 +13,7 @@ const sander = require('sander')
|
||||
function copyImage (filePath, storageKey) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
||||
if (!_.isArray(cachedStorageList)) throw new Error('Target storage doesn\'t exist.')
|
||||
const storage = _.find(cachedStorageList, {key: storageKey})
|
||||
if (storage === undefined) throw new Error('Target storage doesn\'t exist.')
|
||||
const targetStorage = storage
|
||||
const targetStorage = findStorage(storageKey)
|
||||
|
||||
const inputImage = fs.createReadStream(filePath)
|
||||
const imageExt = path.extname(filePath)
|
||||
|
||||
@@ -3,6 +3,7 @@ const keygen = require('browser/lib/keygen')
|
||||
const path = require('path')
|
||||
const resolveStorageData = require('./resolveStorageData')
|
||||
const CSON = require('@rokt33r/season')
|
||||
const { findStorage } = require('browser/lib/findStorage')
|
||||
|
||||
/**
|
||||
* @param {String} storageKey
|
||||
@@ -29,11 +30,7 @@ function createFolder (storageKey, input) {
|
||||
if (!_.isString(input.name)) throw new Error('Name must be a string.')
|
||||
if (!_.isString(input.color)) throw new Error('Color must be a string.')
|
||||
|
||||
rawStorages = JSON.parse(localStorage.getItem('storages'))
|
||||
if (!_.isArray(rawStorages)) throw new Error('Target storage doesn\'t exist.')
|
||||
|
||||
targetStorage = _.find(rawStorages, {key: storageKey})
|
||||
if (targetStorage == null) throw new Error('Target storage doesn\'t exist.')
|
||||
targetStorage = findStorage(storageKey)
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ const _ = require('lodash')
|
||||
const keygen = require('browser/lib/keygen')
|
||||
const path = require('path')
|
||||
const CSON = require('@rokt33r/season')
|
||||
const { findStorage } = require('browser/lib/findStorage')
|
||||
|
||||
function validateInput (input) {
|
||||
if (!_.isArray(input.tags)) input.tags = []
|
||||
@@ -38,11 +39,7 @@ function createNote (storageKey, input) {
|
||||
input = Object.assign({}, input)
|
||||
validateInput(input)
|
||||
|
||||
let cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
||||
if (!_.isArray(cachedStorageList)) throw new Error('Target storage doesn\'t exist.')
|
||||
|
||||
targetStorage = _.find(cachedStorageList, {key: storageKey})
|
||||
if (targetStorage == null) throw new Error('Target storage doesn\'t exist.')
|
||||
targetStorage = findStorage(storageKey)
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ const resolveStorageData = require('./resolveStorageData')
|
||||
const resolveStorageNotes = require('./resolveStorageNotes')
|
||||
const CSON = require('@rokt33r/season')
|
||||
const sander = require('sander')
|
||||
const { findStorage } = require('browser/lib/findStorage')
|
||||
|
||||
/**
|
||||
* @param {String} storageKey
|
||||
@@ -21,11 +22,7 @@ function deleteFolder (storageKey, folderKey) {
|
||||
let rawStorages
|
||||
let targetStorage
|
||||
try {
|
||||
rawStorages = JSON.parse(localStorage.getItem('storages'))
|
||||
if (!_.isArray(rawStorages)) throw new Error('Target storage doesn\'t exist.')
|
||||
|
||||
targetStorage = _.find(rawStorages, {key: storageKey})
|
||||
if (targetStorage == null) throw new Error('Target storage doesn\'t exist.')
|
||||
targetStorage = findStorage(storageKey)
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
|
||||
@@ -2,15 +2,12 @@ const resolveStorageData = require('./resolveStorageData')
|
||||
const _ = require('lodash')
|
||||
const path = require('path')
|
||||
const sander = require('sander')
|
||||
const { findStorage } = require('browser/lib/findStorage')
|
||||
|
||||
function deleteNote (storageKey, noteKey) {
|
||||
let targetStorage
|
||||
try {
|
||||
let cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
||||
if (!_.isArray(cachedStorageList)) throw new Error('Target storage doesn\'t exist.')
|
||||
|
||||
targetStorage = _.find(cachedStorageList, {key: storageKey})
|
||||
if (targetStorage == null) throw new Error('Target storage doesn\'t exist.')
|
||||
targetStorage = findStorage(storageKey)
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
|
||||
@@ -4,17 +4,13 @@ const path = require('path')
|
||||
const CSON = require('@rokt33r/season')
|
||||
const keygen = require('browser/lib/keygen')
|
||||
const sander = require('sander')
|
||||
const { findStorage } = require('browser/lib/findStorage')
|
||||
|
||||
function moveNote (storageKey, noteKey, newStorageKey, newFolderKey) {
|
||||
let oldStorage, newStorage
|
||||
try {
|
||||
let cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
||||
if (!_.isArray(cachedStorageList)) throw new Error('Storage doesn\'t exist.')
|
||||
|
||||
oldStorage = _.find(cachedStorageList, {key: storageKey})
|
||||
if (oldStorage == null) throw new Error('Storage doesn\'t exist.')
|
||||
|
||||
newStorage = _.find(cachedStorageList, {key: newStorageKey})
|
||||
oldStorage = findStorage(storageKey)
|
||||
newStorage = findStorage(newStorageKey)
|
||||
if (newStorage == null) throw new Error('Target storage doesn\'t exist.')
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
const _ = require('lodash')
|
||||
const resolveStorageData = require('./resolveStorageData')
|
||||
const { findStorage } = require('browser/lib/findStorage')
|
||||
|
||||
/**
|
||||
* @param {String} key
|
||||
* @param {String} name
|
||||
|
||||
@@ -2,6 +2,7 @@ const resolveStorageData = require('./resolveStorageData')
|
||||
const _ = require('lodash')
|
||||
const path = require('path')
|
||||
const CSON = require('@rokt33r/season')
|
||||
const { findStorage } = require('browser/lib/findStorage')
|
||||
|
||||
function validateInput (input) {
|
||||
let validatedInput = {}
|
||||
@@ -68,11 +69,7 @@ function updateNote (storageKey, noteKey, input) {
|
||||
if (input == null) throw new Error('No input found.')
|
||||
input = validateInput(input)
|
||||
|
||||
let cachedStorageList = JSON.parse(localStorage.getItem('storages'))
|
||||
if (!_.isArray(cachedStorageList)) throw new Error('Target storage doesn\'t exist.')
|
||||
|
||||
targetStorage = _.find(cachedStorageList, {key: storageKey})
|
||||
if (targetStorage == null) throw new Error('Target storage doesn\'t exist.')
|
||||
targetStorage = findStorage(storageKey)
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ class InitModal extends React.Component {
|
||||
type: 'MARKDOWN_NOTE',
|
||||
folder: data.storage.folders[0].key,
|
||||
title: 'Welcome to Boostnote!',
|
||||
content: '# Welcome to Boostnote! \n### _Click to edit this note._\n\n---\n\nBoostnote is an *open source* note-taking app. \nRepository is published on [GitHub](https://github.com/BoostIO/Boostnote), and tweeting everyday on [@Boostnoteapp](https://twitter.com/boostnoteapp)!\n\n## Features \n- [x] No Internet and Registration Required. \n- [ ] Quick search and copy the content of note. `macOS: Cmd + Alt + S / windows: Ctrl + Alt + S` \n- [ ] Markdown & Snippet note. \n- [ ] Available for `vim` and `emacs` mode. \n- [ ] Choose your favorite theme on UI, Editor and Code Block! \n--- \n\n- Copy Codeblock on Makrdown Preview.\n```javascript\nvar boostnote = document.getElementById(\'enjoy\').innerHTML\n\nconsole.log(boostnote)\n```'
|
||||
content: '# Welcome to Boostnote! \n### _Click to edit this note._\n\n---\n\nBoostnote is an *open source* note-taking app. \nRepository is published on [GitHub](https://github.com/BoostIO/Boostnote), and tweeting everyday on [@Boostnoteapp](https://twitter.com/boostnoteapp)!\n\n## Features \n- [x] No Internet and Registration Required. \n- [ ] Quick search and copy the content of note. `macOS: Cmd + Alt + S / windows: Ctrl + Alt + S` \n- [ ] Markdown & Snippet note. \n- [ ] Available for `vim` and `emacs` mode. \n- [ ] Choose your favorite theme on UI, Editor and Code Block! \n--- \n\n- Copy Codeblock on Markdown Preview.\n```javascript\nvar boostnote = document.getElementById(\'enjoy\').innerHTML\n\nconsole.log(boostnote)\n```'
|
||||
})
|
||||
.then((note) => {
|
||||
store.dispatch({
|
||||
|
||||
@@ -34,15 +34,16 @@ class InfoTab extends React.Component {
|
||||
amaEnabled: this.state.config.amaEnabled
|
||||
}
|
||||
|
||||
if (!newConfig.amaEnabled) {
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('DISABLE_AMA')
|
||||
}
|
||||
|
||||
ConfigManager.set(newConfig)
|
||||
|
||||
store.dispatch({
|
||||
type: 'SET_CONFIG',
|
||||
config: newConfig
|
||||
})
|
||||
if (!newConfig.amaEnabled) {
|
||||
AwsMobileAnalyticsConfig.recordDynamicCustomEvent('DISABLE_AMA')
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
|
||||
@@ -298,8 +298,8 @@ class StorageItem extends React.Component {
|
||||
let index = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||
type: 'warning',
|
||||
message: 'Unlink Storage',
|
||||
detail: 'This work just detatches a storage from Boostnote. (Any data won\'t be deleted.)',
|
||||
buttons: ['Confirm', 'Cancel']
|
||||
detail: 'Unlinking removes this linked storage from Boostnote. No data is removed, please manually delete the folder from your hard drive if needed.',
|
||||
buttons: ['Unlink', 'Cancel']
|
||||
})
|
||||
|
||||
if (index === 0) {
|
||||
|
||||
@@ -192,7 +192,7 @@ class UiTab extends React.Component {
|
||||
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
Switching Preview
|
||||
Switch to Preview
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
<select value={config.editor.switchPreview}
|
||||
@@ -200,7 +200,7 @@ class UiTab extends React.Component {
|
||||
onChange={(e) => this.handleUIChange(e)}
|
||||
>
|
||||
<option value='BLUR'>When Editor Blurred</option>
|
||||
<option value='RIGHTCLICK'>When Right Clicking</option>
|
||||
<option value='RIGHTCLICK'>On Right Click</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -218,7 +218,7 @@ class UiTab extends React.Component {
|
||||
<option value='vim'>vim</option>
|
||||
<option value='emacs'>emacs</option>
|
||||
</select>
|
||||
<span styleName='note-for-keymap'>Please reload boostnote after you change the keymap</span>
|
||||
<span styleName='note-for-keymap'>Please restart boostnote after you change the keymap</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -271,7 +271,7 @@ class UiTab extends React.Component {
|
||||
ref='previewLineNumber'
|
||||
type='checkbox'
|
||||
/>
|
||||
Code block line numbering
|
||||
Show line numbers for preview code blocks
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -106,6 +106,17 @@ const file = {
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Print',
|
||||
accelerator: 'CommandOrControl+P',
|
||||
click () {
|
||||
mainWindow.webContents.send('list:isMarkdownNote')
|
||||
mainWindow.webContents.send('print')
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Delete Note',
|
||||
accelerator: macOS ? 'Control+Backspace' : 'Control+Delete',
|
||||
|
||||
@@ -20,6 +20,15 @@
|
||||
font-weight: normal;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Lato';
|
||||
src: url('../resources/fonts/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
|
||||
url('../resources/fonts/Lato-Regular.woff') format('woff'), /* Modern Browsers */
|
||||
url('../resources/fonts/Lato-Regular.ttf') format('truetype');
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
#loadingCover{
|
||||
background-color: #f4f4f4;
|
||||
position: absolute;
|
||||
|
||||
32
tests/lib/find-storage-test.js
Normal file
32
tests/lib/find-storage-test.js
Normal file
@@ -0,0 +1,32 @@
|
||||
const test = require('ava')
|
||||
const { findStorage } = require('browser/lib/findStorage')
|
||||
|
||||
global.document = require('jsdom').jsdom('<body></body>')
|
||||
global.window = document.defaultView
|
||||
global.navigator = window.navigator
|
||||
|
||||
const Storage = require('dom-storage')
|
||||
const localStorage = window.localStorage = global.localStorage = new Storage(null, { strict: true })
|
||||
const path = require('path')
|
||||
const TestDummy = require('../fixtures/TestDummy')
|
||||
const sander = require('sander')
|
||||
const os = require('os')
|
||||
const storagePath = path.join(os.tmpdir(), 'test/find-storage')
|
||||
|
||||
test.beforeEach((t) => {
|
||||
t.context.storage = TestDummy.dummyStorage(storagePath)
|
||||
localStorage.setItem('storages', JSON.stringify([t.context.storage.cache]))
|
||||
})
|
||||
|
||||
// Unit test
|
||||
test('findStorage() should return a correct storage path(string)', t => {
|
||||
const storageKey = t.context.storage.cache.key
|
||||
|
||||
t.is(findStorage(storageKey).key, storageKey)
|
||||
t.is(findStorage(storageKey).path, storagePath)
|
||||
})
|
||||
|
||||
test.after(function after () {
|
||||
localStorage.clear()
|
||||
sander.rimrafSync(storagePath)
|
||||
})
|
||||
@@ -5,7 +5,7 @@ import _ from 'lodash'
|
||||
|
||||
const pickContents = (notes) => notes.map((note) => { return note.content })
|
||||
|
||||
let noteList = { noteMap: [] }
|
||||
let notes = []
|
||||
let note1, note2
|
||||
|
||||
test.before(t => {
|
||||
@@ -14,7 +14,7 @@ test.before(t => {
|
||||
note1 = dummyNote(data1)
|
||||
note2 = dummyNote(data2)
|
||||
|
||||
noteList.noteMap = [note1, note2]
|
||||
notes = [note1, note2]
|
||||
})
|
||||
|
||||
test('it can find notes by tags or words', t => {
|
||||
@@ -30,7 +30,7 @@ test('it can find notes by tags or words', t => {
|
||||
|
||||
testCases.forEach((testCase) => {
|
||||
const [input, expectedContents] = testCase
|
||||
const results = searchFromNotes(noteList, input)
|
||||
const results = searchFromNotes(notes, input)
|
||||
t.true(_.isEqual(pickContents(results).sort(), expectedContents.sort()))
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user