Skip to content

Commit

Permalink
Fix line number width bug and add syntax highlighting (#161)
Browse files Browse the repository at this point in the history
* Make line numbers fixed-width

* Add support for highlight.js

* Run highlighting on the backend so that we only need the CSS in the client

* Use auto if code type isn't explicitly provided

* Fix tests and tweak line numbering

* Only highlight if lang specified, drop markup when highlighting
  • Loading branch information
smoores-dev committed Jun 29, 2020
1 parent aea8a02 commit af9542e
Show file tree
Hide file tree
Showing 8 changed files with 46 additions and 22 deletions.
4 changes: 4 additions & 0 deletions layouts/partials/head.ejs
Expand Up @@ -8,6 +8,10 @@
<![endif]-->

<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>

<link rel="stylesheet"
href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/10.0.0/styles/default.min.css">

<% if (locals.inlineCSS) { %>
<style type="text/css"><%- inlineCSS %></style>
<% } else { %>
Expand Down
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -16,6 +16,7 @@
"google-auth-library": "^3.1.1",
"googleapis": "^48.0.0",
"helmet-csp": "^2.10.0",
"highlight.js": "^10.1.1",
"js-yaml": "^3.13.1",
"lodash": "^4.17.15",
"md5": "^2.2.1",
Expand Down
2 changes: 1 addition & 1 deletion public/scripts/main.js
Expand Up @@ -3,7 +3,7 @@ $(document).ready(function() {
var $document = $(document)
var $html = $('html')

$("pre").html(function (index, html) {
$("pre code").html(function (index, html) {
return html.split(/\r?\n/).map(function(line) {
return [
'<div class="line">',
Expand Down
2 changes: 1 addition & 1 deletion server/csp.json
@@ -1,7 +1,7 @@
{
"defaultSrc": ["'self'"],
"scriptSrc": ["'self'", "'unsafe-inline'", "cdnjs.cloudflare.com", "*.google-analytics.com"],
"styleSrc": ["'self'", "'unsafe-inline'", "fonts.googleapis.com", "maxcdn.bootstrapcdn.com", "*.googleusercontent.com"],
"styleSrc": ["'self'", "'unsafe-inline'", "cdnjs.cloudflare.com", "fonts.googleapis.com", "maxcdn.bootstrapcdn.com", "*.googleusercontent.com"],
"fontSrc": ["'self'", "fonts.gstatic.com", "maxcdn.bootstrapcdn.com"],
"imgSrc": ["'self'", "data:", "*.googleusercontent.com", "*.google-analytics.com"],
"frameSrc": ["'self'","data:","*.youtube.com"],
Expand Down
11 changes: 9 additions & 2 deletions server/formatter.js
Expand Up @@ -2,6 +2,7 @@ const pretty = require('pretty')
const cheerio = require('cheerio')
const qs = require('querystring')
const unescape = require('unescape')
const hljs = require('highlight.js')
const list = require('./list')

/* Your one stop shop for all your document processing needs. */
Expand Down Expand Up @@ -103,11 +104,17 @@ function normalizeHtml(html) {

function formatCode(html) {
// Expand code blocks
html = html.replace(/<p>```(.*?)<\/p>(.+?)<p>```<\/p>/ig, (match, codeType, content) => {
html = html.replace(/<p>```(.*?)<\/p>(.+?)<p>```<\/p>/ig, (match, lang, content) => {
// strip interior <p> tags added by google
content = content.replace(/<\/p><p>/g, '\n').replace(/<\/?p>/g, '')

return `<pre type="${codeType}">${formatCodeContent(content)}</pre>`
const formattedContent = formatCodeContent(content)
if (lang) {
const textOnlyContent = cheerio.load(formattedContent).text()
const highlighted = hljs.highlight(lang, textOnlyContent, true)
return `<pre><code data-lang="${highlighted.language}">${highlighted.value}</code></pre>`
}
return `<pre><code>${formattedContent}</code></pre>`
})

// Replace single backticks with <code>
Expand Down
35 changes: 21 additions & 14 deletions styles/partials/core/_categories.scss
Expand Up @@ -377,6 +377,19 @@
margin-left: 80px;
}

code {
background: $accent-lightest;
color: $accent-dark;
padding: 1px 0px;
margin: 0 4px;
-webkit-box-shadow: 4px 0px 0px $accent-lightest, -4px 0px 0px $accent-lightest;
-moz-box-shadow: 4px 0px 0px $accent-lightest, -4px 0px 0px $accent-lightest;
box-shadow: 4px 0px 0px $accent-lightest, -4px 0px 0px $accent-lightest;
-webkit-box-decoration-break: clone;
-moz-box-decoration-break: clone;
box-decoration-break: clone;
}

pre{
counter-reset:line-numbering;
background: $accent-lightest;
Expand All @@ -395,10 +408,12 @@

.line-number::before {
content: counter(line-numbering);
display: block;
counter-increment: line-numbering;
/* space after line numbers */
padding-right: 1em;
/* space after numbers */
padding-left:8px;
/* space before numbers */
padding-left:4px;
width: 1.5em;
text-align: right;
opacity: 0.5;
Expand All @@ -417,19 +432,11 @@
margin-top: 20px;
line-height: 20px;
}
}

code {
background: $accent-lightest;
color: $accent-dark;
padding: 1px 0px;
margin: 0 4px;
-webkit-box-shadow: 4px 0px 0px $accent-lightest, -4px 0px 0px $accent-lightest;
-moz-box-shadow: 4px 0px 0px $accent-lightest, -4px 0px 0px $accent-lightest;
box-shadow: 4px 0px 0px $accent-lightest, -4px 0px 0px $accent-lightest;
-webkit-box-decoration-break: clone;
-moz-box-decoration-break: clone;
box-decoration-break: clone;
code {
margin: 0;
padding: 0;
}
}
}

Expand Down
8 changes: 4 additions & 4 deletions test/unit/htmlProcessing.test.js
Expand Up @@ -95,16 +95,16 @@ describe('HTML processing', () => {

it('scrubs smart quotes', function () {
const codeBlock = this.output('pre')
assert.match(codeBlock.html(), /singleQuotedStr = &apos;str&apos;/)
assert.match(codeBlock.html(), /doubleQuotedStr = &quot;str&quot;/)
assert.match(codeBlock.html(), /singleQuotedStr = .*&apos;str&apos;/)
assert.match(codeBlock.html(), /doubleQuotedStr = .*&quot;str&quot;/)
})
})

describe('inline code handling', () => {
describe('with inline code disabled', () => {
it('does not modify code block content', function () {
const codeBlock = this.output("pre:contains('codeblocks will not')")
assert.match(codeBlock.html(), /&lt;%-.*%&gt;/)
assert.match(codeBlock.html(), /&lt;.*%-.*%&gt;/)
})

it('does not unescape delimited code', function () {
Expand All @@ -130,7 +130,7 @@ describe('HTML processing', () => {

it('does not modify code block content', function () {
const codeBlock = this.codeEnabledOut("pre:contains('codeblocks will not')")
assert.match(codeBlock.html(), /&lt;%-.*%&gt;/)
assert.match(codeBlock.html(), /&lt;.*%-.*%&gt;/)
})

it('properly unescapes delimited code', function () {
Expand Down

0 comments on commit af9542e

Please sign in to comment.