Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 86 additions & 11 deletions browser/main/NoteList/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,16 @@ import Markdown from '../../lib/markdown'
import i18n from 'browser/lib/i18n'
import { confirmDeleteNote } from 'browser/lib/confirmDeleteNote'
import context from 'browser/lib/context'
import exportNote from 'browser/main/lib/dataApi/exportNote'
import { findStorage } from 'browser/lib/findStorage'
import escapeStringRegexp from 'escape-string-regexp'
import mdurl from 'mdurl'
import filenamify from 'filenamify'

const { remote } = require('electron')
const { dialog } = remote
const WP_POST_PATH = '/wp/v2/posts'
const STORAGE_FOLDER_PLACEHOLDER = ':storage'

function sortByCreatedAt (a, b) {
return new Date(b.createdAt) - new Date(a.createdAt)
Expand Down Expand Up @@ -544,6 +550,7 @@ class NoteList extends React.Component {
const publishLabel = i18n.__('Publish Blog')
const updateLabel = i18n.__('Update Blog')
const openBlogLabel = i18n.__('Open Blog')
const exportHexoLabel = i18n.__('Export Hexo File')

const templates = []

Expand Down Expand Up @@ -573,18 +580,27 @@ class NoteList extends React.Component {
click: this.copyNoteLink.bind(this, note)
})
if (note.type === 'MARKDOWN_NOTE') {
if (note.blog && note.blog.blogLink && note.blog.blogId) {
templates.push({
label: updateLabel,
click: this.publishMarkdown.bind(this)
}, {
label: openBlogLabel,
click: () => this.openBlog.bind(this)(note)
})
} else {
const config = ConfigManager.get()
const blogType = config.blog.type
if (blogType === 'wordpress') {
if (note.blog && note.blog.blogLink && note.blog.blogId) {
templates.push({
label: updateLabel,
click: this.publishMarkdown.bind(this)
}, {
label: openBlogLabel,
click: () => this.openBlog.bind(this)(note)
})
} else {
templates.push({
label: publishLabel,
click: this.publishMarkdown.bind(this)
})
}
} else if (blogType === 'hexo') {
templates.push({
label: publishLabel,
click: this.publishMarkdown.bind(this)
label: exportHexoLabel,
click: this.exportHexoFile.bind(this)
})
}
}
Expand Down Expand Up @@ -766,6 +782,65 @@ class NoteList extends React.Component {
})
}

exportHexoFile () {
const config = ConfigManager.get()
const {mdFilePath, imgFilePath, imgRelaPath} = config.blog

const { selectedNoteKeys } = this.state
const notes = this.notes.map((note) => Object.assign({}, note))
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
const firstNote = selectedNotes[0]
const storage = findStorage(firstNote.storage)

const nodeKey = firstNote.key
const storageKey = storage.key
const noteContent = firstNote.content
const noteName = filenamify(firstNote.title, {replacement: '_'}) + '.md'
const targetPath = path.join(mdFilePath, noteName)

const outputFormatter = function (exportedData, exportTasks) {
exportTasks.forEach(task => {
task.dst = imgFilePath
})
exportedData = noteContent.replace(new RegExp('/?' +
STORAGE_FOLDER_PLACEHOLDER + '.*?\\)', 'g'), function (match) {
const temp = match
.replace(new RegExp(mdurl.encode(path.win32.sep), 'g'), '/')
.replace(new RegExp(mdurl.encode(path.posix.sep), 'g'), '/')
.replace(new RegExp(escapeStringRegexp(path.win32.sep), 'g'), '/')
.replace(new RegExp(escapeStringRegexp(path.posix.sep), 'g'), '/')
return temp.replace(new RegExp(STORAGE_FOLDER_PLACEHOLDER +
'(' + '/' + nodeKey + ')?', 'g'), imgRelaPath)
})
return exportedData
}

const hasMdFilePath = fs.existsSync(mdFilePath)
const hasImgFilePath = fs.existsSync(imgFilePath)
if (!hasMdFilePath) {
fs.mkdirSync(mdFilePath)
}
if (!hasImgFilePath) {
fs.mkdirSync(imgFilePath)
}

exportNote(nodeKey, storageKey, noteContent, targetPath,
outputFormatter)
.then(res => {
dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'info',
message: `Exported to ${targetPath}`
})
})
.catch(err => {
dialog.showErrorBox(
'Export error',
err ? err.message || err : 'Unexpected error during export'
)
throw err
})
}

publishMarkdown () {
if (this.pendingPublish) {
clearTimeout(this.pendingPublish)
Expand Down
5 changes: 4 additions & 1 deletion browser/main/lib/ConfigManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,10 @@ export const DEFAULT_CONFIG = {
authMethod: 'JWT', // Available value: JWT, USER
token: '',
username: '',
password: ''
password: '',
mdFilePath: 'C:/hexo/source/_posts',
imgFilePath: 'C:/hexo/source/images',
imgRelaPath: '/images'
},
coloredTags: {}
}
Expand Down
182 changes: 125 additions & 57 deletions browser/main/modals/PreferencesModal/Blog.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,11 @@ class Blog extends React.Component {
password: !_.isNil(this.refs.passwordInput) ? this.refs.passwordInput.value : config.blog.password,
username: !_.isNil(this.refs.usernameInput) ? this.refs.usernameInput.value : config.blog.username,
token: !_.isNil(this.refs.tokenInput) ? this.refs.tokenInput.value : config.blog.token,
authMethod: this.refs.authMethodDropdown.value,
address: this.refs.addressInput.value,
authMethod: !_.isNil(this.refs.authMethodDropdown) ? this.refs.authMethodDropdown.value : config.blog.authMethod,
address: !_.isNil(this.refs.addressInput) ? this.refs.addressInput.value : config.blog.address,
mdFilePath: !_.isNil(this.refs.mdFilePathInput) ? this.refs.mdFilePathInput.value : config.blog.mdFilePath,
imgFilePath: !_.isNil(this.refs.imgFilePathInput) ? this.refs.imgFilePathInput.value : config.blog.imgFilePath,
imgRelaPath: !_.isNil(this.refs.imgRelaPathInput) ? this.refs.imgRelaPathInput.value : config.blog.imgRelaPath,
type: this.refs.typeDropdown.value
}
this.setState({
Expand Down Expand Up @@ -97,6 +100,100 @@ class Blog extends React.Component {
{BlogAlert.message}
</p>
: null
if (config.blog.type === 'wordpress') {
return (
<div styleName='root'>
<div styleName='group'>
<div styleName='group-header'>{i18n.__('Blog')}</div>
<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Blog Type')}
</div>
<div styleName='group-section-control'>
<select
value={config.blog.type}
ref='typeDropdown'
onChange={(e) => this.handleBlogChange(e)}
>
<option value='wordpress' key='wordpress'>{i18n.__('wordpress')}</option>
<option value='hexo' key='hexo'>{i18n.__('hexo')}</option>
</select>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Blog Address')}</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
onChange={(e) => this.handleBlogChange(e)}
ref='addressInput'
value={config.blog.address}
type='text'
/>
</div>
</div>
<div styleName='group-control'>
<button styleName='group-control-rightButton'
onClick={(e) => this.handleSaveButtonClick(e)}>{i18n.__('Save')}
</button>
{blogAlertElement}
</div>
</div>
<div styleName='group-header2'>{i18n.__('Auth')}</div>

<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Authentication Method')}
</div>
<div styleName='group-section-control'>
<select
value={config.blog.authMethod}
ref='authMethodDropdown'
onChange={(e) => this.handleBlogChange(e)}
>
<option value='JWT' key='JWT'>{i18n.__('JWT')}</option>
<option value='USER' key='USER'>{i18n.__('USER')}</option>
</select>
</div>
</div>
{ config.blog.authMethod === 'JWT' &&
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Token')}</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
onChange={(e) => this.handleBlogChange(e)}
ref='tokenInput'
value={config.blog.token}
type='text' />
</div>
</div>
}
{ config.blog.authMethod === 'USER' &&
<div>
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('UserName')}</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
onChange={(e) => this.handleBlogChange(e)}
ref='usernameInput'
value={config.blog.username}
type='text' />
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Password')}</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
onChange={(e) => this.handleBlogChange(e)}
ref='passwordInput'
value={config.blog.password}
type='password' />
</div>
</div>
</div>
}
</div>
)
}
return (
<div styleName='root'>
<div styleName='group'>
Expand All @@ -112,80 +209,51 @@ class Blog extends React.Component {
onChange={(e) => this.handleBlogChange(e)}
>
<option value='wordpress' key='wordpress'>{i18n.__('wordpress')}</option>
<option value='hexo' key='hexo'>{i18n.__('hexo')}</option>
</select>
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Blog Address')}</div>
<div styleName='group-section-label'>{i18n.__('Markdown file path')}</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
onChange={(e) => this.handleBlogChange(e)}
ref='addressInput'
value={config.blog.address}
ref='mdFilePathInput'
value={config.blog.mdFilePath}
type='text'
/>
</div>
</div>
<div styleName='group-control'>
<button styleName='group-control-rightButton'
onClick={(e) => this.handleSaveButtonClick(e)}>{i18n.__('Save')}
</button>
{blogAlertElement}
</div>
</div>
<div styleName='group-header2'>{i18n.__('Auth')}</div>

<div styleName='group-section'>
<div styleName='group-section-label'>
{i18n.__('Authentication Method')}
</div>
<div styleName='group-section-control'>
<select
value={config.blog.authMethod}
ref='authMethodDropdown'
onChange={(e) => this.handleBlogChange(e)}
>
<option value='JWT' key='JWT'>{i18n.__('JWT')}</option>
<option value='USER' key='USER'>{i18n.__('USER')}</option>
</select>
</div>
</div>
{ config.blog.authMethod === 'JWT' &&
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Token')}</div>
<div styleName='group-section-label'>{i18n.__('Image file path')}</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
onChange={(e) => this.handleBlogChange(e)}
ref='tokenInput'
value={config.blog.token}
type='text' />
ref='imgFilePathInput'
value={config.blog.imgFilePath}
type='text'
/>
</div>
</div>
}
{ config.blog.authMethod === 'USER' &&
<div>
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('UserName')}</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
onChange={(e) => this.handleBlogChange(e)}
ref='usernameInput'
value={config.blog.username}
type='text' />
</div>
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Password')}</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
onChange={(e) => this.handleBlogChange(e)}
ref='passwordInput'
value={config.blog.password}
type='password' />
</div>
<div styleName='group-section'>
<div styleName='group-section-label'>{i18n.__('Image relative path')}</div>
<div styleName='group-section-control'>
<input styleName='group-section-control-input'
onChange={(e) => this.handleBlogChange(e)}
ref='imgRelaPathInput'
value={config.blog.imgRelaPath}
type='text'
/>
</div>
</div>
}

<div styleName='group-control'>
<button styleName='group-control-rightButton'
onClick={(e) => this.handleSaveButtonClick(e)}>{i18n.__('Save')}
</button>
{blogAlertElement}
</div>
</div>
</div>
)
}
Expand Down