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;
+ }
+}