mirror of
https://github.com/BoostIo/Boostnote
synced 2025-12-13 09:46:22 +00:00
Detail
This commit is contained in:
@@ -2,31 +2,20 @@ import React, { PropTypes } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import modes from '../lib/modes'
|
||||
import _ from 'lodash'
|
||||
import fetchConfig from '../lib/fetchConfig'
|
||||
|
||||
const electron = require('electron')
|
||||
const remote = electron.remote
|
||||
const ipc = electron.ipcRenderer
|
||||
|
||||
const ace = window.ace
|
||||
|
||||
let config = fetchConfig()
|
||||
ipc.on('config-apply', function (e, newConfig) {
|
||||
config = newConfig
|
||||
})
|
||||
|
||||
export default class CodeEditor extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.configApplyHandler = (e, config) => this.handleConfigApply(e, config)
|
||||
this.changeHandler = e => this.handleChange(e)
|
||||
this.changeHandler = (e) => this.handleChange(e)
|
||||
this.blurHandler = (e) => {
|
||||
if (e.relatedTarget === null) {
|
||||
return
|
||||
}
|
||||
|
||||
let isFocusingToSearch = e.relatedTarget.className && e.relatedTarget.className.split(' ').some(clss => {
|
||||
let isFocusingToSearch = e.relatedTarget.className && e.relatedTarget.className.split(' ').some((clss) => {
|
||||
return clss === 'ace_search_field' || clss === 'ace_searchbtn' || clss === 'ace_replacebtn' || clss === 'ace_searchbtn_close' || clss === 'ace_text-input'
|
||||
})
|
||||
if (isFocusingToSearch) {
|
||||
@@ -38,7 +27,7 @@ export default class CodeEditor extends React.Component {
|
||||
|
||||
this.killedBuffer = ''
|
||||
this.execHandler = (e) => {
|
||||
console.log(e.command.name)
|
||||
console.info('ACE COMMAND >> %s', e.command.name)
|
||||
switch (e.command.name) {
|
||||
case 'gotolinestart':
|
||||
e.preventDefault()
|
||||
@@ -84,7 +73,7 @@ export default class CodeEditor extends React.Component {
|
||||
this.afterExecHandler = (e) => {
|
||||
switch (e.command.name) {
|
||||
case 'find':
|
||||
Array.prototype.forEach.call(ReactDOM.findDOMNode(this).querySelectorAll('.ace_search_field, .ace_searchbtn, .ace_replacebtn, .ace_searchbtn_close'), el => {
|
||||
Array.prototype.forEach.call(ReactDOM.findDOMNode(this).querySelectorAll('.ace_search_field, .ace_searchbtn, .ace_replacebtn, .ace_searchbtn_close'), (el) => {
|
||||
el.removeEventListener('blur', this.blurHandler)
|
||||
el.addEventListener('blur', this.blurHandler)
|
||||
})
|
||||
@@ -93,11 +82,6 @@ export default class CodeEditor extends React.Component {
|
||||
}
|
||||
|
||||
this.state = {
|
||||
fontSize: config['editor-font-size'],
|
||||
fontFamily: config['editor-font-family'],
|
||||
indentType: config['editor-indent-type'],
|
||||
indentSize: config['editor-indent-size'],
|
||||
themeSyntax: config['theme-syntax']
|
||||
}
|
||||
|
||||
this.silentChange = false
|
||||
@@ -110,15 +94,15 @@ export default class CodeEditor extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
let { article } = this.props
|
||||
var el = ReactDOM.findDOMNode(this)
|
||||
var editor = this.editor = ace.edit(el)
|
||||
let { mode, value } = this.props
|
||||
let el = ReactDOM.findDOMNode(this)
|
||||
let editor = this.editor = ace.edit(el)
|
||||
editor.$blockScrolling = Infinity
|
||||
editor.renderer.setShowGutter(true)
|
||||
editor.setTheme('ace/theme/' + this.state.themeSyntax)
|
||||
editor.setTheme('ace/theme/xcode')
|
||||
editor.moveCursorTo(0, 0)
|
||||
editor.setReadOnly(!!this.props.readOnly)
|
||||
editor.setFontSize(this.state.fontSize)
|
||||
editor.setFontSize('14')
|
||||
|
||||
editor.on('blur', this.blurHandler)
|
||||
|
||||
@@ -132,31 +116,19 @@ export default class CodeEditor extends React.Component {
|
||||
readOnly: true
|
||||
})
|
||||
editor.commands.addCommand({
|
||||
name: 'Emacs cursor up',
|
||||
name: 'Emacs kill buffer',
|
||||
bindKey: {mac: 'Ctrl-Y'},
|
||||
exec: function (editor) {
|
||||
editor.insert(this.killedBuffer)
|
||||
}.bind(this),
|
||||
readOnly: true
|
||||
})
|
||||
editor.commands.addCommand({
|
||||
name: 'Focus title',
|
||||
bindKey: {win: 'Esc', mac: 'Esc'},
|
||||
exec: function (editor, e) {
|
||||
let currentWindow = remote.getCurrentWebContents()
|
||||
if (config['switch-preview'] === 'rightclick') {
|
||||
currentWindow.send('detail-preview')
|
||||
}
|
||||
currentWindow.send('list-focus')
|
||||
},
|
||||
readOnly: true
|
||||
})
|
||||
|
||||
editor.commands.on('exec', this.execHandler)
|
||||
editor.commands.on('afterExec', this.afterExecHandler)
|
||||
|
||||
var session = editor.getSession()
|
||||
let mode = _.findWhere(modes, {name: article.mode})
|
||||
mode = _.find(modes, {name: mode})
|
||||
let syntaxMode = mode != null
|
||||
? mode.mode
|
||||
: 'text'
|
||||
@@ -166,15 +138,12 @@ export default class CodeEditor extends React.Component {
|
||||
session.setTabSize(!isNaN(this.state.indentSize) ? parseInt(this.state.indentSize, 10) : 4)
|
||||
session.setOption('useWorker', false)
|
||||
session.setUseWrapMode(true)
|
||||
session.setValue(this.props.article.content)
|
||||
session.setValue(_.isString(value) ? value : '')
|
||||
|
||||
session.on('change', this.changeHandler)
|
||||
|
||||
ipc.on('config-apply', this.configApplyHandler)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
ipc.removeListener('config-apply', this.configApplyHandler)
|
||||
this.editor.getSession().removeListener('change', this.changeHandler)
|
||||
this.editor.removeListener('blur', this.blurHandler)
|
||||
this.editor.commands.removeListener('exec', this.execHandler)
|
||||
@@ -183,41 +152,37 @@ export default class CodeEditor extends React.Component {
|
||||
|
||||
componentDidUpdate (prevProps, prevState) {
|
||||
var session = this.editor.getSession()
|
||||
if (this.props.article.key !== prevProps.article.key) {
|
||||
session.removeListener('change', this.changeHandler)
|
||||
session.setValue(this.props.article.content)
|
||||
session.getUndoManager().reset()
|
||||
session.on('change', this.changeHandler)
|
||||
}
|
||||
if (prevProps.article.mode !== this.props.article.mode) {
|
||||
let mode = _.findWhere(modes, {name: this.props.article.mode})
|
||||
|
||||
if (prevProps.mode !== this.props.mode) {
|
||||
let mode = _.find(modes, {name: this.props.mode})
|
||||
let syntaxMode = mode != null
|
||||
? mode.mode
|
||||
: 'text'
|
||||
session.setMode('ace/mode/' + syntaxMode)
|
||||
session.setMode('ace/mode' + syntaxMode)
|
||||
}
|
||||
}
|
||||
|
||||
handleConfigApply (e, config) {
|
||||
this.setState({
|
||||
fontSize: config['editor-font-size'],
|
||||
fontFamily: config['editor-font-family'],
|
||||
indentType: config['editor-indent-type'],
|
||||
indentSize: config['editor-indent-size'],
|
||||
themeSyntax: config['theme-syntax']
|
||||
}, function () {
|
||||
var editor = this.editor
|
||||
editor.setTheme('ace/theme/' + this.state.themeSyntax)
|
||||
// this.setState({
|
||||
// fontSize: config['editor-font-size'],
|
||||
// fontFamily: config['editor-font-family'],
|
||||
// indentType: config['editor-indent-type'],
|
||||
// indentSize: config['editor-indent-size'],
|
||||
// themeSyntax: config['theme-syntax']
|
||||
// }, function () {
|
||||
// var editor = this.editor
|
||||
// editor.setTheme('ace/theme/' + this.state.themeSyntax)
|
||||
|
||||
var session = editor.getSession()
|
||||
session.setUseSoftTabs(this.state.indentType === 'space')
|
||||
session.setTabSize(!isNaN(this.state.indentSize) ? parseInt(this.state.indentSize, 10) : 4)
|
||||
})
|
||||
// var session = editor.getSession()
|
||||
// session.setUseSoftTabs(this.state.indentType === 'space')
|
||||
// session.setTabSize(!isNaN(this.state.indentSize) ? parseInt(this.state.indentSize, 10) : 4)
|
||||
// })
|
||||
}
|
||||
|
||||
handleChange (e) {
|
||||
if (this.props.onChange) {
|
||||
var value = this.editor.getValue()
|
||||
this.props.onChange(value)
|
||||
this.value = this.editor.getValue()
|
||||
this.props.onChange(e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,13 +202,34 @@ export default class CodeEditor extends React.Component {
|
||||
this.editor.scrollToLine(num, false, false)
|
||||
}
|
||||
|
||||
focus () {
|
||||
this.editor.focus()
|
||||
}
|
||||
|
||||
blur () {
|
||||
this.editor.blur()
|
||||
}
|
||||
|
||||
reload () {
|
||||
let session = this.editor.getSession()
|
||||
session.removeListener('change', this.changeHandler)
|
||||
session.setValue(this.props.value)
|
||||
session.getUndoManager().reset()
|
||||
session.on('change', this.changeHandler)
|
||||
}
|
||||
|
||||
render () {
|
||||
let { className } = this.props
|
||||
|
||||
return (
|
||||
<div
|
||||
className={this.props.className == null ? 'CodeEditor' : 'CodeEditor ' + this.props.className}
|
||||
className={className == null
|
||||
? 'CodeEditor'
|
||||
: `CodeEditor ${className}`
|
||||
}
|
||||
style={{
|
||||
fontSize: this.state.fontSize,
|
||||
fontFamily: this.state.fontFamily.trim() + ', monospace'
|
||||
fontSize: '14px',
|
||||
fontFamily: 'monospace'
|
||||
}}
|
||||
/>
|
||||
)
|
||||
@@ -251,11 +237,8 @@ export default class CodeEditor extends React.Component {
|
||||
}
|
||||
|
||||
CodeEditor.propTypes = {
|
||||
article: PropTypes.shape({
|
||||
content: PropTypes.string,
|
||||
mode: PropTypes.string,
|
||||
key: PropTypes.string
|
||||
}),
|
||||
value: PropTypes.string,
|
||||
mode: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
onBlur: PropTypes.func,
|
||||
onChange: PropTypes.func,
|
||||
|
||||
81
browser/components/MarkdownEditor.js
Normal file
81
browser/components/MarkdownEditor.js
Normal file
@@ -0,0 +1,81 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import CSSModules from 'browser/lib/CSSModules'
|
||||
import styles from './MarkdownEditor.styl'
|
||||
import CodeEditor from 'browser/components/CodeEditor'
|
||||
import MarkdownPreview from 'browser/components/MarkdownPreview'
|
||||
|
||||
class MarkdownEditor extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
status: 'CODE'
|
||||
}
|
||||
}
|
||||
|
||||
handleChange (e) {
|
||||
this.value = this.refs.code.value
|
||||
this.props.onChange(e)
|
||||
}
|
||||
|
||||
handleContextMenu (e) {
|
||||
let newStatus = this.state.status === 'PREVIEW'
|
||||
? 'CODE'
|
||||
: 'PREVIEW'
|
||||
this.setState({
|
||||
status: newStatus
|
||||
}, () => {
|
||||
if (newStatus === 'CODE') {
|
||||
this.refs.code.focus()
|
||||
} else {
|
||||
this.refs.code.blur()
|
||||
this.refs.preview.focus()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
reload () {
|
||||
this.refs.code.reload()
|
||||
}
|
||||
|
||||
render () {
|
||||
let { className, value } = this.props
|
||||
|
||||
return (
|
||||
<div className={className == null
|
||||
? 'MarkdownEditor'
|
||||
: `MarkdownEditor ${className}`
|
||||
}
|
||||
onContextMenu={(e) => this.handleContextMenu(e)}
|
||||
>
|
||||
<CodeEditor styleName='codeEditor'
|
||||
ref='code'
|
||||
mode='markdown'
|
||||
value={value}
|
||||
onChange={(e) => this.handleChange(e)}
|
||||
/>
|
||||
<MarkdownPreview styleName={this.state.status === 'PREVIEW'
|
||||
? 'preview'
|
||||
: 'preview--hide'
|
||||
}
|
||||
style={this.props.ignorePreviewPointerEvents
|
||||
? {pointerEvents: 'none'} : {}
|
||||
}
|
||||
ref='preview'
|
||||
onContextMenu={(e) => this.handleContextMenu(e)}
|
||||
tabIndex='0'
|
||||
value={value}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
MarkdownEditor.propTypes = {
|
||||
className: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
ignorePreviewPointerEvents: PropTypes.bool
|
||||
}
|
||||
|
||||
export default CSSModules(MarkdownEditor, styles)
|
||||
23
browser/components/MarkdownEditor.styl
Normal file
23
browser/components/MarkdownEditor.styl
Normal file
@@ -0,0 +1,23 @@
|
||||
.root
|
||||
position relative
|
||||
|
||||
.codeEditor
|
||||
absolute top bottom left right
|
||||
|
||||
.codeEditor--hide
|
||||
@extend .codeEditor
|
||||
|
||||
.preview
|
||||
display block
|
||||
absolute top bottom left right
|
||||
z-index 100
|
||||
background-color white
|
||||
height 100%
|
||||
width 100%
|
||||
|
||||
.preview--hide
|
||||
@extend .preview
|
||||
z-index 0
|
||||
opacity 0
|
||||
pointer-events none
|
||||
|
||||
@@ -1,214 +1,81 @@
|
||||
import React, { PropTypes } from 'react'
|
||||
import markdown from '../lib/markdown'
|
||||
import ReactDOM from 'react-dom'
|
||||
import sanitizeHtml from '@rokt33r/sanitize-html'
|
||||
import _ from 'lodash'
|
||||
import fetchConfig from '../lib/fetchConfig'
|
||||
import markdown from 'browser/lib/markdown'
|
||||
|
||||
const electron = require('electron')
|
||||
const shell = electron.shell
|
||||
const ipc = electron.ipcRenderer
|
||||
|
||||
const katex = window.katex
|
||||
|
||||
const OSX = global.process.platform === 'darwin'
|
||||
|
||||
const sanitizeOpts = {
|
||||
allowedTags: [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol',
|
||||
'nl', 'li', 'b', 'i', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div',
|
||||
'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'img', 'span', 'cite', 'del', 'u', 'sub', 'sup', 's', 'input', 'label' ],
|
||||
allowedClasses: {
|
||||
'a': ['lineAnchor'],
|
||||
'div': ['math'],
|
||||
'pre': ['hljs'],
|
||||
'span': ['math', 'hljs-*', 'lineNumber'],
|
||||
'code': ['language-*']
|
||||
},
|
||||
allowedAttributes: {
|
||||
a: ['href', 'data-key'],
|
||||
img: [ 'src' ],
|
||||
label: ['for'],
|
||||
input: ['checked', 'type'],
|
||||
'*': ['id', 'name']
|
||||
},
|
||||
transformTags: {
|
||||
'*': function (tagName, attribs) {
|
||||
let href = attribs.href
|
||||
if (tagName === 'input' && attribs.type !== 'checkbox') {
|
||||
return false
|
||||
}
|
||||
if (_.isString(href) && href.match(/^#.+$/)) attribs.href = href.replace(/^#/, '#md-anchor-')
|
||||
if (attribs.id) attribs.id = 'md-anchor-' + attribs.id
|
||||
if (attribs.name) attribs.name = 'md-anchor-' + attribs.name
|
||||
if (attribs.for) attribs.for = 'md-anchor-' + attribs.for
|
||||
return {
|
||||
tagName: tagName,
|
||||
attribs: attribs
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleAnchorClick (e) {
|
||||
if (this.attributes.href && this.attributes.href.nodeValue.match(/^#.+/)) {
|
||||
return
|
||||
}
|
||||
const markdownStyle = require('!!css!stylus?sourceMap!./markdown.styl')[0][1]
|
||||
const { shell } = require('electron')
|
||||
const goExternal = function (e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
let href = this.href
|
||||
if (href && href.match(/^http:\/\/|https:\/\/|mailto:\/\//)) {
|
||||
shell.openExternal(href)
|
||||
}
|
||||
shell.openExternal(e.target.href)
|
||||
}
|
||||
|
||||
function stopPropagation (e) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
function math2Katex (display) {
|
||||
return function (el) {
|
||||
try {
|
||||
katex.render(el.innerHTML.replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/&/g, '&'), el, {display: display})
|
||||
el.className = 'math-rendered'
|
||||
} catch (e) {
|
||||
el.innerHTML = e.message
|
||||
el.className = 'math-failed'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let config = fetchConfig()
|
||||
ipc.on('config-apply', function (e, newConfig) {
|
||||
config = newConfig
|
||||
})
|
||||
|
||||
export default class MarkdownPreview extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.configApplyHandler = (e, config) => this.handleConfigApply(e, config)
|
||||
|
||||
this.state = {
|
||||
fontSize: config['preview-font-size'],
|
||||
fontFamily: config['preview-font-family'],
|
||||
lineNumber: config['preview-line-number']
|
||||
}
|
||||
this.contextMenuHandler = (e) => this.handleContextMenu(e)
|
||||
}
|
||||
|
||||
handleContextMenu (e) {
|
||||
this.props.onContextMenu(e)
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.addListener()
|
||||
this.renderMath()
|
||||
ipc.on('config-apply', this.configApplyHandler)
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
this.addListener()
|
||||
this.renderMath()
|
||||
this.refs.root.setAttribute('sandbox', 'allow-same-origin')
|
||||
this.refs.root.contentWindow.document.body.addEventListener('contextmenu', this.contextMenuHandler)
|
||||
this.rewriteIframe()
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.removeListener()
|
||||
ipc.removeListener('config-apply', this.configApplyHandler)
|
||||
this.refs.root.contentWindow.document.body.removeEventListener('contextmenu', this.contextMenuHandler)
|
||||
}
|
||||
|
||||
componentWillUpdate () {
|
||||
this.removeListener()
|
||||
componentDidUpdate (prevProps) {
|
||||
if (prevProps.value !== this.props.value) this.rewriteIframe()
|
||||
}
|
||||
|
||||
renderMath () {
|
||||
let inline = ReactDOM.findDOMNode(this).querySelectorAll('span.math')
|
||||
Array.prototype.forEach.call(inline, math2Katex(false))
|
||||
let block = ReactDOM.findDOMNode(this).querySelectorAll('div.math')
|
||||
Array.prototype.forEach.call(block, math2Katex(true))
|
||||
}
|
||||
|
||||
addListener () {
|
||||
var anchors = ReactDOM.findDOMNode(this).querySelectorAll('a:not(.lineAnchor)')
|
||||
var inputs = ReactDOM.findDOMNode(this).querySelectorAll('input')
|
||||
|
||||
Array.prototype.forEach.call(anchors, anchor => {
|
||||
anchor.addEventListener('click', handleAnchorClick)
|
||||
anchor.addEventListener('mousedown', stopPropagation)
|
||||
anchor.addEventListener('mouseup', stopPropagation)
|
||||
rewriteIframe () {
|
||||
Array.prototype.forEach.call(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
||||
el.removeEventListener('click', goExternal)
|
||||
})
|
||||
Array.prototype.forEach.call(inputs, input => {
|
||||
input.addEventListener('click', stopPropagation)
|
||||
|
||||
let { value } = this.props
|
||||
this.refs.root.contentWindow.document.head.innerHTML = `
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'Lato';
|
||||
src: url('../resources/fonts/Lato-Regular.woff2') format('woff2'), /* Modern Browsers */
|
||||
url('../resources/fonts/Lato-Regular.woff') format('woff'), /* Modern Browsers */
|
||||
url('../resources/fonts/Lato-Regular.ttf') format('truetype');
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
${markdownStyle}
|
||||
</style>
|
||||
<link rel="stylesheet" href="../node_modules/highlight.js/styles/xcode.css" id="hljs-css">
|
||||
<link rel="stylesheet" href="../resources/katex.min.css">
|
||||
`
|
||||
this.refs.root.contentWindow.document.body.innerHTML = markdown(value)
|
||||
|
||||
Array.prototype.forEach.call(this.refs.root.contentWindow.document.querySelectorAll('a'), (el) => {
|
||||
el.addEventListener('click', goExternal)
|
||||
})
|
||||
}
|
||||
|
||||
removeListener () {
|
||||
var anchors = ReactDOM.findDOMNode(this).querySelectorAll('a:not(.lineAnchor)')
|
||||
var inputs = ReactDOM.findDOMNode(this).querySelectorAll('input')
|
||||
|
||||
Array.prototype.forEach.call(anchors, anchor => {
|
||||
anchor.removeEventListener('click', handleAnchorClick)
|
||||
anchor.removeEventListener('mousedown', stopPropagation)
|
||||
anchor.removeEventListener('mouseup', stopPropagation)
|
||||
})
|
||||
Array.prototype.forEach.call(inputs, input => {
|
||||
input.removeEventListener('click', stopPropagation)
|
||||
})
|
||||
}
|
||||
|
||||
handleClick (e) {
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick(e)
|
||||
}
|
||||
}
|
||||
|
||||
handleDoubleClick (e) {
|
||||
if (this.props.onDoubleClick) {
|
||||
this.props.onDoubleClick(e)
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseDown (e) {
|
||||
if (this.props.onMouseDown) {
|
||||
this.props.onMouseDown(e)
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseUp (e) {
|
||||
if (this.props.onMouseUp) {
|
||||
this.props.onMouseUp(e)
|
||||
}
|
||||
}
|
||||
|
||||
handleMouseMove (e) {
|
||||
if (this.props.onMouseMove) {
|
||||
this.props.onMouseMove(e)
|
||||
}
|
||||
}
|
||||
|
||||
handleConfigApply (e, config) {
|
||||
this.setState({
|
||||
fontSize: config['preview-font-size'],
|
||||
fontFamily: config['preview-font-family'],
|
||||
lineNumber: config['preview-line-number']
|
||||
})
|
||||
focus () {
|
||||
this.refs.root.focus()
|
||||
}
|
||||
|
||||
render () {
|
||||
let isEmpty = this.props.content.trim().length === 0
|
||||
let content = isEmpty
|
||||
? '(Empty content)'
|
||||
: this.props.content
|
||||
content = markdown(content)
|
||||
content = sanitizeHtml(content, sanitizeOpts)
|
||||
|
||||
let { className, style, tabIndex } = this.props
|
||||
return (
|
||||
<div
|
||||
className={'MarkdownPreview' + (this.props.className != null ? ' ' + this.props.className : '') + (isEmpty ? ' empty' : '') + (this.state.lineNumber ? ' lineNumbered' : '')}
|
||||
onClick={e => this.handleClick(e)}
|
||||
onDoubleClick={e => this.handleDoubleClick(e)}
|
||||
onMouseDown={e => this.handleMouseDown(e)}
|
||||
onMouseMove={e => this.handleMouseMove(e)}
|
||||
onMouseUp={e => this.handleMouseUp(e)}
|
||||
dangerouslySetInnerHTML={{__html: ' ' + content}}
|
||||
style={{
|
||||
fontSize: this.state.fontSize,
|
||||
fontFamily: this.state.fontFamily.trim() + (OSX ? '' : ', meiryo, \'Microsoft YaHei\'') + ', helvetica, arial, sans-serif'
|
||||
}}
|
||||
<iframe className={className != null
|
||||
? 'MarkdownPreview ' + className
|
||||
: 'MarkdownPreview'
|
||||
}
|
||||
style={style}
|
||||
tabIndex={tabIndex}
|
||||
ref='root'
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -221,5 +88,5 @@ MarkdownPreview.propTypes = {
|
||||
onMouseDown: PropTypes.func,
|
||||
onMouseMove: PropTypes.func,
|
||||
className: PropTypes.string,
|
||||
content: PropTypes.string
|
||||
value: PropTypes.string
|
||||
}
|
||||
|
||||
252
browser/components/markdown.styl
Normal file
252
browser/components/markdown.styl
Normal file
@@ -0,0 +1,252 @@
|
||||
global-reset()
|
||||
|
||||
borderColor = #D0D0D0 // using
|
||||
highlightenBorderColor = darken(borderColor, 20%)
|
||||
invBorderColor = #404849
|
||||
brandBorderColor = #3FB399
|
||||
|
||||
focusBorderColor = #369DCD
|
||||
|
||||
buttonBorderColor = #4C4C4C
|
||||
|
||||
lightButtonColor = #898989
|
||||
|
||||
hoverBackgroundColor= transparentify(#444, 4%) // using
|
||||
|
||||
inactiveTextColor = #888 // using
|
||||
textColor = #4D4D4D // using
|
||||
backgroundColor= white
|
||||
fontSize= 14px // using
|
||||
|
||||
shadowColor= #C5C5C5
|
||||
|
||||
invBackgroundColor = #4C4C4C
|
||||
invTextColor = white
|
||||
|
||||
btnColor = #888
|
||||
btnHighlightenColor = #000
|
||||
|
||||
brandColor = #2BAC8F
|
||||
|
||||
popupShadow = 0 0 5px 0 #888
|
||||
|
||||
|
||||
tableHeadBgColor = white
|
||||
tableOddBgColor = #F9F9F9
|
||||
tableEvenBgColor = white
|
||||
|
||||
facebookColor= #3b5998
|
||||
githubBtn= #201F1F
|
||||
|
||||
// using
|
||||
successBackgroundColor= #E0F0D9
|
||||
successTextColor= #3E753F
|
||||
errorBackgroundColor= #F2DEDE
|
||||
errorTextColor= #A64444
|
||||
infoBackgroundColor= #D9EDF7
|
||||
infoTextColor= #34708E
|
||||
|
||||
popupZIndex= 500
|
||||
|
||||
body
|
||||
font-size 16px
|
||||
padding 15px
|
||||
font-family helvetica, arial, sans-serif
|
||||
line-height 1.6
|
||||
overflow-x hidden
|
||||
.katex
|
||||
font 400 1.2em 'KaTeX_Main'
|
||||
line-height 1.2em
|
||||
white-space nowrap
|
||||
text-indent 0
|
||||
.katex .mfrac>.vlist>span:nth-child(2)
|
||||
top 0 !important
|
||||
.katex-error
|
||||
background-color errorBackgroundColor
|
||||
color errorTextColor
|
||||
padding 5px
|
||||
margin -5px
|
||||
border-radius 5px
|
||||
div.math-rendered
|
||||
text-align center
|
||||
.math-failed
|
||||
background-color alpha(red, 0.1)
|
||||
color darken(red, 15%)
|
||||
padding 5px
|
||||
margin 5px 0
|
||||
border-radius 5px
|
||||
sup
|
||||
position relative
|
||||
top -.4em
|
||||
font-size 0.8em
|
||||
vertical-align top
|
||||
sub
|
||||
position relative
|
||||
bottom -.4em
|
||||
font-size 0.8em
|
||||
vertical-align top
|
||||
a
|
||||
color brandColor
|
||||
text-decoration none
|
||||
padding 5px
|
||||
border-radius 5px
|
||||
margin -5px
|
||||
transition .1s
|
||||
display inline-block
|
||||
img
|
||||
vertical-align sub
|
||||
&:hover
|
||||
color lighten(brandColor, 5%)
|
||||
text-decoration underline
|
||||
background-color alpha(#FFC95C, 0.3)
|
||||
&:visited
|
||||
color brandColor
|
||||
&.lineAnchor
|
||||
padding 0
|
||||
margin 0
|
||||
display block
|
||||
font-size 0
|
||||
height 0
|
||||
hr
|
||||
border-top none
|
||||
border-bottom solid 1px borderColor
|
||||
margin 15px 0
|
||||
h1, h2, h3, h4, h5, h6
|
||||
font-weight bold
|
||||
h1
|
||||
font-size 2.25em
|
||||
padding-bottom 0.3em
|
||||
line-height 1.2em
|
||||
border-bottom solid 1px borderColor
|
||||
margin 1em 0 0.44em
|
||||
&:first-child
|
||||
margin-top 0
|
||||
h2
|
||||
font-size 1.75em
|
||||
padding-bottom 0.3em
|
||||
line-height 1.225em
|
||||
border-bottom solid 1px borderColor
|
||||
margin 1em 0 0.57em
|
||||
&:first-child
|
||||
margin-top 0
|
||||
h3
|
||||
font-size 1.5em
|
||||
line-height 1.43em
|
||||
margin 1em 0 0.66em
|
||||
h4
|
||||
font-size 1.25em
|
||||
line-height 1.4em
|
||||
margin 1em 0 0.8em
|
||||
h5
|
||||
font-size 1em
|
||||
line-height 1.4em
|
||||
margin 1em 0 1em
|
||||
h6
|
||||
font-size 1em
|
||||
line-height 1.4em
|
||||
margin 1em 0 1em
|
||||
color #777
|
||||
|
||||
*:not(a.lineAnchor) + p, *:not(a.lineAnchor) + blockquote, *:not(a.lineAnchor) + ul, *:not(a.lineAnchor) + ol, *:not(a.lineAnchor) + pre
|
||||
margin-top 1em
|
||||
p
|
||||
line-height 1.6em
|
||||
margin 0 0 1em
|
||||
white-space pre-line
|
||||
img
|
||||
max-width 100%
|
||||
strong, b
|
||||
font-weight bold
|
||||
em, i
|
||||
font-style italic
|
||||
s, del, strike
|
||||
text-decoration line-through
|
||||
u
|
||||
text-decoration underline
|
||||
blockquote
|
||||
border-left solid 4px brandBorderColor
|
||||
margin 0 0 1em
|
||||
padding 0 25px
|
||||
ul
|
||||
list-style-type disc
|
||||
padding-left 2em
|
||||
margin-bottom 1em
|
||||
li
|
||||
display list-item
|
||||
&>li>ul, &>li>ol
|
||||
margin 0
|
||||
&>li>ul
|
||||
list-style-type circle
|
||||
&>li>ul
|
||||
list-style-type square
|
||||
ol
|
||||
list-style-type decimal
|
||||
padding-left 2em
|
||||
margin-bottom 1em
|
||||
li
|
||||
display list-item
|
||||
&>li>ul, &>li>ol
|
||||
margin 0
|
||||
code
|
||||
font-family Monaco, Menlo, 'Ubuntu Mono', Consolas, source-code-pro, monospace
|
||||
padding 0.2em 0.4em
|
||||
background-color #f7f7f7
|
||||
border-radius 3px
|
||||
font-size 0.85em
|
||||
text-decoration none
|
||||
margin-right 2px
|
||||
*:not(a.lineAnchor) + code
|
||||
margin-left 2px
|
||||
pre
|
||||
padding 1em !important
|
||||
background-color #f7f7f7 !important
|
||||
border-radius 5px
|
||||
overflow-x auto
|
||||
margin 0 0 1em
|
||||
line-height 1.35
|
||||
code
|
||||
margin 0
|
||||
padding 0
|
||||
border none
|
||||
border-radius 0
|
||||
pre
|
||||
border none
|
||||
margin -5px
|
||||
&>span.lineNumber
|
||||
font-family Monaco, Menlo, 'Ubuntu Mono', Consolas, source-code-pro, monospace
|
||||
display none
|
||||
float left
|
||||
margin 0 0.5em 0 -0.5em
|
||||
border-right 1px solid
|
||||
text-align right
|
||||
&>span
|
||||
display block
|
||||
padding 0 .5em 0 1em
|
||||
table
|
||||
display block
|
||||
width 100%
|
||||
margin 0 0 1em
|
||||
thead
|
||||
tr
|
||||
background-color tableHeadBgColor
|
||||
th
|
||||
border-style solid
|
||||
padding 6px 13px
|
||||
line-height 1.6
|
||||
border-width 1px 0 2px 1px
|
||||
border-color borderColor
|
||||
&:last-child
|
||||
border-right solid 1px borderColor
|
||||
tbody
|
||||
tr:nth-child(2n + 1)
|
||||
background-color tableOddBgColor
|
||||
tr:nth-child(2n)
|
||||
background-color tableEvenBgColor
|
||||
td
|
||||
border-style solid
|
||||
padding 6px 13px
|
||||
line-height 1.6
|
||||
border-width 0 0 1px 1px
|
||||
border-color borderColor
|
||||
&:last-child
|
||||
border-right solid 1px borderColor
|
||||
Reference in New Issue
Block a user