mirror of
https://github.com/BoostIo/Boostnote
synced 2026-02-10 06:21:40 +00:00
merge from master
This commit is contained in:
@@ -14,7 +14,7 @@ class InfoPanel extends React.Component {
|
||||
|
||||
render () {
|
||||
const {
|
||||
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, wordCount, letterCount, type, print
|
||||
storageName, folderName, noteLink, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, exportAsPdf, wordCount, letterCount, type, print
|
||||
} = this.props
|
||||
return (
|
||||
<div className='infoPanel' styleName='control-infoButton-panel' style={{display: 'none'}}>
|
||||
@@ -85,6 +85,11 @@ class InfoPanel extends React.Component {
|
||||
<p>{i18n.__('.html')}</p>
|
||||
</button>
|
||||
|
||||
<button styleName='export--enable' onClick={(e) => exportAsPdf(e, 'export-pdf')}>
|
||||
<i className='fa fa-file-pdf-o' />
|
||||
<p>{i18n.__('.pdf')}</p>
|
||||
</button>
|
||||
|
||||
<button styleName='export--enable' onClick={(e) => print(e, 'print')}>
|
||||
<i className='fa fa-print' />
|
||||
<p>{i18n.__('Print')}</p>
|
||||
@@ -104,6 +109,7 @@ InfoPanel.propTypes = {
|
||||
exportAsMd: PropTypes.func.isRequired,
|
||||
exportAsTxt: PropTypes.func.isRequired,
|
||||
exportAsHtml: PropTypes.func.isRequired,
|
||||
exportAsPdf: PropTypes.func.isRequired,
|
||||
wordCount: PropTypes.number,
|
||||
letterCount: PropTypes.number,
|
||||
type: PropTypes.string.isRequired,
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
right 25px
|
||||
position absolute
|
||||
padding 20px 25px 0 25px
|
||||
width 300px
|
||||
// width 300px
|
||||
overflow auto
|
||||
background-color $ui-noteList-backgroundColor
|
||||
box-shadow 2px 12px 15px 2px rgba(0, 0, 0, 0.1), 2px 1px 50px 2px rgba(0, 0, 0, 0.1)
|
||||
|
||||
@@ -5,7 +5,7 @@ import styles from './InfoPanel.styl'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
const InfoPanelTrashed = ({
|
||||
storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml
|
||||
storageName, folderName, updatedAt, createdAt, exportAsMd, exportAsTxt, exportAsHtml, exportAsPdf
|
||||
}) => (
|
||||
<div className='infoPanel' styleName='control-infoButton-panel-trash' style={{display: 'none'}}>
|
||||
<div>
|
||||
@@ -46,7 +46,7 @@ const InfoPanelTrashed = ({
|
||||
<p>.html</p>
|
||||
</button>
|
||||
|
||||
<button styleName='export--unable'>
|
||||
<button styleName='export--enable' onClick={(e) => exportAsPdf(e, 'export-pdf')}>
|
||||
<i className='fa fa-file-pdf-o' />
|
||||
<p>.pdf</p>
|
||||
</button>
|
||||
@@ -61,7 +61,8 @@ InfoPanelTrashed.propTypes = {
|
||||
createdAt: PropTypes.string.isRequired,
|
||||
exportAsMd: PropTypes.func.isRequired,
|
||||
exportAsTxt: PropTypes.func.isRequired,
|
||||
exportAsHtml: PropTypes.func.isRequired
|
||||
exportAsHtml: PropTypes.func.isRequired,
|
||||
exportAsPdf: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default CSSModules(InfoPanelTrashed, styles)
|
||||
|
||||
@@ -9,7 +9,6 @@ import StarButton from './StarButton'
|
||||
import TagSelect from './TagSelect'
|
||||
import FolderSelect from './FolderSelect'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import { hashHistory } from 'react-router'
|
||||
import ee from 'browser/main/lib/eventEmitter'
|
||||
import markdown from 'browser/lib/markdownTextHelper'
|
||||
import StatusBar from '../StatusBar'
|
||||
@@ -31,6 +30,8 @@ import { getTodoPercentageOfCompleted } from 'browser/lib/getTodoStatus'
|
||||
import striptags from 'striptags'
|
||||
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
||||
import markdownToc from 'browser/lib/markdown-toc-generator'
|
||||
import queryString from 'query-string'
|
||||
import { replace } from 'connected-react-router'
|
||||
|
||||
class MarkdownNoteDetail extends React.Component {
|
||||
constructor (props) {
|
||||
@@ -142,6 +143,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
}
|
||||
|
||||
handleFolderChange (e) {
|
||||
const { dispatch } = this.props
|
||||
const { note } = this.state
|
||||
const value = this.refs.folder.value
|
||||
const splitted = value.split('-')
|
||||
@@ -161,12 +163,12 @@ class MarkdownNoteDetail extends React.Component {
|
||||
originNote: note,
|
||||
note: newNote
|
||||
})
|
||||
hashHistory.replace({
|
||||
dispatch(replace({
|
||||
pathname: location.pathname,
|
||||
query: {
|
||||
search: queryString.stringify({
|
||||
key: newNote.key
|
||||
}
|
||||
})
|
||||
})
|
||||
}))
|
||||
this.setState({
|
||||
isMovingNote: false
|
||||
})
|
||||
@@ -203,6 +205,10 @@ class MarkdownNoteDetail extends React.Component {
|
||||
ee.emit('export:save-html')
|
||||
}
|
||||
|
||||
exportAsPdf () {
|
||||
ee.emit('export:save-pdf')
|
||||
}
|
||||
|
||||
handleKeyDown (e) {
|
||||
switch (e.keyCode) {
|
||||
// tab key
|
||||
@@ -438,6 +444,7 @@ class MarkdownNoteDetail extends React.Component {
|
||||
exportAsHtml={this.exportAsHtml}
|
||||
exportAsMd={this.exportAsMd}
|
||||
exportAsTxt={this.exportAsTxt}
|
||||
exportAsPdf={this.exportAsPdf}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -503,12 +510,13 @@ class MarkdownNoteDetail extends React.Component {
|
||||
<InfoPanel
|
||||
storageName={currentOption.storage.name}
|
||||
folderName={currentOption.folder.name}
|
||||
noteLink={`[${note.title}](:note:${location.query.key})`}
|
||||
noteLink={`[${note.title}](:note:${queryString.parse(location.search).key})`}
|
||||
updatedAt={formatDate(note.updatedAt)}
|
||||
createdAt={formatDate(note.createdAt)}
|
||||
exportAsMd={this.exportAsMd}
|
||||
exportAsTxt={this.exportAsTxt}
|
||||
exportAsHtml={this.exportAsHtml}
|
||||
exportAsPdf={this.exportAsPdf}
|
||||
wordCount={note.content.split(' ').length}
|
||||
letterCount={note.content.replace(/\r?\n/g, '').length}
|
||||
type={note.type}
|
||||
|
||||
@@ -80,4 +80,12 @@ body[data-theme="monokai"]
|
||||
body[data-theme="dracula"]
|
||||
.root
|
||||
border-left 1px solid $ui-dracula-borderColor
|
||||
background-color $ui-dracula-noteDetail-backgroundColor
|
||||
background-color $ui-dracula-noteDetail-backgroundColor
|
||||
|
||||
div
|
||||
> button, div
|
||||
-webkit-user-drag none
|
||||
user-select none
|
||||
> img, span
|
||||
-webkit-user-drag none
|
||||
user-select none
|
||||
|
||||
@@ -8,7 +8,6 @@ import StarButton from './StarButton'
|
||||
import TagSelect from './TagSelect'
|
||||
import FolderSelect from './FolderSelect'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import {hashHistory} from 'react-router'
|
||||
import ee from 'browser/main/lib/eventEmitter'
|
||||
import CodeMirror from 'codemirror'
|
||||
import 'codemirror-mode-elixir'
|
||||
@@ -18,7 +17,6 @@ import context from 'browser/lib/context'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import _ from 'lodash'
|
||||
import {findNoteTitle} from 'browser/lib/findNoteTitle'
|
||||
import convertModeName from 'browser/lib/convertModeName'
|
||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import FullscreenButton from './FullscreenButton'
|
||||
import TrashButton from './TrashButton'
|
||||
@@ -31,6 +29,8 @@ 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'
|
||||
import queryString from 'query-string'
|
||||
import { replace } from 'connected-react-router'
|
||||
|
||||
const electron = require('electron')
|
||||
const { remote } = electron
|
||||
@@ -166,12 +166,12 @@ class SnippetNoteDetail extends React.Component {
|
||||
originNote: note,
|
||||
note: newNote
|
||||
})
|
||||
hashHistory.replace({
|
||||
dispatch(replace({
|
||||
pathname: location.pathname,
|
||||
query: {
|
||||
search: queryString.stringify({
|
||||
key: newNote.key
|
||||
}
|
||||
})
|
||||
})
|
||||
}))
|
||||
this.setState({
|
||||
isMovingNote: false
|
||||
})
|
||||
@@ -657,6 +657,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
'export-txt': 'Text export',
|
||||
'export-md': 'Markdown export',
|
||||
'export-html': 'HTML export',
|
||||
'export-pdf': 'PDF export',
|
||||
'print': 'Print'
|
||||
})[msg]
|
||||
|
||||
@@ -770,6 +771,7 @@ class SnippetNoteDetail extends React.Component {
|
||||
exportAsMd={this.showWarning}
|
||||
exportAsTxt={this.showWarning}
|
||||
exportAsHtml={this.showWarning}
|
||||
exportAsPdf={this.showWarning}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -812,12 +814,13 @@ class SnippetNoteDetail extends React.Component {
|
||||
<InfoPanel
|
||||
storageName={currentOption.storage.name}
|
||||
folderName={currentOption.folder.name}
|
||||
noteLink={`[${note.title}](:note:${location.query.key})`}
|
||||
noteLink={`[${note.title}](:note:${queryString.parse(location.search).key})`}
|
||||
updatedAt={formatDate(note.updatedAt)}
|
||||
createdAt={formatDate(note.createdAt)}
|
||||
exportAsMd={this.showWarning}
|
||||
exportAsTxt={this.showWarning}
|
||||
exportAsHtml={this.showWarning}
|
||||
exportAsPdf={this.showWarning}
|
||||
type={note.type}
|
||||
print={this.showWarning}
|
||||
/>
|
||||
|
||||
@@ -10,6 +10,7 @@ import StatusBar from '../StatusBar'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import debounceRender from 'react-debounce-render'
|
||||
import searchFromNotes from 'browser/lib/search'
|
||||
import queryString from 'query-string'
|
||||
|
||||
const OSX = global.process.platform === 'darwin'
|
||||
|
||||
@@ -36,11 +37,11 @@ class Detail extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { location, data, params, config } = this.props
|
||||
const { location, data, match: { params }, config } = this.props
|
||||
const noteKey = location.search !== '' && queryString.parse(location.search).key
|
||||
let note = null
|
||||
|
||||
if (location.query.key != null) {
|
||||
const noteKey = location.query.key
|
||||
if (location.search !== '') {
|
||||
const allNotes = data.noteMap.map(note => note)
|
||||
const trashedNotes = data.trashedSet.toJS().map(uniqueKey => data.noteMap.get(uniqueKey))
|
||||
let displayedNotes = allNotes
|
||||
|
||||
16
browser/main/DevTools/index.dev.js
Normal file
16
browser/main/DevTools/index.dev.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import React from 'react'
|
||||
import { createDevTools } from 'redux-devtools'
|
||||
import LogMonitor from 'redux-devtools-log-monitor'
|
||||
import DockMonitor from 'redux-devtools-dock-monitor'
|
||||
|
||||
const DevTools = createDevTools(
|
||||
<DockMonitor
|
||||
toggleVisibilityKey='ctrl-h'
|
||||
changePositionKey='ctrl-q'
|
||||
defaultIsVisible={false}
|
||||
>
|
||||
<LogMonitor theme='tomorrow' />
|
||||
</DockMonitor>
|
||||
)
|
||||
|
||||
export default DevTools
|
||||
8
browser/main/DevTools/index.js
Normal file
8
browser/main/DevTools/index.js
Normal file
@@ -0,0 +1,8 @@
|
||||
/* eslint-disable no-undef */
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
// eslint-disable-next-line global-require
|
||||
module.exports = require('./index.prod').default
|
||||
} else {
|
||||
// eslint-disable-next-line global-require
|
||||
module.exports = require('./index.dev').default
|
||||
}
|
||||
6
browser/main/DevTools/index.prod.js
Normal file
6
browser/main/DevTools/index.prod.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import React from 'react'
|
||||
|
||||
const DevTools = () => <div />
|
||||
DevTools.instrument = () => {}
|
||||
|
||||
export default DevTools
|
||||
@@ -12,11 +12,11 @@ import _ from 'lodash'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import mobileAnalytics from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import eventEmitter from 'browser/main/lib/eventEmitter'
|
||||
import { hashHistory } from 'react-router'
|
||||
import store from 'browser/main/store'
|
||||
import { store } from 'browser/main/store'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import { getLocales } from 'browser/lib/Languages'
|
||||
import applyShortcuts from 'browser/main/lib/shortcutManager'
|
||||
import { push } from 'connected-react-router'
|
||||
const path = require('path')
|
||||
const electron = require('electron')
|
||||
const { remote } = electron
|
||||
@@ -132,7 +132,7 @@ class Main extends React.Component {
|
||||
.then(() => data.storage)
|
||||
})
|
||||
.then(storage => {
|
||||
hashHistory.push('/storages/' + storage.key)
|
||||
store.dispatch(push('/storages/' + storage.key))
|
||||
})
|
||||
.catch(err => {
|
||||
throw err
|
||||
@@ -311,7 +311,7 @@ class Main extends React.Component {
|
||||
onMouseUp={e => this.handleMouseUp(e)}
|
||||
>
|
||||
<SideNav
|
||||
{..._.pick(this.props, ['dispatch', 'data', 'config', 'params', 'location'])}
|
||||
{..._.pick(this.props, ['dispatch', 'data', 'config', 'match', 'location'])}
|
||||
width={this.state.navWidth}
|
||||
/>
|
||||
{!config.isSideNavFolded &&
|
||||
@@ -341,7 +341,7 @@ class Main extends React.Component {
|
||||
'dispatch',
|
||||
'config',
|
||||
'data',
|
||||
'params',
|
||||
'match',
|
||||
'location'
|
||||
])}
|
||||
/>
|
||||
@@ -351,7 +351,7 @@ class Main extends React.Component {
|
||||
'dispatch',
|
||||
'data',
|
||||
'config',
|
||||
'params',
|
||||
'match',
|
||||
'location'
|
||||
])}
|
||||
/>
|
||||
@@ -373,7 +373,7 @@ class Main extends React.Component {
|
||||
'dispatch',
|
||||
'data',
|
||||
'config',
|
||||
'params',
|
||||
'match',
|
||||
'location'
|
||||
])}
|
||||
ignorePreviewPointerEvents={this.state.isRightSliderFocused}
|
||||
|
||||
@@ -21,23 +21,20 @@ class NewNoteButton extends React.Component {
|
||||
this.state = {
|
||||
}
|
||||
|
||||
this.newNoteHandler = () => {
|
||||
this.handleNewNoteButtonClick()
|
||||
}
|
||||
this.handleNewNoteButtonClick = this.handleNewNoteButtonClick.bind(this)
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
eventEmitter.on('top:new-note', this.newNoteHandler)
|
||||
eventEmitter.on('top:new-note', this.handleNewNoteButtonClick)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
eventEmitter.off('top:new-note', this.newNoteHandler)
|
||||
eventEmitter.off('top:new-note', this.handleNewNoteButtonClick)
|
||||
}
|
||||
|
||||
handleNewNoteButtonClick (e) {
|
||||
const { location, params, dispatch, config } = this.props
|
||||
const { location, dispatch, match: { params }, config } = this.props
|
||||
const { storage, folder } = this.resolveTargetFolder()
|
||||
|
||||
if (config.ui.defaultNote === 'MARKDOWN_NOTE') {
|
||||
createMarkdownNote(storage.key, folder.key, dispatch, location, params, config)
|
||||
} else if (config.ui.defaultNote === 'SNIPPET_NOTE') {
|
||||
@@ -55,9 +52,8 @@ class NewNoteButton extends React.Component {
|
||||
}
|
||||
|
||||
resolveTargetFolder () {
|
||||
const { data, params } = this.props
|
||||
const { data, match: { params } } = this.props
|
||||
let storage = data.storageMap.get(params.storageKey)
|
||||
|
||||
// Find first storage
|
||||
if (storage == null) {
|
||||
for (const kv of data.storageMap) {
|
||||
@@ -93,7 +89,7 @@ class NewNoteButton extends React.Component {
|
||||
>
|
||||
<div styleName='control'>
|
||||
<button styleName='control-newNoteButton'
|
||||
onClick={(e) => this.handleNewNoteButtonClick(e)}>
|
||||
onClick={this.handleNewNoteButtonClick}>
|
||||
<img styleName='iconTag' src='../resources/icon/icon-newnote.svg' />
|
||||
<span styleName='control-newNoteButton-tooltip'>
|
||||
{i18n.__('Make a note')} {OSX ? '⌘' : i18n.__('Ctrl')} + N
|
||||
|
||||
@@ -14,23 +14,42 @@ import NoteItemSimple from 'browser/components/NoteItemSimple'
|
||||
import searchFromNotes from 'browser/lib/search'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { hashHistory } from 'react-router'
|
||||
import { push, replace } from 'connected-react-router'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import Markdown from '../../lib/markdown'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
|
||||
import context from 'browser/lib/context'
|
||||
import queryString from 'query-string'
|
||||
|
||||
const { remote } = require('electron')
|
||||
const { dialog } = remote
|
||||
const WP_POST_PATH = '/wp/v2/posts'
|
||||
|
||||
const regexMatchStartingTitleNumber = new RegExp('^([0-9]*\.?[0-9]+).*$')
|
||||
|
||||
function sortByCreatedAt (a, b) {
|
||||
return new Date(b.createdAt) - new Date(a.createdAt)
|
||||
}
|
||||
|
||||
function sortByAlphabetical (a, b) {
|
||||
const matchA = regexMatchStartingTitleNumber.exec(a.title)
|
||||
const matchB = regexMatchStartingTitleNumber.exec(b.title)
|
||||
|
||||
if (matchA && matchA.length === 2 && matchB && matchB.length === 2) {
|
||||
// Both note titles are starting with a float. We will compare it now.
|
||||
const floatA = parseFloat(matchA[1])
|
||||
const floatB = parseFloat(matchB[1])
|
||||
|
||||
const diff = floatA - floatB
|
||||
if (diff !== 0) {
|
||||
return diff
|
||||
}
|
||||
|
||||
// The float values are equal. We will compare the full title.
|
||||
}
|
||||
|
||||
return a.title.localeCompare(b.title)
|
||||
}
|
||||
|
||||
@@ -127,15 +146,15 @@ class NoteList extends React.Component {
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
const { location } = this.props
|
||||
const { dispatch, location } = this.props
|
||||
const { selectedNoteKeys } = this.state
|
||||
const visibleNoteKeys = this.notes.map(note => note.key)
|
||||
const note = this.notes[0]
|
||||
const prevKey = prevProps.location.query.key
|
||||
const visibleNoteKeys = this.notes && this.notes.map(note => note.key)
|
||||
const note = this.notes && this.notes[0]
|
||||
const key = location.search && queryString.parse(location.search).key
|
||||
const prevKey = prevProps.location.search && queryString.parse(prevProps.location.search).key
|
||||
const noteKey = visibleNoteKeys.includes(prevKey) ? prevKey : note && note.key
|
||||
|
||||
if (note && location.query.key == null) {
|
||||
const { router } = this.context
|
||||
if (note && location.search === '') {
|
||||
if (!location.pathname.match(/\/searched/)) this.contextNotes = this.getContextNotes()
|
||||
|
||||
// A visible note is an active note
|
||||
@@ -145,17 +164,17 @@ class NoteList extends React.Component {
|
||||
ee.emit('list:moved')
|
||||
}
|
||||
|
||||
router.replace({
|
||||
dispatch(replace({ // was passed with context - we can use connected router here
|
||||
pathname: location.pathname,
|
||||
query: {
|
||||
search: queryString.stringify({
|
||||
key: noteKey
|
||||
}
|
||||
})
|
||||
})
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
// Auto scroll
|
||||
if (_.isString(location.query.key) && prevProps.location.query.key === location.query.key) {
|
||||
if (_.isString(key) && prevKey === key) {
|
||||
const targetIndex = this.getTargetIndex()
|
||||
if (targetIndex > -1) {
|
||||
const list = this.refs.list
|
||||
@@ -176,18 +195,18 @@ class NoteList extends React.Component {
|
||||
}
|
||||
|
||||
focusNote (selectedNoteKeys, noteKey, pathname) {
|
||||
const { router } = this.context
|
||||
const { dispatch } = this.props
|
||||
|
||||
this.setState({
|
||||
selectedNoteKeys
|
||||
})
|
||||
|
||||
router.push({
|
||||
dispatch(push({
|
||||
pathname,
|
||||
query: {
|
||||
search: queryString.stringify({
|
||||
key: noteKey
|
||||
}
|
||||
})
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
getNoteKeyFromTargetIndex (targetIndex) {
|
||||
@@ -259,13 +278,22 @@ class NoteList extends React.Component {
|
||||
}
|
||||
|
||||
jumpNoteByHashHandler (event, noteHash) {
|
||||
const { data } = this.props
|
||||
|
||||
// first argument event isn't used.
|
||||
if (this.notes === null || this.notes.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const selectedNoteKeys = [noteHash]
|
||||
this.focusNote(selectedNoteKeys, noteHash, '/home')
|
||||
|
||||
let locationToSelect = '/home'
|
||||
const noteByHash = data.noteMap.map((note) => note).find(note => note.key === noteHash)
|
||||
if (noteByHash !== undefined) {
|
||||
locationToSelect = '/storages/' + noteByHash.storage + '/folders/' + noteByHash.folder
|
||||
}
|
||||
|
||||
this.focusNote(selectedNoteKeys, noteHash, locationToSelect)
|
||||
|
||||
ee.emit('list:moved')
|
||||
}
|
||||
@@ -321,8 +349,7 @@ class NoteList extends React.Component {
|
||||
}
|
||||
|
||||
getNotes () {
|
||||
const { data, params, location } = this.props
|
||||
|
||||
const { data, match: { params }, location } = this.props
|
||||
if (location.pathname.match(/\/home/) || location.pathname.match(/alltags/)) {
|
||||
const allNotes = data.noteMap.map((note) => note)
|
||||
this.contextNotes = allNotes
|
||||
@@ -363,7 +390,7 @@ class NoteList extends React.Component {
|
||||
|
||||
// get notes in the current folder
|
||||
getContextNotes () {
|
||||
const { data, params } = this.props
|
||||
const { data, match: { params } } = this.props
|
||||
const storageKey = params.storageKey
|
||||
const folderKey = params.folderKey
|
||||
const storage = data.storageMap.get(storageKey)
|
||||
@@ -403,8 +430,7 @@ class NoteList extends React.Component {
|
||||
}
|
||||
|
||||
handleNoteClick (e, uniqueKey) {
|
||||
const { router } = this.context
|
||||
const { location } = this.props
|
||||
const { dispatch, location } = this.props
|
||||
let { selectedNoteKeys, prevShiftNoteIndex } = this.state
|
||||
const { ctrlKeyDown, shiftKeyDown } = this.state
|
||||
const hasSelectedNoteKey = selectedNoteKeys.length > 0
|
||||
@@ -455,16 +481,16 @@ class NoteList extends React.Component {
|
||||
prevShiftNoteIndex
|
||||
})
|
||||
|
||||
router.push({
|
||||
dispatch(push({
|
||||
pathname: location.pathname,
|
||||
query: {
|
||||
search: queryString.stringify({
|
||||
key: uniqueKey
|
||||
}
|
||||
})
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
handleSortByChange (e) {
|
||||
const { dispatch, params: { folderKey } } = this.props
|
||||
const { dispatch, match: { params: { folderKey } } } = this.props
|
||||
|
||||
const config = {
|
||||
[folderKey]: { sortBy: e.target.value }
|
||||
@@ -496,6 +522,7 @@ class NoteList extends React.Component {
|
||||
'export-txt': 'Text export',
|
||||
'export-md': 'Markdown export',
|
||||
'export-html': 'HTML export',
|
||||
'export-pdf': 'PDF export',
|
||||
'print': 'Print'
|
||||
})[msg]
|
||||
|
||||
@@ -716,7 +743,11 @@ class NoteList extends React.Component {
|
||||
folder: folder.key,
|
||||
title: firstNote.title + ' ' + i18n.__('copy'),
|
||||
content: firstNote.content,
|
||||
linesHighlighted: firstNote.linesHighlighted
|
||||
linesHighlighted: firstNote.linesHighlighted,
|
||||
description: firstNote.description,
|
||||
snippets: firstNote.snippets,
|
||||
tags: firstNote.tags,
|
||||
isStarred: firstNote.isStarred
|
||||
})
|
||||
.then((note) => {
|
||||
attachmentManagement.cloneAttachments(firstNote, note)
|
||||
@@ -732,10 +763,10 @@ class NoteList extends React.Component {
|
||||
selectedNoteKeys: [note.key]
|
||||
})
|
||||
|
||||
hashHistory.push({
|
||||
dispatch(push({
|
||||
pathname: location.pathname,
|
||||
query: {key: note.key}
|
||||
})
|
||||
search: queryString.stringify({key: note.key})
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -745,13 +776,13 @@ class NoteList extends React.Component {
|
||||
}
|
||||
|
||||
navigate (sender, pathname) {
|
||||
const { router } = this.context
|
||||
router.push({
|
||||
const { dispatch } = this.props
|
||||
dispatch(push({
|
||||
pathname,
|
||||
query: {
|
||||
search: queryString.stringify({
|
||||
// key: noteKey
|
||||
}
|
||||
})
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
save (note) {
|
||||
@@ -881,7 +912,7 @@ class NoteList extends React.Component {
|
||||
if (!location.pathname.match(/\/trashed/)) this.addNotesFromFiles(filepaths)
|
||||
}
|
||||
|
||||
// Add notes to the current folder
|
||||
// Add notes to the current folder
|
||||
addNotesFromFiles (filepaths) {
|
||||
const { dispatch, location } = this.props
|
||||
const { storage, folder } = this.resolveTargetFolder()
|
||||
@@ -905,13 +936,20 @@ class NoteList extends React.Component {
|
||||
}
|
||||
dataApi.createNote(storage.key, newNote)
|
||||
.then((note) => {
|
||||
dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note: note
|
||||
})
|
||||
hashHistory.push({
|
||||
pathname: location.pathname,
|
||||
query: {key: getNoteKey(note)}
|
||||
attachmentManagement.importAttachments(note.content, filepath, storage.key, note.key)
|
||||
.then((newcontent) => {
|
||||
note.content = newcontent
|
||||
|
||||
dataApi.updateNote(storage.key, note.key, note)
|
||||
|
||||
dispatch({
|
||||
type: 'UPDATE_NOTE',
|
||||
note: note
|
||||
})
|
||||
dispatch(push({
|
||||
pathname: location.pathname,
|
||||
search: queryString.stringify({key: getNoteKey(note)})
|
||||
}))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -921,14 +959,15 @@ class NoteList extends React.Component {
|
||||
|
||||
getTargetIndex () {
|
||||
const { location } = this.props
|
||||
const key = queryString.parse(location.search).key
|
||||
const targetIndex = _.findIndex(this.notes, (note) => {
|
||||
return getNoteKey(note) === location.query.key
|
||||
return getNoteKey(note) === key
|
||||
})
|
||||
return targetIndex
|
||||
}
|
||||
|
||||
resolveTargetFolder () {
|
||||
const { data, params } = this.props
|
||||
const { data, match: { params } } = this.props
|
||||
let storage = data.storageMap.get(params.storageKey)
|
||||
|
||||
// Find first storage
|
||||
@@ -976,7 +1015,7 @@ class NoteList extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { location, config, params: { folderKey } } = this.props
|
||||
const { location, config, match: { params: { folderKey } } } = this.props
|
||||
let { notes } = this.props
|
||||
const { selectedNoteKeys } = this.state
|
||||
const sortBy = _.get(config, [folderKey, 'sortBy'], config.sortBy.default)
|
||||
|
||||
@@ -2,7 +2,6 @@ import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './StorageItem.styl'
|
||||
import { hashHistory } from 'react-router'
|
||||
import modal from 'browser/main/lib/modal'
|
||||
import CreateFolderModal from 'browser/main/modals/CreateFolderModal'
|
||||
import RenameFolderModal from 'browser/main/modals/RenameFolderModal'
|
||||
@@ -12,6 +11,7 @@ import _ from 'lodash'
|
||||
import { SortableElement } from 'react-sortable-hoc'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import context from 'browser/lib/context'
|
||||
import { push } from 'connected-react-router'
|
||||
|
||||
const { remote } = require('electron')
|
||||
const { dialog } = remote
|
||||
@@ -134,14 +134,14 @@ class StorageItem extends React.Component {
|
||||
}
|
||||
|
||||
handleHeaderInfoClick (e) {
|
||||
const { storage } = this.props
|
||||
hashHistory.push('/storages/' + storage.key)
|
||||
const { storage, dispatch } = this.props
|
||||
dispatch(push('/storages/' + storage.key))
|
||||
}
|
||||
|
||||
handleFolderButtonClick (folderKey) {
|
||||
return (e) => {
|
||||
const { storage } = this.props
|
||||
hashHistory.push('/storages/' + storage.key + '/folders/' + folderKey)
|
||||
const { storage, dispatch } = this.props
|
||||
dispatch(push('/storages/' + storage.key + '/folders/' + folderKey))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types'
|
||||
import React from 'react'
|
||||
import { push } from 'connected-react-router'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import styles from './SideNav.styl'
|
||||
@@ -62,7 +63,7 @@ class SideNav extends React.Component {
|
||||
})
|
||||
|
||||
if (selectedButton === 0) {
|
||||
const { data, dispatch, location, params } = this.props
|
||||
const { data, dispatch, location, match: { params } } = this.props
|
||||
|
||||
const notes = data.noteMap
|
||||
.map(note => note)
|
||||
@@ -92,7 +93,7 @@ class SideNav extends React.Component {
|
||||
if (index !== -1) {
|
||||
tags.splice(index, 1)
|
||||
|
||||
this.context.router.push(`/tags/${tags.map(tag => encodeURIComponent(tag)).join(' ')}`)
|
||||
dispatch(push(`/tags/${tags.map(tag => encodeURIComponent(tag)).join(' ')}`))
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -104,13 +105,13 @@ class SideNav extends React.Component {
|
||||
}
|
||||
|
||||
handleHomeButtonClick (e) {
|
||||
const { router } = this.context
|
||||
router.push('/home')
|
||||
const { dispatch } = this.props
|
||||
dispatch(push('/home'))
|
||||
}
|
||||
|
||||
handleStarredButtonClick (e) {
|
||||
const { router } = this.context
|
||||
router.push('/starred')
|
||||
const { dispatch } = this.props
|
||||
dispatch(push('/starred'))
|
||||
}
|
||||
|
||||
handleTagContextMenu (e, tag) {
|
||||
@@ -190,18 +191,18 @@ class SideNav extends React.Component {
|
||||
}
|
||||
|
||||
handleTrashedButtonClick (e) {
|
||||
const { router } = this.context
|
||||
router.push('/trashed')
|
||||
const { dispatch } = this.props
|
||||
dispatch(push('/trashed'))
|
||||
}
|
||||
|
||||
handleSwitchFoldersButtonClick () {
|
||||
const { router } = this.context
|
||||
router.push('/home')
|
||||
const { dispatch } = this.props
|
||||
dispatch(push('/home'))
|
||||
}
|
||||
|
||||
handleSwitchTagsButtonClick () {
|
||||
const { router } = this.context
|
||||
router.push('/alltags')
|
||||
const { dispatch } = this.props
|
||||
dispatch(push('/alltags'))
|
||||
}
|
||||
|
||||
onSortEnd (storage) {
|
||||
@@ -348,8 +349,8 @@ class SideNav extends React.Component {
|
||||
}
|
||||
|
||||
handleClickTagListItem (name) {
|
||||
const { router } = this.context
|
||||
router.push(`/tags/${encodeURIComponent(name)}`)
|
||||
const { dispatch } = this.props
|
||||
dispatch(push(`/tags/${encodeURIComponent(name)}`))
|
||||
}
|
||||
|
||||
handleSortTagsByChange (e) {
|
||||
@@ -367,8 +368,7 @@ class SideNav extends React.Component {
|
||||
}
|
||||
|
||||
handleClickNarrowToTag (tag) {
|
||||
const { router } = this.context
|
||||
const { location } = this.props
|
||||
const { dispatch, location } = this.props
|
||||
const listOfTags = this.getActiveTags(location.pathname)
|
||||
const indexOfTag = listOfTags.indexOf(tag)
|
||||
if (indexOfTag > -1) {
|
||||
@@ -376,7 +376,7 @@ class SideNav extends React.Component {
|
||||
} else {
|
||||
listOfTags.push(tag)
|
||||
}
|
||||
router.push(`/tags/${encodeURIComponent(listOfTags.join(' '))}`)
|
||||
dispatch(push(`/tags/${encodeURIComponent(listOfTags.join(' '))}`))
|
||||
}
|
||||
|
||||
emptyTrash (entries) {
|
||||
|
||||
@@ -7,6 +7,8 @@ import ee from 'browser/main/lib/eventEmitter'
|
||||
import NewNoteButton from 'browser/main/NewNoteButton'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import debounce from 'lodash/debounce'
|
||||
import { push } from 'connected-react-router'
|
||||
import queryString from 'query-string'
|
||||
|
||||
class TopBar extends React.Component {
|
||||
constructor (props) {
|
||||
@@ -26,6 +28,8 @@ class TopBar extends React.Component {
|
||||
}
|
||||
|
||||
this.codeInitHandler = this.handleCodeInit.bind(this)
|
||||
this.updateKeyword = this.updateKeyword.bind(this)
|
||||
this.handleSearchClearButton = this.handleSearchClearButton.bind(this)
|
||||
|
||||
this.updateKeyword = debounce(this.updateKeyword, 1000 / 60, {
|
||||
maxWait: 1000 / 8
|
||||
@@ -33,8 +37,8 @@ class TopBar extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { params } = this.props
|
||||
const searchWord = params.searchword
|
||||
const { match: { params } } = this.props
|
||||
const searchWord = params && params.searchword
|
||||
if (searchWord !== undefined) {
|
||||
this.setState({
|
||||
search: searchWord,
|
||||
@@ -51,13 +55,13 @@ class TopBar extends React.Component {
|
||||
}
|
||||
|
||||
handleSearchClearButton (e) {
|
||||
const { router } = this.context
|
||||
const { dispatch } = this.props
|
||||
this.setState({
|
||||
search: '',
|
||||
isSearching: false
|
||||
})
|
||||
this.refs.search.childNodes[0].blur
|
||||
router.push('/searched')
|
||||
dispatch(push('/searched'))
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
@@ -124,7 +128,8 @@ class TopBar extends React.Component {
|
||||
}
|
||||
|
||||
updateKeyword (keyword) {
|
||||
this.context.router.push(`/searched/${encodeURIComponent(keyword)}`)
|
||||
const { dispatch } = this.props
|
||||
dispatch(push(`/searched/${encodeURIComponent(keyword)}`))
|
||||
this.setState({
|
||||
search: keyword
|
||||
})
|
||||
@@ -210,8 +215,8 @@ class TopBar extends React.Component {
|
||||
'dispatch',
|
||||
'data',
|
||||
'config',
|
||||
'params',
|
||||
'location'
|
||||
'location',
|
||||
'match'
|
||||
])}
|
||||
/>}
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { Provider } from 'react-redux'
|
||||
import Main from './Main'
|
||||
import store from './store'
|
||||
import React from 'react'
|
||||
import { store, history } from './store'
|
||||
import React, { Fragment } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
require('!!style!css!stylus?sourceMap!./global.styl')
|
||||
import { Router, Route, IndexRoute, IndexRedirect, hashHistory } from 'react-router'
|
||||
import { syncHistoryWithStore } from 'react-router-redux'
|
||||
import { Route, Switch, Redirect } from 'react-router-dom'
|
||||
import { ConnectedRouter } from 'connected-react-router'
|
||||
import DevTools from './DevTools'
|
||||
|
||||
require('./lib/ipcClient')
|
||||
require('../lib/customMeta')
|
||||
import i18n from 'browser/lib/i18n'
|
||||
@@ -77,7 +79,6 @@ document.addEventListener('click', function (e) {
|
||||
})
|
||||
|
||||
const el = document.getElementById('content')
|
||||
const history = syncHistoryWithStore(hashHistory, store)
|
||||
|
||||
function notify (...args) {
|
||||
return new window.Notification(...args)
|
||||
@@ -98,29 +99,24 @@ function updateApp () {
|
||||
|
||||
ReactDOM.render((
|
||||
<Provider store={store}>
|
||||
<Router history={history}>
|
||||
<Route path='/' component={Main}>
|
||||
<IndexRedirect to='/home' />
|
||||
<Route path='home' />
|
||||
<Route path='starred' />
|
||||
<Route path='searched'>
|
||||
<Route path=':searchword' />
|
||||
</Route>
|
||||
<Route path='trashed' />
|
||||
<Route path='alltags' />
|
||||
<Route path='tags'>
|
||||
<IndexRedirect to='/alltags' />
|
||||
<Route path=':tagname' />
|
||||
</Route>
|
||||
<Route path='storages'>
|
||||
<IndexRedirect to='/home' />
|
||||
<Route path=':storageKey'>
|
||||
<IndexRoute />
|
||||
<Route path='folders/:folderKey' />
|
||||
</Route>
|
||||
</Route>
|
||||
</Route>
|
||||
</Router>
|
||||
<ConnectedRouter history={history}>
|
||||
<Fragment>
|
||||
<Switch>
|
||||
<Redirect path='/' to='/home' exact />
|
||||
<Route path='/(home|alltags|starred|trashed)' component={Main} />
|
||||
<Route path='/searched' component={Main} exact />
|
||||
<Route path='/searched/:searchword' component={Main} />
|
||||
<Redirect path='/tags' to='/alltags' exact />
|
||||
<Route path='/tags/:tagname' component={Main} />
|
||||
|
||||
{/* storages */}
|
||||
<Redirect path='/storages' to='/home' exact />
|
||||
<Route path='/storages/:storageKey' component={Main} exact />
|
||||
<Route path='/storages/:storageKey/folders/:folderKey' component={Main} />
|
||||
</Switch>
|
||||
<DevTools />
|
||||
</Fragment>
|
||||
</ConnectedRouter>
|
||||
</Provider>
|
||||
), el, function () {
|
||||
const loadingCover = document.getElementById('loadingCover')
|
||||
|
||||
@@ -11,6 +11,10 @@ const consts = require('browser/lib/consts')
|
||||
|
||||
let isInitialized = false
|
||||
|
||||
const DEFAULT_MARKDOWN_LINT_CONFIG = `{
|
||||
"default": true
|
||||
}`
|
||||
|
||||
export const DEFAULT_CONFIG = {
|
||||
zoom: 1,
|
||||
isSideNavFolded: false,
|
||||
@@ -47,7 +51,7 @@ export const DEFAULT_CONFIG = {
|
||||
enableRulers: false,
|
||||
rulers: [80, 120],
|
||||
displayLineNumbers: true,
|
||||
matchingPairs: '()[]{}\'\'""$$**``',
|
||||
matchingPairs: '()[]{}\'\'""$$**``~~__',
|
||||
matchingTriples: '```"""\'\'\'',
|
||||
explodingPairs: '[]{}``$$',
|
||||
switchPreview: 'BLUR', // 'BLUR', 'DBL_CLICK', 'RIGHTCLICK'
|
||||
@@ -60,7 +64,9 @@ export const DEFAULT_CONFIG = {
|
||||
enableFrontMatterTitle: true,
|
||||
frontMatterTitleField: 'title',
|
||||
spellcheck: false,
|
||||
enableSmartPaste: false
|
||||
enableSmartPaste: false,
|
||||
enableMarkdownLint: false,
|
||||
customMarkdownLintConfig: DEFAULT_MARKDOWN_LINT_CONFIG
|
||||
},
|
||||
preview: {
|
||||
fontSize: '14',
|
||||
@@ -133,16 +139,12 @@ function get () {
|
||||
document.head.appendChild(editorTheme)
|
||||
}
|
||||
|
||||
config.editor.theme = consts.THEMES.some((theme) => theme === config.editor.theme)
|
||||
? config.editor.theme
|
||||
: 'default'
|
||||
const theme = consts.THEMES.find(theme => theme.name === config.editor.theme)
|
||||
|
||||
if (config.editor.theme !== 'default') {
|
||||
if (config.editor.theme.startsWith('solarized')) {
|
||||
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/solarized.css')
|
||||
} else {
|
||||
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/' + config.editor.theme + '.css')
|
||||
}
|
||||
if (theme) {
|
||||
editorTheme.setAttribute('href', `../${theme.path}`)
|
||||
} else {
|
||||
config.editor.theme = 'default'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,16 +180,11 @@ function set (updates) {
|
||||
editorTheme.setAttribute('rel', 'stylesheet')
|
||||
document.head.appendChild(editorTheme)
|
||||
}
|
||||
const newTheme = consts.THEMES.some((theme) => theme === newConfig.editor.theme)
|
||||
? newConfig.editor.theme
|
||||
: 'default'
|
||||
|
||||
if (newTheme !== 'default') {
|
||||
if (newTheme.startsWith('solarized')) {
|
||||
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/solarized.css')
|
||||
} else {
|
||||
editorTheme.setAttribute('href', '../node_modules/codemirror/theme/' + newTheme + '.css')
|
||||
}
|
||||
const newTheme = consts.THEMES.find(theme => theme.name === newConfig.editor.theme)
|
||||
|
||||
if (newTheme) {
|
||||
editorTheme.setAttribute('href', `../${newTheme.path}`)
|
||||
}
|
||||
|
||||
ipcRenderer.send('config-renew', {
|
||||
|
||||
@@ -85,7 +85,7 @@ function getOrientation (file) {
|
||||
return view.getUint16(offset + (i * 12) + 8, little)
|
||||
}
|
||||
}
|
||||
} else if ((marker & 0xFF00) !== 0xFF00) { // If not start with 0xFF, not a Marker
|
||||
} else if ((marker & 0xFF00) !== 0xFF00) { // If not start with 0xFF, not a Marker.
|
||||
break
|
||||
} else {
|
||||
offset += view.getUint16(offset, false)
|
||||
@@ -278,27 +278,40 @@ function handleAttachmentDrop (codeEditor, storageKey, noteKey, dropEvent) {
|
||||
let promise
|
||||
if (dropEvent.dataTransfer.files.length > 0) {
|
||||
promise = Promise.all(Array.from(dropEvent.dataTransfer.files).map(file => {
|
||||
if (file.type.startsWith('image')) {
|
||||
if (file.type === 'image/gif' || file.type === 'image/svg+xml') {
|
||||
return copyAttachment(file.path, storageKey, noteKey).then(fileName => ({
|
||||
const filePath = file.path
|
||||
const fileType = file.type // EX) 'image/gif' or 'text/html'
|
||||
if (fileType.startsWith('image')) {
|
||||
if (fileType === 'image/gif' || fileType === 'image/svg+xml') {
|
||||
return copyAttachment(filePath, storageKey, noteKey).then(fileName => ({
|
||||
fileName,
|
||||
title: path.basename(file.path),
|
||||
title: path.basename(filePath),
|
||||
isImage: true
|
||||
}))
|
||||
} else {
|
||||
return fixRotate(file)
|
||||
.then(data => copyAttachment({type: 'base64', data: data, sourceFilePath: file.path}, storageKey, noteKey)
|
||||
.then(fileName => ({
|
||||
return getOrientation(file)
|
||||
.then((orientation) => {
|
||||
if (orientation === -1) { // The image rotation is correct and does not need adjustment
|
||||
return copyAttachment(filePath, storageKey, noteKey)
|
||||
} else {
|
||||
return fixRotate(file).then(data => copyAttachment({
|
||||
type: 'base64',
|
||||
data: data,
|
||||
sourceFilePath: filePath
|
||||
}, storageKey, noteKey))
|
||||
}
|
||||
})
|
||||
.then(fileName =>
|
||||
({
|
||||
fileName,
|
||||
title: path.basename(file.path),
|
||||
title: path.basename(filePath),
|
||||
isImage: true
|
||||
}))
|
||||
})
|
||||
)
|
||||
}
|
||||
} else {
|
||||
return copyAttachment(file.path, storageKey, noteKey).then(fileName => ({
|
||||
return copyAttachment(filePath, storageKey, noteKey).then(fileName => ({
|
||||
fileName,
|
||||
title: path.basename(file.path),
|
||||
title: path.basename(filePath),
|
||||
isImage: false
|
||||
}))
|
||||
}
|
||||
@@ -325,13 +338,18 @@ function handleAttachmentDrop (codeEditor, storageKey, noteKey, dropEvent) {
|
||||
canvas.height = image.height
|
||||
context.drawImage(image, 0, 0)
|
||||
|
||||
return copyAttachment({type: 'base64', data: canvas.toDataURL(), sourceFilePath: imageURL}, storageKey, noteKey)
|
||||
return copyAttachment({
|
||||
type: 'base64',
|
||||
data: canvas.toDataURL(),
|
||||
sourceFilePath: imageURL
|
||||
}, storageKey, noteKey)
|
||||
})
|
||||
.then(fileName => ({
|
||||
fileName,
|
||||
title: imageURL,
|
||||
isImage: true
|
||||
}))])
|
||||
}))
|
||||
])
|
||||
}
|
||||
|
||||
promise.then(files => {
|
||||
@@ -449,6 +467,54 @@ function getAbsolutePathsOfAttachmentsInContent (markdownContent, storagePath) {
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Copies the attachments to the storage folder and returns the mardown content it should be replaced with
|
||||
* @param {String} markDownContent content in which the attachment paths should be found
|
||||
* @param {String} filepath The path of the file with attachments to import
|
||||
* @param {String} storageKey Storage key of the destination storage
|
||||
* @param {String} noteKey Key of the current note. Will be used as subfolder in :storage
|
||||
*/
|
||||
function importAttachments (markDownContent, filepath, storageKey, noteKey) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const nameRegex = /(!\[.*?]\()(.+?\..+?)(\))/g
|
||||
let attachPath = nameRegex.exec(markDownContent)
|
||||
const promiseArray = []
|
||||
const attachmentPaths = []
|
||||
const groupIndex = 2
|
||||
|
||||
while (attachPath) {
|
||||
let attachmentPath = attachPath[groupIndex]
|
||||
attachmentPaths.push(attachmentPath)
|
||||
attachmentPath = path.isAbsolute(attachmentPath) ? attachmentPath : path.join(path.dirname(filepath), attachmentPath)
|
||||
promiseArray.push(this.copyAttachment(attachmentPath, storageKey, noteKey))
|
||||
attachPath = nameRegex.exec(markDownContent)
|
||||
}
|
||||
|
||||
let numResolvedPromises = 0
|
||||
|
||||
if (promiseArray.length === 0) {
|
||||
resolve(markDownContent)
|
||||
}
|
||||
|
||||
for (let j = 0; j < promiseArray.length; j++) {
|
||||
promiseArray[j]
|
||||
.then((fileName) => {
|
||||
const newPath = path.join(STORAGE_FOLDER_PLACEHOLDER, noteKey, fileName)
|
||||
markDownContent = markDownContent.replace(attachmentPaths[j], newPath)
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error('File does not exist in path: ' + attachmentPaths[j])
|
||||
})
|
||||
.finally(() => {
|
||||
numResolvedPromises++
|
||||
if (numResolvedPromises === promiseArray.length) {
|
||||
resolve(markDownContent)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Moves the attachments of the current note to the new location.
|
||||
* Returns a modified version of the given content so that the links to the attachments point to the new note key.
|
||||
@@ -656,6 +722,7 @@ module.exports = {
|
||||
handlePasteNativeImage,
|
||||
getAttachmentsInMarkdownContent,
|
||||
getAbsolutePathsOfAttachmentsInContent,
|
||||
importAttachments,
|
||||
removeStorageAndNoteReferences,
|
||||
deleteAttachmentFolder,
|
||||
deleteAttachmentsNotPresentInNote,
|
||||
|
||||
@@ -43,19 +43,18 @@ function exportFolder (storageKey, folderKey, fileType, exportDir) {
|
||||
.then(function exportNotes (data) {
|
||||
const { storage, notes } = data
|
||||
|
||||
notes
|
||||
return Promise.all(notes
|
||||
.filter(note => note.folder === folderKey && note.isTrashed === false && note.type === 'MARKDOWN_NOTE')
|
||||
.forEach(note => {
|
||||
.map(note => {
|
||||
const notePath = path.join(exportDir, `${filenamify(note.title, {replacement: '_'})}.${fileType}`)
|
||||
exportNote(note.key, storage.path, note.content, notePath, null)
|
||||
return exportNote(note.key, storage.path, note.content, notePath, null)
|
||||
})
|
||||
|
||||
return {
|
||||
).then(() => ({
|
||||
storage,
|
||||
folderKey,
|
||||
fileType,
|
||||
exportDir
|
||||
}
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -43,14 +43,17 @@ function exportNote (nodeKey, storageKey, noteContent, targetPath, outputFormatt
|
||||
)
|
||||
|
||||
if (outputFormatter) {
|
||||
exportedData = outputFormatter(exportedData, exportTasks)
|
||||
exportedData = outputFormatter(exportedData, exportTasks, path.dirname(targetPath))
|
||||
} else {
|
||||
exportedData = Promise.resolve(exportedData)
|
||||
}
|
||||
|
||||
const tasks = prepareTasks(exportTasks, storagePath, path.dirname(targetPath))
|
||||
|
||||
return Promise.all(tasks.map((task) => copyFile(task.src, task.dst)))
|
||||
.then(() => {
|
||||
return saveToFile(exportedData, targetPath)
|
||||
.then(() => exportedData)
|
||||
.then(data => {
|
||||
return saveToFile(data, targetPath)
|
||||
}).catch((err) => {
|
||||
rollbackExport(tasks)
|
||||
throw err
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
import { Provider } from 'react-redux'
|
||||
import ReactDOM from 'react-dom'
|
||||
import store from '../store'
|
||||
import { store } from '../store'
|
||||
|
||||
class ModalBase extends React.Component {
|
||||
constructor (props) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './CreateFolderModal.styl'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import store from 'browser/main/store'
|
||||
import { store } from 'browser/main/store'
|
||||
import consts from 'browser/lib/consts'
|
||||
import ModalEscButton from 'browser/components/ModalEscButton'
|
||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
|
||||
@@ -4,11 +4,12 @@ import styles from './NewNoteModal.styl'
|
||||
import ModalEscButton from 'browser/components/ModalEscButton'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
import { createMarkdownNote, createSnippetNote } from 'browser/lib/newNote'
|
||||
import queryString from 'query-string'
|
||||
|
||||
class NewNoteModal extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.lock = false
|
||||
this.state = {}
|
||||
}
|
||||
|
||||
@@ -21,10 +22,14 @@ class NewNoteModal extends React.Component {
|
||||
}
|
||||
|
||||
handleMarkdownNoteButtonClick (e) {
|
||||
const { storage, folder, dispatch, location, params, config } = this.props
|
||||
createMarkdownNote(storage, folder, dispatch, location, params, config).then(() => {
|
||||
setTimeout(this.props.close, 200)
|
||||
})
|
||||
const { storage, folder, dispatch, location, config } = this.props
|
||||
const params = location.search !== '' && queryString.parse(location.search)
|
||||
if (!this.lock) {
|
||||
this.lock = true
|
||||
createMarkdownNote(storage, folder, dispatch, location, params, config).then(() => {
|
||||
setTimeout(this.props.close, 200)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
handleMarkdownNoteButtonKeyDown (e) {
|
||||
@@ -35,10 +40,14 @@ class NewNoteModal extends React.Component {
|
||||
}
|
||||
|
||||
handleSnippetNoteButtonClick (e) {
|
||||
const { storage, folder, dispatch, location, params, config } = this.props
|
||||
createSnippetNote(storage, folder, dispatch, location, params, config).then(() => {
|
||||
setTimeout(this.props.close, 200)
|
||||
})
|
||||
const { storage, folder, dispatch, location, config } = this.props
|
||||
const params = location.search !== '' && queryString.parse(location.search)
|
||||
if (!this.lock) {
|
||||
this.lock = true
|
||||
createSnippetNote(storage, folder, dispatch, location, params, config).then(() => {
|
||||
setTimeout(this.props.close, 200)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
handleSnippetNoteButtonKeyDown (e) {
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './ConfigTab.styl'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import store from 'browser/main/store'
|
||||
import { store } from 'browser/main/store'
|
||||
import PropTypes from 'prop-types'
|
||||
import _ from 'lodash'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
@@ -18,6 +18,14 @@
|
||||
margin-bottom 15px
|
||||
margin-top 30px
|
||||
|
||||
.group-header--sub
|
||||
@extend .group-header
|
||||
margin-bottom 10px
|
||||
|
||||
.group-header2--sub
|
||||
@extend .group-header2
|
||||
margin-bottom 10px
|
||||
|
||||
.group-section
|
||||
margin-bottom 20px
|
||||
display flex
|
||||
@@ -148,10 +156,12 @@ body[data-theme="dark"]
|
||||
color $ui-dark-text-color
|
||||
|
||||
.group-header
|
||||
.group-header--sub
|
||||
color $ui-dark-text-color
|
||||
border-color $ui-dark-borderColor
|
||||
|
||||
.group-header2
|
||||
.group-header2--sub
|
||||
color $ui-dark-text-color
|
||||
|
||||
.group-section-control-input
|
||||
@@ -176,10 +186,12 @@ body[data-theme="solarized-dark"]
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
.group-header
|
||||
.group-header--sub
|
||||
color $ui-solarized-dark-text-color
|
||||
border-color $ui-solarized-dark-borderColor
|
||||
|
||||
.group-header2
|
||||
.group-header2--sub
|
||||
color $ui-solarized-dark-text-color
|
||||
|
||||
.group-section-control-input
|
||||
@@ -203,10 +215,12 @@ body[data-theme="monokai"]
|
||||
color $ui-monokai-text-color
|
||||
|
||||
.group-header
|
||||
.group-header--sub
|
||||
color $ui-monokai-text-color
|
||||
border-color $ui-monokai-borderColor
|
||||
|
||||
.group-header2
|
||||
.group-header2--sub
|
||||
color $ui-monokai-text-color
|
||||
|
||||
.group-section-control-input
|
||||
@@ -230,10 +244,12 @@ body[data-theme="dracula"]
|
||||
color $ui-dracula-text-color
|
||||
|
||||
.group-header
|
||||
.group-header--sub
|
||||
color $ui-dracula-text-color
|
||||
border-color $ui-dracula-borderColor
|
||||
|
||||
.group-header2
|
||||
.group-header2--sub
|
||||
color $ui-dracula-text-color
|
||||
|
||||
.group-section-control-input
|
||||
|
||||
@@ -22,18 +22,16 @@ class Crowdfunding extends React.Component {
|
||||
render () {
|
||||
return (
|
||||
<div styleName='root'>
|
||||
<div styleName='header'>{i18n.__('Crowdfunding')}</div>
|
||||
<div styleName='group-header'>{i18n.__('Crowdfunding')}</div>
|
||||
<p>{i18n.__('Thank you for using Boostnote!')}</p>
|
||||
<br />
|
||||
<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.__('### Sustainable Open Source Ecosystem')}</p>
|
||||
<div styleName='group-header2--sub'>{i18n.__('Sustainable Open Source Ecosystem')}</div>
|
||||
<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. We’ve 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.__('### We believe Meritocracy')}</p>
|
||||
<div styleName='group-header2--sub'>{i18n.__('We believe Meritocracy')}</div>
|
||||
<p>{i18n.__('We think developers who have skills and do 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>
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
@import('./Tab')
|
||||
@import('./ConfigTab')
|
||||
|
||||
.root
|
||||
padding 15px
|
||||
white-space pre
|
||||
line-height 1.4
|
||||
color alpha($ui-text-color, 90%)
|
||||
width 100%
|
||||
font-size 14px
|
||||
p
|
||||
font-size 16px
|
||||
line-height 1.4
|
||||
|
||||
.cf-link
|
||||
height 35px
|
||||
|
||||
@@ -4,7 +4,7 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import ReactDOM from 'react-dom'
|
||||
import styles from './FolderItem.styl'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import store from 'browser/main/store'
|
||||
import { store } from 'browser/main/store'
|
||||
import { SketchPicker } from 'react-color'
|
||||
import { SortableElement, SortableHandle } from 'react-sortable-hoc'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import styles from './FolderList.styl'
|
||||
import store from 'browser/main/store'
|
||||
import { store } from 'browser/main/store'
|
||||
import FolderItem from './FolderItem'
|
||||
import { SortableContainer } from 'react-sortable-hoc'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './ConfigTab.styl'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import store from 'browser/main/store'
|
||||
import { store } from 'browser/main/store'
|
||||
import _ from 'lodash'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './InfoTab.styl'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import store from 'browser/main/store'
|
||||
import { store } from 'browser/main/store'
|
||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||
import _ from 'lodash'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
@@ -69,8 +69,7 @@ class InfoTab extends React.Component {
|
||||
render () {
|
||||
return (
|
||||
<div styleName='root'>
|
||||
|
||||
<div styleName='header--sub'>{i18n.__('Community')}</div>
|
||||
<div styleName='group-header'>{i18n.__('Community')}</div>
|
||||
<div styleName='top'>
|
||||
<ul styleName='list'>
|
||||
<li>
|
||||
@@ -108,7 +107,7 @@ class InfoTab extends React.Component {
|
||||
|
||||
<hr />
|
||||
|
||||
<div styleName='header--sub'>{i18n.__('About')}</div>
|
||||
<div styleName='group-header--sub'>{i18n.__('About')}</div>
|
||||
|
||||
<div styleName='top'>
|
||||
<div styleName='icon-space'>
|
||||
@@ -143,7 +142,7 @@ class InfoTab extends React.Component {
|
||||
|
||||
<hr styleName='separate-line' />
|
||||
|
||||
<div styleName='policy'>{i18n.__('Analytics')}</div>
|
||||
<div styleName='group-header2--sub'>{i18n.__('Analytics')}</div>
|
||||
<div>{i18n.__('Boostnote collects anonymous data for the sole purpose of improving the application, and strictly does not collect any personal information such the contents of your notes.')}</div>
|
||||
<div>{i18n.__('You can see how it works on ')}<a href='https://github.com/BoostIO/Boostnote' onClick={(e) => this.handleLinkClick(e)}>GitHub</a>.</div>
|
||||
<br />
|
||||
|
||||
@@ -1,16 +1,4 @@
|
||||
@import('./Tab')
|
||||
|
||||
.root
|
||||
padding 15px
|
||||
white-space pre
|
||||
line-height 1.4
|
||||
color alpha($ui-text-color, 90%)
|
||||
width 100%
|
||||
font-size 14px
|
||||
|
||||
.top
|
||||
text-align left
|
||||
margin-bottom 20px
|
||||
@import('./ConfigTab.styl')
|
||||
|
||||
.icon-space
|
||||
margin 20px 0
|
||||
@@ -45,13 +33,21 @@
|
||||
.separate-line
|
||||
margin 40px 0
|
||||
|
||||
.policy
|
||||
width 100%
|
||||
font-size 20px
|
||||
margin-bottom 10px
|
||||
|
||||
.policy-submit
|
||||
margin-top 10px
|
||||
height 35px
|
||||
border-radius 2px
|
||||
border none
|
||||
background-color alpha(#1EC38B, 90%)
|
||||
padding-left 20px
|
||||
padding-right 20px
|
||||
text-decoration none
|
||||
color white
|
||||
font-weight 600
|
||||
font-size 16px
|
||||
&:hover
|
||||
background-color #1EC38B
|
||||
transition 0.2s
|
||||
|
||||
.policy-confirm
|
||||
margin-top 10px
|
||||
@@ -60,11 +56,14 @@
|
||||
body[data-theme="dark"]
|
||||
.root
|
||||
color alpha($tab--dark-text-color, 80%)
|
||||
|
||||
.appId
|
||||
color $ui-dark-text-color
|
||||
|
||||
body[data-theme="solarized-dark"]
|
||||
.root
|
||||
color $ui-solarized-dark-text-color
|
||||
.appId
|
||||
color $ui-solarized-dark-text-color
|
||||
.list
|
||||
a
|
||||
color $ui-solarized-dark-active-color
|
||||
@@ -72,6 +71,8 @@ body[data-theme="solarized-dark"]
|
||||
body[data-theme="monokai"]
|
||||
.root
|
||||
color $ui-monokai-text-color
|
||||
.appId
|
||||
color $ui-monokai-text-color
|
||||
.list
|
||||
a
|
||||
color $ui-monokai-active-color
|
||||
@@ -79,6 +80,8 @@ body[data-theme="monokai"]
|
||||
body[data-theme="dracula"]
|
||||
.root
|
||||
color $ui-dracula-text-color
|
||||
.appId
|
||||
color $ui-dracula-text-color
|
||||
.list
|
||||
a
|
||||
color $ui-dracula-active-color
|
||||
color $ui-dracula-active-color
|
||||
@@ -4,6 +4,7 @@ import _ from 'lodash'
|
||||
import styles from './SnippetTab.styl'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import snippetManager from '../../../lib/SnippetManager'
|
||||
|
||||
const defaultEditorFontFamily = ['Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', 'monospace']
|
||||
const buildCMRulers = (rulers, enableRulers) =>
|
||||
@@ -64,7 +65,9 @@ class SnippetEditor extends React.Component {
|
||||
}
|
||||
|
||||
saveSnippet () {
|
||||
dataApi.updateSnippet(this.snippet).catch((err) => { throw err })
|
||||
dataApi.updateSnippet(this.snippet)
|
||||
.then(snippets => snippetManager.assignSnippets(snippets))
|
||||
.catch((err) => { throw err })
|
||||
}
|
||||
|
||||
render () {
|
||||
|
||||
@@ -91,7 +91,7 @@ class SnippetTab extends React.Component {
|
||||
if (!(editorFontSize > 0 && editorFontSize < 132)) editorIndentSize = 4
|
||||
return (
|
||||
<div styleName='root'>
|
||||
<div styleName='header'>{i18n.__('Snippets')}</div>
|
||||
<div styleName='group-header'>{i18n.__('Snippets')}</div>
|
||||
<SnippetList
|
||||
onSnippetSelect={this.handleSnippetSelect.bind(this)}
|
||||
onSnippetDeleted={this.handleDeleteSnippet.bind(this)}
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
@import('./Tab')
|
||||
@import('./ConfigTab')
|
||||
|
||||
.root
|
||||
padding 15px
|
||||
white-space pre
|
||||
line-height 1.4
|
||||
color alpha($ui-text-color, 90%)
|
||||
width 100%
|
||||
font-size 14px
|
||||
|
||||
.group
|
||||
margin-bottom 45px
|
||||
|
||||
@@ -127,7 +118,7 @@
|
||||
background darken(#f5f5f5, 5)
|
||||
|
||||
.snippet-detail
|
||||
width 70%
|
||||
width 67%
|
||||
height calc(100% - 200px)
|
||||
position absolute
|
||||
left 33%
|
||||
|
||||
@@ -4,7 +4,7 @@ import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './StorageItem.styl'
|
||||
import consts from 'browser/lib/consts'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import store from 'browser/main/store'
|
||||
import { store } from 'browser/main/store'
|
||||
import FolderList from './FolderList'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
@import('./Tab')
|
||||
|
||||
.root
|
||||
padding 15px
|
||||
color $ui-text-color
|
||||
@import('./ConfigTab')
|
||||
|
||||
.list
|
||||
margin-bottom 15px
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './ConfigTab.styl'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import store from 'browser/main/store'
|
||||
import { store } from 'browser/main/store'
|
||||
import consts from 'browser/lib/consts'
|
||||
import ReactCodeMirror from 'react-codemirror'
|
||||
import CodeMirror from 'codemirror'
|
||||
@@ -30,7 +30,9 @@ class UiTab extends React.Component {
|
||||
componentDidMount () {
|
||||
CodeMirror.autoLoadMode(this.codeMirrorInstance.getCodeMirror(), 'javascript')
|
||||
CodeMirror.autoLoadMode(this.customCSSCM.getCodeMirror(), 'css')
|
||||
CodeMirror.autoLoadMode(this.customMarkdownLintConfigCM.getCodeMirror(), 'javascript')
|
||||
this.customCSSCM.getCodeMirror().setSize('400px', '400px')
|
||||
this.customMarkdownLintConfigCM.getCodeMirror().setSize('400px', '200px')
|
||||
this.handleSettingDone = () => {
|
||||
this.setState({UiAlert: {
|
||||
type: 'success',
|
||||
@@ -101,7 +103,9 @@ class UiTab extends React.Component {
|
||||
matchingTriples: this.refs.matchingTriples.value,
|
||||
explodingPairs: this.refs.explodingPairs.value,
|
||||
spellcheck: this.refs.spellcheck.checked,
|
||||
enableSmartPaste: this.refs.enableSmartPaste.checked
|
||||
enableSmartPaste: this.refs.enableSmartPaste.checked,
|
||||
enableMarkdownLint: this.refs.enableMarkdownLint.checked,
|
||||
customMarkdownLintConfig: this.customMarkdownLintConfigCM.getCodeMirror().getValue()
|
||||
},
|
||||
preview: {
|
||||
fontSize: this.refs.previewFontSize.value,
|
||||
@@ -128,8 +132,13 @@ class UiTab extends React.Component {
|
||||
const newCodemirrorTheme = this.refs.editorTheme.value
|
||||
|
||||
if (newCodemirrorTheme !== codemirrorTheme) {
|
||||
checkHighLight.setAttribute('href', `../node_modules/codemirror/theme/${newCodemirrorTheme.split(' ')[0]}.css`)
|
||||
const theme = consts.THEMES.find(theme => theme.name === newCodemirrorTheme)
|
||||
|
||||
if (theme) {
|
||||
checkHighLight.setAttribute('href', `../${theme.path}`)
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({ config: newConfig, codemirrorTheme: newCodemirrorTheme }, () => {
|
||||
const {ui, editor, preview} = this.props.config
|
||||
this.currentConfig = {ui, editor, preview}
|
||||
@@ -355,7 +364,7 @@ class UiTab extends React.Component {
|
||||
>
|
||||
{
|
||||
themes.map((theme) => {
|
||||
return (<option value={theme} key={theme}>{theme}</option>)
|
||||
return (<option value={theme.name} key={theme.name}>{theme.name}</option>)
|
||||
})
|
||||
}
|
||||
</select>
|
||||
@@ -492,7 +501,7 @@ class UiTab extends React.Component {
|
||||
ref='editorSnippetDefaultLanguage'
|
||||
onChange={(e) => this.handleUIChange(e)}
|
||||
>
|
||||
<option key='Auto Detect' value='Auto Detect'>Auto Detect</option>
|
||||
<option key='Auto Detect' value='Auto Detect'>{i18n.__('Auto Detect')}</option>
|
||||
{
|
||||
_.sortBy(CodeMirror.modeInfo.map(mode => mode.name)).map(name => (<option key={name} value={name}>{name}</option>))
|
||||
}
|
||||
@@ -632,6 +641,34 @@ class UiTab extends React.Component {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div styleName='group-section'>
|
||||
<div styleName='group-section-label'>
|
||||
{i18n.__('Custom MarkdownLint Rules')}
|
||||
</div>
|
||||
<div styleName='group-section-control'>
|
||||
<input onChange={(e) => this.handleUIChange(e)}
|
||||
checked={this.state.config.editor.enableMarkdownLint}
|
||||
ref='enableMarkdownLint'
|
||||
type='checkbox'
|
||||
/>
|
||||
{i18n.__('Enable MarkdownLint')}
|
||||
<div style={{fontFamily, display: this.state.config.editor.enableMarkdownLint ? 'block' : 'none'}}>
|
||||
<ReactCodeMirror
|
||||
width='400px'
|
||||
height='200px'
|
||||
onChange={e => this.handleUIChange(e)}
|
||||
ref={e => (this.customMarkdownLintConfigCM = e)}
|
||||
value={config.editor.customMarkdownLintConfig}
|
||||
options={{
|
||||
lineNumbers: true,
|
||||
mode: 'application/json',
|
||||
theme: codemirrorTheme,
|
||||
lint: true,
|
||||
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers']
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div styleName='group-header2'>{i18n.__('Preview')}</div>
|
||||
<div styleName='group-section'>
|
||||
@@ -670,7 +707,7 @@ class UiTab extends React.Component {
|
||||
>
|
||||
{
|
||||
themes.map((theme) => {
|
||||
return (<option value={theme} key={theme}>{theme}</option>)
|
||||
return (<option value={theme.name} key={theme.name}>{theme.name}</option>)
|
||||
})
|
||||
}
|
||||
</select>
|
||||
@@ -846,6 +883,7 @@ class UiTab extends React.Component {
|
||||
onChange={e => this.handleUIChange(e)}
|
||||
ref={e => (this.customCSSCM = e)}
|
||||
value={config.preview.customCSS}
|
||||
defaultValue={'/* Drop Your Custom CSS Code Here */\n'}
|
||||
options={{
|
||||
lineNumbers: true,
|
||||
mode: 'css',
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './RenameFolderModal.styl'
|
||||
import dataApi from 'browser/main/lib/dataApi'
|
||||
import store from 'browser/main/store'
|
||||
import { store } from 'browser/main/store'
|
||||
import ModalEscButton from 'browser/components/ModalEscButton'
|
||||
import i18n from 'browser/lib/i18n'
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { combineReducers, createStore } from 'redux'
|
||||
import { routerReducer } from 'react-router-redux'
|
||||
import { combineReducers, createStore, compose, applyMiddleware } from 'redux'
|
||||
import { connectRouter, routerMiddleware } from 'connected-react-router'
|
||||
import { createHashHistory as createHistory } from 'history'
|
||||
import ConfigManager from 'browser/main/lib/ConfigManager'
|
||||
import { Map, Set } from 'browser/lib/Mutable'
|
||||
import _ from 'lodash'
|
||||
import DevTools from './DevTools'
|
||||
|
||||
function defaultDataMap () {
|
||||
return {
|
||||
@@ -44,7 +46,9 @@ function data (state = defaultDataMap(), action) {
|
||||
const folderNoteSet = getOrInitItem(state.folderNoteMap, folderKey)
|
||||
folderNoteSet.add(uniqueKey)
|
||||
|
||||
assignToTags(note.tags, state, uniqueKey)
|
||||
if (!note.isTrashed) {
|
||||
assignToTags(note.tags, state, uniqueKey)
|
||||
}
|
||||
})
|
||||
return state
|
||||
case 'UPDATE_NOTE':
|
||||
@@ -463,13 +467,16 @@ function getOrInitItem (target, key) {
|
||||
return results
|
||||
}
|
||||
|
||||
const history = createHistory()
|
||||
|
||||
const reducer = combineReducers({
|
||||
data,
|
||||
config,
|
||||
status,
|
||||
routing: routerReducer
|
||||
router: connectRouter(history)
|
||||
})
|
||||
|
||||
const store = createStore(reducer)
|
||||
const store = createStore(reducer, undefined, compose(
|
||||
applyMiddleware(routerMiddleware(history)), DevTools.instrument()))
|
||||
|
||||
export default store
|
||||
export { store, history }
|
||||
|
||||
Reference in New Issue
Block a user