Skip to content

repo sync #1603

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Nov 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
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., enterprise-server@2.21, 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