diff --git a/package.json b/package.json index 0e4a6617..5752fcc5 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ "exports-loader": "^0.6.3", "file-loader": "^0.8.5", "fsevents": "^1.0.11", + "highlight.js": "^9.4.0", "history": "^1.17.0", "imports-loader": "^0.6.5", "json-loader": "^0.5.4", diff --git a/server/models/Document.js b/server/models/Document.js index b689773f..c6995e6d 100644 --- a/server/models/Document.js +++ b/server/models/Document.js @@ -2,6 +2,10 @@ import { DataTypes, sequelize, } from '../sequelize'; +import { + convertToMarkdown, + truncateMarkdown, +} from '../utils/markdown'; import Atlas from './Atlas'; import Team from './Team'; import User from './User'; @@ -10,6 +14,15 @@ const Document = sequelize.define('document', { id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true }, title: DataTypes.STRING, text: DataTypes.TEXT, + html: DataTypes.TEXT, + preview: DataTypes.TEXT, +}, { + hooks: { + beforeCreate: (doc) => { + doc.html = convertToMarkdown(doc.text); + doc.preview = truncateMarkdown(doc.text, 160); + }, + } }); Document.belongsTo(Atlas, { as: 'atlas' }); diff --git a/server/presenters.js b/server/presenters.js index 58aa898c..4f598c34 100644 --- a/server/presenters.js +++ b/server/presenters.js @@ -1,6 +1,3 @@ -import marked from 'marked'; -import { truncateMarkdown } from './utils/truncate'; - import Document from './models/Document'; export function presentUser(user) { @@ -60,8 +57,8 @@ export async function presentDocument(document, includeAtlas=false) { id: document.id, title: document.title, text: document.text, - html: marked(document.text), - preview: truncateMarkdown(document.text, 160), + html: document.html, + preview: document.preview, createdAt: document.createdAt, updatedAt: document.updatedAt, atlas: document.atlaId, diff --git a/server/utils/markdown.js b/server/utils/markdown.js new file mode 100644 index 00000000..a5d22d23 --- /dev/null +++ b/server/utils/markdown.js @@ -0,0 +1,45 @@ +import truncate from 'truncate-html'; +import marked, { Renderer } from 'marked'; +import highlight from 'highlight.js'; + +const renderer = new Renderer(); +renderer.code = (code, language) => { + const validLang = !!(language && highlight.getLanguage(language)); + const highlighted = validLang ? highlight.highlight(language, code).value : code; + return `
${highlighted}
`; +}; + + +marked.setOptions({ + renderer: renderer, + gfm: true, + tables: true, + breaks: false, + pedantic: false, + sanitize: true, + smartLists: true, + smartypants: true, +}); + +// TODO: This is syncronous and can be costly, +// should be performed outside http request +const convertToMarkdown = (text) => { + return marked(text); +}; + +truncate.defaultOptions = { + stripTags: false, + ellipsis: '...', + decodeEntities: false, + excludes: ['h1', 'pre', ], +}; + +const truncateMarkdown = (text, length) => { + const html = convertToMarkdown(text); + return truncate(html, length); +}; + +export { + convertToMarkdown, + truncateMarkdown, +}; diff --git a/server/utils/truncate.js b/server/utils/truncate.js deleted file mode 100644 index 2dd378fb..00000000 --- a/server/utils/truncate.js +++ /dev/null @@ -1,18 +0,0 @@ -import truncate from 'truncate-html'; -import marked from 'marked'; - -truncate.defaultOptions = { - stripTags: false, - ellipsis: '...', - decodeEntities: false, - excludes: ['h1', 'pre',], -}; - -const truncateMarkdown = (text, length) => { - const html = marked(text); - return truncate(html, length); -}; - -export { - truncateMarkdown -}; diff --git a/src/assets/styles/github-gist.scss b/src/assets/styles/github-gist.scss new file mode 100644 index 00000000..2732313f --- /dev/null +++ b/src/assets/styles/github-gist.scss @@ -0,0 +1,73 @@ +:global { + /** + * GitHub Gist Theme + * Author : Louis Barranqueiro - https://github.com/LouisBarranqueiro + */ + + .hljs { + display: block; + background: white; + padding: 0.5em; + color: #333333; + overflow-x: auto; + } + + .hljs-comment, + .hljs-meta { + color: #969896; + } + + .hljs-string, + .hljs-variable, + .hljs-template-variable, + .hljs-strong, + .hljs-emphasis, + .hljs-quote { + color: #df5000; + } + + .hljs-keyword, + .hljs-selector-tag, + .hljs-type { + color: #a71d5d; + } + + .hljs-literal, + .hljs-symbol, + .hljs-bullet, + .hljs-attribute { + color: #0086b3; + } + + .hljs-section, + .hljs-name { + color: #63a35c; + } + + .hljs-tag { + color: #333333; + } + + .hljs-title, + .hljs-attr, + .hljs-selector-id, + .hljs-selector-class, + .hljs-selector-attr, + .hljs-selector-pseudo { + color: #795da3; + } + + .hljs-addition { + color: #55a532; + background-color: #eaffea; + } + + .hljs-deletion { + color: #bd2c00; + background-color: #ffecec; + } + + .hljs-link { + text-decoration: underline; + } +} \ No newline at end of file diff --git a/src/components/CenteredContent/CenteredContent.js b/src/components/CenteredContent/CenteredContent.js index 71dfdad0..b81e4def 100644 --- a/src/components/CenteredContent/CenteredContent.js +++ b/src/components/CenteredContent/CenteredContent.js @@ -16,7 +16,7 @@ const CenteredContent = (props) => { }; CenteredContent.defaultProps = { - maxWidth: '600px', + maxWidth: '740px', }; CenteredContent.propTypes = { diff --git a/src/index.js b/src/index.js index c6295b3e..88e9afe0 100644 --- a/src/index.js +++ b/src/index.js @@ -18,6 +18,7 @@ import reducers from 'reducers'; import 'normalize.css/normalize.css'; import 'utils/base-styles.scss'; import 'fonts/atlas/atlas.css'; +import 'assets/styles/github-gist.scss'; import Home from 'scenes/Home'; import Editor from 'scenes/Editor'; diff --git a/src/utils/base-styles.scss b/src/utils/base-styles.scss index 461eccfa..3240bfb5 100644 --- a/src/utils/base-styles.scss +++ b/src/utils/base-styles.scss @@ -85,3 +85,11 @@ hr { border-bottom-style: solid; border-bottom-color: #ccc; } + +:global { + .hljs { + border: 1px solid rgba(0,0,0,.0625); + padding: 1rem; + border-radius: 0.25rem; + } +}