forked from github/docs
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move render-content code to this repo (github#16544)
* 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
Showing
9 changed files
with
724 additions
and
161 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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] | ||
} | ||
} |
Oops, something went wrong.