Skip to content

Tab widget (prototype) #1606

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

Closed
wants to merge 11 commits into from
69 changes: 69 additions & 0 deletions content/actions/reference/workflow-commands-for-github-actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions includes/tabs.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<nav class="UnderlineNav my-3">
<div class="UnderlineNav-body tabs">
{% for tab in tabs %}<a href="#" class="UnderlineNav-item" data-group="{{ group }}" data-tab="{{ tab.id }}">{{ tab.title }}</a>{% endfor %}
</div>
</nav>
83 changes: 83 additions & 0 deletions javascripts/display-tab-content.js
Original file line number Diff line number Diff line change
@@ -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 = ''
})
})
*/
}
2 changes: 2 additions & 0 deletions javascripts/index.js
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -23,6 +24,7 @@ import airgapLinks from './airgap-links'

document.addEventListener('DOMContentLoaded', async () => {
displayPlatformSpecificContent()
displayTabContent()
explorer()
scrollUp()
search()
Expand Down
117 changes: 117 additions & 0 deletions lib/liquid-tags/tabs.js
Original file line number Diff line number Diff line change
@@ -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 ? '</div>' : '' }<div class="tab tab-${tabId}">\n`
this.tabs.push({ title: tabTitle, id: tabId })
return this.nodelist.push(block)
}

/*
const template = '<div class="extended-markdown {{ tagName }} {{ classes }}">{{ output }}</div>'

class ExtendedMarkdown extends Liquid.Block {
async render (context) {
const chunks = await super.render(context)
const output = Liquid.Helpers.toFlatString(chunks)
return this.template.engine.parseAndRender(template, {
tagName: this.tagName,
classes: tags[this.tagName],
output
})
}
}
*/

/*
this.templatePath = path.join(__dirname, `../../includes/tabs.html`)
this.template = null

async getTemplate () {
if (!this.template) {
this.template = await fs.promises.readFile(this.templatePath, 'utf8')
this.template = this.template.replace(/\r/g, '')
}

return this.template
}
*/

endTag () {
this.nodelist.push('</div>')
return super.endTag()
}

async render (context) {

// TODO: push a dummy as first node, then update it here? (but from which callback?)
// TODO: 4+ leading spaces in tabs.html = markdown code block, how to avoid? Use '{% include tabs %}' as inline template?
//const src = await this.template.engine.fileSystem.readTemplateFile(this.name())
const src = `{% include tabs %}`
this.tabs.forEach(async tab => {
// Could use textOnly mode to strip out markup, but slugger does that and more, see pushBlock()
//await renderContent(tab.title, null, { textOnly: true })
const htmlTitle = await renderContent(tab.title, null)
assert(htmlTitle.startsWith('<p>') && htmlTitle.endsWith('</p>'), 'expected wrapping <p></p>')
tab.title = htmlTitle.slice(3, -4) // HACK: remove surrounding <p></p>
})
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
}
}
1 change: 1 addition & 0 deletions lib/render-content/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 %}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down