diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js
index 463f0a16..17d2cb82 100755
--- a/browser/components/MarkdownPreview.js
+++ b/browser/components/MarkdownPreview.js
@@ -291,26 +291,7 @@ export default class MarkdownPreview extends React.Component {
}
handleSaveAsMd () {
- this.exportAsDocument('md', (noteContent, exportTasks) => {
- 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
- })
+ this.exportAsDocument('md')
}
handleSaveAsHtml () {
@@ -339,11 +320,6 @@ export default class MarkdownPreview extends React.Component {
)
let body = this.markdown.render(noteContent)
const files = [this.GetCodeThemeLink(codeBlockTheme), ...CSS_FILES]
- const attachmentsAbsolutePaths = attachmentManagement.getAbsolutePathsOfAttachmentsInContent(
- noteContent,
- this.props.storagePath
- )
-
files.forEach(file => {
if (global.process.platform === 'win32') {
file = file.replace('file:///', '')
@@ -355,16 +331,6 @@ export default class MarkdownPreview extends React.Component {
dst: 'css'
})
})
- attachmentsAbsolutePaths.forEach(attachment => {
- exportTasks.push({
- src: attachment,
- dst: attachmentManagement.DESTINATION_FOLDER
- })
- })
- body = attachmentManagement.removeStorageAndNoteReferences(
- body,
- this.props.noteKey
- )
let styles = ''
files.forEach(file => {
@@ -397,8 +363,9 @@ export default class MarkdownPreview extends React.Component {
if (filename) {
const content = this.props.value
const storage = this.props.storagePath
+ const nodeKey = this.props.noteKey
- exportNote(storage, content, filename, contentFormatter)
+ exportNote(nodeKey, storage, content, filename, contentFormatter)
.then(res => {
dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'info',
diff --git a/browser/main/Detail/InfoPanel.js b/browser/main/Detail/InfoPanel.js
index 4ce610fa..15535186 100644
--- a/browser/main/Detail/InfoPanel.js
+++ b/browser/main/Detail/InfoPanel.js
@@ -70,22 +70,22 @@ class InfoPanel extends React.Component {
-
-
exportAsMd(e)}>
+ exportAsMd(e, 'export-md')}>
.md
- exportAsTxt(e)}>
+ exportAsTxt(e, 'export-txt')}>
.txt
- exportAsHtml(e)}>
+ exportAsHtml(e, 'export-html')}>
.html
diff --git a/browser/main/Detail/SnippetNoteDetail.js b/browser/main/Detail/SnippetNoteDetail.js
index 4a38ffe5..4a5076da 100644
--- a/browser/main/Detail/SnippetNoteDetail.js
+++ b/browser/main/Detail/SnippetNoteDetail.js
@@ -645,11 +645,18 @@ class SnippetNoteDetail extends React.Component {
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(), {
type: 'warning',
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')]
})
}
@@ -800,7 +807,9 @@ class SnippetNoteDetail extends React.Component {
createdAt={formatDate(note.createdAt)}
exportAsMd={this.showWarning}
exportAsTxt={this.showWarning}
+ exportAsHtml={this.showWarning}
type={note.type}
+ print={this.showWarning}
/>
diff --git a/browser/main/NoteList/index.js b/browser/main/NoteList/index.js
index 08d7d2e2..d1c8d14a 100644
--- a/browser/main/NoteList/index.js
+++ b/browser/main/NoteList/index.js
@@ -64,13 +64,14 @@ class NoteList extends React.Component {
this.focusHandler = () => {
this.refs.list.focus()
}
- this.alertIfSnippetHandler = () => {
- this.alertIfSnippet()
+ this.alertIfSnippetHandler = (event, msg) => {
+ this.alertIfSnippet(msg)
}
this.importFromFileHandler = this.importFromFile.bind(this)
this.jumpNoteByHash = this.jumpNoteByHashHandler.bind(this)
this.handleNoteListKeyUp = this.handleNoteListKeyUp.bind(this)
this.getNoteKeyFromTargetIndex = this.getNoteKeyFromTargetIndex.bind(this)
+ this.cloneNote = this.cloneNote.bind(this)
this.deleteNote = this.deleteNote.bind(this)
this.focusNote = this.focusNote.bind(this)
this.pinToTop = this.pinToTop.bind(this)
@@ -96,6 +97,7 @@ class NoteList extends React.Component {
this.refreshTimer = setInterval(() => this.forceUpdate(), 60 * 1000)
ee.on('list:next', this.selectNextNoteHandler)
ee.on('list:prior', this.selectPriorNoteHandler)
+ ee.on('list:clone', this.cloneNote)
ee.on('list:focus', this.focusHandler)
ee.on('list:isMarkdownNote', this.alertIfSnippetHandler)
ee.on('import:file', this.importFromFileHandler)
@@ -118,6 +120,7 @@ class NoteList extends React.Component {
ee.off('list:next', this.selectNextNoteHandler)
ee.off('list:prior', this.selectPriorNoteHandler)
+ ee.off('list:clone', this.cloneNote)
ee.off('list:focus', this.focusHandler)
ee.off('list:isMarkdownNote', this.alertIfSnippetHandler)
ee.off('import:file', this.importFromFileHandler)
@@ -277,12 +280,6 @@ class NoteList extends React.Component {
ee.emit('top:new-note')
}
- // D key
- if (e.keyCode === 68) {
- e.preventDefault()
- this.deleteNote()
- }
-
// E key
if (e.keyCode === 69) {
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()
if (this.notes[targetIndex].type === 'SNIPPET_NOTE') {
dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'warning',
message: i18n.__('Sorry!'),
- detail: i18n.__('md/text import is available only a markdown note.'),
- buttons: [i18n.__('OK'), i18n.__('Cancel')]
+ detail: i18n.__(warningMessage(msg) + ' is available only in markdown notes.'),
+ buttons: [i18n.__('OK')]
})
}
}
diff --git a/browser/main/SideNav/StorageItem.js b/browser/main/SideNav/StorageItem.js
index 7b5caf72..3b11e2f4 100644
--- a/browser/main/SideNav/StorageItem.js
+++ b/browser/main/SideNav/StorageItem.js
@@ -204,6 +204,20 @@ class StorageItem extends React.Component {
folderKey: data.folderKey,
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
})
}
})
diff --git a/browser/main/lib/dataApi/copyFile.js b/browser/main/lib/dataApi/copyFile.js
index 2dc66309..6f23aae2 100755
--- a/browser/main/lib/dataApi/copyFile.js
+++ b/browser/main/lib/dataApi/copyFile.js
@@ -16,7 +16,7 @@ function copyFile (srcPath, dstPath) {
const dstFolder = path.dirname(dstPath)
if (!fs.existsSync(dstFolder)) fs.mkdirSync(dstFolder)
- const input = fs.createReadStream(srcPath)
+ const input = fs.createReadStream(decodeURI(srcPath))
const output = fs.createWriteStream(dstPath)
output.on('error', reject)
diff --git a/browser/main/lib/dataApi/exportFolder.js b/browser/main/lib/dataApi/exportFolder.js
index 3e998f15..771f77dc 100644
--- a/browser/main/lib/dataApi/exportFolder.js
+++ b/browser/main/lib/dataApi/exportFolder.js
@@ -1,9 +1,9 @@
import { findStorage } from 'browser/lib/findStorage'
import resolveStorageData from './resolveStorageData'
import resolveStorageNotes from './resolveStorageNotes'
+import exportNote from './exportNote'
import filenamify from 'filenamify'
import * as path from 'path'
-import * as fs from 'fs'
/**
* @param {String} storageKey
@@ -45,9 +45,9 @@ function exportFolder (storageKey, folderKey, fileType, exportDir) {
notes
.filter(note => note.folder === folderKey && note.isTrashed === false && note.type === 'MARKDOWN_NOTE')
- .forEach(snippet => {
- const notePath = path.join(exportDir, `${filenamify(snippet.title, {replacement: '_'})}.${fileType}`)
- fs.writeFileSync(notePath, snippet.content)
+ .forEach(note => {
+ const notePath = path.join(exportDir, `${filenamify(note.title, {replacement: '_'})}.${fileType}`)
+ exportNote(note.key, storage.path, note.content, notePath, null)
})
return {
diff --git a/browser/main/lib/dataApi/exportNote.js b/browser/main/lib/dataApi/exportNote.js
index e4fec5f4..b358e548 100755
--- a/browser/main/lib/dataApi/exportNote.js
+++ b/browser/main/lib/dataApi/exportNote.js
@@ -4,27 +4,43 @@ import { findStorage } from 'browser/lib/findStorage'
const fs = require('fs')
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
- * and copies images to it. Changes links to images in the content of the note
+ * If attachments are stored in the storage, creates 'attachments' subfolder in target directory
+ * 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} noteContent Content to export
* @param {String} targetPath Path to exported file
* @param {function} outputFormatter
* @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 exportTasks = []
if (!storagePath) {
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) {
exportedData = outputFormatter(exportedData, exportTasks)
diff --git a/lib/main-menu.js b/lib/main-menu.js
index 91f3c8c6..05921347 100644
--- a/lib/main-menu.js
+++ b/lib/main-menu.js
@@ -85,39 +85,24 @@ const file = {
},
{
label: 'Focus Note',
- accelerator: 'Control+E',
+ accelerator: macOS ? 'Command+E' : 'Control+E',
click () {
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',
- submenu: [
- {
- label: 'Plain Text (.txt)',
- 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')
- }
- }
- ]
+ label: 'Clone Note',
+ accelerator: macOS ? 'Command+D' : 'Control+D',
+ click () {
+ mainWindow.webContents.send('list:clone')
+ }
},
{
type: 'separator'
@@ -134,13 +119,30 @@ const file = {
]
},
{
- type: 'separator'
- },
- {
- label: 'Format Table',
- click () {
- mainWindow.webContents.send('code:format-table')
- }
+ label: 'Export as',
+ submenu: [
+ {
+ label: 'Plain Text (.txt)',
+ click () {
+ 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'
@@ -153,24 +155,20 @@ const file = {
}
},
{
- type: 'separator'
- },
- {
- label: 'Print',
- accelerator: 'CommandOrControl+P',
+ label: 'Format Table',
click () {
- mainWindow.webContents.send('list:isMarkdownNote')
- mainWindow.webContents.send('print')
+ mainWindow.webContents.send('code:format-table')
}
},
{
type: 'separator'
},
{
- label: 'Delete Note',
- accelerator: macOS ? 'Control+Backspace' : 'Control+Delete',
+ label: 'Print',
+ accelerator: 'CommandOrControl+P',
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())
}
},
- {
- type: 'separator'
- },
{
label: 'Toggle Side Bar',
accelerator: 'CommandOrControl+B',
diff --git a/package.json b/package.json
index 1fb8e380..128a21af 100644
--- a/package.json
+++ b/package.json
@@ -62,7 +62,7 @@
"escape-string-regexp": "^1.0.5",
"file-uri-to-path": "^1.0.0",
"file-url": "^2.0.2",
- "filenamify": "^2.0.0",
+ "filenamify": "^2.1.0",
"flowchart.js": "^1.6.5",
"font-awesome": "^4.3.0",
"fs-extra": "^5.0.0",
diff --git a/tests/dataApi/copyFile-test.js b/tests/dataApi/copyFile-test.js
new file mode 100644
index 00000000..412d510a
--- /dev/null
+++ b/tests/dataApi/copyFile-test.js
@@ -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)
+})
+
diff --git a/yarn.lock b/yarn.lock
index 4b24d45d..48dd9056 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3591,9 +3591,9 @@ filename-reserved-regex@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz#abf73dfab735d045440abfea2d91f389ebbfa229"
-filenamify@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-2.0.0.tgz#bd162262c0b6e94bfbcdcf19a3bbb3764f785695"
+filenamify@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-2.1.0.tgz#88faf495fb1b47abfd612300002a16228c677ee9"
dependencies:
filename-reserved-regex "^2.0.0"
strip-outer "^1.0.0"