diff --git a/content/README.md b/content/README.md
index 515b0a19b1cd..c0afd17097de 100644
--- a/content/README.md
+++ b/content/README.md
@@ -21,6 +21,7 @@ See the [contributing docs](/CONTRIBUTING.md) for general information about work
- [`miniTocMaxHeadingLevel`](#minitocmaxheadinglevel)
- [`allowTitleToDifferFromFilename`](#allowtitletodifferfromfilename)
- [`defaultPlatform`](#defaultplatform)
+ - [`defaultTool`](#defaulttool)
- [`learningTracks`](#learningTracks)
- [`includeGuides`](#includeGuides)
- [`type`](#type)
@@ -202,6 +203,16 @@ Example:
defaultPlatform: linux
```
+### `defaultTool`
+
+- Purpose: Override the initial tool selection for a page, where tool refers to the application the reader is using to work with GitHub, such as GitHub.com's web UI, the GitHub CLI, or GitHub Desktop. If this frontmatter is omitted, then the tool-specific content matching the GitHub web UI is shown by default. This behavior can be changed for individual pages, for which a manual selection is more reasonable.
+- Type: `String`, one of: `webui`, `cli`, `desktop`.
+- Optional.
+
+```yaml
+defaultTool: cli
+```
+
### `learningTracks`
- Purpose: Render a list of learning tracks on a product's sub-landing page.
- type: `String`. This should reference learning tracks' names defined in [`data/learning-tracks/*.yml`](../data/learning-tracks/README.md).
diff --git a/contributing/content-markup-reference.md b/contributing/content-markup-reference.md
index 77e5c74ed346..51eec2b2e173 100644
--- a/contributing/content-markup-reference.md
+++ b/contributing/content-markup-reference.md
@@ -100,6 +100,40 @@ These instructions are pertinent to Windows users.
You can define a default platform in the frontmatter. For more information, see the [content README](../content/README.md#defaultplatform).
+## Tool tags
+
+We occasionally need to write documentation for different tools (GitHub UI, GitHub CLI, GitHub Desktop). Each tool may require a different set of instructions. We use tool tags to demarcate information for each tool.
+
+### Usage
+
+```
+{% webui %}
+
+These instructions are pertinent to GitHub UI users.
+
+{% endwebui %}
+```
+
+```
+{% cli %}
+
+These instructions are pertinent to GitHub CLI users.
+
+{% endcli %}
+```
+
+```
+{% desktop %}
+
+ These instructions are pertinent to GitHub Desktop.
+
+{% enddesktop %}
+```
+
+Unlike [operating system tags](#operating-system-tags), which will automatically add tabs to select the operating system at the top of the article, you must add `{% include tool-switcher %}` wherever you want to display tabs to select the tool. This allows you to display the tabs at the top of the article or immediately before a relevant section.
+
+You can define a default tool in the frontmatter. For more information, see the [content README](../content/README.md#defaulttool).
+
## Reusable and variable strings of text
Reusable strings (commonly called content references or conrefs) contain content that’s used in more than one place in our documentation and allow us to change the content in a single location rather than every place the string appears.
diff --git a/includes/tool-switcher.html b/includes/tool-switcher.html
new file mode 100644
index 000000000000..58658c19111f
--- /dev/null
+++ b/includes/tool-switcher.html
@@ -0,0 +1,8 @@
+
diff --git a/javascripts/display-tool-specific-content.js b/javascripts/display-tool-specific-content.js
new file mode 100644
index 000000000000..60945c105c3f
--- /dev/null
+++ b/javascripts/display-tool-specific-content.js
@@ -0,0 +1,82 @@
+const supportedTools = ['cli', 'desktop', 'webui']
+const detectedTools = new Set()
+
+export default function displayToolSpecificContent () {
+ let tool = getDefaultTool()
+
+ if (!tool) tool = 'webui'
+
+ const toolsInContent = findToolSpecificContent(tool)
+
+ hideSwitcherLinks(toolsInContent)
+
+ showContentForTool(tool)
+
+ // configure links for switching tool content
+ switcherLinks().forEach(link => {
+ link.addEventListener('click', (event) => {
+ event.preventDefault()
+ showContentForTool(event.target.dataset.tool)
+ findToolSpecificContent(event.target.dataset.tool)
+ })
+ })
+}
+
+function showContentForTool (tool) {
+ // (de)activate switcher link appearances
+ switcherLinks().forEach(link => {
+ (link.dataset.tool === tool)
+ ? link.classList.add('selected')
+ : link.classList.remove('selected')
+ })
+}
+
+function findToolSpecificContent (tool) {
+ // find all tool-specific *block* elements and hide or show as appropriate
+ // example: {{ #cli }} block content {{/cli}}
+ Array.from(document.querySelectorAll('.extended-markdown'))
+ .filter(el => supportedTools.some(tool => el.classList.contains(tool)))
+ .forEach(el => {
+ detectTools(el)
+ el.style.display = el.classList.contains(tool)
+ ? ''
+ : 'none'
+ })
+
+ // find all tool-specific *inline* elements and hide or show as appropriate
+ // example: inline content
+ Array.from(document.querySelectorAll('.tool-cli, .tool-desktop, .tool-webui'))
+ .forEach(el => {
+ detectTools(el)
+ el.style.display = el.classList.contains('tool-' + tool)
+ ? ''
+ : 'none'
+ })
+
+ return Array.from(detectedTools)
+}
+
+// hide links for any tool-specific sections that are not present
+function hideSwitcherLinks (toolsInContent) {
+ Array.from(document.querySelectorAll('a.tool-switcher'))
+ .forEach(link => {
+ if (toolsInContent.includes(link.dataset.tool)) return
+ link.style.display = 'none'
+ })
+}
+
+function detectTools (el) {
+ el.classList.forEach(elClass => {
+ const value = elClass.replace(/tool-/, '')
+ if (supportedTools.includes(value)) detectedTools.add(value)
+ })
+}
+
+function getDefaultTool () {
+ const el = document.querySelector('[data-default-tool]')
+ if (el) return el.dataset.defaultTool
+}
+
+function switcherLinks () {
+ return Array.from(document.querySelectorAll('a.tool-switcher'))
+}
diff --git a/javascripts/index.js b/javascripts/index.js
index d0ef8efce6bf..dd24d46c11c6 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 displayToolSpecificContent from './display-tool-specific-content'
import explorer from './explorer'
import scrollUp from './scroll-up'
import search from './search'
@@ -24,6 +25,7 @@ import toggleImages from './toggle-images'
document.addEventListener('DOMContentLoaded', async () => {
displayPlatformSpecificContent()
+ displayToolSpecificContent()
explorer()
scrollUp()
search()
diff --git a/lib/frontmatter.js b/lib/frontmatter.js
index 70562b2e2709..4462eb362807 100644
--- a/lib/frontmatter.js
+++ b/lib/frontmatter.js
@@ -119,6 +119,11 @@ const schema = {
type: 'string',
enum: ['mac', 'windows', 'linux']
},
+ // Tool-specific content preference
+ defaultTool: {
+ type: 'string',
+ enum: ['webui', 'cli', 'desktop']
+ },
// Documentation contributed by a third party, such as a GitHub Partner
contributor: {
type: 'object',
diff --git a/lib/liquid-tags/extended-markdown.js b/lib/liquid-tags/extended-markdown.js
index 449b36429d1d..8375ca5e37c8 100644
--- a/lib/liquid-tags/extended-markdown.js
+++ b/lib/liquid-tags/extended-markdown.js
@@ -2,6 +2,9 @@ const tags = {
mac: '',
windows: '',
linux: '',
+ cli: '',
+ desktop: '',
+ webui: '',
all: '',
tip: 'border rounded-1 mb-4 p-3 color-border-info color-bg-info f5',
note: 'border rounded-1 mb-4 p-3 color-border-info color-bg-info f5',
diff --git a/tests/fixtures/default-tool.md b/tests/fixtures/default-tool.md
new file mode 100644
index 000000000000..f9167eaa0a12
--- /dev/null
+++ b/tests/fixtures/default-tool.md
@@ -0,0 +1,25 @@
+---
+title: Default tool
+versions:
+ free-pro-team: '*'
+ enterprise-server: '*'
+ github-ae: '*'
+defaultTool: cli
+---
+
+
+Intro text
+
+### Section 1
+
+generic text
+
+{% include tool-switcher %}
+{% webui %} dotcom text {% endwebui %}
+{% cli %} cli text {% endcli %}
+{% desktop %} desktop text {% enddesktop %}
+
+{% include tool-switcher %}
+{% webui %} dotcom text 2 {% endwebui %}
+{% cli %} cli text 2 {% endcli %}
+{% desktop %} desktop text 2 {% enddesktop %}
\ No newline at end of file
diff --git a/tests/unit/page.js b/tests/unit/page.js
index c9af716b5c59..7503dc44ded2 100644
--- a/tests/unit/page.js
+++ b/tests/unit/page.js
@@ -540,6 +540,18 @@ describe('Page class', () => {
expect(page.defaultPlatform).toBe('linux')
})
})
+
+ describe('tool specific content', () => {
+ test('page.defaultTool frontmatter', async () => {
+ const page = await Page.init({
+ relativePath: 'default-tool.md',
+ basePath: path.join(__dirname, '../fixtures'),
+ languageCode: 'en'
+ })
+ expect(page.defaultTool).toBeDefined()
+ expect(page.defaultTool).toBe('cli')
+ })
+ })
})
describe('catches errors thrown in Page class', () => {