Skip to content

Commit

Permalink
Move render-content code to this repo (github#16544)
Browse files Browse the repository at this point in the history
* Move render-content files in here

* Replace existing file with nested index.js

* Copy in tests and jest-ify

* Update docs

* Uninstall @github-docs/render-content

* Bring over README

* Add missing dependencies

* Fix require paths
  • Loading branch information
JasonEtco authored Nov 25, 2020
1 parent 9d9a694 commit 4484068
Show file tree
Hide file tree
Showing 9 changed files with 724 additions and 161 deletions.
2 changes: 1 addition & 1 deletion contributing/content-markup-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

[Markdown](http://daringfireball.net/projects/markdown/) is a human-friendly syntax for formatting plain text. Our documentation is written with [GitHub Flavored Markdown](https://docs.github.com/en/github/writing-on-github/about-writing-and-formatting-on-github), a custom version of Markdown used across GitHub.

This site's Markdown rendering is powered by the [`@github-docs/render-content`](https://github.com/docs/render-content) and [`hubdown`](https://github.com/electron/hubdown) npm packages, which are in turn built on the [`remark`](https://remark.js.org/) Markdown processor.
This site's Markdown rendering is powered by the [`/lib/render-content`](/lib/render-content) and [`hubdown`](https://github.com/electron/hubdown) npm packages, which are in turn built on the [`remark`](https://remark.js.org/) Markdown processor.

## Callout tags

Expand Down
53 changes: 53 additions & 0 deletions lib/render-content/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
Markdown and Liquid rendering pipeline.

## Usage

```js
const renderContent = require('.')

const html = await renderContent(`
# Beep
{{ foo }}
`, {
foo: 'bar'
})
```

Creates:

```html
<h1 id="beep"><a href="#beep">Beep</a></h1>
<p>bar</p>
```

## API

### renderContent(markdown, context = {}, options = {})

Render a string of `markdown` with optional `context`. Returns a `Promise`.

Liquid will be looking for includes in `${process.cwd()}/includes`.

Options:

- `encodeEntities`: Encode html entities. Default: `false`.
- `fileName`: File name for debugging purposes.
- `textOnly`: Output text instead of html using [cheerio](https://ghub.io/cheerio).

### .liquid

The [Liquid](https://ghub.io/liquid) instance used internally.

### Code block headers

You can add a header to code blocks by adding the `{:copy}` annotation after the code fences:

```js{:copy}
const copyMe = true
```

This renders:

![image](https://user-images.githubusercontent.com/10660468/95881747-e96c6900-0d46-11eb-9abf-1e8ad16c7646.png)

The un-highlighted text is available as `button.js-btn-copy`'s `data-clipboard-text` attribute.
24 changes: 12 additions & 12 deletions lib/render-content.js → lib/render-content/index.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
const renderContent = require('@github-docs/render-content')
const { ExtendedMarkdown, tags } = require('./liquid-tags/extended-markdown')
const renderContent = require('./renderContent')
const { ExtendedMarkdown, tags } = require('../liquid-tags/extended-markdown')

// Include custom tags like {% link_with_intro /article/foo %}
renderContent.liquid.registerTag('liquid_tag', require('./liquid-tags/liquid-tag'))
renderContent.liquid.registerTag('link', require('./liquid-tags/link'))
renderContent.liquid.registerTag('link_with_intro', require('./liquid-tags/link-with-intro'))
renderContent.liquid.registerTag('homepage_link_with_intro', require('./liquid-tags/homepage-link-with-intro'))
renderContent.liquid.registerTag('link_in_list', require('./liquid-tags/link-in-list'))
renderContent.liquid.registerTag('topic_link_in_list', require('./liquid-tags/topic-link-in-list'))
renderContent.liquid.registerTag('link_with_short_title', require('./liquid-tags/link-with-short-title'))
renderContent.liquid.registerTag('indented_data_reference', require('./liquid-tags/indented-data-reference'))
renderContent.liquid.registerTag('data', require('./liquid-tags/data'))
renderContent.liquid.registerTag('octicon', require('./liquid-tags/octicon'))
renderContent.liquid.registerTag('liquid_tag', require('../liquid-tags/liquid-tag'))
renderContent.liquid.registerTag('link', require('../liquid-tags/link'))
renderContent.liquid.registerTag('link_with_intro', require('../liquid-tags/link-with-intro'))
renderContent.liquid.registerTag('homepage_link_with_intro', require('../liquid-tags/homepage-link-with-intro'))
renderContent.liquid.registerTag('link_in_list', require('../liquid-tags/link-in-list'))
renderContent.liquid.registerTag('topic_link_in_list', require('../liquid-tags/topic-link-in-list'))
renderContent.liquid.registerTag('link_with_short_title', require('../liquid-tags/link-with-short-title'))
renderContent.liquid.registerTag('indented_data_reference', require('../liquid-tags/indented-data-reference'))
renderContent.liquid.registerTag('data', require('../liquid-tags/data'))
renderContent.liquid.registerTag('octicon', require('../liquid-tags/octicon'))

for (const tag in tags) {
// Register all the extended markdown tags, like {% note %} and {% warning %}
Expand Down
61 changes: 61 additions & 0 deletions lib/render-content/liquid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
const Liquid = require('liquid')
const semver = require('semver')
const path = require('path')
const engine = new Liquid.Engine()
engine.registerFileSystem(
new Liquid.LocalFileSystem(path.join(process.cwd(), 'includes'))
)

// GHE versions are not valid SemVer, but can be coerced...
// https://github.com/npm/node-semver#coercion

Liquid.Condition.operators.ver_gt = (cond, left, right) => {
if (!matchesVersionString(left)) return false
if (!matchesVersionString(right)) return false

const [leftPlan, leftRelease] = splitVersion(left)
const [rightPlan, rightRelease] = splitVersion(right)

if (leftPlan !== rightPlan) return false

return semver.gt(semver.coerce(leftRelease), semver.coerce(rightRelease))
}

Liquid.Condition.operators.ver_lt = (cond, left, right) => {
if (!matchesVersionString(left)) return false
if (!matchesVersionString(right)) return false

const [leftPlan, leftRelease] = splitVersion(left)
const [rightPlan, rightRelease] = splitVersion(right)

if (leftPlan !== rightPlan) return false

return semver.lt(semver.coerce(leftRelease), semver.coerce(rightRelease))
}

module.exports = engine

function matchesVersionString (input) {
return input && input.match(/^(?:[a-z](?:[a-z-]*[a-z])?@)?\d+(?:\.\d+)*/)
}
// Support new version formats where version = plan@release
// e.g., [email protected], where enterprise-server is the plan and 2.21 is the release
// e.g., free-pro-team@latest, where free-pro-team is the plan and latest is the release
// in addition to legacy formats where the version passed is simply 2.21
function splitVersion (version) {
// The default plan when working with versions is "enterprise-server".
// Default to that value here to support backward compatibility from before
// plans were explicitly included.
let plan = 'enterprise-server'
let release

const lastIndex = version.lastIndexOf('@')
if (lastIndex === -1) {
release = version
} else {
plan = version.slice(0, lastIndex)
release = version.slice(lastIndex + 1)
}

return [plan, release]
}
138 changes: 138 additions & 0 deletions lib/render-content/plugins/code-header.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
const h = require('hastscript')
const octicons = require('@primer/octicons')
const parse5 = require('parse5')
const fromParse5 = require('hast-util-from-parse5')

const LANGUAGE_MAP = {
asp: 'ASP',
aspx: 'ASP',
'aspx-vb': 'ASP',
as3: 'ActionScript',
apache: 'ApacheConf',
nasm: 'Assembly',
bat: 'Batchfile',
'c#': 'C#',
csharp: 'C#',
c: 'C',
'c++': 'C++',
cpp: 'C++',
chpl: 'Chapel',
coffee: 'CoffeeScript',
'coffee-script': 'CoffeeScript',
cfm: 'ColdFusion',
'common-lisp': 'Common Lisp',
lisp: 'Common Lisp',
dpatch: 'Darcs Patch',
dart: 'Dart',
elisp: 'Emacs Lisp',
emacs: 'Emacs Lisp',
'emacs-lisp': 'Emacs Lisp',
pot: 'Gettext Catalog',
html: 'HTML',
xhtml: 'HTML',
'html+erb': 'HTML+ERB',
erb: 'HTML+ERB',
irc: 'IRC log',
json: 'JSON',
jsp: 'Java Server Pages',
java: 'Java',
javascript: 'JavaScript',
js: 'JavaScript',
lhs: 'Literate Haskell',
'literate-haskell': 'Literate Haskell',
objc: 'Objective-C',
openedge: 'OpenEdge ABL',
progress: 'OpenEdge ABL',
abl: 'OpenEdge ABL',
pir: 'Parrot Internal Representation',
posh: 'PowerShell',
puppet: 'Puppet',
'pure-data': 'Pure Data',
raw: 'Raw token data',
rb: 'Ruby',
ruby: 'Ruby',
r: 'R',
scheme: 'Scheme',
bash: 'Shell',
sh: 'Shell',
shell: 'Shell',
zsh: 'Shell',
supercollider: 'SuperCollider',
tex: 'TeX',
ts: 'TypeScript',
vim: 'Vim script',
viml: 'Vim script',
rst: 'reStructuredText',
xbm: 'X BitMap',
xpm: 'X PixMap',
yaml: 'YAML',
yml: 'YAML',

// Unofficial languages
shellsession: 'Shell',
jsx: 'JSX'
}

const COPY_REGEX = /\{:copy\}$/

/**
* Adds a bar above code blocks that shows the language and a copy button
*/
module.exports = function addCodeHeader (node) {
// Check if the language matches `lang{:copy}`
const hasCopy = node.lang && COPY_REGEX.test(node.lang)

if (hasCopy) {
// js{:copy} => js
node.lang = node.lang.replace(COPY_REGEX, '')
} else {
// It doesn't have the copy annotation, so don't add the header
return
}

// Display the language using the above map of `{ [shortCode]: language }`
const language = LANGUAGE_MAP[node.lang] || node.lang || 'Code'

const btnIconHtml = octicons.clippy.toSVG()
const btnIconAst = parse5.parse(String(btnIconHtml))
const btnIcon = fromParse5(btnIconAst, btnIconHtml)

// Need to create the header using Markdown AST utilities, to fit
// into the Unified processor ecosystem.
const header = h(
'header',
{
class: [
'd-flex',
'flex-items-center',
'flex-justify-between',
'p-2',
'text-small',
'rounded-top-1',
'border'
]
},
[
h('span', language),
h(
'button',
{
class: [
'js-btn-copy',
'btn',
'btn-sm',
'tooltipped',
'tooltipped-nw'
],
'data-clipboard-text': node.value,
'aria-label': 'Copy code to clipboard'
},
btnIcon
)
]
)

return {
before: [header]
}
}
Loading

0 comments on commit 4484068

Please sign in to comment.