1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-21 05:31:45 +00:00
This commit is contained in:
Nguyễn Việt Hưng
2018-12-10 11:56:13 +07:00
13 changed files with 156 additions and 116 deletions

View File

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

View File

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

View File

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

View File

@@ -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')]
}) })
} }
@@ -800,7 +807,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>

View File

@@ -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')]
}) })
} }
} }

View File

@@ -204,6 +204,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
}) })
} }
}) })

View File

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

View File

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

View File

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

View File

@@ -85,40 +85,25 @@ 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',
{
label: 'Export as',
submenu: [
{
label: 'Plain Text (.txt)',
click () { click () {
mainWindow.webContents.send('list:isMarkdownNote') mainWindow.webContents.send('detail:delete')
mainWindow.webContents.send('export:save-text')
} }
}, },
{ {
label: 'MarkDown (.md)', label: 'Clone Note',
accelerator: macOS ? 'Command+D' : 'Control+D',
click () { click () {
mainWindow.webContents.send('list:isMarkdownNote') mainWindow.webContents.send('list:clone')
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,14 +119,31 @@ const file = {
] ]
}, },
{ {
type: 'separator' label: 'Export as',
submenu: [
{
label: 'Plain Text (.txt)',
click () {
mainWindow.webContents.send('list:isMarkdownNote', 'export-txt')
mainWindow.webContents.send('export:save-text')
}
}, },
{ {
label: 'Format Table', label: 'MarkDown (.md)',
click () { click () {
mainWindow.webContents.send('code:format-table') 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',

View File

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

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

View File

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