1
0
mirror of https://github.com/BoostIo/Boostnote synced 2025-12-13 01:36:22 +00:00

Merge pull request #3099 from AWolf81/html-to-md

Html to md feature
This commit is contained in:
Junyoung Choi
2019-09-03 02:03:51 +09:00
committed by GitHub
15 changed files with 590 additions and 12 deletions

View File

@@ -21,7 +21,7 @@ const { ipcRenderer, remote, clipboard } = require('electron')
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
const spellcheck = require('browser/lib/spellcheck')
const buildEditorContextMenu = require('browser/lib/contextMenuBuilder').buildEditorContextMenu
import TurndownService from 'turndown'
import { createTurndownService } from '../lib/turndown'
import {languageMaps} from '../lib/CMLanguageList'
import snippetManager from '../lib/SnippetManager'
import {generateInEditor, tocExistsInEditor} from 'browser/lib/markdown-toc-generator'
@@ -101,7 +101,7 @@ export default class CodeEditor extends React.Component {
this.editorActivityHandler = () => this.handleEditorActivity()
this.turndownService = new TurndownService()
this.turndownService = createTurndownService()
}
handleSearch (msg) {

9
browser/lib/turndown.js Normal file
View File

@@ -0,0 +1,9 @@
const TurndownService = require('turndown')
const { gfm } = require('turndown-plugin-gfm')
export const createTurndownService = function () {
const turndown = new TurndownService()
turndown.use(gfm)
turndown.remove('script')
return turndown
}

View File

@@ -0,0 +1,69 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './FromUrlButton.styl'
import _ from 'lodash'
import i18n from 'browser/lib/i18n'
class FromUrlButton extends React.Component {
constructor (props) {
super(props)
this.state = {
isActive: false
}
}
handleMouseDown (e) {
this.setState({
isActive: true
})
}
handleMouseUp (e) {
this.setState({
isActive: false
})
}
handleMouseLeave (e) {
this.setState({
isActive: false
})
}
render () {
const { className } = this.props
return (
<button className={_.isString(className)
? 'FromUrlButton ' + className
: 'FromUrlButton'
}
styleName={this.state.isActive || this.props.isActive
? 'root--active'
: 'root'
}
onMouseDown={(e) => this.handleMouseDown(e)}
onMouseUp={(e) => this.handleMouseUp(e)}
onMouseLeave={(e) => this.handleMouseLeave(e)}
onClick={this.props.onClick}>
<img styleName='icon'
src={this.state.isActive || this.props.isActive
? '../resources/icon/icon-external.svg'
: '../resources/icon/icon-external.svg'
}
/>
<span styleName='tooltip'>{i18n.__('Convert URL to Markdown')}</span>
</button>
)
}
}
FromUrlButton.propTypes = {
isActive: PropTypes.bool,
onClick: PropTypes.func,
className: PropTypes.string
}
export default CSSModules(FromUrlButton, styles)

View File

@@ -0,0 +1,41 @@
.root
top 45px
topBarButtonRight()
&:hover
transition 0.2s
color alpha($ui-favorite-star-button-color, 0.6)
&:hover .tooltip
opacity 1
.tooltip
tooltip()
position absolute
pointer-events none
top 50px
right 125px
width 90px
z-index 200
padding 5px
line-height normal
border-radius 2px
opacity 0
transition 0.1s
.root--active
@extend .root
transition 0.15s
color $ui-favorite-star-button-color
&:hover
transition 0.2s
color alpha($ui-favorite-star-button-color, 0.6)
.icon
transition transform 0.15s
height 13px
body[data-theme="dark"]
.root
topBarButtonDark()
&:hover
transition 0.2s
color alpha($ui-favorite-star-button-color, 0.6)

View File

@@ -472,6 +472,7 @@ class MarkdownNoteDetail extends React.Component {
</div>
<div styleName='info-right'>
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
<StarButton
onClick={(e) => this.handleStarButtonClick(e)}
isActive={note.isStarred}

View File

@@ -42,4 +42,4 @@ body[data-theme="dark"]
topBarButtonDark()
&:hover
transition 0.2s
color alpha($ui-favorite-star-button-color, 0.6)
color alpha($ui-favorite-star-button-color, 0.6)

View File

@@ -0,0 +1,79 @@
const http = require('http')
const https = require('https')
const { createTurndownService } = require('../../../lib/turndown')
const createNote = require('./createNote')
import { push } from 'connected-react-router'
import ee from 'browser/main/lib/eventEmitter'
function validateUrl (str) {
if (/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(str)) {
return true
} else {
return false
}
}
function createNoteFromUrl (url, storage, folder, dispatch = null, location = null) {
return new Promise((resolve, reject) => {
const td = createTurndownService()
if (!validateUrl(url)) {
reject({result: false, error: 'Please check your URL is in correct format. (Example, https://www.google.com)'})
}
const request = url.startsWith('https') ? https : http
const req = request.request(url, (res) => {
let data = ''
res.on('data', (chunk) => {
data += chunk
})
res.on('end', () => {
const markdownHTML = td.turndown(data)
if (dispatch !== null) {
createNote(storage, {
type: 'MARKDOWN_NOTE',
folder: folder,
title: '',
content: markdownHTML
})
.then((note) => {
const noteHash = note.key
dispatch({
type: 'UPDATE_NOTE',
note: note
})
dispatch(push({
pathname: location.pathname,
query: {key: noteHash}
}))
ee.emit('list:jump', noteHash)
ee.emit('detail:focus')
resolve({result: true, error: null})
})
} else {
createNote(storage, {
type: 'MARKDOWN_NOTE',
folder: folder,
title: '',
content: markdownHTML
}).then((note) => {
resolve({result: true, note, error: null})
})
}
})
})
req.on('error', (e) => {
console.error('error in parsing URL', e)
reject({result: false, error: e})
})
req.end()
})
}
module.exports = createNoteFromUrl

View File

@@ -11,6 +11,7 @@ const dataApi = {
exportFolder: require('./exportFolder'),
exportStorage: require('./exportStorage'),
createNote: require('./createNote'),
createNoteFromUrl: require('./createNoteFromUrl'),
updateNote: require('./updateNote'),
deleteNote: require('./deleteNote'),
moveNote: require('./moveNote'),

View File

@@ -0,0 +1,118 @@
import PropTypes from 'prop-types'
import React from 'react'
import CSSModules from 'browser/lib/CSSModules'
import styles from './CreateMarkdownFromURLModal.styl'
import dataApi from 'browser/main/lib/dataApi'
import ModalEscButton from 'browser/components/ModalEscButton'
import i18n from 'browser/lib/i18n'
class CreateMarkdownFromURLModal extends React.Component {
constructor (props) {
super(props)
this.state = {
name: '',
showerror: false,
errormessage: ''
}
}
componentDidMount () {
this.refs.name.focus()
this.refs.name.select()
}
handleCloseButtonClick (e) {
this.props.close()
}
handleChange (e) {
this.setState({
name: this.refs.name.value
})
}
handleKeyDown (e) {
if (e.keyCode === 27) {
this.props.close()
}
}
handleInputKeyDown (e) {
switch (e.keyCode) {
case 13:
this.confirm()
}
}
handleConfirmButtonClick (e) {
this.confirm()
}
showError (message) {
this.setState({
showerror: true,
errormessage: message
})
}
hideError () {
this.setState({
showerror: false,
errormessage: ''
})
}
confirm () {
this.hideError()
const { storage, folder, dispatch, location } = this.props
dataApi.createNoteFromUrl(this.state.name, storage, folder, dispatch, location).then((result) => {
this.props.close()
}).catch((result) => {
this.showError(result.error)
})
}
render () {
return (
<div styleName='root'
tabIndex='-1'
onKeyDown={(e) => this.handleKeyDown(e)}
>
<div styleName='header'>
<div styleName='title'>{i18n.__('Import Markdown From URL')}</div>
</div>
<ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} />
<div styleName='control'>
<div styleName='control-folder'>
<div styleName='control-folder-label'>{i18n.__('Insert URL Here')}</div>
<input styleName='control-folder-input'
ref='name'
value={this.state.name}
onChange={(e) => this.handleChange(e)}
onKeyDown={(e) => this.handleInputKeyDown(e)}
/>
</div>
<button styleName='control-confirmButton'
onClick={(e) => this.handleConfirmButtonClick(e)}
>
{i18n.__('Import')}
</button>
<div className='error' styleName='error'>{this.state.errormessage}</div>
</div>
</div>
)
}
}
CreateMarkdownFromURLModal.propTypes = {
storage: PropTypes.string,
folder: PropTypes.string,
dispatch: PropTypes.func,
location: PropTypes.shape({
pathname: PropTypes.string
})
}
export default CSSModules(CreateMarkdownFromURLModal, styles)

View File

@@ -0,0 +1,160 @@
.root
modal()
width 500px
height 270px
overflow hidden
position relative
.header
height 80px
margin-bottom 10px
margin-top 20px
font-size 18px
line-height 50px
background-color $ui-backgroundColor
color $ui-text-color
.title
font-size 36px
font-weight 600
.control-folder-label
text-align left
font-size 14px
color $ui-text-color
.control-folder-input
display block
height 40px
width 490px
padding 0 5px
margin 10px 0
border 1px solid $ui-input--create-folder-modal
border-radius 2px
background-color transparent
outline none
vertical-align middle
font-size 16px
&:disabled
background-color $ui-input--disabled-backgroundColor
&:focus, &:active
border-color $ui-active-color
.control-confirmButton
display block
height 35px
width 140px
border none
border-radius 2px
padding 0 25px
margin 20px auto
font-size 14px
colorPrimaryButton()
body[data-theme="dark"]
.root
modalDark()
width 500px
height 270px
overflow hidden
position relative
.header
background-color transparent
border-color $ui-dark-borderColor
color $ui-dark-text-color
.control-folder-label
color $ui-dark-text-color
.control-folder-input
border 1px solid $ui-input--create-folder-modal
color white
.description
color $ui-inactive-text-color
.control-confirmButton
colorDarkPrimaryButton()
body[data-theme="solarized-dark"]
.root
modalSolarizedDark()
width 500px
height 270px
overflow hidden
position relative
.header
background-color transparent
border-color $ui-dark-borderColor
color $ui-solarized-dark-text-color
.control-folder-label
color $ui-solarized-dark-text-color
.control-folder-input
border 1px solid $ui-input--create-folder-modal
color white
.description
color $ui-inactive-text-color
.control-confirmButton
colorSolarizedDarkPrimaryButton()
.error
text-align center
color #F44336
body[data-theme="monokai"]
.root
modalMonokai()
width 500px
height 270px
overflow hidden
position relative
.header
background-color transparent
border-color $ui-dark-borderColor
color $ui-monokai-text-color
.control-folder-label
color $ui-monokai-text-color
.control-folder-input
border 1px solid $ui-input--create-folder-modal
color white
.description
color $ui-inactive-text-color
.control-confirmButton
colorMonokaiPrimaryButton()
body[data-theme="dracula"]
.root
modalDracula()
width 500px
height 270px
overflow hidden
position relative
.header
background-color transparent
border-color $ui-dracula-borderColor
color $ui-dracula-text-color
.control-folder-label
color $ui-dracula-text-color
.control-folder-input
border 1px solid $ui-input--create-folder-modal
color white
.description
color $ui-inactive-text-color
.control-confirmButton
colorDraculaPrimaryButton()

View File

@@ -3,6 +3,8 @@ import CSSModules from 'browser/lib/CSSModules'
import styles from './NewNoteModal.styl'
import ModalEscButton from 'browser/components/ModalEscButton'
import i18n from 'browser/lib/i18n'
import { openModal } from 'browser/main/lib/modal'
import CreateMarkdownFromURLModal from '../modals/CreateMarkdownFromURLModal'
import { createMarkdownNote, createSnippetNote } from 'browser/lib/newNote'
import queryString from 'query-string'
@@ -21,6 +23,18 @@ class NewNoteModal extends React.Component {
this.props.close()
}
handleCreateMarkdownFromUrlClick (e) {
this.props.close()
const { storage, folder, dispatch, location } = this.props
openModal(CreateMarkdownFromURLModal, {
storage: storage,
folder: folder,
dispatch,
location
})
}
handleMarkdownNoteButtonClick (e) {
const { storage, folder, dispatch, location, config } = this.props
const params = location.search !== '' && queryString.parse(location.search)
@@ -115,10 +129,8 @@ class NewNoteModal extends React.Component {
</button>
</div>
<div styleName='description'>
<i className='fa fa-arrows-h' />{i18n.__('Tab to switch format')}
</div>
<div styleName='description'><i className='fa fa-arrows-h' />{i18n.__('Tab to switch format')}</div>
<div styleName='from-url' onClick={(e) => this.handleCreateMarkdownFromUrlClick(e)}>Or, create a new markdown note from a URL</div>
</div>
)
}

View File

@@ -48,6 +48,12 @@
text-align center
margin-bottom 25px
.from-url
color $ui-inactive-text-color
text-align center
margin-bottom 25px
cursor pointer
body[data-theme="dark"]
.root
modalDark()
@@ -62,7 +68,7 @@ body[data-theme="dark"]
&:focus
colorDarkPrimaryButton()
.description
.description, .from-url
color $ui-inactive-text-color
body[data-theme="solarized-dark"]
@@ -79,7 +85,7 @@ body[data-theme="solarized-dark"]
&:focus
colorDarkPrimaryButton()
.description
.description, .from-url
color $ui-solarized-dark-text-color
body[data-theme="monokai"]
@@ -96,7 +102,7 @@ body[data-theme="monokai"]
&:focus
colorDarkPrimaryButton()
.description
.description, .from-url
color $ui-monokai-text-color
body[data-theme="dracula"]