mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-13 17:56:25 +00:00
Merge branch 'master' into feature/autoBracketMatching
This commit is contained in:
@@ -78,12 +78,16 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.scrollToLineHandeler = this.scrollToLine.bind(this)
|
this.scrollToLineHandeler = this.scrollToLine.bind(this)
|
||||||
|
|
||||||
this.formatTable = () => this.handleFormatTable()
|
this.formatTable = () => this.handleFormatTable()
|
||||||
this.contextMenuHandler = function (editor, event) {
|
|
||||||
const menu = buildEditorContextMenu(editor, event)
|
if (props.switchPreview !== 'RIGHTCLICK') {
|
||||||
if (menu != null) {
|
this.contextMenuHandler = function (editor, event) {
|
||||||
setTimeout(() => menu.popup(remote.getCurrentWindow()), 30)
|
const menu = buildEditorContextMenu(editor, event)
|
||||||
|
if (menu != null) {
|
||||||
|
setTimeout(() => menu.popup(remote.getCurrentWindow()), 30)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.editorActivityHandler = () => this.handleEditorActivity()
|
this.editorActivityHandler = () => this.handleEditorActivity()
|
||||||
|
|
||||||
this.turndownService = new TurndownService()
|
this.turndownService = new TurndownService()
|
||||||
@@ -152,10 +156,7 @@ export default class CodeEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
const {
|
const { rulers, enableRulers, switchPreview } = this.props
|
||||||
rulers,
|
|
||||||
enableRulers
|
|
||||||
} = this.props
|
|
||||||
const expandSnippet = this.expandSnippet.bind(this)
|
const expandSnippet = this.expandSnippet.bind(this)
|
||||||
eventEmitter.on('line:jump', this.scrollToLineHandeler)
|
eventEmitter.on('line:jump', this.scrollToLineHandeler)
|
||||||
|
|
||||||
@@ -257,7 +258,9 @@ export default class CodeEditor extends React.Component {
|
|||||||
this.editor.on('blur', this.blurHandler)
|
this.editor.on('blur', this.blurHandler)
|
||||||
this.editor.on('change', this.changeHandler)
|
this.editor.on('change', this.changeHandler)
|
||||||
this.editor.on('paste', this.pasteHandler)
|
this.editor.on('paste', this.pasteHandler)
|
||||||
this.editor.on('contextmenu', this.contextMenuHandler)
|
if (switchPreview !== 'RIGHTCLICK') {
|
||||||
|
this.editor.on('contextmenu', this.contextMenuHandler)
|
||||||
|
}
|
||||||
eventEmitter.on('top:search', this.searchHandler)
|
eventEmitter.on('top:search', this.searchHandler)
|
||||||
|
|
||||||
eventEmitter.emit('code:init')
|
eventEmitter.emit('code:init')
|
||||||
|
|||||||
@@ -281,6 +281,7 @@ class MarkdownEditor extends React.Component {
|
|||||||
onChange={(e) => this.handleChange(e)}
|
onChange={(e) => this.handleChange(e)}
|
||||||
onBlur={(e) => this.handleBlur(e)}
|
onBlur={(e) => this.handleBlur(e)}
|
||||||
spellCheck={config.editor.spellcheck}
|
spellCheck={config.editor.spellcheck}
|
||||||
|
switchPreview={config.editor.switchPreview}
|
||||||
/>
|
/>
|
||||||
<MarkdownPreview styleName={this.state.status === 'PREVIEW'
|
<MarkdownPreview styleName={this.state.status === 'PREVIEW'
|
||||||
? 'preview'
|
? 'preview'
|
||||||
|
|||||||
@@ -291,26 +291,7 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleSaveAsMd () {
|
handleSaveAsMd () {
|
||||||
this.exportAsDocument('md', (noteContent, exportTasks) => {
|
this.exportAsDocument('md')
|
||||||
let result = noteContent
|
|
||||||
if (this.props && this.props.storagePath && this.props.noteKey) {
|
|
||||||
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(
|
|
||||||
noteContent,
|
|
||||||
this.props.storagePath
|
|
||||||
)
|
|
||||||
attachmentsAbsolutePaths.forEach(attachment => {
|
|
||||||
exportTasks.push({
|
|
||||||
src: attachment,
|
|
||||||
dst: attachmentManagement.DESTINATION_FOLDER
|
|
||||||
})
|
|
||||||
})
|
|
||||||
result = attachmentManagement.removeStorageAndNoteReferences(
|
|
||||||
noteContent,
|
|
||||||
this.props.noteKey
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSaveAsHtml () {
|
handleSaveAsHtml () {
|
||||||
@@ -339,11 +320,6 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
)
|
)
|
||||||
let body = this.markdown.render(noteContent)
|
let body = this.markdown.render(noteContent)
|
||||||
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
|
||||||
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(
|
|
||||||
noteContent,
|
|
||||||
this.props.storagePath
|
|
||||||
)
|
|
||||||
|
|
||||||
files.forEach(file => {
|
files.forEach(file => {
|
||||||
if (global.process.platform === 'win32') {
|
if (global.process.platform === 'win32') {
|
||||||
file = file.replace('file:///', '')
|
file = file.replace('file:///', '')
|
||||||
@@ -355,16 +331,6 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
dst: 'css'
|
dst: 'css'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
attachmentsAbsolutePaths.forEach(attachment => {
|
|
||||||
exportTasks.push({
|
|
||||||
src: attachment,
|
|
||||||
dst: attachmentManagement.DESTINATION_FOLDER
|
|
||||||
})
|
|
||||||
})
|
|
||||||
body = attachmentManagement.removeStorageAndNoteReferences(
|
|
||||||
body,
|
|
||||||
this.props.noteKey
|
|
||||||
)
|
|
||||||
|
|
||||||
let styles = ''
|
let styles = ''
|
||||||
files.forEach(file => {
|
files.forEach(file => {
|
||||||
@@ -397,8 +363,9 @@ export default class MarkdownPreview extends React.Component {
|
|||||||
if (filename) {
|
if (filename) {
|
||||||
const content = this.props.value
|
const content = this.props.value
|
||||||
const storage = this.props.storagePath
|
const storage = this.props.storagePath
|
||||||
|
const nodeKey = this.props.noteKey
|
||||||
|
|
||||||
exportNote(storage, content, filename, contentFormatter)
|
exportNote(nodeKey, storage, content, filename, contentFormatter)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
dialog.showMessageBox(remote.getCurrentWindow(), {
|
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
type: 'info',
|
type: 'info',
|
||||||
|
|||||||
@@ -175,6 +175,7 @@ class MarkdownSplitEditor extends React.Component {
|
|||||||
onChange={this.handleOnChange.bind(this)}
|
onChange={this.handleOnChange.bind(this)}
|
||||||
onScroll={this.handleScroll.bind(this)}
|
onScroll={this.handleScroll.bind(this)}
|
||||||
spellCheck={config.editor.spellcheck}
|
spellCheck={config.editor.spellcheck}
|
||||||
|
switchPreview={config.editor.switchPreview}
|
||||||
/>
|
/>
|
||||||
<div styleName='slider' style={{left: this.state.codeEditorWidthInPercent + '%'}} onMouseDown={e => this.handleMouseDown(e)} >
|
<div styleName='slider' style={{left: this.state.codeEditorWidthInPercent + '%'}} onMouseDown={e => this.handleMouseDown(e)} >
|
||||||
<div styleName='slider-hitbox' />
|
<div styleName='slider-hitbox' />
|
||||||
|
|||||||
@@ -70,22 +70,22 @@ class InfoPanel extends React.Component {
|
|||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<div id='export-wrap'>
|
<div id='export-wrap'>
|
||||||
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
|
<button styleName='export--enable' onClick={(e) => exportAsMd(e, 'export-md')}>
|
||||||
<i className='fa fa-file-code-o' />
|
<i className='fa fa-file-code-o' />
|
||||||
<p>{i18n.__('.md')}</p>
|
<p>{i18n.__('.md')}</p>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button styleName='export--enable' onClick={(e) => exportAsTxt(e)}>
|
<button styleName='export--enable' onClick={(e) => exportAsTxt(e, 'export-txt')}>
|
||||||
<i className='fa fa-file-text-o' />
|
<i className='fa fa-file-text-o' />
|
||||||
<p>{i18n.__('.txt')}</p>
|
<p>{i18n.__('.txt')}</p>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button styleName='export--enable' onClick={(e) => exportAsHtml(e)}>
|
<button styleName='export--enable' onClick={(e) => exportAsHtml(e, 'export-html')}>
|
||||||
<i className='fa fa-html5' />
|
<i className='fa fa-html5' />
|
||||||
<p>{i18n.__('.html')}</p>
|
<p>{i18n.__('.html')}</p>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button styleName='export--enable' onClick={(e) => print(e)}>
|
<button styleName='export--enable' onClick={(e) => print(e, 'print')}>
|
||||||
<i className='fa fa-print' />
|
<i className='fa fa-print' />
|
||||||
<p>{i18n.__('Print')}</p>
|
<p>{i18n.__('Print')}</p>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -31,17 +31,17 @@ const InfoPanelTrashed = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id='export-wrap'>
|
<div id='export-wrap'>
|
||||||
<button styleName='export--enable' onClick={(e) => exportAsMd(e)}>
|
<button styleName='export--enable' onClick={(e) => exportAsMd(e, 'export-md')}>
|
||||||
<i className='fa fa-file-code-o' />
|
<i className='fa fa-file-code-o' />
|
||||||
<p>.md</p>
|
<p>.md</p>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button styleName='export--enable' onClick={(e) => exportAsTxt(e)}>
|
<button styleName='export--enable' onClick={(e) => exportAsTxt(e, 'export-txt')}>
|
||||||
<i className='fa fa-file-text-o' />
|
<i className='fa fa-file-text-o' />
|
||||||
<p>.txt</p>
|
<p>.txt</p>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button styleName='export--enable' onClick={(e) => exportAsHtml(e)}>
|
<button styleName='export--enable' onClick={(e) => exportAsHtml(e, 'export-html')}>
|
||||||
<i className='fa fa-html5' />
|
<i className='fa fa-html5' />
|
||||||
<p>.html</p>
|
<p>.html</p>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -645,11 +645,18 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
if (infoPanel.style) infoPanel.style.display = infoPanel.style.display === 'none' ? 'inline' : 'none'
|
if (infoPanel.style) infoPanel.style.display = infoPanel.style.display === 'none' ? 'inline' : 'none'
|
||||||
}
|
}
|
||||||
|
|
||||||
showWarning () {
|
showWarning (e, msg) {
|
||||||
|
const warningMessage = (msg) => ({
|
||||||
|
'export-txt': 'Text export',
|
||||||
|
'export-md': 'Markdown export',
|
||||||
|
'export-html': 'HTML export',
|
||||||
|
'print': 'Print'
|
||||||
|
})[msg]
|
||||||
|
|
||||||
dialog.showMessageBox(remote.getCurrentWindow(), {
|
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
message: i18n.__('Sorry!'),
|
message: i18n.__('Sorry!'),
|
||||||
detail: i18n.__('md/text import is available only a markdown note.'),
|
detail: i18n.__(warningMessage(msg) + ' is available only in markdown notes.'),
|
||||||
buttons: [i18n.__('OK')]
|
buttons: [i18n.__('OK')]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -803,7 +810,9 @@ class SnippetNoteDetail extends React.Component {
|
|||||||
createdAt={formatDate(note.createdAt)}
|
createdAt={formatDate(note.createdAt)}
|
||||||
exportAsMd={this.showWarning}
|
exportAsMd={this.showWarning}
|
||||||
exportAsTxt={this.showWarning}
|
exportAsTxt={this.showWarning}
|
||||||
|
exportAsHtml={this.showWarning}
|
||||||
type={note.type}
|
type={note.type}
|
||||||
|
print={this.showWarning}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -64,13 +64,14 @@ class NoteList extends React.Component {
|
|||||||
this.focusHandler = () => {
|
this.focusHandler = () => {
|
||||||
this.refs.list.focus()
|
this.refs.list.focus()
|
||||||
}
|
}
|
||||||
this.alertIfSnippetHandler = () => {
|
this.alertIfSnippetHandler = (event, msg) => {
|
||||||
this.alertIfSnippet()
|
this.alertIfSnippet(msg)
|
||||||
}
|
}
|
||||||
this.importFromFileHandler = this.importFromFile.bind(this)
|
this.importFromFileHandler = this.importFromFile.bind(this)
|
||||||
this.jumpNoteByHash = this.jumpNoteByHashHandler.bind(this)
|
this.jumpNoteByHash = this.jumpNoteByHashHandler.bind(this)
|
||||||
this.handleNoteListKeyUp = this.handleNoteListKeyUp.bind(this)
|
this.handleNoteListKeyUp = this.handleNoteListKeyUp.bind(this)
|
||||||
this.getNoteKeyFromTargetIndex = this.getNoteKeyFromTargetIndex.bind(this)
|
this.getNoteKeyFromTargetIndex = this.getNoteKeyFromTargetIndex.bind(this)
|
||||||
|
this.cloneNote = this.cloneNote.bind(this)
|
||||||
this.deleteNote = this.deleteNote.bind(this)
|
this.deleteNote = this.deleteNote.bind(this)
|
||||||
this.focusNote = this.focusNote.bind(this)
|
this.focusNote = this.focusNote.bind(this)
|
||||||
this.pinToTop = this.pinToTop.bind(this)
|
this.pinToTop = this.pinToTop.bind(this)
|
||||||
@@ -96,6 +97,7 @@ class NoteList extends React.Component {
|
|||||||
this.refreshTimer = setInterval(() => this.forceUpdate(), 60 * 1000)
|
this.refreshTimer = setInterval(() => this.forceUpdate(), 60 * 1000)
|
||||||
ee.on('list:next', this.selectNextNoteHandler)
|
ee.on('list:next', this.selectNextNoteHandler)
|
||||||
ee.on('list:prior', this.selectPriorNoteHandler)
|
ee.on('list:prior', this.selectPriorNoteHandler)
|
||||||
|
ee.on('list:clone', this.cloneNote)
|
||||||
ee.on('list:focus', this.focusHandler)
|
ee.on('list:focus', this.focusHandler)
|
||||||
ee.on('list:isMarkdownNote', this.alertIfSnippetHandler)
|
ee.on('list:isMarkdownNote', this.alertIfSnippetHandler)
|
||||||
ee.on('import:file', this.importFromFileHandler)
|
ee.on('import:file', this.importFromFileHandler)
|
||||||
@@ -118,6 +120,7 @@ class NoteList extends React.Component {
|
|||||||
|
|
||||||
ee.off('list:next', this.selectNextNoteHandler)
|
ee.off('list:next', this.selectNextNoteHandler)
|
||||||
ee.off('list:prior', this.selectPriorNoteHandler)
|
ee.off('list:prior', this.selectPriorNoteHandler)
|
||||||
|
ee.off('list:clone', this.cloneNote)
|
||||||
ee.off('list:focus', this.focusHandler)
|
ee.off('list:focus', this.focusHandler)
|
||||||
ee.off('list:isMarkdownNote', this.alertIfSnippetHandler)
|
ee.off('list:isMarkdownNote', this.alertIfSnippetHandler)
|
||||||
ee.off('import:file', this.importFromFileHandler)
|
ee.off('import:file', this.importFromFileHandler)
|
||||||
@@ -277,12 +280,6 @@ class NoteList extends React.Component {
|
|||||||
ee.emit('top:new-note')
|
ee.emit('top:new-note')
|
||||||
}
|
}
|
||||||
|
|
||||||
// D key
|
|
||||||
if (e.keyCode === 68) {
|
|
||||||
e.preventDefault()
|
|
||||||
this.deleteNote()
|
|
||||||
}
|
|
||||||
|
|
||||||
// E key
|
// E key
|
||||||
if (e.keyCode === 69) {
|
if (e.keyCode === 69) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -495,14 +492,21 @@ class NoteList extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
alertIfSnippet () {
|
alertIfSnippet (msg) {
|
||||||
|
const warningMessage = (msg) => ({
|
||||||
|
'export-txt': 'Text export',
|
||||||
|
'export-md': 'Markdown export',
|
||||||
|
'export-html': 'HTML export',
|
||||||
|
'print': 'Print'
|
||||||
|
})[msg]
|
||||||
|
|
||||||
const targetIndex = this.getTargetIndex()
|
const targetIndex = this.getTargetIndex()
|
||||||
if (this.notes[targetIndex].type === 'SNIPPET_NOTE') {
|
if (this.notes[targetIndex].type === 'SNIPPET_NOTE') {
|
||||||
dialog.showMessageBox(remote.getCurrentWindow(), {
|
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
message: i18n.__('Sorry!'),
|
message: i18n.__('Sorry!'),
|
||||||
detail: i18n.__('md/text import is available only a markdown note.'),
|
detail: i18n.__(warningMessage(msg) + ' is available only in markdown notes.'),
|
||||||
buttons: [i18n.__('OK'), i18n.__('Cancel')]
|
buttons: [i18n.__('OK')]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ class StorageItem extends React.Component {
|
|||||||
const { storage } = this.props
|
const { storage } = this.props
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
isOpen: !!storage.isOpen
|
isOpen: !!storage.isOpen,
|
||||||
|
draggedOver: null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,6 +205,20 @@ class StorageItem extends React.Component {
|
|||||||
folderKey: data.folderKey,
|
folderKey: data.folderKey,
|
||||||
fileType: data.fileType
|
fileType: data.fileType
|
||||||
})
|
})
|
||||||
|
return data
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
|
type: 'info',
|
||||||
|
message: 'Exported to "' + data.exportDir + '"'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
dialog.showErrorBox(
|
||||||
|
'Export error',
|
||||||
|
err ? err.message || err : 'Unexpected error during export'
|
||||||
|
)
|
||||||
|
throw err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -231,14 +246,20 @@ class StorageItem extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDragEnter (e) {
|
handleDragEnter (e, key) {
|
||||||
e.dataTransfer.setData('defaultColor', e.target.style.backgroundColor)
|
e.preventDefault()
|
||||||
e.target.style.backgroundColor = 'rgba(129, 130, 131, 0.08)'
|
if (this.state.draggedOver === key) { return }
|
||||||
|
this.setState({
|
||||||
|
draggedOver: key
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDragLeave (e) {
|
handleDragLeave (e) {
|
||||||
e.target.style.opacity = '1'
|
e.preventDefault()
|
||||||
e.target.style.backgroundColor = e.dataTransfer.getData('defaultColor')
|
if (this.state.draggedOver === null) { return }
|
||||||
|
this.setState({
|
||||||
|
draggedOver: null
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
dropNote (storage, folder, dispatch, location, noteData) {
|
dropNote (storage, folder, dispatch, location, noteData) {
|
||||||
@@ -263,8 +284,12 @@ class StorageItem extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleDrop (e, storage, folder, dispatch, location) {
|
handleDrop (e, storage, folder, dispatch, location) {
|
||||||
e.target.style.opacity = '1'
|
e.preventDefault()
|
||||||
e.target.style.backgroundColor = e.dataTransfer.getData('defaultColor')
|
if (this.state.draggedOver !== null) {
|
||||||
|
this.setState({
|
||||||
|
draggedOver: null
|
||||||
|
})
|
||||||
|
}
|
||||||
const noteData = JSON.parse(e.dataTransfer.getData('note'))
|
const noteData = JSON.parse(e.dataTransfer.getData('note'))
|
||||||
this.dropNote(storage, folder, dispatch, location, noteData)
|
this.dropNote(storage, folder, dispatch, location, noteData)
|
||||||
}
|
}
|
||||||
@@ -291,16 +316,22 @@ class StorageItem extends React.Component {
|
|||||||
<SortableStorageItemChild
|
<SortableStorageItemChild
|
||||||
key={folder.key}
|
key={folder.key}
|
||||||
index={index}
|
index={index}
|
||||||
isActive={isActive}
|
isActive={isActive || folder.key === this.state.draggedOver}
|
||||||
handleButtonClick={(e) => this.handleFolderButtonClick(folder.key)(e)}
|
handleButtonClick={(e) => this.handleFolderButtonClick(folder.key)(e)}
|
||||||
handleContextMenu={(e) => this.handleFolderButtonContextMenu(e, folder)}
|
handleContextMenu={(e) => this.handleFolderButtonContextMenu(e, folder)}
|
||||||
folderName={folder.name}
|
folderName={folder.name}
|
||||||
folderColor={folder.color}
|
folderColor={folder.color}
|
||||||
isFolded={isFolded}
|
isFolded={isFolded}
|
||||||
noteCount={noteCount}
|
noteCount={noteCount}
|
||||||
handleDrop={(e) => this.handleDrop(e, storage, folder, dispatch, location)}
|
handleDrop={(e) => {
|
||||||
handleDragEnter={this.handleDragEnter}
|
this.handleDrop(e, storage, folder, dispatch, location)
|
||||||
handleDragLeave={this.handleDragLeave}
|
}}
|
||||||
|
handleDragEnter={(e) => {
|
||||||
|
this.handleDragEnter(e, folder.key)
|
||||||
|
}}
|
||||||
|
handleDragLeave={(e) => {
|
||||||
|
this.handleDragLeave(e, folder)
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ function copyFile (srcPath, dstPath) {
|
|||||||
const dstFolder = path.dirname(dstPath)
|
const dstFolder = path.dirname(dstPath)
|
||||||
if (!fs.existsSync(dstFolder)) fs.mkdirSync(dstFolder)
|
if (!fs.existsSync(dstFolder)) fs.mkdirSync(dstFolder)
|
||||||
|
|
||||||
const input = fs.createReadStream(srcPath)
|
const input = fs.createReadStream(decodeURI(srcPath))
|
||||||
const output = fs.createWriteStream(dstPath)
|
const output = fs.createWriteStream(dstPath)
|
||||||
|
|
||||||
output.on('error', reject)
|
output.on('error', reject)
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { findStorage } from 'browser/lib/findStorage'
|
import { findStorage } from 'browser/lib/findStorage'
|
||||||
import resolveStorageData from './resolveStorageData'
|
import resolveStorageData from './resolveStorageData'
|
||||||
import resolveStorageNotes from './resolveStorageNotes'
|
import resolveStorageNotes from './resolveStorageNotes'
|
||||||
|
import exportNote from './exportNote'
|
||||||
import filenamify from 'filenamify'
|
import filenamify from 'filenamify'
|
||||||
import * as path from 'path'
|
import * as path from 'path'
|
||||||
import * as fs from 'fs'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {String} storageKey
|
* @param {String} storageKey
|
||||||
@@ -45,9 +45,9 @@ function exportFolder (storageKey, folderKey, fileType, exportDir) {
|
|||||||
|
|
||||||
notes
|
notes
|
||||||
.filter(note => note.folder === folderKey && note.isTrashed === false && note.type === 'MARKDOWN_NOTE')
|
.filter(note => note.folder === folderKey && note.isTrashed === false && note.type === 'MARKDOWN_NOTE')
|
||||||
.forEach(snippet => {
|
.forEach(note => {
|
||||||
const notePath = path.join(exportDir, `${filenamify(snippet.title, {replacement: '_'})}.${fileType}`)
|
const notePath = path.join(exportDir, `${filenamify(note.title, {replacement: '_'})}.${fileType}`)
|
||||||
fs.writeFileSync(notePath, snippet.content)
|
exportNote(note.key, storage.path, note.content, notePath, null)
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -4,27 +4,43 @@ import { findStorage } from 'browser/lib/findStorage'
|
|||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
|
|
||||||
|
const attachmentManagement = require('./attachmentManagement')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export note together with images
|
* Export note together with attachments
|
||||||
*
|
*
|
||||||
* If images is stored in the storage, creates 'images' subfolder in target directory
|
* If attachments are stored in the storage, creates 'attachments' subfolder in target directory
|
||||||
* and copies images to it. Changes links to images in the content of the note
|
* and copies attachments to it. Changes links to images in the content of the note
|
||||||
*
|
*
|
||||||
|
* @param {String} nodeKey key of the node that should be exported
|
||||||
* @param {String} storageKey or storage path
|
* @param {String} storageKey or storage path
|
||||||
* @param {String} noteContent Content to export
|
* @param {String} noteContent Content to export
|
||||||
* @param {String} targetPath Path to exported file
|
* @param {String} targetPath Path to exported file
|
||||||
* @param {function} outputFormatter
|
* @param {function} outputFormatter
|
||||||
* @return {Promise.<*[]>}
|
* @return {Promise.<*[]>}
|
||||||
*/
|
*/
|
||||||
function exportNote (storageKey, noteContent, targetPath, outputFormatter) {
|
function exportNote (nodeKey, storageKey, noteContent, targetPath, outputFormatter) {
|
||||||
const storagePath = path.isAbsolute(storageKey) ? storageKey : findStorage(storageKey).path
|
const storagePath = path.isAbsolute(storageKey) ? storageKey : findStorage(storageKey).path
|
||||||
const exportTasks = []
|
const exportTasks = []
|
||||||
|
|
||||||
if (!storagePath) {
|
if (!storagePath) {
|
||||||
throw new Error('Storage path is not found')
|
throw new Error('Storage path is not found')
|
||||||
}
|
}
|
||||||
|
const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(
|
||||||
|
noteContent,
|
||||||
|
storagePath
|
||||||
|
)
|
||||||
|
attachmentsAbsolutePaths.forEach(attachment => {
|
||||||
|
exportTasks.push({
|
||||||
|
src: attachment,
|
||||||
|
dst: attachmentManagement.DESTINATION_FOLDER
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
let exportedData = noteContent
|
let exportedData = attachmentManagement.removeStorageAndNoteReferences(
|
||||||
|
noteContent,
|
||||||
|
nodeKey
|
||||||
|
)
|
||||||
|
|
||||||
if (outputFormatter) {
|
if (outputFormatter) {
|
||||||
exportedData = outputFormatter(exportedData, exportTasks)
|
exportedData = outputFormatter(exportedData, exportTasks)
|
||||||
|
|||||||
@@ -85,39 +85,24 @@ const file = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Focus Note',
|
label: 'Focus Note',
|
||||||
accelerator: 'Control+E',
|
accelerator: macOS ? 'Command+E' : 'Control+E',
|
||||||
click () {
|
click () {
|
||||||
mainWindow.webContents.send('detail:focus')
|
mainWindow.webContents.send('detail:focus')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'separator'
|
label: 'Delete Note',
|
||||||
|
accelerator: macOS ? 'Command+Shift+Backspace' : 'Control+Shift+Backspace',
|
||||||
|
click () {
|
||||||
|
mainWindow.webContents.send('detail:delete')
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Export as',
|
label: 'Clone Note',
|
||||||
submenu: [
|
accelerator: macOS ? 'Command+D' : 'Control+D',
|
||||||
{
|
click () {
|
||||||
label: 'Plain Text (.txt)',
|
mainWindow.webContents.send('list:clone')
|
||||||
click () {
|
}
|
||||||
mainWindow.webContents.send('list:isMarkdownNote')
|
|
||||||
mainWindow.webContents.send('export:save-text')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'MarkDown (.md)',
|
|
||||||
click () {
|
|
||||||
mainWindow.webContents.send('list:isMarkdownNote')
|
|
||||||
mainWindow.webContents.send('export:save-md')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'HTML (.html)',
|
|
||||||
click () {
|
|
||||||
mainWindow.webContents.send('list:isMarkdownNote')
|
|
||||||
mainWindow.webContents.send('export:save-html')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
@@ -134,13 +119,30 @@ const file = {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'separator'
|
label: 'Export as',
|
||||||
},
|
submenu: [
|
||||||
{
|
{
|
||||||
label: 'Format Table',
|
label: 'Plain Text (.txt)',
|
||||||
click () {
|
click () {
|
||||||
mainWindow.webContents.send('code:format-table')
|
mainWindow.webContents.send('list:isMarkdownNote', 'export-txt')
|
||||||
}
|
mainWindow.webContents.send('export:save-text')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'MarkDown (.md)',
|
||||||
|
click () {
|
||||||
|
mainWindow.webContents.send('list:isMarkdownNote', 'export-md')
|
||||||
|
mainWindow.webContents.send('export:save-md')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'HTML (.html)',
|
||||||
|
click () {
|
||||||
|
mainWindow.webContents.send('list:isMarkdownNote', 'export-html')
|
||||||
|
mainWindow.webContents.send('export:save-html')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
@@ -153,24 +155,20 @@ const file = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'separator'
|
label: 'Format Table',
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Print',
|
|
||||||
accelerator: 'CommandOrControl+P',
|
|
||||||
click () {
|
click () {
|
||||||
mainWindow.webContents.send('list:isMarkdownNote')
|
mainWindow.webContents.send('code:format-table')
|
||||||
mainWindow.webContents.send('print')
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'separator'
|
type: 'separator'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Delete Note',
|
label: 'Print',
|
||||||
accelerator: macOS ? 'Control+Backspace' : 'Control+Delete',
|
accelerator: 'CommandOrControl+P',
|
||||||
click () {
|
click () {
|
||||||
mainWindow.webContents.send('detail:delete')
|
mainWindow.webContents.send('list:isMarkdownNote', 'print')
|
||||||
|
mainWindow.webContents.send('print')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -296,9 +294,6 @@ const view = {
|
|||||||
mainWindow.setFullScreen(!mainWindow.isFullScreen())
|
mainWindow.setFullScreen(!mainWindow.isFullScreen())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: 'separator'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: 'Toggle Side Bar',
|
label: 'Toggle Side Bar',
|
||||||
accelerator: 'CommandOrControl+B',
|
accelerator: 'CommandOrControl+B',
|
||||||
|
|||||||
@@ -37,9 +37,15 @@
|
|||||||
"White": "白",
|
"White": "白",
|
||||||
"Solarized Dark": "明灰",
|
"Solarized Dark": "明灰",
|
||||||
"Dark": "暗灰",
|
"Dark": "暗灰",
|
||||||
|
"Default New Note": "新規ノートの形式",
|
||||||
|
"Always Ask": "作成時に聞く",
|
||||||
"Show a confirmation dialog when deleting notes": "ノートを削除する時に確認ダイアログを表示する",
|
"Show a confirmation dialog when deleting notes": "ノートを削除する時に確認ダイアログを表示する",
|
||||||
"Disable Direct Write (It will be applied after restarting)": "Disable Direct Write (It will be applied after restarting)",
|
"Disable Direct Write (It will be applied after restarting)": "Disable Direct Write (It will be applied after restarting)",
|
||||||
|
"Save tags of a note in alphabetical order": "ノートのタグをアルファベット順に保存する",
|
||||||
|
"Show tags of a note in alphabetical order": "ノートのタグをアルファベット順に表示する",
|
||||||
"Show only related tags": "関連するタグのみ表示する",
|
"Show only related tags": "関連するタグのみ表示する",
|
||||||
|
"Enable live count of notes": "タグ選択時にノート数を再計算して表示する",
|
||||||
|
"New notes are tagged with the filtering tags": "新規ノートに選択中のタグを付与する",
|
||||||
"Editor Theme": "エディタのテーマ",
|
"Editor Theme": "エディタのテーマ",
|
||||||
"Editor Font Size": "エディタのフォントサイズ",
|
"Editor Font Size": "エディタのフォントサイズ",
|
||||||
"Editor Font Family": "エディタのフォント",
|
"Editor Font Family": "エディタのフォント",
|
||||||
@@ -55,21 +61,28 @@
|
|||||||
"vim": "vim",
|
"vim": "vim",
|
||||||
"emacs": "emacs",
|
"emacs": "emacs",
|
||||||
"⚠️ Please restart boostnote after you change the keymap": "⚠️ キーマップ変更後は Boostnote を再起動してください",
|
"⚠️ Please restart boostnote after you change the keymap": "⚠️ キーマップ変更後は Boostnote を再起動してください",
|
||||||
|
"Snippet Default Language": "スニペットのデフォルト言語",
|
||||||
|
"Extract title from front matter": "Front matterからタイトルを抽出する",
|
||||||
"Show line numbers in the editor": "エディタ内に行番号を表示",
|
"Show line numbers in the editor": "エディタ内に行番号を表示",
|
||||||
"Allow editor to scroll past the last line": "エディタが最終行以降にスクロールできるようにする",
|
"Allow editor to scroll past the last line": "エディタが最終行以降にスクロールできるようにする",
|
||||||
"Enable smart quotes": "スマートクォートを有効にする",
|
"Enable smart quotes": "スマートクォートを有効にする",
|
||||||
"Bring in web page title when pasting URL on editor": "URLを貼り付けた時にWebページのタイトルを取得する",
|
"Bring in web page title when pasting URL on editor": "URLを貼り付けた時にWebページのタイトルを取得する",
|
||||||
|
"Enable smart table editor": "スマートテーブルエディタを有効にする",
|
||||||
"Preview": "プレビュー",
|
"Preview": "プレビュー",
|
||||||
"Preview Font Size": "プレビュー時フォントサイズ",
|
"Preview Font Size": "プレビュー時フォントサイズ",
|
||||||
"Preview Font Family": "プレビュー時フォント",
|
"Preview Font Family": "プレビュー時フォント",
|
||||||
"Code Block Theme": "コードブロックのテーマ",
|
"Code Block Theme": "コードブロックのテーマ",
|
||||||
|
"Allow line through checkbox": "チェック済みチェックボックスのテキストに取り消し線を付与する",
|
||||||
"Allow preview to scroll past the last line": "プレビュー時に最終行以降にスクロールできるようにする",
|
"Allow preview to scroll past the last line": "プレビュー時に最終行以降にスクロールできるようにする",
|
||||||
|
"When scrolling, synchronize preview with editor": "エディタとプレビューのスクロールを同期する",
|
||||||
"Show line numbers for preview code blocks": "プレビュー時のコードブロック内に行番号を表示する",
|
"Show line numbers for preview code blocks": "プレビュー時のコードブロック内に行番号を表示する",
|
||||||
"LaTeX Inline Open Delimiter": "LaTeX 開始デリミタ(インライン)",
|
"LaTeX Inline Open Delimiter": "LaTeX 開始デリミタ(インライン)",
|
||||||
"LaTeX Inline Close Delimiter": "LaTeX 終了デリミタ(インライン)",
|
"LaTeX Inline Close Delimiter": "LaTeX 終了デリミタ(インライン)",
|
||||||
"LaTeX Block Open Delimiter": "LaTeX 開始デリミタ(ブロック)",
|
"LaTeX Block Open Delimiter": "LaTeX 開始デリミタ(ブロック)",
|
||||||
"LaTeX Block Close Delimiter": "LaTeX 終了デリミタ(ブロック)",
|
"LaTeX Block Close Delimiter": "LaTeX 終了デリミタ(ブロック)",
|
||||||
"PlantUML Server": "PlantUML サーバー",
|
"PlantUML Server": "PlantUML サーバー",
|
||||||
|
"Custom CSS": "カスタムCSS",
|
||||||
|
"Allow custom CSS for preview": "プレビュー用のカスタムCSSを許可する",
|
||||||
"Community": "コミュニティ",
|
"Community": "コミュニティ",
|
||||||
"Subscribe to Newsletter": "ニュースレターを購読する",
|
"Subscribe to Newsletter": "ニュースレターを購読する",
|
||||||
"GitHub": "GitHub",
|
"GitHub": "GitHub",
|
||||||
@@ -135,6 +148,7 @@
|
|||||||
"Hotkeys": "ホットキー",
|
"Hotkeys": "ホットキー",
|
||||||
"Show/Hide Boostnote": "Boostnote の表示/非表示",
|
"Show/Hide Boostnote": "Boostnote の表示/非表示",
|
||||||
"Toggle Editor Mode": "エディタモードの切替",
|
"Toggle Editor Mode": "エディタモードの切替",
|
||||||
|
"Delete Note": "ノート削除",
|
||||||
"Restore": "リストア",
|
"Restore": "リストア",
|
||||||
"Permanent Delete": "永久に削除",
|
"Permanent Delete": "永久に削除",
|
||||||
"Confirm note deletion": "ノート削除確認",
|
"Confirm note deletion": "ノート削除確認",
|
||||||
|
|||||||
@@ -62,7 +62,7 @@
|
|||||||
"escape-string-regexp": "^1.0.5",
|
"escape-string-regexp": "^1.0.5",
|
||||||
"file-uri-to-path": "^1.0.0",
|
"file-uri-to-path": "^1.0.0",
|
||||||
"file-url": "^2.0.2",
|
"file-url": "^2.0.2",
|
||||||
"filenamify": "^2.0.0",
|
"filenamify": "^2.1.0",
|
||||||
"flowchart.js": "^1.6.5",
|
"flowchart.js": "^1.6.5",
|
||||||
"font-awesome": "^4.3.0",
|
"font-awesome": "^4.3.0",
|
||||||
"fs-extra": "^5.0.0",
|
"fs-extra": "^5.0.0",
|
||||||
|
|||||||
@@ -5,8 +5,11 @@
|
|||||||
<h4 align="center">Note-taking app for programmers. </h4>
|
<h4 align="center">Note-taking app for programmers. </h4>
|
||||||
<h5 align="center">Apps available for Mac, Windows, Linux, Android, and iOS.</h5>
|
<h5 align="center">Apps available for Mac, Windows, Linux, Android, and iOS.</h5>
|
||||||
<h5 align="center">Built with Electron, React + Redux, Webpack, and CSSModules.</h5>
|
<h5 align="center">Built with Electron, React + Redux, Webpack, and CSSModules.</h5>
|
||||||
|
<p align="center">
|
||||||
[](https://travis-ci.org/BoostIO/Boostnote)
|
<a href="https://travis-ci.org/BoostIO/Boostnote">
|
||||||
|
<img src="https://travis-ci.org/BoostIO/Boostnote.svg?branch=master" alt="Build Status" />
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
## Authors & Maintainers
|
## Authors & Maintainers
|
||||||
- [Rokt33r](https://github.com/rokt33r)
|
- [Rokt33r](https://github.com/rokt33r)
|
||||||
|
|||||||
35
tests/dataApi/copyFile-test.js
Normal file
35
tests/dataApi/copyFile-test.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
const test = require('ava')
|
||||||
|
const copyFile = require('browser/main/lib/dataApi/copyFile')
|
||||||
|
|
||||||
|
const path = require('path')
|
||||||
|
const fs = require('fs')
|
||||||
|
|
||||||
|
const testFile = 'test.txt'
|
||||||
|
const srcFolder = path.join(__dirname, '🤔')
|
||||||
|
const srcPath = path.join(srcFolder, testFile)
|
||||||
|
const dstFolder = path.join(__dirname, '😇')
|
||||||
|
const dstPath = path.join(dstFolder, testFile)
|
||||||
|
|
||||||
|
test.before((t) => {
|
||||||
|
if (!fs.existsSync(srcFolder)) fs.mkdirSync(srcFolder)
|
||||||
|
|
||||||
|
fs.writeFileSync(srcPath, 'test')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('`copyFile` should handle encoded URI on src path', (t) => {
|
||||||
|
return copyFile(encodeURI(srcPath), dstPath)
|
||||||
|
.then(() => {
|
||||||
|
t.true(true)
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
t.true(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test.after((t) => {
|
||||||
|
fs.unlinkSync(srcPath)
|
||||||
|
fs.unlinkSync(dstPath)
|
||||||
|
fs.rmdirSync(srcFolder)
|
||||||
|
fs.rmdirSync(dstFolder)
|
||||||
|
})
|
||||||
|
|
||||||
@@ -3591,9 +3591,9 @@ filename-reserved-regex@^2.0.0:
|
|||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz#abf73dfab735d045440abfea2d91f389ebbfa229"
|
resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz#abf73dfab735d045440abfea2d91f389ebbfa229"
|
||||||
|
|
||||||
filenamify@^2.0.0:
|
filenamify@^2.1.0:
|
||||||
version "2.0.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-2.0.0.tgz#bd162262c0b6e94bfbcdcf19a3bbb3764f785695"
|
resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-2.1.0.tgz#88faf495fb1b47abfd612300002a16228c677ee9"
|
||||||
dependencies:
|
dependencies:
|
||||||
filename-reserved-regex "^2.0.0"
|
filename-reserved-regex "^2.0.0"
|
||||||
strip-outer "^1.0.0"
|
strip-outer "^1.0.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user