72
.github/workflows/optimize-images.yml
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
name: Optimize images
|
||||
|
||||
# **What it does**: Optimize images.
|
||||
# **Why we have it**: Reduce bandwidth needs.
|
||||
# **Who does it impact**: Docs engineering.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
paths:
|
||||
- '**/*.png'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
optimize-images-on-pr:
|
||||
# We can't make commits on forks
|
||||
if: github.repository == 'github/docs-internal'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out repo on head ref
|
||||
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
# Need to specify a PAT here because otherwise GITHUB_TOKEN is used
|
||||
# by default. Workflows won't trigger in that case because actions
|
||||
# performed with GITHUB_TOKEN don't trigger other workflows.
|
||||
token: ${{ secrets.DOCUBOT_REPO_PAT }}
|
||||
|
||||
- name: Check out base ref
|
||||
run: git fetch --no-tags --depth=1 origin $GITHUB_BASE_REF
|
||||
|
||||
- name: Install the Optipng package
|
||||
run: sudo apt-get update && sudo apt-get -y install optipng
|
||||
|
||||
- name: Run optipng on new or changed images
|
||||
run: |
|
||||
set -e # exit when any command fails
|
||||
|
||||
echo "Ensure we can view $GITHUB_BASE_REF"
|
||||
git checkout $GITHUB_BASE_REF
|
||||
|
||||
echo "Ensure we can view $GITHUB_HEAD_REF"
|
||||
git checkout $GITHUB_HEAD_REF
|
||||
|
||||
echo "List the files that changed"
|
||||
git diff --name-only --diff-filter=d $GITHUB_BASE_REF $GITHUB_HEAD_REF
|
||||
|
||||
echo "Run optipng on pngs in from the diff"
|
||||
git diff --name-only -z --diff-filter=d $GITHUB_BASE_REF $GITHUB_HEAD_REF -- '*.png' | xargs -0 optipng -nx
|
||||
|
||||
- name: Make a commit and a push
|
||||
run: |
|
||||
echo "If there's no changes, exit"
|
||||
if [[ ! `git status --porcelain` ]]
|
||||
then
|
||||
echo "No changes found"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Make a commit"
|
||||
git config user.name github-actions
|
||||
git config user.email github-actions@github.com
|
||||
git add "*.png"
|
||||
git commit --message="Optimize images"
|
||||
|
||||
echo "Push up changes"
|
||||
git push
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
133
.github/workflows/repo-sync.yml
vendored
@@ -1,17 +1,11 @@
|
||||
# The docs.github.com project has two repositories: github/docs (public) and github/docs-internal (private)
|
||||
#
|
||||
# This GitHub Actions workflow keeps the `main` branch of those two repos in sync.
|
||||
#
|
||||
# For more details, see https://github.com/repo-sync/repo-sync#how-it-works
|
||||
|
||||
name: Repo Sync
|
||||
|
||||
# **What it does**:
|
||||
# Syncs docs and docs-internal.
|
||||
# **Why we have it**:
|
||||
# To keep the open-source repository up-to-date, while still having an internal
|
||||
# repository for sensitive work.
|
||||
# **What it does**: GitHub Docs has two repositories: github/docs (public) and github/docs-internal (private).
|
||||
# This GitHub Actions workflow keeps the `main` branch of those two repos in sync.
|
||||
# **Why we have it**: To keep the open-source repository up-to-date
|
||||
# while still having an internal repository for sensitive work.
|
||||
# **Who does it impact**: Open-source.
|
||||
# For more details, see https://github.com/repo-sync/repo-sync#how-it-works
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -31,18 +25,6 @@ jobs:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
|
||||
# Set up npm and run npm ci to get custom husky githooks error
|
||||
# messages if they exist. We could also remove these steps
|
||||
# because currently we have no hooks with customer error messages.
|
||||
# See pull #32064 where they were removed.
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516
|
||||
with:
|
||||
node-version-file: 'package.json'
|
||||
cache: npm
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Sync repo to branch
|
||||
uses: repo-sync/github-sync@3832fe8e2be32372e1b3970bbae8e7079edeec88
|
||||
env:
|
||||
@@ -53,41 +35,64 @@ jobs:
|
||||
destination_branch: repo-sync
|
||||
github_token: ${{ secrets.OCTOMERGER_PAT_WITH_REPO_AND_WORKFLOW_SCOPE }}
|
||||
|
||||
- name: Create pull request
|
||||
uses: repo-sync/pull-request@65785d95a5a466e46a9d0708933a3bd51bbf9dde
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.OCTOMERGER_PAT_WITH_REPO_AND_WORKFLOW_SCOPE }}
|
||||
- name: Find or create pull request
|
||||
id: pull-request
|
||||
uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410
|
||||
with:
|
||||
source_branch: repo-sync
|
||||
destination_branch: main
|
||||
pr_title: 'repo sync'
|
||||
pr_body: "This is an automated pull request to sync changes between the public and private repos.\n\n:robot: This pull request should be merged (not squashed) to preserve continuity across repos, so please let a bot do the merging!"
|
||||
pr_label: automated-reposync-pr
|
||||
github_token: ${{ secrets.OCTOMERGER_PAT_WITH_REPO_AND_WORKFLOW_SCOPE }}
|
||||
# This will exit 0 if there's no difference between `repo-sync`
|
||||
# and `main`. And if so, no PR will be created.
|
||||
pr_allow_empty: false
|
||||
github-token: ${{ secrets.OCTOMERGER_PAT_WITH_REPO_AND_WORKFLOW_SCOPE }}
|
||||
result-encoding: string
|
||||
script: |
|
||||
const { owner, repo } = context.repo
|
||||
const head = 'repo-sync'
|
||||
const base = 'main'
|
||||
const label = 'automated-reposync-pr'
|
||||
|
||||
- name: Find pull request
|
||||
uses: juliangruber/find-pull-request-action@3a4c7c62101755c3778d397dcb6a760a558992f1
|
||||
id: find-pull-request
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
branch: repo-sync
|
||||
base: main
|
||||
author: Octomerger
|
||||
state: open
|
||||
// Check if a pull request already exists
|
||||
let { data: pulls } = await github.rest.pulls.list({ owner, repo, head, base })
|
||||
let foundPull = pulls.find(pr => pr.labels.some(xlabel => xlabel.name === label))
|
||||
if (foundPull) {
|
||||
console.log('Found pull request and will not create a new one', foundPull.html_url)
|
||||
return foundPull.number
|
||||
}
|
||||
|
||||
const body = `
|
||||
This is an automated pull request to sync changes between the public and private repos.
|
||||
Our bot will merge this pull request automatically.
|
||||
To preserve continuity across repos, _do not squash_ this pull request.
|
||||
`
|
||||
console.log('Create a new pull request')
|
||||
let { data: pull } = await github.rest.pulls.create({
|
||||
owner,
|
||||
repo,
|
||||
head,
|
||||
base,
|
||||
title: 'Repo sync',
|
||||
body,
|
||||
})
|
||||
|
||||
console.log('Add label', label)
|
||||
await github.rest.issues.addLabels({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: pull.number,
|
||||
labels: [label]
|
||||
})
|
||||
|
||||
console.log('Created pull request successfully', pull.html_url)
|
||||
return pull.number
|
||||
|
||||
# Because we get far too much spam ;_;
|
||||
- name: Lock conversations
|
||||
if: ${{ github.repository == 'github/docs' && steps.find-pull-request.outputs.number }}
|
||||
if: ${{ github.repository == 'github/docs' && steps.pull-request.outputs.result }}
|
||||
uses: actions/github-script@98814c53be79b1d30f795b907e553d8679345975
|
||||
env:
|
||||
PR_NUMBER: ${{ steps.pull-request.outputs.result }}
|
||||
with:
|
||||
script: |
|
||||
try {
|
||||
await github.rest.issues.lock({
|
||||
...context.repo,
|
||||
issue_number: parseInt(${{ steps.find-pull-request.outputs.number }}),
|
||||
issue_number: parseInt(process.env.PR_NUMBER),
|
||||
lock_reason: 'spam'
|
||||
})
|
||||
console.log('Locked the pull request to prevent spam!')
|
||||
@@ -96,10 +101,13 @@ jobs:
|
||||
console.error(`Failed to lock the pull request. Error: ${error}`)
|
||||
}
|
||||
|
||||
# There are cases where the branch becomes out-of-date in between the time this workflow began and when the pull request is created/updated
|
||||
# There are cases where the branch becomes out-of-date in between the time this workflow began
|
||||
# and when the pull request is created/updated
|
||||
- name: Update branch
|
||||
if: ${{ steps.find-pull-request.outputs.number }}
|
||||
if: ${{ steps.pull-request.outputs.result }}
|
||||
uses: actions/github-script@98814c53be79b1d30f795b907e553d8679345975
|
||||
env:
|
||||
PR_NUMBER: ${{ steps.pull-request.outputs.result }}
|
||||
with:
|
||||
github-token: ${{ secrets.OCTOMERGER_PAT_WITH_REPO_AND_WORKFLOW_SCOPE }}
|
||||
script: |
|
||||
@@ -109,9 +117,10 @@ jobs:
|
||||
})
|
||||
console.log(`heads/main sha: ${mainHeadSha.data.object.sha}`)
|
||||
|
||||
const pull_number = parseInt(process.env.PR_NUMBER)
|
||||
const pull = await github.rest.pulls.get({
|
||||
...context.repo,
|
||||
pull_number: parseInt(${{ steps.find-pull-request.outputs.number }})
|
||||
pull_number,
|
||||
})
|
||||
console.log(`Pull request base sha: ${pull.data.base.sha}`)
|
||||
|
||||
@@ -119,7 +128,7 @@ jobs:
|
||||
try {
|
||||
const updateBranch = await github.rest.pulls.updateBranch({
|
||||
...context.repo,
|
||||
pull_number: parseInt(${{ steps.find-pull-request.outputs.number }})
|
||||
pull_number,
|
||||
})
|
||||
console.log(updateBranch.data.message)
|
||||
} catch (error) {
|
||||
@@ -129,7 +138,7 @@ jobs:
|
||||
try {
|
||||
const updateBranch = await github.rest.pulls.updateBranch({
|
||||
...context.repo,
|
||||
pull_number: parseInt(${{ steps.find-pull-request.outputs.number }})
|
||||
pull_number,
|
||||
})
|
||||
console.log(updateBranch.data.message)
|
||||
} catch (error) {
|
||||
@@ -147,11 +156,11 @@ jobs:
|
||||
}
|
||||
|
||||
- name: Check pull request file count after updating
|
||||
if: ${{ steps.find-pull-request.outputs.number }}
|
||||
if: ${{ steps.pull-request.outputs.result }}
|
||||
uses: actions/github-script@98814c53be79b1d30f795b907e553d8679345975
|
||||
id: pr-files
|
||||
env:
|
||||
PR_NUMBER: ${{ steps.find-pull-request.outputs.number }}
|
||||
PR_NUMBER: ${{ steps.pull-request.outputs.result }}
|
||||
with:
|
||||
github-token: ${{ secrets.OCTOMERGER_PAT_WITH_REPO_AND_WORKFLOW_SCOPE }}
|
||||
result-encoding: string
|
||||
@@ -165,24 +174,26 @@ jobs:
|
||||
# Sometimes after updating the branch, there aren't any remaining files changed.
|
||||
# If not, we should close the PR instead of merging it and triggering deployments.
|
||||
- name: Close the pull request if no files remain
|
||||
if: ${{ steps.find-pull-request.outputs.number && steps.pr-files.outputs.count == '0' }}
|
||||
if: ${{ steps.pull-request.outputs.result && steps.pr-files.outputs.count == '0' }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_NUMBER: ${{ steps.pull-request.outputs.result }}
|
||||
run: |
|
||||
gh pr close ${{ steps.find-pull-request.outputs.number }} --repo $GITHUB_REPOSITORY
|
||||
gh pr close $PR_NUMBER --repo $GITHUB_REPOSITORY
|
||||
|
||||
- name: Approve pull request
|
||||
if: ${{ steps.find-pull-request.outputs.number && steps.pr-files.outputs.count != '0' }}
|
||||
if: ${{ steps.steps.pull-request.outputs.result && steps.pr-files.outputs.count != '0' }}
|
||||
uses: juliangruber/approve-pull-request-action@dcc4effb325c0b503408619918d56e40653dcc91
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
number: ${{ steps.find-pull-request.outputs.number }}
|
||||
number: ${{ steps.pull-request.outputs.result }}
|
||||
|
||||
# Admin merge to avoid being squashed in the merge queue
|
||||
- name: Admin merge the pull request
|
||||
if: ${{ steps.find-pull-request.outputs.number && steps.pr-files.outputs.count != '0' }}
|
||||
if: ${{ steps.pull-request.outputs.result && steps.pr-files.outputs.count != '0' }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.OCTOMERGER_PAT_WITH_REPO_AND_WORKFLOW_SCOPE }}
|
||||
PR_NUMBER: ${{ steps.find-pull-request.outputs.number }}
|
||||
PR_NUMBER: ${{ steps.pull-request.outputs.result }}
|
||||
run: |
|
||||
gh pr merge $PR_NUMBER --admin --merge
|
||||
|
||||
@@ -193,4 +204,4 @@ jobs:
|
||||
channel: ${{ secrets.DOCS_ALERTS_SLACK_CHANNEL_ID }}
|
||||
bot-token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}
|
||||
color: failure
|
||||
text: The last repo-sync run for ${{github.repository}} failed. See https://github.com/${{github.repository}}/actions?query=workflow%3A%22Repo+Sync%22
|
||||
text: The last repo-sync run for ${{github.repository}} failed. See https://github.com/${{github.repository}}/actions/workflows/repo-sync.yml
|
||||
|
||||
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 194 KiB After Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 783 KiB After Width: | Height: | Size: 486 KiB |
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 162 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 227 KiB After Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 339 KiB After Width: | Height: | Size: 227 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 167 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 425 KiB After Width: | Height: | Size: 315 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 3.2 KiB |
@@ -10,7 +10,7 @@
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
[class~="code-extra"] {
|
||||
[class~="code-example"] {
|
||||
margin-top: 1.5rem;
|
||||
|
||||
pre,
|
||||
|
||||
@@ -14,7 +14,6 @@ import erb from 'highlight.js/lib/languages/erb'
|
||||
import powershell from 'highlight.js/lib/languages/powershell'
|
||||
import graphql from 'highlight.js/lib/languages/graphql'
|
||||
import html from 'rehype-stringify'
|
||||
import remarkCodeExtra from 'remark-code-extra'
|
||||
import codeHeader from './plugins/code-header.js'
|
||||
import rewriteLocalLinks from './plugins/rewrite-local-links.js'
|
||||
import rewriteImgSources from './plugins/rewrite-asset-urls.js'
|
||||
@@ -34,7 +33,6 @@ export default function createProcessor(context) {
|
||||
.use(process.env.COMMONMARK ? gfm : null)
|
||||
// Markdown AST below vvv
|
||||
.use(parseInfoString)
|
||||
.use(remarkCodeExtra, { transform: codeHeader })
|
||||
.use(emoji)
|
||||
// Markdown AST above ^^^
|
||||
.use(remark2rehype, { allowDangerousHtml: true })
|
||||
@@ -42,6 +40,7 @@ export default function createProcessor(context) {
|
||||
.use(slug)
|
||||
.use(useEnglishHeadings, context)
|
||||
.use(headingLinks)
|
||||
.use(codeHeader)
|
||||
.use(annotate)
|
||||
.use(highlight, {
|
||||
languages: { graphql, dockerfile, http, groovy, erb, powershell },
|
||||
|
||||
@@ -35,6 +35,7 @@ import { visit } from 'unist-util-visit'
|
||||
import { h } from 'hastscript'
|
||||
import { fromMarkdown } from 'mdast-util-from-markdown'
|
||||
import { toHast } from 'mdast-util-to-hast'
|
||||
// import { header } from './code-header.js'
|
||||
|
||||
const languages = yaml.load(fs.readFileSync('./data/variables/code-languages.yml', 'utf8'))
|
||||
|
||||
@@ -71,7 +72,7 @@ function createAnnotatedNode(node) {
|
||||
const rows = chunk(groups, 2)
|
||||
|
||||
// Render the HTML
|
||||
return template({ lang, rows })
|
||||
return template({ lang, code, rows })
|
||||
}
|
||||
|
||||
function validate(lang, code) {
|
||||
@@ -116,10 +117,11 @@ function matchComment(lang) {
|
||||
return (line) => regex.test(line)
|
||||
}
|
||||
|
||||
function template({ lang, rows }) {
|
||||
function template({ lang, code, rows }) {
|
||||
return h(
|
||||
'div',
|
||||
{ class: 'annotate' },
|
||||
// header(lang, code),
|
||||
rows.map(([note, code]) =>
|
||||
h('div', { className: 'annotate-row' }, [
|
||||
h(
|
||||
|
||||
@@ -1,105 +1,42 @@
|
||||
/**
|
||||
* Adds a bar above code blocks that shows the language and a copy button
|
||||
*/
|
||||
|
||||
import yaml from 'js-yaml'
|
||||
import fs from 'fs'
|
||||
import { visit } from 'unist-util-visit'
|
||||
import { h } from 'hastscript'
|
||||
import octicons from '@primer/octicons'
|
||||
import { parse } from 'parse5'
|
||||
import { fromParse5 } from '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',
|
||||
const languages = yaml.load(fs.readFileSync('./data/variables/code-languages.yml', 'utf8'))
|
||||
|
||||
// Unofficial languages
|
||||
shellsession: 'Shell',
|
||||
jsx: 'JSX',
|
||||
const matcher = (node) =>
|
||||
node.type === 'element' &&
|
||||
node.tagName === 'pre' &&
|
||||
// For now, limit to ones with the copy meta,
|
||||
// but we may enable for all examples later.
|
||||
getPreMeta(node).copy &&
|
||||
// Don't add this header for annotated examples.
|
||||
!getPreMeta(node).annotate
|
||||
|
||||
export default function codeHeader() {
|
||||
return (tree) => {
|
||||
visit(tree, matcher, (node, index, parent) => {
|
||||
parent.children[index] = wrapCodeExample(node)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const COPY_REGEX = /\{:copy\}$/
|
||||
function wrapCodeExample(node) {
|
||||
const lang = node.children[0].properties.className?.[0].replace('language-', '')
|
||||
const code = node.children[0].children[0].value
|
||||
return h('div', { className: 'code-example' }, [header(lang, code), node])
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a bar above code blocks that shows the language and a copy button
|
||||
*/
|
||||
export default 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.copy.toSVG()
|
||||
const btnIconAst = parse(String(btnIconHtml), { sourceCodeLocationInfo: true })
|
||||
const btnIcon = fromParse5(btnIconAst, { file: btnIconHtml })
|
||||
|
||||
// Need to create the header using Markdown AST utilities, to fit
|
||||
// into the Unified processor ecosystem.
|
||||
const header = h(
|
||||
export function header(lang, code) {
|
||||
return h(
|
||||
'header',
|
||||
{
|
||||
class: [
|
||||
@@ -109,24 +46,35 @@ export default function addCodeHeader(node) {
|
||||
'p-2',
|
||||
'text-small',
|
||||
'rounded-top-1',
|
||||
'border',
|
||||
'border-top',
|
||||
'border-left',
|
||||
'border-right',
|
||||
],
|
||||
},
|
||||
[
|
||||
h('span', language),
|
||||
h('span', languages[lang]?.name),
|
||||
h(
|
||||
'button',
|
||||
{
|
||||
class: ['js-btn-copy', 'btn', 'btn-sm', 'tooltipped', 'tooltipped-nw'],
|
||||
'data-clipboard-text': node.value,
|
||||
'data-clipboard-text': code,
|
||||
'aria-label': 'Copy code to clipboard',
|
||||
},
|
||||
btnIcon
|
||||
btnIcon()
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
return {
|
||||
before: [header],
|
||||
}
|
||||
}
|
||||
|
||||
function btnIcon() {
|
||||
const btnIconHtml = octicons.copy.toSVG()
|
||||
const btnIconAst = parse(String(btnIconHtml), { sourceCodeLocationInfo: true })
|
||||
const btnIcon = fromParse5(btnIconAst, { file: btnIconHtml })
|
||||
return btnIcon
|
||||
}
|
||||
|
||||
function getPreMeta(node) {
|
||||
// Here's why this monstrosity works:
|
||||
// https://github.com/syntax-tree/mdast-util-to-hast/blob/c87cd606731c88a27dbce4bfeaab913a9589bf83/lib/handlers/code.js#L40-L42
|
||||
return node.children[0]?.data?.meta || {}
|
||||
}
|
||||
|
||||
@@ -7,17 +7,26 @@
|
||||
|
||||
import { visit } from 'unist-util-visit'
|
||||
|
||||
const matcher = (node) => node.type === 'code' && node.lang && node.meta
|
||||
const matcher = (node) => node.type === 'code' && node.lang
|
||||
|
||||
export default function parseInfoString() {
|
||||
return (tree) => {
|
||||
visit(tree, matcher, (node) => {
|
||||
node.meta = strToObj(node.meta)
|
||||
|
||||
// Temporary, change {:copy} to ` copy` to avoid conflict in styles.
|
||||
// We may end up enabling copy on all code examples later.
|
||||
const copyTag = '{:copy}'
|
||||
if (node.lang.includes(copyTag)) {
|
||||
node.meta.copy = true
|
||||
node.lang = node.lang.replace(copyTag, '')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function strToObj(str) {
|
||||
if (!str) return {}
|
||||
return Object.fromEntries(
|
||||
str
|
||||
.split(/\s+/g)
|
||||
|
||||
49
package-lock.json
generated
@@ -73,7 +73,6 @@
|
||||
"rehype-raw": "^6.1.1",
|
||||
"rehype-slug": "^5.0.1",
|
||||
"rehype-stringify": "^9.0.3",
|
||||
"remark-code-extra": "^1.0.1",
|
||||
"remark-gemoji-to-emoji": "^1.1.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-parse": "^10.0.1",
|
||||
@@ -16424,31 +16423,6 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/remark-code-extra": {
|
||||
"version": "1.0.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"unist-util-visit": "^1.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/remark-code-extra/node_modules/unist-util-is": {
|
||||
"version": "3.0.0",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/remark-code-extra/node_modules/unist-util-visit": {
|
||||
"version": "1.4.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"unist-util-visit-parents": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/remark-code-extra/node_modules/unist-util-visit-parents": {
|
||||
"version": "2.1.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"unist-util-is": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/remark-gemoji-to-emoji": {
|
||||
"version": "1.1.0",
|
||||
"license": "MIT",
|
||||
@@ -30242,29 +30216,6 @@
|
||||
"unified": "^10.0.0"
|
||||
}
|
||||
},
|
||||
"remark-code-extra": {
|
||||
"version": "1.0.1",
|
||||
"requires": {
|
||||
"unist-util-visit": "^1.4.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"unist-util-is": {
|
||||
"version": "3.0.0"
|
||||
},
|
||||
"unist-util-visit": {
|
||||
"version": "1.4.1",
|
||||
"requires": {
|
||||
"unist-util-visit-parents": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"unist-util-visit-parents": {
|
||||
"version": "2.1.2",
|
||||
"requires": {
|
||||
"unist-util-is": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"remark-gemoji-to-emoji": {
|
||||
"version": "1.1.0",
|
||||
"requires": {
|
||||
|
||||
@@ -120,7 +120,6 @@
|
||||
"rehype-raw": "^6.1.1",
|
||||
"rehype-slug": "^5.0.1",
|
||||
"rehype-stringify": "^9.0.3",
|
||||
"remark-code-extra": "^1.0.1",
|
||||
"remark-gemoji-to-emoji": "^1.1.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-parse": "^10.0.1",
|
||||
|
||||
@@ -86,7 +86,8 @@ export async function convertContentToDocs(content, frontmatterDefaults = {}) {
|
||||
visitParents(ast, matcher, (node, ancestors) => {
|
||||
// Add the copy button to the example command
|
||||
if (node.type === 'code' && node.value.startsWith(`codeql ${frontmatter.title}`)) {
|
||||
node.lang = 'shell{:copy}'
|
||||
node.lang = 'shell'
|
||||
node.meta = 'copy'
|
||||
}
|
||||
|
||||
// This is the beginning of a secondary options section. For example,
|
||||
|
||||
@@ -248,12 +248,12 @@ $resourceGroupName = "octocat-testgroup"
|
||||
test('does not autoguess code block language', async () => {
|
||||
const template = nl(`
|
||||
\`\`\`
|
||||
some code
|
||||
\`\`\`\
|
||||
var a = 1
|
||||
\`\`\`
|
||||
`)
|
||||
const html = await renderContent(template)
|
||||
const $ = cheerio.load(html, { xmlMode: true })
|
||||
expect($.html().includes('<pre><code>some code\n</code></pre>')).toBeTruthy()
|
||||
expect($.html().includes('var a = 1')).toBeTruthy()
|
||||
})
|
||||
|
||||
test('renders a line break in a table', async () => {
|
||||
@@ -267,15 +267,15 @@ some code
|
||||
)
|
||||
})
|
||||
|
||||
test('renders a copy button for code blocks with {:copy} annotation', async () => {
|
||||
test('renders a copy button for code blocks with language specified', async () => {
|
||||
const template = nl(`
|
||||
\`\`\`js{:copy}
|
||||
some code
|
||||
\`\`\`\
|
||||
\`\`\`javascript copy
|
||||
var a = 1
|
||||
\`\`\`
|
||||
`)
|
||||
const html = await renderContent(template)
|
||||
const $ = cheerio.load(html)
|
||||
const el = $('button.js-btn-copy')
|
||||
expect(el.data('clipboard-text')).toBe('some code')
|
||||
expect(el.data('clipboard-text')).toBe('var a = 1\n')
|
||||
})
|
||||
})
|
||||
|
||||