mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-13 01:36:22 +00:00
allow publishing markdown to wordpress
This commit is contained in:
@@ -123,7 +123,11 @@ NoteItem.propTypes = {
|
|||||||
title: PropTypes.string.isrequired,
|
title: PropTypes.string.isrequired,
|
||||||
tags: PropTypes.array,
|
tags: PropTypes.array,
|
||||||
isStarred: PropTypes.bool.isRequired,
|
isStarred: PropTypes.bool.isRequired,
|
||||||
isTrashed: PropTypes.bool.isRequired
|
isTrashed: PropTypes.bool.isRequired,
|
||||||
|
blog: {
|
||||||
|
blogLink: PropTypes.string,
|
||||||
|
blogId: PropTypes.number
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
handleNoteClick: PropTypes.func.isRequired,
|
handleNoteClick: PropTypes.func.isRequired,
|
||||||
handleNoteContextMenu: PropTypes.func.isRequired,
|
handleNoteContextMenu: PropTypes.func.isRequired,
|
||||||
|
|||||||
@@ -13,10 +13,13 @@ import searchFromNotes from 'browser/lib/search'
|
|||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { hashHistory } from 'react-router'
|
import { hashHistory } from 'react-router'
|
||||||
|
import electron from 'electron'
|
||||||
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
import AwsMobileAnalyticsConfig from 'browser/main/lib/AwsMobileAnalyticsConfig'
|
||||||
|
import markdown from '../../lib/markdown'
|
||||||
|
|
||||||
const { remote } = require('electron')
|
const { remote } = require('electron')
|
||||||
const { Menu, MenuItem, dialog } = remote
|
const { Menu, MenuItem, dialog } = remote
|
||||||
|
const WP_POST_PATH = '/wp/v2/posts'
|
||||||
|
|
||||||
function sortByCreatedAt (a, b) {
|
function sortByCreatedAt (a, b) {
|
||||||
return new Date(b.createdAt) - new Date(a.createdAt)
|
return new Date(b.createdAt) - new Date(a.createdAt)
|
||||||
@@ -458,6 +461,9 @@ class NoteList extends React.Component {
|
|||||||
const deleteLabel = 'Delete Note'
|
const deleteLabel = 'Delete Note'
|
||||||
const cloneNote = 'Clone Note'
|
const cloneNote = 'Clone Note'
|
||||||
const restoreNote = 'Restore Note'
|
const restoreNote = 'Restore Note'
|
||||||
|
const publishLabel = 'Publish Blog'
|
||||||
|
const updateLabel = 'Update Blog'
|
||||||
|
const openBlogLabel = 'Open Blog'
|
||||||
|
|
||||||
const menu = new Menu()
|
const menu = new Menu()
|
||||||
if (!location.pathname.match(/\/starred|\/trash/)) {
|
if (!location.pathname.match(/\/starred|\/trash/)) {
|
||||||
@@ -482,6 +488,24 @@ class NoteList extends React.Component {
|
|||||||
label: cloneNote,
|
label: cloneNote,
|
||||||
click: this.cloneNote.bind(this)
|
click: this.cloneNote.bind(this)
|
||||||
}))
|
}))
|
||||||
|
if (note.type === 'MARKDOWN_NOTE') {
|
||||||
|
if (note.blog) {
|
||||||
|
menu.append(new MenuItem({
|
||||||
|
label: updateLabel,
|
||||||
|
click: this.publishMarkdown.bind(this)
|
||||||
|
}))
|
||||||
|
menu.append(new MenuItem({
|
||||||
|
label: openBlogLabel,
|
||||||
|
click: () => this.openBlog.bind(this)(note)
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
menu.append(new MenuItem({
|
||||||
|
label: publishLabel,
|
||||||
|
click: this.publishMarkdown.bind(this)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
menu.popup()
|
menu.popup()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -630,6 +654,107 @@ class NoteList extends React.Component {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
save (note) {
|
||||||
|
const { dispatch } = this.props
|
||||||
|
dataApi
|
||||||
|
.updateNote(note.storage, note.key, note)
|
||||||
|
.then((note) => {
|
||||||
|
dispatch({
|
||||||
|
type: 'UPDATE_NOTE',
|
||||||
|
note: note
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
publishMarkdown () {
|
||||||
|
if (this.pendingPublish) {
|
||||||
|
clearTimeout(this.pendingPublish)
|
||||||
|
}
|
||||||
|
this.pendingPublish = setTimeout(() => {
|
||||||
|
this.publishMarkdownNow()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
publishMarkdownNow () {
|
||||||
|
const {selectedNoteKeys} = this.state
|
||||||
|
const notes = this.notes.map((note) => Object.assign({}, note))
|
||||||
|
const selectedNotes = findNotesByKeys(notes, selectedNoteKeys)
|
||||||
|
const firstNote = selectedNotes[0]
|
||||||
|
const config = ConfigManager.get()
|
||||||
|
let {address, token, authMethod, username, password} = config.blog
|
||||||
|
if (authMethod === 'USER') {
|
||||||
|
token = `Basic ${window.btoa(`${username}:${password}`)}`
|
||||||
|
} else {
|
||||||
|
token = `Bearer ${token}`
|
||||||
|
}
|
||||||
|
var data = {
|
||||||
|
title: firstNote.title,
|
||||||
|
content: markdown.render(firstNote.content),
|
||||||
|
status: 'publish'
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = ''
|
||||||
|
let method = ''
|
||||||
|
if (firstNote.blog) {
|
||||||
|
url = `${address}${WP_POST_PATH}/${firstNote.blog.blogId}`
|
||||||
|
method = 'PUT'
|
||||||
|
} else {
|
||||||
|
url = `${address}${WP_POST_PATH}`
|
||||||
|
method = 'POST'
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
fetch(url, {
|
||||||
|
method: method,
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
headers: {
|
||||||
|
'Authorization': token,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
}).then(res => res.json())
|
||||||
|
.then(response => {
|
||||||
|
firstNote.blog = {
|
||||||
|
blogLink: response.link,
|
||||||
|
blogId: response.id
|
||||||
|
}
|
||||||
|
this.save(firstNote)
|
||||||
|
this.confirmPublish(firstNote)
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.confirmPublishError()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmPublishError () {
|
||||||
|
const { remote } = electron
|
||||||
|
const { dialog } = remote
|
||||||
|
const alertError = {
|
||||||
|
type: 'warning',
|
||||||
|
message: 'Publish Failed',
|
||||||
|
detail: 'Check and update your blog setting and try again.',
|
||||||
|
buttons: ['Confirm']
|
||||||
|
}
|
||||||
|
dialog.showMessageBox(remote.getCurrentWindow(), alertError)
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmPublish (note) {
|
||||||
|
const buttonIndex = dialog.showMessageBox(remote.getCurrentWindow(), {
|
||||||
|
type: 'warning',
|
||||||
|
message: 'Publish Succeeded',
|
||||||
|
detail: `${note.title} is published at ${note.blog.blogLink}`,
|
||||||
|
buttons: ['Confirm', 'Open Blog']
|
||||||
|
})
|
||||||
|
|
||||||
|
if (buttonIndex === 1) {
|
||||||
|
this.openBlog(note)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
openBlog (note) {
|
||||||
|
const { shell } = electron
|
||||||
|
console.log(note)
|
||||||
|
shell.openExternal(note.blog.blogLink)
|
||||||
|
}
|
||||||
|
|
||||||
importFromFile () {
|
importFromFile () {
|
||||||
const options = {
|
const options = {
|
||||||
filters: [
|
filters: [
|
||||||
|
|||||||
@@ -49,6 +49,14 @@ export const DEFAULT_CONFIG = {
|
|||||||
latexBlockOpen: '$$',
|
latexBlockOpen: '$$',
|
||||||
latexBlockClose: '$$',
|
latexBlockClose: '$$',
|
||||||
scrollPastEnd: false
|
scrollPastEnd: false
|
||||||
|
},
|
||||||
|
blog: {
|
||||||
|
type: 'wordpress', // Available value: wordpress, add more types in the future plz
|
||||||
|
address: 'http://wordpress.com/wp-json',
|
||||||
|
authMethod: 'JWT', // Available value: JWT, USER
|
||||||
|
token: '',
|
||||||
|
username: '',
|
||||||
|
password: ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,6 +159,7 @@ function set (updates) {
|
|||||||
function assignConfigValues (originalConfig, rcConfig) {
|
function assignConfigValues (originalConfig, rcConfig) {
|
||||||
const config = Object.assign({}, DEFAULT_CONFIG, originalConfig, rcConfig)
|
const config = Object.assign({}, DEFAULT_CONFIG, originalConfig, rcConfig)
|
||||||
config.hotkey = Object.assign({}, DEFAULT_CONFIG.hotkey, originalConfig.hotkey, rcConfig.hotkey)
|
config.hotkey = Object.assign({}, DEFAULT_CONFIG.hotkey, originalConfig.hotkey, rcConfig.hotkey)
|
||||||
|
config.blog = Object.assign({}, DEFAULT_CONFIG.blog, originalConfig.blog, rcConfig.blog)
|
||||||
config.ui = Object.assign({}, DEFAULT_CONFIG.ui, originalConfig.ui, rcConfig.ui)
|
config.ui = Object.assign({}, DEFAULT_CONFIG.ui, originalConfig.ui, rcConfig.ui)
|
||||||
config.editor = Object.assign({}, DEFAULT_CONFIG.editor, originalConfig.editor, rcConfig.editor)
|
config.editor = Object.assign({}, DEFAULT_CONFIG.editor, originalConfig.editor, rcConfig.editor)
|
||||||
config.preview = Object.assign({}, DEFAULT_CONFIG.preview, originalConfig.preview, rcConfig.preview)
|
config.preview = Object.assign({}, DEFAULT_CONFIG.preview, originalConfig.preview, rcConfig.preview)
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ function validateInput (input) {
|
|||||||
validatedInput.isPinned = !!input.isPinned
|
validatedInput.isPinned = !!input.isPinned
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!_.isNil(input.blog)) {
|
||||||
|
validatedInput.blog = input.blog
|
||||||
|
}
|
||||||
validatedInput.type = input.type
|
validatedInput.type = input.type
|
||||||
switch (input.type) {
|
switch (input.type) {
|
||||||
case 'MARKDOWN_NOTE':
|
case 'MARKDOWN_NOTE':
|
||||||
|
|||||||
198
browser/main/modals/PreferencesModal/Blog.js
Normal file
198
browser/main/modals/PreferencesModal/Blog.js
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
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 PropTypes from 'prop-types'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
const electron = require('electron')
|
||||||
|
const { shell } = electron
|
||||||
|
const ipc = electron.ipcRenderer
|
||||||
|
class Blog extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
config: props.config,
|
||||||
|
BlogAlert: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleLinkClick (e) {
|
||||||
|
shell.openExternal(e.currentTarget.href)
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
clearMessage () {
|
||||||
|
_.debounce(() => {
|
||||||
|
this.setState({
|
||||||
|
BlogAlert: null
|
||||||
|
})
|
||||||
|
}, 2000)()
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
this.handleSettingDone = () => {
|
||||||
|
this.setState({BlogAlert: {
|
||||||
|
type: 'success',
|
||||||
|
message: 'Successfully applied!'
|
||||||
|
}})
|
||||||
|
}
|
||||||
|
this.handleSettingError = (err) => {
|
||||||
|
this.setState({BlogAlert: {
|
||||||
|
type: 'error',
|
||||||
|
message: err.message != null ? err.message : 'Error occurs!'
|
||||||
|
}})
|
||||||
|
}
|
||||||
|
this.oldBlog = this.state.config.blog
|
||||||
|
ipc.addListener('APP_SETTING_DONE', this.handleSettingDone)
|
||||||
|
ipc.addListener('APP_SETTING_ERROR', this.handleSettingError)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleBlogChange (e) {
|
||||||
|
const { config } = this.state
|
||||||
|
config.blog = {
|
||||||
|
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,
|
||||||
|
type: this.refs.typeDropdown.value
|
||||||
|
}
|
||||||
|
this.setState({
|
||||||
|
config
|
||||||
|
})
|
||||||
|
if (_.isEqual(this.oldBlog, config.blog)) {
|
||||||
|
this.props.haveToSave()
|
||||||
|
} else {
|
||||||
|
this.props.haveToSave({
|
||||||
|
tab: 'Blog',
|
||||||
|
type: 'warning',
|
||||||
|
message: 'You have to save!'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSaveButtonClick (e) {
|
||||||
|
const newConfig = {
|
||||||
|
blog: this.state.config.blog
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigManager.set(newConfig)
|
||||||
|
|
||||||
|
store.dispatch({
|
||||||
|
type: 'SET_UI',
|
||||||
|
config: newConfig
|
||||||
|
})
|
||||||
|
this.clearMessage()
|
||||||
|
this.props.haveToSave()
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const {config, BlogAlert} = this.state
|
||||||
|
const blogAlertElement = BlogAlert != null
|
||||||
|
? <p className={`alert ${BlogAlert.type}`}>
|
||||||
|
{BlogAlert.message}
|
||||||
|
</p>
|
||||||
|
: null
|
||||||
|
return (
|
||||||
|
<div styleName='root'>
|
||||||
|
<div styleName='group'>
|
||||||
|
<div styleName='group-header'>Blog</div>
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-label'>
|
||||||
|
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'>wordpress</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-label'>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)}>Save
|
||||||
|
</button>
|
||||||
|
{blogAlertElement}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div styleName='group-header2'>Auth</div>
|
||||||
|
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-label'>
|
||||||
|
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'>JWT</option>
|
||||||
|
<option value='USER' key='USER'>USER</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{ config.blog.authMethod === 'JWT' &&
|
||||||
|
<div styleName='group-section'>
|
||||||
|
<div styleName='group-section-label'>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'>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'>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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Blog.propTypes = {
|
||||||
|
dispatch: PropTypes.func,
|
||||||
|
haveToSave: PropTypes.func
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CSSModules(Blog, styles)
|
||||||
@@ -6,6 +6,7 @@ import UiTab from './UiTab'
|
|||||||
import InfoTab from './InfoTab'
|
import InfoTab from './InfoTab'
|
||||||
import Crowdfunding from './Crowdfunding'
|
import Crowdfunding from './Crowdfunding'
|
||||||
import StoragesTab from './StoragesTab'
|
import StoragesTab from './StoragesTab'
|
||||||
|
import Blog from './Blog'
|
||||||
import ModalEscButton from 'browser/components/ModalEscButton'
|
import ModalEscButton from 'browser/components/ModalEscButton'
|
||||||
import CSSModules from 'browser/lib/CSSModules'
|
import CSSModules from 'browser/lib/CSSModules'
|
||||||
import styles from './PreferencesModal.styl'
|
import styles from './PreferencesModal.styl'
|
||||||
@@ -19,7 +20,8 @@ class Preferences extends React.Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
currentTab: 'STORAGES',
|
currentTab: 'STORAGES',
|
||||||
UIAlert: '',
|
UIAlert: '',
|
||||||
HotkeyAlert: ''
|
HotkeyAlert: '',
|
||||||
|
BlogAlert: ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +77,14 @@ class Preferences extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<Crowdfunding />
|
<Crowdfunding />
|
||||||
)
|
)
|
||||||
|
case 'BLOG':
|
||||||
|
return (
|
||||||
|
<Blog
|
||||||
|
dispatch={dispatch}
|
||||||
|
config={config}
|
||||||
|
haveToSave={alert => this.setState({BlogAlert: alert})}
|
||||||
|
/>
|
||||||
|
)
|
||||||
case 'STORAGES':
|
case 'STORAGES':
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
@@ -111,7 +121,8 @@ class Preferences extends React.Component {
|
|||||||
{target: 'HOTKEY', label: 'Hotkeys', Hotkey: this.state.HotkeyAlert},
|
{target: 'HOTKEY', label: 'Hotkeys', Hotkey: this.state.HotkeyAlert},
|
||||||
{target: 'UI', label: 'Interface', UI: this.state.UIAlert},
|
{target: 'UI', label: 'Interface', UI: this.state.UIAlert},
|
||||||
{target: 'INFO', label: 'About'},
|
{target: 'INFO', label: 'About'},
|
||||||
{target: 'CROWDFUNDING', label: 'Crowdfunding'}
|
{target: 'CROWDFUNDING', label: 'Crowdfunding'},
|
||||||
|
{target: 'BLOG', label: 'Blog', Blog: this.state.BlogAlert}
|
||||||
]
|
]
|
||||||
|
|
||||||
const navButtons = tabs.map((tab) => {
|
const navButtons = tabs.map((tab) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user