diff --git a/content/actions/reference/workflow-commands-for-github-actions.md b/content/actions/reference/workflow-commands-for-github-actions.md index 3c2e13b996d3..c840e21dc96f 100644 --- a/content/actions/reference/workflow-commands-for-github-actions.md +++ b/content/actions/reference/workflow-commands-for-github-actions.md @@ -19,6 +19,75 @@ versions: {% data reusables.actions.enterprise-github-hosted-runners %} {% data reusables.actions.ae-beta %} +### Tab test + +{% capture example_capture %}{% octicon "link-external" aria-label="The external link icon" %}Something to reuse, e.g. in _tabs_.{% endcapture %} + +{% tabs "shell" %} + +**No content** should be allowed here! + +{% tab "Bash" %} +```bash +VAR=${ date } +echo "::set-output name=var::$VAR" +``` + +This is a `tab`! +- With a list +- just for funsies {% octicon "squirrel" aria-label="Sideway view of a squirrel with arms raised" %} + +{% danger %} +Callouts are supported inside tabs +{% enddanger %} + +{% tab "PowerShell" %} + +Another tab. {{ example_capture }} + +{% tab "Windows `cmd`" %} + +Markdown in tab title. Also using a capture: + +{{ example_capture}} + +{% endtabs %} + +{% tabs "shell" %} +{% tab "Bash" %} +```bash +echo "$GITHUB_PATH" +``` +{% tab "PowerShell" %} +```powershell +echo "$Env:GITHUB_PATH" +``` +{% tab "Windows `cmd`" %} +```cmd +echo %GITHUB_PATH% +``` +{% endtabs %} + +### Stress test + +{% tabs "other" %} +{% tab "Bash" %} +This is a different tab group! +{% tab "PowerShell" %} +Still different group! +{% endtabs %} + +{% tabs "shell" %} +{% tab "PowerShell" %} +```powershell +echo "$Env.GITHUB_ENV" +``` +{% tab "Bash" %} +```bash +echo "${GITHUB_ENV}" +``` +{% endtabs %} + ### About workflow commands Actions can communicate with the runner machine to set environment variables, output values used by other actions, add debug messages to the output logs, and other tasks. diff --git a/includes/tabs.html b/includes/tabs.html new file mode 100644 index 000000000000..396b0cb1ae67 --- /dev/null +++ b/includes/tabs.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/javascripts/display-tab-content.js b/javascripts/display-tab-content.js new file mode 100644 index 000000000000..94c345c49154 --- /dev/null +++ b/javascripts/display-tab-content.js @@ -0,0 +1,83 @@ +export default function tabs () { + const tabContent = findTabContent() + + hideTabContent(tabContent) + showFirstTab() + + // configure links for switching platform content + tabLinks().forEach(link => { + // TODO: stop browser from scrolling + link.addEventListener('click', (event) => { + event.preventDefault() + const tab = event.target + console.log(`Tab group: ${tab.dataset.group}, id: ${tab.dataset.tab}`) + const offset = tab.getBoundingClientRect().top - window.scrollY + hideTabContent(tabContent) + Array.from(document.querySelectorAll(`.tab.tab-${ tab.dataset.tab }`)) + //.filter( tab => tab.dataset.group === tab.dataset.group ) + .forEach( block => { + block.style.display = '' + }) + tabLinks().forEach(link_ => { + link_.dataset.tab == tab.dataset.tab + ? link_.classList.add('selected') + : link_.classList.remove('selected') + }) + // Keep relative offset of tab in viewport to avoid jumping content + window.scrollTo(window.scrollX, tab.getBoundingClientRect().top - offset) + }) + }) +} + +function findTabs () { + return Array.from(document.querySelectorAll('.tabs')) +} + +function findTabContent () { + return Array.from(document.querySelectorAll('.tab')) +} + +function tabLinks () { + return Array.from(document.querySelectorAll('.tabs a')) +} + +function hideTabContent (tabContent) { + tabContent + .forEach(block => { + block.style.display = 'none' + }) +} + +// TODO: For every tab group, determine default (first) tab and select respective tab in other groups (order can vary!) +function showFirstTab () { + let initialTabPerGroup = new Map() + tabLinks().forEach( tab => { + if (!initialTabPerGroup.has(tab.dataset.group)) { + initialTabPerGroup.set(tab.dataset.group, tab.dataset.tab) + } + }) + tabLinks().forEach( tab => { + if (tab.dataset.tab === initialTabPerGroup.get(tab.dataset.group)) { + tab.classList.add('selected') + // TODO: distinguish groups + Array.from(document.querySelectorAll(`.tab.tab-${ tab.dataset.tab }`)) + .forEach( block => { + block.style.display = '' + }) + } + }) + + /* + console.dir(initialTabPerGroup) + Array.from(document.querySelectorAll('.tabs a:nth-child(1)')) + .forEach( tab => { + tab.classList.add('selected') + //tab.dataset.group + Array.from(document.querySelectorAll(`.tab.tab-${ tab.dataset.tab }`)) + .forEach(block => { + //console.log(`Show first tab: ${ Array.from(block.classList).join(', ') }`) + block.style.display = '' + }) + }) + */ +} diff --git a/javascripts/index.js b/javascripts/index.js index 0cf22b13a9e3..5c1d96b6e19a 100644 --- a/javascripts/index.js +++ b/javascripts/index.js @@ -1,6 +1,7 @@ // Import our SCSS files so webpack will process them import '../stylesheets/index.scss' import displayPlatformSpecificContent from './display-platform-specific-content' +import displayTabContent from './display-tab-content' import explorer from './explorer' import scrollUp from './scroll-up' import search from './search' @@ -23,6 +24,7 @@ import airgapLinks from './airgap-links' document.addEventListener('DOMContentLoaded', async () => { displayPlatformSpecificContent() + displayTabContent() explorer() scrollUp() search() diff --git a/lib/liquid-tags/tabs.js b/lib/liquid-tags/tabs.js new file mode 100644 index 000000000000..93eaffa2872c --- /dev/null +++ b/lib/liquid-tags/tabs.js @@ -0,0 +1,117 @@ +const Liquid = require('liquid') +const renderContent = require('../render-content/renderContent') +//const path = require('path') +const slugger = new (require('github-slugger'))() +const assert = require('assert') + +const SyntaxHelp = "Syntax Error in tag 'tabs' - Valid syntax: tabs [group]" +const TabSyntaxHelp = "Syntax Error in tag 'tab' - Valid syntax: tab [title]" +const Syntax = /"([^"]+)"|'([^']+)'/ + +// TODO: Re-use code for templating? Or inline as string? Also see below. +//const LiquidTag = require('./liquid-tag') + +module.exports = class Tabs extends Liquid.Block { + constructor (template, tagName, markup) { + super(template, tagName, markup) + const match = Syntax.exec(markup) + if (!match || !match[1]) { + throw new Liquid.SyntaxError(SyntaxHelp) + } + slugger.reset() + this.group = slugger.slug(match[1]) + this.tabs = [] + } + + unknownTag (tag, markup) { + if (tag === 'tab') { + return this.pushBlock(tag, markup) + } else { + return super.unknownTag(tag, markup) + } + } + + async pushBlock (tag, markup) { + //const tab = Liquid.Helpers.scan(markup, Syntax) + const match = Syntax.exec(markup) + if (!match || !match[1]) { + throw new Liquid.SyntaxError(TabSyntaxHelp) + } + const tabTitle = match[1] + slugger.reset() // TODO: Only reset once per tab group so that it generates unique IDs in case the input leads to the same slug? + const tabId = slugger.slug(tabTitle) + // line breaks seems to be needed in case the source markup has no empty line between start tag and Markdown content + const block = `${ this.tabs.length > 0 ? '' : '' }
') && htmlTitle.endsWith('
'), 'expected wrapping ') + tab.title = htmlTitle.slice(3, -4) // HACK: remove surrounding + }) + const result = await this.template.engine.parseAndRender(src, { + tabs: this.tabs, + group: this.group + }) + //console.log(`tabs.html:\n${result}\n---`) + //this.nodelist.unshift(result) + //const rendered = this.renderAll(this.nodelist, context) + const rendered = [result] + for (let n of this.nodelist) { + if (typeof n === 'string') { + rendered.push(n) + } else { + rendered.push(await n.render(context)) + } + } + //const temp = await Promise.all([result, rendered]).then(([a, b]) => [a, ...b]) + //console.log(JSON.stringify(temp, null, 2)) + //return temp // TODO: Does the result get rendered once more? How to (partially) avoid it? Return a render() closure? + return rendered + } +} diff --git a/lib/render-content/index.js b/lib/render-content/index.js index 9b58102ad89f..477b172fe2ca 100644 --- a/lib/render-content/index.js +++ b/lib/render-content/index.js @@ -12,6 +12,7 @@ renderContent.liquid.registerTag('indented_data_reference', require('../liquid-t renderContent.liquid.registerTag('data', require('../liquid-tags/data')) renderContent.liquid.registerTag('octicon', require('../liquid-tags/octicon')) renderContent.liquid.registerTag('link_as_article_card', require('../liquid-tags/link-as-article-card')) +renderContent.liquid.registerTag('tabs', require('../liquid-tags/tabs')) for (const tag in tags) { // Register all the extended markdown tags, like {% note %} and {% warning %} diff --git a/package.json b/package.json index 57dab2f1978b..5886fbe9deb7 100644 --- a/package.json +++ b/package.json @@ -166,7 +166,7 @@ "xlsx-populate": "^1.21.0" }, "scripts": { - "start": "cross-env NODE_ENV=development ENABLED_LANGUAGES='en,ja' nodemon server.js", + "start": "cross-env NODE_ENV=development ENABLED_LANGUAGES='en' nodemon server.js", "dev": "npm start", "debug": "cross-env NODE_ENV=development ENABLED_LANGUAGES='en,ja' nodemon --inspect server.js", "rest-dev": "script/rest/update-files.js && npm run dev",