Skip to content

Commit 68b3077

Browse files
authored
Merge pull request #3099 from AWolf81/html-to-md
Html to md feature
2 parents 1332b33 + ec47ee8 commit 68b3077

15 files changed

+590
-12
lines changed

browser/components/CodeEditor.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const { ipcRenderer, remote, clipboard } = require('electron')
2121
import normalizeEditorFontFamily from 'browser/lib/normalizeEditorFontFamily'
2222
const spellcheck = require('browser/lib/spellcheck')
2323
const buildEditorContextMenu = require('browser/lib/contextMenuBuilder').buildEditorContextMenu
24-
import TurndownService from 'turndown'
24+
import { createTurndownService } from '../lib/turndown'
2525
import {languageMaps} from '../lib/CMLanguageList'
2626
import snippetManager from '../lib/SnippetManager'
2727
import {generateInEditor, tocExistsInEditor} from 'browser/lib/markdown-toc-generator'
@@ -101,7 +101,7 @@ export default class CodeEditor extends React.Component {
101101

102102
this.editorActivityHandler = () => this.handleEditorActivity()
103103

104-
this.turndownService = new TurndownService()
104+
this.turndownService = createTurndownService()
105105
}
106106

107107
handleSearch (msg) {

browser/lib/turndown.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const TurndownService = require('turndown')
2+
const { gfm } = require('turndown-plugin-gfm')
3+
4+
export const createTurndownService = function () {
5+
const turndown = new TurndownService()
6+
turndown.use(gfm)
7+
turndown.remove('script')
8+
return turndown
9+
}

browser/main/Detail/FromUrlButton.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import PropTypes from 'prop-types'
2+
import React from 'react'
3+
import CSSModules from 'browser/lib/CSSModules'
4+
import styles from './FromUrlButton.styl'
5+
import _ from 'lodash'
6+
import i18n from 'browser/lib/i18n'
7+
8+
class FromUrlButton extends React.Component {
9+
constructor (props) {
10+
super(props)
11+
12+
this.state = {
13+
isActive: false
14+
}
15+
}
16+
17+
handleMouseDown (e) {
18+
this.setState({
19+
isActive: true
20+
})
21+
}
22+
23+
handleMouseUp (e) {
24+
this.setState({
25+
isActive: false
26+
})
27+
}
28+
29+
handleMouseLeave (e) {
30+
this.setState({
31+
isActive: false
32+
})
33+
}
34+
35+
render () {
36+
const { className } = this.props
37+
38+
return (
39+
<button className={_.isString(className)
40+
? 'FromUrlButton ' + className
41+
: 'FromUrlButton'
42+
}
43+
styleName={this.state.isActive || this.props.isActive
44+
? 'root--active'
45+
: 'root'
46+
}
47+
onMouseDown={(e) => this.handleMouseDown(e)}
48+
onMouseUp={(e) => this.handleMouseUp(e)}
49+
onMouseLeave={(e) => this.handleMouseLeave(e)}
50+
onClick={this.props.onClick}>
51+
<img styleName='icon'
52+
src={this.state.isActive || this.props.isActive
53+
? '../resources/icon/icon-external.svg'
54+
: '../resources/icon/icon-external.svg'
55+
}
56+
/>
57+
<span styleName='tooltip'>{i18n.__('Convert URL to Markdown')}</span>
58+
</button>
59+
)
60+
}
61+
}
62+
63+
FromUrlButton.propTypes = {
64+
isActive: PropTypes.bool,
65+
onClick: PropTypes.func,
66+
className: PropTypes.string
67+
}
68+
69+
export default CSSModules(FromUrlButton, styles)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
.root
2+
top 45px
3+
topBarButtonRight()
4+
&:hover
5+
transition 0.2s
6+
color alpha($ui-favorite-star-button-color, 0.6)
7+
&:hover .tooltip
8+
opacity 1
9+
10+
.tooltip
11+
tooltip()
12+
position absolute
13+
pointer-events none
14+
top 50px
15+
right 125px
16+
width 90px
17+
z-index 200
18+
padding 5px
19+
line-height normal
20+
border-radius 2px
21+
opacity 0
22+
transition 0.1s
23+
24+
.root--active
25+
@extend .root
26+
transition 0.15s
27+
color $ui-favorite-star-button-color
28+
&:hover
29+
transition 0.2s
30+
color alpha($ui-favorite-star-button-color, 0.6)
31+
32+
.icon
33+
transition transform 0.15s
34+
height 13px
35+
36+
body[data-theme="dark"]
37+
.root
38+
topBarButtonDark()
39+
&:hover
40+
transition 0.2s
41+
color alpha($ui-favorite-star-button-color, 0.6)

browser/main/Detail/MarkdownNoteDetail.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,7 @@ class MarkdownNoteDetail extends React.Component {
472472
</div>
473473
<div styleName='info-right'>
474474
<ToggleModeButton onClick={(e) => this.handleSwitchMode(e)} editorType={editorType} />
475+
475476
<StarButton
476477
onClick={(e) => this.handleStarButtonClick(e)}
477478
isActive={note.isStarred}

browser/main/Detail/StarButton.styl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,4 @@ body[data-theme="dark"]
4242
topBarButtonDark()
4343
&:hover
4444
transition 0.2s
45-
color alpha($ui-favorite-star-button-color, 0.6)
45+
color alpha($ui-favorite-star-button-color, 0.6)
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
const http = require('http')
2+
const https = require('https')
3+
const { createTurndownService } = require('../../../lib/turndown')
4+
const createNote = require('./createNote')
5+
6+
import { push } from 'connected-react-router'
7+
import ee from 'browser/main/lib/eventEmitter'
8+
9+
function validateUrl (str) {
10+
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)) {
11+
return true
12+
} else {
13+
return false
14+
}
15+
}
16+
17+
function createNoteFromUrl (url, storage, folder, dispatch = null, location = null) {
18+
return new Promise((resolve, reject) => {
19+
const td = createTurndownService()
20+
21+
if (!validateUrl(url)) {
22+
reject({result: false, error: 'Please check your URL is in correct format. (Example, https://www.google.com)'})
23+
}
24+
25+
const request = url.startsWith('https') ? https : http
26+
27+
const req = request.request(url, (res) => {
28+
let data = ''
29+
30+
res.on('data', (chunk) => {
31+
data += chunk
32+
})
33+
34+
res.on('end', () => {
35+
const markdownHTML = td.turndown(data)
36+
37+
if (dispatch !== null) {
38+
createNote(storage, {
39+
type: 'MARKDOWN_NOTE',
40+
folder: folder,
41+
title: '',
42+
content: markdownHTML
43+
})
44+
.then((note) => {
45+
const noteHash = note.key
46+
dispatch({
47+
type: 'UPDATE_NOTE',
48+
note: note
49+
})
50+
dispatch(push({
51+
pathname: location.pathname,
52+
query: {key: noteHash}
53+
}))
54+
ee.emit('list:jump', noteHash)
55+
ee.emit('detail:focus')
56+
resolve({result: true, error: null})
57+
})
58+
} else {
59+
createNote(storage, {
60+
type: 'MARKDOWN_NOTE',
61+
folder: folder,
62+
title: '',
63+
content: markdownHTML
64+
}).then((note) => {
65+
resolve({result: true, note, error: null})
66+
})
67+
}
68+
})
69+
})
70+
71+
req.on('error', (e) => {
72+
console.error('error in parsing URL', e)
73+
reject({result: false, error: e})
74+
})
75+
req.end()
76+
})
77+
}
78+
79+
module.exports = createNoteFromUrl

browser/main/lib/dataApi/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const dataApi = {
1111
exportFolder: require('./exportFolder'),
1212
exportStorage: require('./exportStorage'),
1313
createNote: require('./createNote'),
14+
createNoteFromUrl: require('./createNoteFromUrl'),
1415
updateNote: require('./updateNote'),
1516
deleteNote: require('./deleteNote'),
1617
moveNote: require('./moveNote'),
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import PropTypes from 'prop-types'
2+
import React from 'react'
3+
import CSSModules from 'browser/lib/CSSModules'
4+
import styles from './CreateMarkdownFromURLModal.styl'
5+
import dataApi from 'browser/main/lib/dataApi'
6+
import ModalEscButton from 'browser/components/ModalEscButton'
7+
import i18n from 'browser/lib/i18n'
8+
9+
class CreateMarkdownFromURLModal extends React.Component {
10+
constructor (props) {
11+
super(props)
12+
13+
this.state = {
14+
name: '',
15+
showerror: false,
16+
errormessage: ''
17+
}
18+
}
19+
20+
componentDidMount () {
21+
this.refs.name.focus()
22+
this.refs.name.select()
23+
}
24+
25+
handleCloseButtonClick (e) {
26+
this.props.close()
27+
}
28+
29+
handleChange (e) {
30+
this.setState({
31+
name: this.refs.name.value
32+
})
33+
}
34+
35+
handleKeyDown (e) {
36+
if (e.keyCode === 27) {
37+
this.props.close()
38+
}
39+
}
40+
41+
handleInputKeyDown (e) {
42+
switch (e.keyCode) {
43+
case 13:
44+
this.confirm()
45+
}
46+
}
47+
48+
handleConfirmButtonClick (e) {
49+
this.confirm()
50+
}
51+
52+
showError (message) {
53+
this.setState({
54+
showerror: true,
55+
errormessage: message
56+
})
57+
}
58+
59+
hideError () {
60+
this.setState({
61+
showerror: false,
62+
errormessage: ''
63+
})
64+
}
65+
66+
confirm () {
67+
this.hideError()
68+
const { storage, folder, dispatch, location } = this.props
69+
70+
dataApi.createNoteFromUrl(this.state.name, storage, folder, dispatch, location).then((result) => {
71+
this.props.close()
72+
}).catch((result) => {
73+
this.showError(result.error)
74+
})
75+
}
76+
77+
render () {
78+
return (
79+
<div styleName='root'
80+
tabIndex='-1'
81+
onKeyDown={(e) => this.handleKeyDown(e)}
82+
>
83+
<div styleName='header'>
84+
<div styleName='title'>{i18n.__('Import Markdown From URL')}</div>
85+
</div>
86+
<ModalEscButton handleEscButtonClick={(e) => this.handleCloseButtonClick(e)} />
87+
<div styleName='control'>
88+
<div styleName='control-folder'>
89+
<div styleName='control-folder-label'>{i18n.__('Insert URL Here')}</div>
90+
<input styleName='control-folder-input'
91+
ref='name'
92+
value={this.state.name}
93+
onChange={(e) => this.handleChange(e)}
94+
onKeyDown={(e) => this.handleInputKeyDown(e)}
95+
/>
96+
</div>
97+
<button styleName='control-confirmButton'
98+
onClick={(e) => this.handleConfirmButtonClick(e)}
99+
>
100+
{i18n.__('Import')}
101+
</button>
102+
<div className='error' styleName='error'>{this.state.errormessage}</div>
103+
</div>
104+
</div>
105+
)
106+
}
107+
}
108+
109+
CreateMarkdownFromURLModal.propTypes = {
110+
storage: PropTypes.string,
111+
folder: PropTypes.string,
112+
dispatch: PropTypes.func,
113+
location: PropTypes.shape({
114+
pathname: PropTypes.string
115+
})
116+
}
117+
118+
export default CSSModules(CreateMarkdownFromURLModal, styles)

0 commit comments

Comments
 (0)