diff --git a/browser/components/MarkdownPreview.js b/browser/components/MarkdownPreview.js index bee629f6..98ee30d3 100644 --- a/browser/components/MarkdownPreview.js +++ b/browser/components/MarkdownPreview.js @@ -1,16 +1,34 @@ -var React = require('react') -var { PropTypes } = React +import React, { PropTypes } from 'react' import markdown from '../lib/markdown' -var ReactDOM = require('react-dom') +import ReactDOM from 'react-dom' +import sanitizeHtml from '@rokt33r/sanitize-html' +import hljs from 'highlight.js' const electron = require('electron') const shell = electron.shell const katex = window.katex +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' ], + allowedClasses: { + 'a': ['lineAnchor'], + 'div': ['math'], + 'span': ['math', 'hljs-*'], + 'code': ['language-*'] + }, + allowedAttributes: { + a: ['href', 'data-key'], + img: [ 'src' ] + } +} + function handleAnchorClick (e) { e.preventDefault() e.stopPropagation() + console.log(e.target.href) shell.openExternal(e.target.href) } @@ -34,11 +52,13 @@ function math2Katex (display) { export default class MarkdownPreview extends React.Component { componentDidMount () { this.addListener() + // this.renderCode() this.renderMath() } componentDidUpdate () { this.addListener() + // this.renderCode() this.renderMath() } @@ -50,6 +70,20 @@ export default class MarkdownPreview extends React.Component { this.removeListener() } + renderCode () { + let codes = ReactDOM.findDOMNode(this).querySelectorAll('code:not(.rendered)') + Array.prototype.forEach.call(codes, el => { + let matched = el.className.match(/language-(.+)/) + let lang = matched ? matched[1] : null + try { + let result = hljs.highlight(lang, el.innerHTML) + el.innerHTML = result.value + el.className += ' rendered' + } catch (e) { + } + }) + } + renderMath () { let inline = ReactDOM.findDOMNode(this).querySelectorAll('span.math') Array.prototype.forEach.call(inline, math2Katex(false)) @@ -112,6 +146,9 @@ export default class MarkdownPreview extends React.Component { let content = isEmpty ? '(Empty content)' : this.props.content + content = markdown(content) + content = sanitizeHtml(content, sanitizeOpts) + return (
this.handleMouseDown(e)} onMouseMove={e => this.handleMouseMove(e)} onMouseUp={e => this.handleMouseUp(e)} - dangerouslySetInnerHTML={{__html: ' ' + markdown(content)}} + dangerouslySetInnerHTML={{__html: ' ' + content}} /> ) } diff --git a/browser/lib/markdown.js b/browser/lib/markdown.js index e3192fa5..3ba78b5c 100644 --- a/browser/lib/markdown.js +++ b/browser/lib/markdown.js @@ -1,23 +1,20 @@ import markdownit from 'markdown-it' -import hljs from 'highlight.js' import emoji from 'markdown-it-emoji' import math from 'markdown-it-math' +import hljs from 'highlight.js' var md = markdownit({ typographer: true, linkify: true, + html: true, + xhtmlOut: true, highlight: function (str, lang) { if (lang && hljs.getLanguage(lang)) { try { return hljs.highlight(lang, str).value - } catch (__) {} + } catch (e) {} } - - try { - return hljs.highlightAuto(str).value - } catch (__) {} - - return '' + return str } }) md.use(emoji) @@ -33,6 +30,7 @@ md.use(math, { let originalRenderToken = md.renderer.renderToken md.renderer.renderToken = function renderToken (tokens, idx, options) { let token = tokens[idx] + let result = originalRenderToken.call(md.renderer, tokens, idx, options) if (token.map != null) { return result + '' diff --git a/browser/main/HomePage/ArticleDetail/ArticleEditor.js b/browser/main/HomePage/ArticleDetail/ArticleEditor.js index 7538ed60..66af881d 100644 --- a/browser/main/HomePage/ArticleDetail/ArticleEditor.js +++ b/browser/main/HomePage/ArticleDetail/ArticleEditor.js @@ -56,7 +56,8 @@ export default class ArticleEditor extends React.Component { } render () { - if (this.props.mode === 'markdown' && this.state.status === PREVIEW_MODE) { + let showPreview = this.props.mode === 'markdown' && this.state.status === PREVIEW_MODE + if (showPreview) { return (
this.handleUnsavedItemClick(combinedArticle)(e)} className={className}>
  - {combinedArticle.title} + {combinedArticle.title.trim().length > 0 + ? combinedArticle.title + : (Untitled)}
diff --git a/browser/main/HomePage/index.js b/browser/main/HomePage/index.js index c46cb830..cb67fefb 100644 --- a/browser/main/HomePage/index.js +++ b/browser/main/HomePage/index.js @@ -144,9 +144,8 @@ function remap (state) { articles.sort((a, b) => { let match = new Date(b.updatedAt) - new Date(a.updatedAt) - if (match === 0) match = new Date(b.createdAt) - new Date(a.createdAt) - if (match === 0) match = b.title.compare(a.title) - if (match === 0) match = b.key.compare(a.key) + if (match === 0) match = b.title.localeCompare(a.title) + if (match === 0) match = b.key.localeCompare(a.key) return match }) let allArticles = articles.slice() diff --git a/browser/styles/mixins/marked.styl b/browser/styles/mixins/marked.styl index afbfe684..4d34005e 100644 --- a/browser/styles/mixins/marked.styl +++ b/browser/styles/mixins/marked.styl @@ -26,6 +26,7 @@ marked() padding 0 margin 0 display inline + font-size 0 hr border-top none border-bottom solid 1px borderColor @@ -33,7 +34,7 @@ marked() h1, h2, h3, h4, h5, h6 margin 0 0 15px font-weight 600 - * + h1, * + h2, * + h3, * + h4, * + h5, * + h6 + *:not(a.lineAnchor) + h1, *:not(a.lineAnchor) + h2, *:not(a.lineAnchor) + h3, *:not(a.lineAnchor) + h4, *:not(a.lineAnchor) + h5, *:not(a.lineAnchor) + h6 margin-top 25px h1 font-size 2em @@ -55,26 +56,28 @@ marked() font-size 0.8em line-height 1em - * + p, * + blockquote, * + ul, * + ol, * + pre + *:not(a.lineAnchor) + p, *:not(a.lineAnchor) + blockquote, *:not(a.lineAnchor) + ul, *:not(a.lineAnchor) + ol, *:not(a.lineAnchor) + pre margin-top 15px p line-height 1.9em margin 0 0 15px img max-width 100% - strong + strong, b font-weight bold - em + em, i font-style italic s text-decoration line-through + u + text-decoration underline blockquote border-left solid 4px brandBorderColor margin 0 0 15px padding 0 25px ul list-style-type disc - padding-left 35px + padding-left 25px margin-bottom 15px li display list-item @@ -87,7 +90,7 @@ marked() list-style-type square ol list-style-type decimal - padding-left 35px + padding-left 25px margin-bottom 15px li display list-item @@ -97,18 +100,18 @@ marked() code font-family Monaco, Menlo, 'Ubuntu Mono', Consolas, source-code-pro, monospace padding 2px 4px - border solid 1px borderColor + border solid 1px alpha(borderColor, 0.3) border-radius 4px font-size 0.9em color black text-decoration none background-color #F6F6F6 margin-right 2px - * + code + *:not(a.lineAnchor) + code margin-left 2px pre padding 5px - border solid 1px borderColor + border solid 1px alpha(borderColor, 0.3) border-radius 5px overflow-x auto margin 0 0 15px diff --git a/package.json b/package.json index 27cc6add..20c6f35c 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "homepage": "https://github.com/Rokt33r/codexen-app#readme", "dependencies": { "@rokt33r/node-ipc": "^5.0.4", + "@rokt33r/sanitize-html": "^1.11.2", "devicon": "^2.0.0", "electron-gh-releases": "^2.0.2", "font-awesome": "^4.3.0", diff --git a/resources/fonts/Lato-Regular.ttf b/resources/fonts/Lato-Regular.ttf new file mode 100644 index 00000000..74decd9e Binary files /dev/null and b/resources/fonts/Lato-Regular.ttf differ diff --git a/resources/fonts/Lato-Regular.woff b/resources/fonts/Lato-Regular.woff new file mode 100644 index 00000000..ae1307ff Binary files /dev/null and b/resources/fonts/Lato-Regular.woff differ diff --git a/resources/fonts/Lato-Regular.woff2 b/resources/fonts/Lato-Regular.woff2 new file mode 100644 index 00000000..3bf98433 Binary files /dev/null and b/resources/fonts/Lato-Regular.woff2 differ diff --git a/webpack-skeleton.js b/webpack-skeleton.js index 55eeb57a..b9b21fdf 100644 --- a/webpack-skeleton.js +++ b/webpack-skeleton.js @@ -31,7 +31,8 @@ var config = { 'highlight.js', 'markdown-it-emoji', 'fs-jetpack', - 'markdown-it-math' + 'markdown-it-math', + '@rokt33r/sanitize-html' ] }