3
.gitignore
vendored
3
.gitignore
vendored
@@ -7,14 +7,13 @@ npm-debug.log
|
||||
coverage/
|
||||
.linkinator
|
||||
/assets/images/early-access
|
||||
/assets/fonts/inter
|
||||
/content/early-access
|
||||
/data/early-access
|
||||
dist
|
||||
.next
|
||||
.eslintcache
|
||||
|
||||
# blc: broken link checker
|
||||
blc_output.log
|
||||
blc_output_internal.log
|
||||
/dist/
|
||||
broken_links.md
|
||||
|
||||
@@ -46,7 +46,6 @@ COPY lib ./lib
|
||||
# one part of the build relies on this content file to pull all-products
|
||||
COPY content/index.md ./content/index.md
|
||||
|
||||
COPY webpack.config.js ./webpack.config.js
|
||||
COPY next.config.js ./next.config.js
|
||||
COPY tsconfig.json ./tsconfig.json
|
||||
|
||||
@@ -73,7 +72,6 @@ USER node
|
||||
COPY --chown=node:node --from=prod_deps /usr/src/docs/node_modules /usr/src/docs/node_modules
|
||||
|
||||
# Copy our front-end code
|
||||
COPY --chown=node:node --from=builder /usr/src/docs/dist /usr/src/docs/dist
|
||||
COPY --chown=node:node --from=builder /usr/src/docs/.next /usr/src/docs/.next
|
||||
|
||||
# We should always be running in production mode
|
||||
@@ -87,7 +85,6 @@ COPY --chown=node:node assets ./assets
|
||||
COPY --chown=node:node content ./content
|
||||
COPY --chown=node:node data ./data
|
||||
COPY --chown=node:node includes ./includes
|
||||
COPY --chown=node:node layouts ./layouts
|
||||
COPY --chown=node:node lib ./lib
|
||||
COPY --chown=node:node middleware ./middleware
|
||||
COPY --chown=node:node translations ./translations
|
||||
|
||||
@@ -74,8 +74,9 @@ The recommended formats explicitly define which versions are used for all direct
|
||||
| Package manager | Languages | Recommended formats | All supported formats |
|
||||
| --- | --- | --- | ---|
|
||||
| Composer | PHP | `composer.lock` | `composer.json`, `composer.lock` |
|
||||
| `dotnet` CLI | .NET languages (C#, C++, F#, VB) | `.csproj`, `.vbproj`, `.nuspec`, `.vcxproj`, `.fsproj` | `.csproj`, `.vbproj`, `.nuspec`, `.vcxproj`, `.fsproj`, `packages.config` |
|
||||
| Maven | Java, Scala | `pom.xml` | `pom.xml` |
|
||||
| `dotnet` CLI | .NET languages (C#, C++, F#, VB) | `.csproj`, `.vbproj`, `.nuspec`, `.vcxproj`, `.fsproj` | `.csproj`, `.vbproj`, `.nuspec`, `.vcxproj`, `.fsproj`, `packages.config` |{% ifversion fpt or ghes > 3.1 %}
|
||||
| Go modules | Go | `go.mod` | `go.mod` |
|
||||
{% endif %}| Maven | Java, Scala | `pom.xml` | `pom.xml` |
|
||||
| npm | JavaScript | `package-lock.json` | `package-lock.json`, `package.json`|
|
||||
| Python PIP | Python | `requirements.txt`, `pipfile.lock` | `requirements.txt`, `pipfile`, `pipfile.lock`, `setup.py`* |
|
||||
| RubyGems | Ruby | `Gemfile.lock` | `Gemfile.lock`, `Gemfile`, `*.gemspec` |
|
||||
|
||||
@@ -1 +1 @@
|
||||
| Go {% ifversion fpt %}| {% octicon "check" aria-label="The check icon" %} | {% octicon "check" aria-label="The check icon" %} | {% octicon "x" aria-label="The X icon" %} | {% octicon "check" aria-label="The check icon" %}<br> Go modules | {% octicon "check" aria-label="The check icon" %} | {% octicon "x" aria-label="The X icon" %} |{% elsif ghes > 2.21 %}| {% octicon "check" aria-label="The check icon" %} | {% octicon "x" aria-label="The X icon" %} | {% octicon "check" aria-label="The check icon" %} | {% octicon "x" aria-label="The X icon" %} |{% elsif ghae %}| {% octicon "check" aria-label="The check icon" %} | {% octicon "check" aria-label="The check icon" %} | {% octicon "x" aria-label="The X icon" %} |{% endif %}
|
||||
| Go {% ifversion fpt %}| {% octicon "check" aria-label="The check icon" %} | {% octicon "check" aria-label="The check icon" %} | {% octicon "check" aria-label="The check icon" %}<br>Go modules | {% octicon "check" aria-label="The check icon" %}<br> Go modules | {% octicon "check" aria-label="The check icon" %} | {% octicon "x" aria-label="The X icon" %} |{% elsif ghes > 3.1 %}| {% octicon "check" aria-label="The check icon" %} | {% octicon "check" aria-label="The check icon" %}<br>Go modules | {% octicon "check" aria-label="The check icon" %} | {% octicon "x" aria-label="The X icon" %} |{% elsif ghes > 2.21 %}| {% octicon "check" aria-label="The check icon" %} | {% octicon "x" aria-label="The X icon" %} | {% octicon "check" aria-label="The check icon" %} | {% octicon "x" aria-label="The X icon" %} |{% elsif ghae %}| {% octicon "check" aria-label="The check icon" %} | {% octicon "check" aria-label="The check icon" %} | {% octicon "x" aria-label="The X icon" %} |{% endif %}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<a class="f6 no-underline color-text-tertiary pt-1" href="/{{currentLanguage}}/{{enterpriseServerVersions[0]}}/admin/all-releases">See all Enterprise releases</a>
|
||||
@@ -1,8 +0,0 @@
|
||||
{% if currentVersion != 'homepage' %}
|
||||
<li title="Home">
|
||||
<a href="/{{currentLanguage}}" class="f6 pl-4 pr-5 ml-n1 pb-1 color-text-primary">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="octicon" viewBox="0 0 16 16" width="16" height="16"> <path fill-rule="evenodd" clip-rule="evenodd" d="M7.78033 12.5303C7.48744 12.8232 7.01256 12.8232 6.71967 12.5303L2.46967 8.28033C2.17678 7.98744 2.17678 7.51256 2.46967 7.21967L6.71967 2.96967C7.01256 2.67678 7.48744 2.67678 7.78033 2.96967C8.07322 3.26256 8.07322 3.73744 7.78033 4.03033L4.81066 7L12.25 7C12.6642 7 13 7.33579 13 7.75C13 8.16421 12.6642 8.5 12.25 8.5L4.81066 8.5L7.78033 11.4697C8.07322 11.7626 8.07322 12.2374 7.78033 12.5303Z"></path></svg>
|
||||
All products
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
@@ -1,16 +0,0 @@
|
||||
{% unless enterpriseServerReleases.isOldestReleaseDeprecated and currentVersion contains enterpriseServerReleases.oldestSupported %}
|
||||
<div class="f5 contribution">
|
||||
<h2 class="f4">{% data ui.contribution_cta.title %}</h2>
|
||||
<p class="color-text-secondary f6">{% data ui.contribution_cta.body %}</p>
|
||||
{% if page.relativePath %}
|
||||
{% assign contribution_href = "https://github.com/github/docs/edit/main/content/" | append: page.relativePath %}
|
||||
{% else %}
|
||||
{% assign contribution_href = "https://github.com/github/docs" %}
|
||||
{% endif %}
|
||||
<a class="btn btn-outline" href="{{ contribution_href }}">
|
||||
{% octicon "git-pull-request" height="16" %}
|
||||
{% data ui.contribution_cta.button %}
|
||||
</a>
|
||||
<p class="color-text-secondary f6 mt-2">{% data ui.contribution_cta.or %} <a href="https://github.com/github/docs/blob/main/CONTRIBUTING.md" target="_blank">{% data ui.contribution_cta.to_guidelines %}</a></p>
|
||||
</div>
|
||||
{% endunless %}
|
||||
@@ -1,12 +0,0 @@
|
||||
{% if enterpriseServerReleases.deprecated contains currentVersion %}
|
||||
{% assign deprecatedDate = enterpriseServerReleases.dates[currentVersion].deprecationDate %}
|
||||
<div class="deprecation-banner border rounded-1 mb-2 color-bg-warning p-3 color-border-warning f5">
|
||||
<p>
|
||||
<b>
|
||||
{% data reusables.enterprise_deprecation.version_was_deprecated %}
|
||||
<span data-date="{{ deprecatedDate }}" data-format="%B %d, %Y" title="{{ deprecatedDate }}">{{ deprecatedDate }}</span>.
|
||||
</b>
|
||||
The documentation page you are looking for may have moved. Try going to the <a href="/{{ currentLanguage }}/enterprise/{{ currentVersion}}">GitHub Enterprise Server {{ currentVersion }} documentation homepage</a> or searching by keyword.
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -1,33 +0,0 @@
|
||||
<head>
|
||||
{% comment %} For human readers {% endcomment %}
|
||||
<meta charset="utf-8" />
|
||||
<title>{% if error == '404' %}{% data ui.errors.oops %}{% elsif page.documentType == 'homepage' and currentVersion == 'free-pro-team@latest' %}GitHub Documentation{% elsif page.fullTitle %}{{ page.fullTitle }}{% else %}GitHub Documentation{% endif %}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="alternate icon" type="image/png" href="/assets/images/site/favicon.png">
|
||||
<link rel="icon" type="image/svg+xml" href="/assets/images/site/favicon.svg">
|
||||
<link rel="stylesheet" href="{{ builtAssets.main.css }}">
|
||||
|
||||
{% comment %} For Google and Bots {% endcomment %}
|
||||
{% if page.intro %}
|
||||
<meta name="description" content="{{ page.introPlainText }}">
|
||||
{% endif %}
|
||||
{% if page.topics %}
|
||||
<meta name="keywords" content="{{ page.topics | join: ',' }}">
|
||||
{% endif %}
|
||||
{% if page.hidden %}
|
||||
<meta name="robots" content="noindex" />
|
||||
{% endif %}
|
||||
{% for languageVariant in page.languageVariants %}
|
||||
<link
|
||||
rel="alternate"
|
||||
hreflang="{{ languageVariant.hreflang }}"
|
||||
href="https://docs.github.com{{ languageVariant.href }}"
|
||||
/>
|
||||
{% endfor %}
|
||||
<meta name="google-site-verification" content="OgdQc0GZfjDI52wDv1bkMT-SLpBUo_h5nn9mI9L22xQ" />
|
||||
<meta name="google-site-verification" content="c1kuD-K2HIVF635lypcsWPoD4kilo5-jA_wBFyT4uMY" />
|
||||
|
||||
{% comment %} For our JS {% endcomment %}
|
||||
<meta name="csrf-token" content="$CSRFTOKEN$">
|
||||
<meta name="site.data.ui.search.placeholder" content="{% data ui.search.placeholder %}">
|
||||
</head>
|
||||
@@ -1,69 +0,0 @@
|
||||
<!-- START TRANSLATIONS NOTICES -->
|
||||
{% if currentLanguage != 'en' %}
|
||||
{% assign translation_notification_type = "true" %}
|
||||
|
||||
<!-- Site policy translations notice -->
|
||||
{% if page.relativePath contains '/site-policy/' %}
|
||||
{% assign translation_notification = site.data.reusables.policies.translation %}
|
||||
|
||||
<!-- Completed translations notice -->
|
||||
{% elsif languages[currentLanguage].wip != true %}
|
||||
{% assign translation_notification = site.data.ui.header.notices.localization_complete %}
|
||||
|
||||
<!-- In-progress translations notice -->
|
||||
{% elsif languages[currentLanguage].wip %}
|
||||
{% assign translation_notification = site.data.ui.header.notices.localization_in_progress %}
|
||||
{% endif %}
|
||||
|
||||
{% else %}
|
||||
<!-- User language redirect notice -->
|
||||
{% if languages[userLanguage].wip == false and userLanguage != 'en' %}
|
||||
{% assign user_language_redirect_notification_type = true %}
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
<!-- END TRANSLATIONS NOTICES -->
|
||||
|
||||
<!-- START RELEASE NOTICES -->
|
||||
<!-- Custom GitHub AE notice -->
|
||||
{% if currentVersion == "github-ae@latest" %}
|
||||
{% assign release_notification_type = "true" %}
|
||||
{% assign release_notification = site.data.ui.header.notices.ghae_silent_launch %}
|
||||
|
||||
<!-- Release candidate notice -->
|
||||
{% elsif currentVersion == site.data.variables.release_candidate.version %}
|
||||
{% assign release_notification_type = "true" %}
|
||||
{% assign release_notification = allVersions[currentVersion].versionTitle | append: site.data.ui.header.notices.release_candidate %}
|
||||
{% endif %}
|
||||
<!-- END RELEASE NOTICES -->
|
||||
|
||||
<!-- ONEOFF EARLY ACCESS NOTICE -->
|
||||
{% if page.hidden and page.relativePath contains "early-access/" %}
|
||||
{% assign early_access_notification_type = true %}
|
||||
{% assign early_access_notification = site.data.ui.header.notices.early_access %}
|
||||
{% endif %}
|
||||
<!-- END ONEOFF EARLY ACCESS NOTICE -->
|
||||
|
||||
{% if translation_notification_type %}
|
||||
<div class="header-notifications text-center f5 color-bg-info color-text-primary py-4 px-6 translation_notice{% if release_notification_type %} border-bottom color-border-tertiary{% endif %}">
|
||||
{{ translation_notification }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if user_language_redirect_notification_type %}
|
||||
<div class="header-notifications text-center f5 color-bg-info color-text-primary py-4 px-6 translation_notice">
|
||||
This article is also available in your language of choice. Click <a href="/{{userLanguage}}{{currentPathWithoutLanguage}}">here</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if release_notification_type %}
|
||||
<div class="header-notifications text-center f5 color-bg-info color-text-primary py-4 px-6 release_notice{% if early_access_notification_type %} border-bottom color-border-tertiary{% endif %}">
|
||||
{{ release_notification }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if early_access_notification_type %}
|
||||
<div class="header-notifications text-center f5 color-bg-danger color-text-primary py-4 px-6 early_access">
|
||||
{{ early_access_notification }}
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -1,26 +0,0 @@
|
||||
<!-- Versions picker that only appears in the header on homepage or product landing -->
|
||||
{% if page.relativePath == 'index.md' or page.layout == 'product-landing' or page.layout == 'product-sublanding' or page.layout == 'release-notes' %}
|
||||
{% if page.permalinks and page.permalinks.length > 1 %}
|
||||
<div class="d-md-inline-block">
|
||||
<div class="border-top border-md-top-0 py-2 py-md-0 d-md-inline-block">
|
||||
<details class="dropdown-withArrow position-relative details details-reset mr-md-3 close-when-clicked-outside">
|
||||
<summary class="py-2 color-text-primary" role="button" aria-label="Toggle versions list">
|
||||
<div class="d-flex flex-items-center flex-justify-between">
|
||||
{{ allVersions[currentVersion].versionTitle }}
|
||||
<svg class="arrow ml-md-1" width="14px" height="8px" viewBox="0 0 14 8" xml:space="preserve" fill="none" stroke="currentColor"><path d="M1,1l6.2,6L13,1"></path></svg>
|
||||
</div>
|
||||
</summary>
|
||||
<div id="versions-selector" class="position-md-absolute nav-desktop-langDropdown p-md-4 right-md-n4 top-md-6" style="z-index: 6;">
|
||||
{% for permalink in page.permalinks %}
|
||||
<a
|
||||
href="{{ permalink.href }}"
|
||||
class="d-block py-2 no-underline {% if currentPath == permalink.href %}active{% else %}Link--primary{% endif %}"
|
||||
style="white-space: nowrap"
|
||||
>{{ allVersions[permalink.pageVersion].versionTitle }}</a>
|
||||
{% endfor %}
|
||||
{% include all-enterprise-releases-link %}
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@@ -1,104 +0,0 @@
|
||||
<div class="border-bottom color-border-secondary no-print">
|
||||
{% unless error == '404' %}
|
||||
{% include header-notification %}
|
||||
{% endunless %}
|
||||
|
||||
<header class="container-xl px-3 px-md-6 pt-3 pb-2 position-relative d-flex flex-justify-between width-full {% if error == '404' %} d-md-none {% endif %}">
|
||||
|
||||
<div class="d-flex flex-items-center d-lg-none" style="z-index: 3;" id="github-logo-mobile" role="banner">
|
||||
<a href="/{{ currentLanguage }}" aria-hidden="true" tabindex="-1">
|
||||
{% octicon "mark-github" height="32" class="color-icon-primary" %}
|
||||
</a>
|
||||
<a href="/{{ currentLanguage }}" class="h4-mktg color-text-primary no-underline no-wrap pl-2">{% data ui.header.github_docs %}</a>
|
||||
</div>
|
||||
|
||||
<div class="width-full">
|
||||
<div class="d-inline-block width-full d-md-flex" style="z-index: 1;">
|
||||
<button class="nav-mobile-burgerIcon float-right mt-1 border-0 d-md-none" type="button" aria-label="Toggle navigation">
|
||||
<!-- Hamburger icon added with css -->
|
||||
</button>
|
||||
<div style="z-index: 2;" class="nav-mobile-dropdown width-full">
|
||||
<div class="d-md-flex flex-justify-between flex-items-center">
|
||||
<div class="py-2 py-md-0 d-md-inline-block">
|
||||
<h4 class="text-mono f5 text-normal color-text-secondary d-md-none">{% data ui.homepage.explore_by_product %}</h4>
|
||||
<details class="dropdown-withArrow position-relative details details-reset d-md-none close-when-clicked-outside">
|
||||
<summary class="nav-desktop-productDropdownButton color-text-link py-2" role="button" aria-label="Toggle products list">
|
||||
<div id="current-product" class="d-flex flex-items-center flex-justify-between" style="padding-top: 2px;">
|
||||
<!-- Product switcher - GitHub.com, Enterprise Server, etc -->
|
||||
<!-- 404 and 500 error layouts are not real pages so we need to hardcode the name for those -->
|
||||
{{ productMap[currentProduct].name }}
|
||||
<svg class="arrow ml-md-1" width="14px" height="8px" viewBox="0 0 14 8" xml:space="preserve" fill="none" stroke="currentColor"><path d="M1,1l6.2,6L13,1"></path></svg>
|
||||
</div>
|
||||
</summary>
|
||||
<!-- Mobile-only product dropdown -->
|
||||
<div id="homepages" class="position-md-absolute nav-desktop-productDropdown p-md-4 left-md-n4 top-md-6" style="z-index: 6;">
|
||||
{% for product in activeProducts %}
|
||||
<a href="{% unless product.external %}/{{ currentLanguage }}{% endunless %}{{ product.href }}"
|
||||
{% if product.external %}target="_blank"{% endif %} class="d-block py-2
|
||||
{% if product.id == currentProduct %}color-text-link text-underline active{% elsif product.id == currentProduct.id %}color-text-link text-underline active{% else %}Link--primary no-underline{% endif %}">
|
||||
{{ product.name }}
|
||||
{% if product.external %}
|
||||
<span class="ml-1"><svg width="9" height="10" viewBox="0 0 9 10" fill="none" xmlns="http://www.w3.org/2000/svg"><path stroke="currentColor" d="M.646 8.789l8-8M8.5 9V1M1 .643h8"/></svg></span>
|
||||
{% endif %}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<!-- Versions picker that only appears in the header on landing pages -->
|
||||
{% include header-version-switcher %}
|
||||
|
||||
<div class="d-md-inline-block">
|
||||
|
||||
<!-- Language picker - 'English', 'Japanese', etc -->
|
||||
{% if page.languageVariants.length > 0 and error != '404' and !page.hidden %}
|
||||
<div class="border-top border-md-top-0 py-2 py-md-0 d-md-inline-block">
|
||||
<details class="dropdown-withArrow position-relative details details-reset mr-md-3 close-when-clicked-outside">
|
||||
<summary class="py-2 color-text-primary" role="button" aria-label="Toggle languages list">
|
||||
<div class="d-flex flex-items-center flex-justify-between">
|
||||
{% if languages[page.languageCode].nativeName %}
|
||||
{{ languages[page.languageCode].nativeName }} ({{ languages[page.languageCode].name }})
|
||||
{% else %}
|
||||
{{ languages[page.languageCode].name }}
|
||||
{% endif %}
|
||||
<svg class="arrow ml-md-1" width="14px" height="8px" viewBox="0 0 14 8" xml:space="preserve" fill="none" stroke="currentColor"><path d="M1,1l6.2,6L13,1"></path></svg>
|
||||
</div>
|
||||
</summary>
|
||||
<div id="languages-selector" class="position-md-absolute nav-desktop-langDropdown p-md-4 right-md-n4 top-md-6" style="z-index: 6;">
|
||||
{% for languageVariant in page.languageVariants %}
|
||||
{% unless languages[languageVariant.code].wip %}
|
||||
<a
|
||||
href="{{ languageVariant.href }}"
|
||||
class="d-block py-2 no-underline {% if currentPath == languageVariant.href %}active {% else %}Link--primary{% endif %}"
|
||||
style="white-space: nowrap"
|
||||
>
|
||||
{% if languages[languageVariant.code].nativeName %}
|
||||
{{ languages[languageVariant.code].nativeName }} ({{ languageVariant.name }})
|
||||
{% else %}
|
||||
{{ languageVariant.name }}
|
||||
{% endif %}
|
||||
</a>
|
||||
{% endunless %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- GitHub.com homepage and 404 page has a stylized search; Enterprise homepages do not -->
|
||||
{% if page.relativePath != 'index.md' and error != '404' %}
|
||||
<div class="pt-3 pt-md-0 d-md-inline-block ml-md-3 border-top border-md-top-0">
|
||||
{% include search-form %}
|
||||
<div id="search-results-container"></div>
|
||||
<div class="search-overlay-desktop"></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
@@ -1,4 +0,0 @@
|
||||
<a class="link-with-intro Bump-link--hover no-underline d-block offset-lg-2 col-lg-8 mb-5" href="{{ fullPath }}">
|
||||
<h4 class="link-with-intro-title h4-mktg">{{ title }}<span class="Bump-link-symbol">→</span></h4>
|
||||
<p class="link-with-intro-intro f5">{{ intro }}</p>
|
||||
</a>
|
||||
@@ -11,4 +11,4 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1 +1 @@
|
||||
- <a class="article-link link Bump-link--hover no-underline" href="{{ fullPath }}">{{ title }}</a>
|
||||
- <a class="article-link link Bump-link--hover no-underline" href="{{ fullPath }}">{{ title }}</a>
|
||||
@@ -1,4 +1,4 @@
|
||||
<a class="link-with-intro Bump-link--hover no-underline" href="{{ fullPath }}">
|
||||
<h2 class="link-with-intro-title f4">{{ title }}<span class="Bump-link-symbol">→</span></h2>
|
||||
</a>
|
||||
{% if intro %}<p class="link-with-intro-intro">{{ intro }}</p>{% endif %}
|
||||
{% if intro %}<p class="link-with-intro-intro">{{ intro }}</p>{% endif %}
|
||||
@@ -1 +1 @@
|
||||
<a class="link-title Bump-link--hover no-underline" href="{{ fullPath }}">{{ title }}</a>
|
||||
<a class="link-title Bump-link--hover no-underline" href="{{ fullPath }}">{{ title }}</a>
|
||||
@@ -1 +1 @@
|
||||
- <a class="topic-link link Bump-link--hover no-underline" href="{{ fullPath }}">{{ title }}</a>
|
||||
- <a class="topic-link link Bump-link--hover no-underline" href="{{ fullPath }}">{{ title }}</a>
|
||||
@@ -1,2 +0,0 @@
|
||||
<script id="expose" type="application/json">{{ expose }}</script>
|
||||
<script src="{{ builtAssets.main.js }}"></script>
|
||||
@@ -1,3 +0,0 @@
|
||||
<button class="arrow-for-scrolling-top tooltipped tooltipped-n tooltipped-no-delay" aria-label="{% data ui.scroll_button.scroll_to_top %}" id="js-scroll-top">
|
||||
{% octicon "chevron-up" %}
|
||||
</button>
|
||||
@@ -1,10 +0,0 @@
|
||||
<!--
|
||||
This form is used in two places:
|
||||
|
||||
- On the homepage, front and center
|
||||
- On all other pages, in the header
|
||||
-->
|
||||
|
||||
<div id="search-input-container" aria-hidden="true">
|
||||
<!-- will add a search input here -->
|
||||
</div>
|
||||
@@ -1,19 +0,0 @@
|
||||
{% if currentVersion != 'free-pro-team@latest' %}
|
||||
{% include all-products-link %}
|
||||
{% endif %}
|
||||
|
||||
{% for product in activeProducts %}
|
||||
{% if currentVersion == 'free-pro-team@latest' or product.external or product.versions contains currentVersion %}
|
||||
<li
|
||||
title="{{product.name}}{% if product.external %} (External Site){% endif %}"
|
||||
class="sidebar-product"
|
||||
>
|
||||
<a href="{% unless product.external %}/{{currentLanguage}}{% endunless %}{% if product.versions contains currentVersion and currentVersion != 'free-pro-team@latest' %}/{{currentVersion}}/{{product.id}}{% else %}{{product.href}}{% endif %}" {% if product.external %}target="_blank"{% endif %} class="f4 pl-4 pr-5 py-2 color-text-primary">
|
||||
{{ product.name }}
|
||||
{% if product.external %}
|
||||
<span class="ml-1"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="octicon" width="16" height="16"> <path fill-rule="evenodd" clip-rule="evenodd" d="M14.75 1C14.8163 1 14.8799 1.02634 14.9268 1.07322C14.9737 1.12011 15 1.1837 15 1.25V5.396C15.0001 5.44551 14.9855 5.49393 14.958 5.53513C14.9305 5.57632 14.8915 5.60843 14.8457 5.62739C14.8 5.64635 14.7497 5.6513 14.7011 5.64162C14.6525 5.63194 14.608 5.60805 14.573 5.573L13.03 4.03L8.53 8.53C8.38783 8.66248 8.19978 8.7346 8.00548 8.73118C7.81118 8.72775 7.62579 8.64903 7.48838 8.51162C7.35097 8.37421 7.27225 8.18882 7.26883 7.99452C7.2654 7.80022 7.33752 7.61217 7.47 7.47L11.97 2.97L10.427 1.427C10.3919 1.39204 10.3681 1.34745 10.3584 1.2989C10.3487 1.25034 10.3536 1.20001 10.3726 1.15427C10.3916 1.10853 10.4237 1.06945 10.4649 1.04199C10.5061 1.01453 10.5545 0.999912 10.604 1H14.75ZM2.75 2C2.28587 2 1.84075 2.18437 1.51256 2.51256C1.18437 2.84075 1 3.28587 1 3.75V13.25C1 14.216 1.784 15 2.75 15H12.25C12.7141 15 13.1592 14.8156 13.4874 14.4874C13.8156 14.1592 14 13.7141 14 13.25V9.75C14 9.55109 13.921 9.36032 13.7803 9.21967C13.6397 9.07902 13.4489 9 13.25 9C13.0511 9 12.8603 9.07902 12.7197 9.21967C12.579 9.36032 12.5 9.55109 12.5 9.75V13.25C12.5 13.3163 12.4737 13.3799 12.4268 13.4268C12.3799 13.4737 12.3163 13.5 12.25 13.5H2.75C2.6837 13.5 2.62011 13.4737 2.57322 13.4268C2.52634 13.3799 2.5 13.3163 2.5 13.25V3.75C2.5 3.6837 2.52634 3.62011 2.57322 3.57322C2.62011 3.52634 2.6837 3.5 2.75 3.5H6.25C6.44891 3.5 6.63968 3.42098 6.78033 3.28033C6.92098 3.13968 7 2.94891 7 2.75C7 2.55109 6.92098 2.36032 6.78033 2.21967C6.63968 2.07902 6.44891 2 6.25 2H2.75Z"></path></svg></span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
@@ -1,76 +0,0 @@
|
||||
<!--
|
||||
Styling note:
|
||||
|
||||
Categories, Maptopics, and Articles list items get a class of `active` when they correspond to content
|
||||
hierarchy of the current page. If an item's URL is also the same as the current URL, the item
|
||||
also gets an `is-current-page` class.
|
||||
-->
|
||||
|
||||
{% include all-products-link %}
|
||||
|
||||
{% unless currentProductTree.page.hidden %}
|
||||
|
||||
{% if currentProductTree.renderedShortTitle %}{% assign productTitle = currentProductTree.renderedShortTitle %}{% else %}{% assign productTitle = currentProductTree.renderedFullTitle %}{% endif %}
|
||||
|
||||
<li title="" class="sidebar-product mb-2">
|
||||
<a href="{{currentProductTree.href}}" class="pl-4 pr-5 pb-1 f4 color-text-primary">{{ productTitle }}</a>
|
||||
</li>
|
||||
<li>
|
||||
<ul class="sidebar-categories list-style-none">
|
||||
{% for childPage in currentProductTree.childPages %}
|
||||
{% unless childPage.page.hidden %}
|
||||
{% if childPage.page.documentType == "article" %}{% assign standaloneCategory = true %}{% else %}{% assign standaloneCategory = false %}{% endif %}
|
||||
<li class="sidebar-category py-1 {% if currentPath contains childPage.href %}active {% if currentPath == childPage.href %}is-current-page {% endif %}{% endif %}{% if standaloneCategory %}standalone-category{% endif %}">
|
||||
{% if childPage.renderedShortTitle %}{% assign childTitle = childPage.renderedShortTitle %}{% else %}{% assign childTitle = childPage.renderedFullTitle %}{% endif %}
|
||||
{% if standaloneCategory %}
|
||||
<a href="{{childPage.href}}" class="pl-4 pr-2 py-2 f6 text-uppercase d-block flex-auto mr-3 color-text-primary">{{ childTitle }}</a>
|
||||
{% else %}
|
||||
<details class="dropdown-withArrow details details-reset" {% if currentPath contains childPage.href or forloop.index < 4 %}open{% endif %}>
|
||||
<summary>
|
||||
<div class="d-flex flex-justify-between">
|
||||
<a href="{{childPage.href}}" class="pl-4 pr-2 py-2 f6 text-uppercase d-block flex-auto mr-3 color-text-primary">{{ childTitle }}</a>
|
||||
{% if forloop.index < 4 %}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="octicon flex-shrink-0 arrow mr-3" style="margin-top:7px" viewBox="0 0 16 16" width="16" height="16"> <path fill-rule="evenodd" clip-rule="evenodd" d="M12.7803 6.21967C13.0732 6.51256 13.0732 6.98744 12.7803 7.28033L8.53033 11.5303C8.23744 11.8232 7.76256 11.8232 7.46967 11.5303L3.21967 7.28033C2.92678 6.98744 2.92678 6.51256 3.21967 6.21967C3.51256 5.92678 3.98744 5.92678 4.28033 6.21967L8 9.93934L11.7197 6.21967C12.0126 5.92678 12.4874 5.92678 12.7803 6.21967Z"></path></svg>
|
||||
{% endif %}
|
||||
</div>
|
||||
</summary>
|
||||
{% endif %}
|
||||
<!-- some categories have maptopics with child articles -->
|
||||
{% if currentPath contains childPage.href or forloop.index < 4 %}
|
||||
{% if childPage.childPages[0].page.documentType == "mapTopic" %}
|
||||
<ul class="sidebar-topics list-style-none position-relative">
|
||||
{% for grandchildPage in childPage.childPages %}
|
||||
{% if grandchildPage.renderedShortTitle %}{% assign grandchildTitle = grandchildPage.renderedShortTitle %}{% else %}{% assign grandchildTitle = grandchildPage.renderedFullTitle %}{% endif %}
|
||||
<li class="sidebar-maptopic {% if currentPath contains grandchildPage.href %}active {% if currentPath == grandchildPage.href %}is-current-page{% endif %}{% endif %}">
|
||||
<a href="{{grandchildPage.href}}" class="pl-4 pr-5 py-2 color-text-primary">{{ grandchildTitle }} </a>
|
||||
<ul class="sidebar-articles my-2">
|
||||
{% for greatgrandchildPage in grandchildPage.childPages %}
|
||||
{% if greatgrandchildPage.renderedShortTitle %}{% assign greatgrandchildTitle = greatgrandchildPage.renderedShortTitle %}{% else %}{% assign greatgrandchildTitle = greatgrandchildPage.renderedFullTitle %}{% endif %}
|
||||
<li class="sidebar-article {% if currentPath contains greatgrandchildPage.href %}active {% if currentPath == greatgrandchildPage.href %}is-current-page{% endif %}{% endif %}">
|
||||
<a href="{{greatgrandchildPage.href}}" class="pl-6 pl-4 pr-5 py-1{% if forloop.last %} pb-2{% endif %} color-text-primary">{{ greatgrandchildTitle }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<!-- some categories have no maptopics, only articles -->
|
||||
{% elsif childPage.childPages[0].page.documentType == "article" %}
|
||||
<ul class="sidebar-articles list-style-none">
|
||||
{% for grandchildPage in childPage.childPages %}
|
||||
{% if grandchildPage.renderedShortTitle %}{% assign grandchildTitle = grandchildPage.renderedShortTitle %}{% else %}{% assign grandchildTitle = grandchildPage.renderedFullTitle %}{% endif %}
|
||||
<li class="sidebar-article {% if currentPath contains grandchildPage.href %}active {% if currentPath == grandchildPage.href %}is-current-page{% endif %}{% endif %}">
|
||||
<a href="{{grandchildPage.href}}" class="pl-6 pl-4 pr-5 py-1{% if forloop.last %} pb-2{% endif %} color-text-primary">{{ grandchildTitle }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</details>
|
||||
</li>
|
||||
{% endunless %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
{% endunless %}
|
||||
@@ -1,22 +0,0 @@
|
||||
<!-- product > category > maptopic > article -->
|
||||
<div class="sidebar d-none d-lg-block color-bg-tertiary">
|
||||
|
||||
<div class="d-flex flex-items-center p-4 position-sticky top-0 color-bg-tertiary" style="z-index: 3;" id="github-logo" role="banner">
|
||||
<a href="/{{ currentLanguage }}" class="color-text-primary" aria-hidden="true" tabindex="-1">
|
||||
{% octicon "mark-github" height="32" %}
|
||||
</a>
|
||||
<a href="/{{ currentLanguage }}" class="h4-mktg color-text-primary no-underline no-wrap pl-2 flex-auto">{% data ui.header.github_docs %}</a>
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
{% if error == '404' or page.relativePath == 'index.md' %}
|
||||
<ul class="sidebar-products mt-4">
|
||||
{% include sidebar-homepage %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<ul class="sidebar-products">
|
||||
{% include sidebar-product %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
@@ -1,22 +0,0 @@
|
||||
<footer class="py-6 text-small">
|
||||
<div class="container-xl d-flex px-3 px-md-6">
|
||||
<ul class="d-flex list-style-none flex-wrap flex-justify-center flex-xl-justify-start">
|
||||
<li class="d-flex mr-xl-3 color-text-secondary">
|
||||
{% octicon "mark-github" height="20" class="mr-2 mr-xl-3" %}
|
||||
<span>© {{ "now" | date: "%Y" }} GitHub, Inc.</span>
|
||||
</li>
|
||||
<li class="ml-3"><a href="/github/site-policy/github-terms-of-service">{% data ui.footer.terms %} </a></li>
|
||||
<li class="ml-3"><a href="/github/site-policy/github-privacy-statement">{% data ui.footer.privacy %} </a></li>
|
||||
<li class="ml-3"><a href="https://github.com/security">{% data ui.footer.product.links.security %}</a></li>
|
||||
<li class="ml-3"><a href="https://www.githubstatus.com/">{% data ui.footer.support.links.status %}</a></li>
|
||||
<li class="ml-3"><a href="/">{% data ui.footer.support.links.help %}</a></li>
|
||||
<li class="ml-3"><a href="https://support.github.com">{% data ui.footer.support.links.contact_github %}</a></li>
|
||||
<li class="ml-3"><a href="https://github.com/pricing">{% data ui.footer.product.links.pricing %}</a></li>
|
||||
<li class="ml-3"><a href="/developers">{% data ui.footer.platform.links.developer_api %}</a></li>
|
||||
<li class="ml-3"><a href="https://services.github.com/">{% data ui.footer.support.links.training %}</a></li>
|
||||
<li class="ml-3"><a href="https://github.com/about">{% data ui.footer.company.links.about %}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
{% include scripts %}
|
||||
@@ -1,16 +0,0 @@
|
||||
<section class="mt-lg-9 py-7 px-3 px-md-6 no-print color-bg-tertiary">
|
||||
<div class="container-xl gutter-lg-spacious d-flex flex-wrap">
|
||||
<div class="col-12 col-lg-6 col-xl-4 mb-6 mb-xl-0">
|
||||
{% include survey %}
|
||||
</div>
|
||||
<div class="col-12 col-lg-6 col-xl-4 mb-6 mb-xl-0">
|
||||
{% include contribution %}
|
||||
</div>
|
||||
<div class="col-12 col-lg-6 col-xl-4 mb-6 mb-xl-0">
|
||||
{% include support %}
|
||||
</div>
|
||||
<div class="col-12 col-lg-6 col-xl-4 mb-6 mb-xl-0">
|
||||
{% include toggle-images %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,15 +0,0 @@
|
||||
<!-- Contact support banner -->
|
||||
<div>
|
||||
<h3 class="mb-2 f4">
|
||||
{% data ui.support.still_need_help %}
|
||||
</h3>
|
||||
{% if currentVersion contains 'enterprise' %}{% assign isEnterprise = true %}{% else %}{% assign isEnterprise = false %}{% endif %}
|
||||
<a id="ask-community" href="https://github.community" class="btn btn-outline mr-4 mt-2">
|
||||
{% octicon "people" width="16" %}
|
||||
{% data ui.support.ask_community %}
|
||||
</a>
|
||||
<a id="contact-us" href="{% unless isEnterprise %}https://support.github.com/contact{% else %}https://enterprise.github.com/support{% endunless %}" class="btn btn-outline mt-2">
|
||||
{% octicon "comment-discussion" width="16" %}
|
||||
{% data ui.support.contact_support %}
|
||||
</a>
|
||||
</div>
|
||||
@@ -1,107 +0,0 @@
|
||||
{% unless enterpriseServerReleases.isOldestReleaseDeprecated and currentVersion contains enterpriseServerReleases.oldestSupported %}
|
||||
<form class="js-survey f5">
|
||||
<h2
|
||||
data-help-start
|
||||
data-help-yes
|
||||
data-help-no
|
||||
class="mb-1 f4"
|
||||
>
|
||||
{% data ui.survey.able_to_find %}
|
||||
<a
|
||||
class="f6 text-normal ml-3 color-text-link"
|
||||
href="/github/site-policy/github-privacy-statement"
|
||||
target="_blank"
|
||||
>{% data ui.survey.privacy_policy %}</a>
|
||||
</h2>
|
||||
<p
|
||||
class="radio-group"
|
||||
data-help-start
|
||||
data-help-yes
|
||||
data-help-no
|
||||
>
|
||||
<input
|
||||
hidden
|
||||
id="survey-yes"
|
||||
type="radio"
|
||||
name="survey-vote"
|
||||
value="Yes"
|
||||
aria-label="{% data ui.survey.yes %}"
|
||||
/>
|
||||
<label class="btn x-radio-label" for="survey-yes">
|
||||
{% octicon "thumbsup" height="24" %}
|
||||
</label>
|
||||
<input
|
||||
hidden
|
||||
id="survey-no"
|
||||
type="radio"
|
||||
name="survey-vote"
|
||||
value="No"
|
||||
aria-label="{% data ui.survey.no %}"
|
||||
/>
|
||||
<label class="btn x-radio-label" for="survey-no">
|
||||
{% octicon "thumbsdown" height="24" %}
|
||||
</label>
|
||||
</p>
|
||||
<p class="color-text-secondary f6" hidden data-help-yes>
|
||||
{% data ui.survey.yes_feedback %}
|
||||
</p>
|
||||
<p class="color-text-secondary f6" hidden data-help-no>
|
||||
{% data ui.survey.no_feedback %}
|
||||
</p>
|
||||
<input
|
||||
type="text"
|
||||
class="d-none"
|
||||
name="survey-token"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<p hidden data-help-yes data-help-no>
|
||||
<label
|
||||
class="d-block mb-1 f6"
|
||||
for="survey-comment"
|
||||
>
|
||||
<span hidden data-help-yes>{% data ui.survey.comment_yes_label %}</span>
|
||||
<span hidden data-help-no>{% data ui.survey.comment_no_label %}</span>
|
||||
<span class="text-normal color-text-tertiary float-right ml-1">
|
||||
{% data ui.survey.optional %}
|
||||
</span>
|
||||
</label>
|
||||
<textarea
|
||||
class="form-control input-sm width-full"
|
||||
name="survey-comment"
|
||||
id="survey-comment"
|
||||
></textarea>
|
||||
</p>
|
||||
<p hidden data-help-yes data-help-no>
|
||||
<label
|
||||
class="d-block mb-1 f6"
|
||||
for="survey-email"
|
||||
>
|
||||
{% data ui.survey.email_label %}
|
||||
<span class="text-normal color-text-tertiary float-right ml-1">
|
||||
{% data ui.survey.optional %}
|
||||
</span>
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
class="form-control input-sm width-full"
|
||||
name="survey-email"
|
||||
id="survey-email"
|
||||
placeholder="{% data ui.survey.email_placeholder %}"
|
||||
/>
|
||||
<span class="f6 color-text-secondary">{% data ui.survey.not_support %}</span>
|
||||
</p>
|
||||
<p
|
||||
class="text-right"
|
||||
hidden
|
||||
data-help-yes
|
||||
data-help-no
|
||||
>
|
||||
<button type="submit" class="btn btn-sm">
|
||||
{% data ui.survey.send %}
|
||||
</button>
|
||||
</p>
|
||||
<p class="color-text-secondary f6" hidden data-help-end>
|
||||
{% data ui.survey.feedback %}
|
||||
</p>
|
||||
</form>
|
||||
{% endunless %}
|
||||
@@ -1,4 +0,0 @@
|
||||
<button class="toggle-images tooltipped tooltipped-nw tooltipped-no-delay" id="js-toggle-images" hidden>
|
||||
<span id="js-off-icon" aria-label="{% data ui.toggle_images.off %}" hidden>{% octicon "eye-closed" %}</span>
|
||||
<span id="js-on-icon" aria-label="{% data ui.toggle_images.on %}" hidden>{% octicon "eye" %}</span>
|
||||
</button>
|
||||
@@ -6,4 +6,4 @@
|
||||
<a href="#" class="UnderlineNav-item tool-switcher" data-tool="curl">cURL</a>
|
||||
<a href="#" class="UnderlineNav-item tool-switcher" data-tool="desktop">Desktop</a>
|
||||
</div>
|
||||
</nav>
|
||||
</nav>
|
||||
@@ -1,16 +0,0 @@
|
||||
const explorerUrl =
|
||||
location.hostname === 'localhost'
|
||||
? 'http://localhost:3000'
|
||||
: 'https://graphql.github.com/explorer'
|
||||
|
||||
// Pass non-search query params to Explorer app via the iFrame
|
||||
export default function () {
|
||||
const graphiqlExplorer = document.getElementById('graphiql') as HTMLIFrameElement
|
||||
const queryString = window.location.search
|
||||
|
||||
if (!(queryString && graphiqlExplorer)) return
|
||||
|
||||
window.onload = () => {
|
||||
graphiqlExplorer?.contentWindow?.postMessage(queryString, explorerUrl)
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
const xmlns = 'http://www.w3.org/2000/svg'
|
||||
|
||||
const plainObjectConstructor = {}.constructor
|
||||
|
||||
function exists(value: any) {
|
||||
return value !== null && typeof value !== 'undefined'
|
||||
}
|
||||
|
||||
function isPlainObject(value: any) {
|
||||
return value.constructor === plainObjectConstructor
|
||||
}
|
||||
|
||||
function isString(value: any) {
|
||||
return typeof value === 'string'
|
||||
}
|
||||
|
||||
function renderChildren(el: HTMLElement | SVGElement, children: Array<any>) {
|
||||
for (const child of children) {
|
||||
if (isPlainObject(child)) {
|
||||
Object.entries(child)
|
||||
.filter(([, value]) => exists(value))
|
||||
.forEach(([key, value]) => el.setAttribute(key, value as string))
|
||||
} else if (Array.isArray(child)) {
|
||||
renderChildren(el, child)
|
||||
} else if (isString(child)) {
|
||||
el.append(document.createTextNode(child))
|
||||
} else {
|
||||
el.append(child)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default function h(tagName: string, ...children: Array<any>) {
|
||||
const el = ['svg', 'path'].includes(tagName)
|
||||
? document.createElementNS(xmlns, tagName)
|
||||
: document.createElement(tagName)
|
||||
renderChildren(el, children)
|
||||
return el
|
||||
}
|
||||
|
||||
export const tags = Object.fromEntries(
|
||||
['div', 'form', 'a', 'input', 'button', 'ol', 'li', 'mark'].map((tagName) => [
|
||||
tagName,
|
||||
(...args: Array<any>) => h(tagName, ...args),
|
||||
])
|
||||
)
|
||||
@@ -2,11 +2,7 @@
|
||||
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'
|
||||
import wrapCodeTerms from './wrap-code-terms'
|
||||
import print from './print'
|
||||
import localization from './localization'
|
||||
import experiment from './experiment'
|
||||
import copyCode from './copy-code'
|
||||
@@ -16,11 +12,7 @@ import toggleImages from './toggle-images'
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
displayPlatformSpecificContent()
|
||||
displayToolSpecificContent()
|
||||
explorer()
|
||||
scrollUp()
|
||||
search()
|
||||
wrapCodeTerms()
|
||||
print()
|
||||
localization()
|
||||
copyCode()
|
||||
initializeEvents()
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import { EventType, sendEvent } from './events'
|
||||
|
||||
export default function () {
|
||||
const printButtons = document.querySelectorAll('.js-print')
|
||||
|
||||
Array.from(printButtons).forEach((btn) => {
|
||||
// Open the print dialog when the button is clicked
|
||||
btn.addEventListener('click', () => {
|
||||
window.print()
|
||||
})
|
||||
})
|
||||
|
||||
// Track print events
|
||||
window.onbeforeprint = function () {
|
||||
sendEvent({ type: EventType.print })
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
export default function () {
|
||||
// function to scroll up to page top
|
||||
const PageTopBtn = document.getElementById('js-scroll-top')
|
||||
if (!PageTopBtn) return
|
||||
|
||||
PageTopBtn.addEventListener('click', () => {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth',
|
||||
})
|
||||
})
|
||||
|
||||
// show scroll button only when display is scroll down
|
||||
window.addEventListener('scroll', function () {
|
||||
const y = document.documentElement.scrollTop // get the height from page top
|
||||
if (y < 100) {
|
||||
PageTopBtn.classList.remove('show')
|
||||
} else if (y >= 100) {
|
||||
PageTopBtn.classList.add('show')
|
||||
}
|
||||
})
|
||||
}
|
||||
3
javascripts/search-with-your-keyboard.d.ts
vendored
3
javascripts/search-with-your-keyboard.d.ts
vendored
@@ -1,3 +0,0 @@
|
||||
declare module 'search-with-your-keyboard' {
|
||||
export default function searchWithYourKeyboard(inputSelector: string, hitsSelector: string): void
|
||||
}
|
||||
@@ -1,307 +0,0 @@
|
||||
import { tags } from './hyperscript'
|
||||
import { sendEvent, EventType } from './events'
|
||||
import searchWithYourKeyboard from 'search-with-your-keyboard'
|
||||
|
||||
let $searchInputContainer: HTMLElement | null | undefined
|
||||
let $searchResultsContainer: HTMLElement | null | undefined
|
||||
let $searchOverlay: HTMLElement | null | undefined
|
||||
let $searchInput: HTMLInputElement | null | undefined
|
||||
|
||||
let isExplorerPage: boolean
|
||||
|
||||
// This is our default placeholder, but it can be localized with a <meta> tag
|
||||
let placeholder = 'Search topics, products...'
|
||||
let version: string
|
||||
let language: string
|
||||
|
||||
export default function search() {
|
||||
// @ts-ignore
|
||||
if (window.IS_NEXTJS_PAGE) return
|
||||
|
||||
// We don't want to mess with query params intended for the GraphQL Explorer
|
||||
isExplorerPage = Boolean(document.getElementById('graphiql'))
|
||||
|
||||
// First, only initialize search if the elements are on the page
|
||||
$searchInputContainer = document.getElementById('search-input-container')
|
||||
$searchResultsContainer = document.getElementById('search-results-container')
|
||||
if (!$searchInputContainer || !$searchResultsContainer) return
|
||||
|
||||
// This overlay exists so if you click off the search, it closes
|
||||
$searchOverlay = document.querySelector('.search-overlay-desktop') as HTMLElement
|
||||
|
||||
// There's an index for every version/language combination
|
||||
const { languages, versions, nonEnterpriseDefaultVersion } = JSON.parse(
|
||||
(document.getElementById('expose') as HTMLScriptElement)?.text || ''
|
||||
).searchOptions
|
||||
version = deriveVersionFromPath(versions, nonEnterpriseDefaultVersion)
|
||||
language = deriveLanguageCodeFromPath(languages)
|
||||
|
||||
// Find search placeholder text in a <meta> tag, falling back to a default
|
||||
const $placeholderMeta = document.querySelector(
|
||||
'meta[name="site.data.ui.search.placeholder"]'
|
||||
) as HTMLMetaElement
|
||||
if ($placeholderMeta) {
|
||||
placeholder = $placeholderMeta.content
|
||||
}
|
||||
|
||||
// Write the search form into its container
|
||||
$searchInputContainer.append(tmplSearchInput())
|
||||
$searchInput = $searchInputContainer.querySelector('input') as HTMLInputElement
|
||||
|
||||
// Prevent 'enter' from refreshing the page
|
||||
;($searchInputContainer.querySelector('form') as HTMLFormElement).addEventListener(
|
||||
'submit',
|
||||
(evt) => evt.preventDefault()
|
||||
)
|
||||
|
||||
// Search when the user finished typing
|
||||
$searchInput.addEventListener('keyup', debounce(onSearch))
|
||||
|
||||
// Adds ability to navigate search results with keyboard (up, down, enter, esc)
|
||||
searchWithYourKeyboard('#search-input-container input', '.ais-Hits-item')
|
||||
|
||||
// If the user already has a query in the URL, parse it and search away
|
||||
if (!isExplorerPage) {
|
||||
parseExistingSearch()
|
||||
}
|
||||
|
||||
// If not on home page, decide if search panel should be open
|
||||
toggleSearchDisplay() // must come after parseExistingSearch
|
||||
}
|
||||
|
||||
// The home page and 404 pages have a standalone search
|
||||
function hasStandaloneSearch() {
|
||||
return document.getElementById('landing') || document.querySelector('body.error-404') !== null
|
||||
}
|
||||
|
||||
function toggleSearchDisplay() {
|
||||
// Clear/close search, if ESC is clicked
|
||||
document.addEventListener('keyup', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
closeSearch()
|
||||
}
|
||||
})
|
||||
|
||||
// If not on homepage...
|
||||
if (hasStandaloneSearch()) return
|
||||
|
||||
// Open panel if input is clicked
|
||||
$searchInput?.addEventListener('focus', openSearch)
|
||||
|
||||
// Close panel if overlay is clicked
|
||||
if ($searchOverlay) {
|
||||
$searchOverlay.addEventListener('click', closeSearch)
|
||||
}
|
||||
|
||||
// Open panel if page loads with query in the params/input
|
||||
if ($searchInput?.value) {
|
||||
openSearch()
|
||||
}
|
||||
}
|
||||
|
||||
// On most pages, opens the search panel
|
||||
function openSearch() {
|
||||
$searchInput?.classList.add('js-open')
|
||||
$searchResultsContainer?.classList.add('js-open')
|
||||
$searchOverlay?.classList.add('js-open')
|
||||
}
|
||||
|
||||
// Close panel if not on homepage
|
||||
function closeSearch() {
|
||||
if (!hasStandaloneSearch()) {
|
||||
$searchInput?.classList.remove('js-open')
|
||||
$searchResultsContainer?.classList.remove('js-open')
|
||||
$searchOverlay?.classList.remove('js-open')
|
||||
}
|
||||
|
||||
if ($searchInput) $searchInput.value = ''
|
||||
onSearch()
|
||||
}
|
||||
|
||||
function deriveLanguageCodeFromPath(languageCodes: Array<string>) {
|
||||
let languageCode = location.pathname.split('/')[1]
|
||||
if (!languageCodes.includes(languageCode)) languageCode = 'en'
|
||||
return languageCode
|
||||
}
|
||||
|
||||
function deriveVersionFromPath(
|
||||
allVersions: Record<string, string>,
|
||||
nonEnterpriseDefaultVersion: string
|
||||
) {
|
||||
// fall back to the non-enterprise default version (FPT currently) on the homepage, 404 page, etc.
|
||||
const versionStr = location.pathname.split('/')[2] || nonEnterpriseDefaultVersion
|
||||
return allVersions[versionStr] || allVersions[nonEnterpriseDefaultVersion]
|
||||
}
|
||||
|
||||
// Wait for the event to stop triggering for X milliseconds before responding
|
||||
function debounce(fn: Function, delay = 300) {
|
||||
let timer: number
|
||||
return (...args: Array<any>) => {
|
||||
clearTimeout(timer)
|
||||
timer = window.setTimeout(() => fn.apply(null, args), delay)
|
||||
}
|
||||
}
|
||||
|
||||
// When the user finishes typing, update the results
|
||||
async function onSearch() {
|
||||
const query = $searchInput?.value || ''
|
||||
|
||||
// Update the URL with the search parameters in the query string
|
||||
// UNLESS this is the GraphQL Explorer page, where a query in the URL is a GraphQL query
|
||||
const pushUrl = new URL(location.toString())
|
||||
pushUrl.search = query && !isExplorerPage ? new URLSearchParams({ query }).toString() : ''
|
||||
history.pushState({}, '', pushUrl.toString())
|
||||
|
||||
// If there's a query, call the endpoint
|
||||
// Otherwise, there's no results by default
|
||||
let results = []
|
||||
if (query.trim()) {
|
||||
const endpointUrl = new URL(location.origin)
|
||||
endpointUrl.pathname = '/search'
|
||||
endpointUrl.search = new URLSearchParams({ language, version, query }).toString()
|
||||
|
||||
const response = await fetch(endpointUrl.toString(), {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
results = response.ok ? await response.json() : []
|
||||
}
|
||||
|
||||
// Either way, update the display
|
||||
$searchResultsContainer?.querySelectorAll('*').forEach((el) => el.remove())
|
||||
$searchResultsContainer?.append(tmplSearchResults(results))
|
||||
toggleStandaloneSearch()
|
||||
|
||||
// Analytics tracking
|
||||
if (query.trim()) {
|
||||
sendEvent({
|
||||
type: EventType.search,
|
||||
search_query: query,
|
||||
// search_context
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// If on homepage, toggle results container if query is present
|
||||
function toggleStandaloneSearch() {
|
||||
if (!hasStandaloneSearch()) return
|
||||
|
||||
const query = $searchInput?.value
|
||||
const queryPresent = Boolean(query && query.length > 0)
|
||||
const $results = document.querySelector('.ais-Hits') as HTMLElement
|
||||
|
||||
// Primer classNames for showing and hiding the results container
|
||||
const activeClass = $searchResultsContainer?.getAttribute('data-active-class')
|
||||
const inactiveClass = $searchResultsContainer?.getAttribute('data-inactive-class')
|
||||
|
||||
if (!activeClass) {
|
||||
console.error(
|
||||
'container is missing required `data-active-class` attribute',
|
||||
$searchResultsContainer
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (!inactiveClass) {
|
||||
console.error(
|
||||
'container is missing required `data-inactive-class` attribute',
|
||||
$searchResultsContainer
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// hide the container when no query is present
|
||||
$searchResultsContainer?.classList.toggle(activeClass, queryPresent)
|
||||
$searchResultsContainer?.classList.toggle(inactiveClass, !queryPresent)
|
||||
|
||||
if (queryPresent && $results) $results.style.display = 'block'
|
||||
}
|
||||
|
||||
// If the user shows up with a query in the URL, go ahead and search for it
|
||||
function parseExistingSearch() {
|
||||
const params = new URLSearchParams(location.search)
|
||||
if (!params.has('query')) return
|
||||
if ($searchInput) $searchInput.value = params.get('query') || ''
|
||||
onSearch()
|
||||
}
|
||||
|
||||
/** * Template functions ***/
|
||||
|
||||
function tmplSearchInput() {
|
||||
// only autofocus on the homepage, and only if no #hash is present in the URL
|
||||
const autofocus = (hasStandaloneSearch() && !location.hash.length) || null
|
||||
const { div, form, input, button } = tags
|
||||
return div(
|
||||
{ class: 'ais-SearchBox' },
|
||||
form(
|
||||
{ role: 'search', class: 'ais-SearchBox-form', novalidate: true },
|
||||
input({
|
||||
class: 'ais-SearchBox-input',
|
||||
type: 'search',
|
||||
placeholder,
|
||||
autofocus,
|
||||
autocomplete: 'off',
|
||||
autocorrect: 'off',
|
||||
autocapitalize: 'off',
|
||||
spellcheck: 'false',
|
||||
maxlength: '512',
|
||||
}),
|
||||
button({
|
||||
class: 'ais-SearchBox-submit',
|
||||
type: 'submit',
|
||||
title: 'Submit the search query.',
|
||||
hidden: true,
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
type SearchResult = {
|
||||
url: string
|
||||
breadcrumbs: string
|
||||
heading: string
|
||||
title: string
|
||||
content: string
|
||||
}
|
||||
|
||||
function tmplSearchResults(items: Array<SearchResult>) {
|
||||
const { div, ol, li } = tags
|
||||
return div(
|
||||
{ class: 'ais-Hits', style: 'display:block' },
|
||||
ol(
|
||||
{ class: 'ais-Hits-list' },
|
||||
items.map((item) => li({ class: 'ais-Hits-item' }, tmplSearchResult(item)))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
function tmplSearchResult({ url, breadcrumbs, heading, title, content }: SearchResult) {
|
||||
const { div, a } = tags
|
||||
return div(
|
||||
{ class: 'search-result border-top color-border-secondary py-3 px-2' },
|
||||
a(
|
||||
{ href: url, class: 'no-underline' },
|
||||
div(
|
||||
{
|
||||
class: 'search-result-breadcrumbs d-block color-text-primary opacity-60 text-small pb-1',
|
||||
},
|
||||
// Breadcrumbs in search records don't include the page title
|
||||
markify(breadcrumbs || '')
|
||||
),
|
||||
div(
|
||||
{ class: 'search-result-title d-block h4-mktg color-text-primary' },
|
||||
// Display page title and heading (if present exists)
|
||||
markify(heading ? `${title}: ${heading}` : title)
|
||||
),
|
||||
div({ class: 'search-result-content d-block color-text-secondary' }, markify(content))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Convert mark tags in search responses
|
||||
function markify(text: string) {
|
||||
const { mark } = tags
|
||||
return text.split(/<\/?mark>/g).map((el, i) => (i % 2 ? mark(el) : el))
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
# Layouts
|
||||
|
||||
The files in this directory are layouts which can be wrapped around pages.
|
||||
|
||||
See also [includes](includes), which are snippets of HTML or Markdown that
|
||||
can be reused in multiple layouts.
|
||||
|
||||
## Using Layouts
|
||||
|
||||
Be default, `layouts/default.html` will be used for all pages.
|
||||
|
||||
To use a custom layout, add a `layout` value to the page's frontmatter:
|
||||
|
||||
```
|
||||
---
|
||||
title: Hello World
|
||||
layout: some-layout
|
||||
---
|
||||
```
|
||||
|
||||
The example above will use the `layouts/some-layout.html` layout.
|
||||
|
||||
To render a page with no layout, set `layout: false`.
|
||||
|
||||
## Writing Layouts
|
||||
|
||||
Layout files should have a `.html` or `.md` extension, and they
|
||||
must include the string `{{ content}}` to specify where inner content should
|
||||
be injected.
|
||||
@@ -1,36 +0,0 @@
|
||||
<!doctype html>
|
||||
{% assign error = '404' %}
|
||||
<html lang="{{currentLanguage}}" data-color-mode="$COLORMODE$" data-dark-theme="$DARKTHEME$" data-light-theme="$LIGHTTHEME$">
|
||||
{% include head %}
|
||||
|
||||
<body class="d-lg-flex error-404">
|
||||
{% include sidebar %}
|
||||
|
||||
<main class="width-full">
|
||||
{% include header %}
|
||||
|
||||
<div class="container-xl p-responsive py-6">
|
||||
<article class="markdown-body col-md-10 col-lg-7 mx-auto">
|
||||
{% include error-404-deprecation-message %}
|
||||
|
||||
<h1>{% data ui.errors.oops %}</h1>
|
||||
|
||||
<div class="lead-mktg mb-5">
|
||||
{% data ui.errors.page_doesnt_exist %}
|
||||
</div>
|
||||
|
||||
<div class="col-lg-12 mt-6">
|
||||
<h3 class="mb-3">{% data ui.search.need_help %}</h3>
|
||||
{% include search-form %}
|
||||
</div>
|
||||
<div id="search-results-container" class="mt-4 d-none" data-active-class="d-block" data-inactive-class="d-none"></div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<!-- {{ content }} -->
|
||||
{% include support-section %}
|
||||
{% include small-footer %}
|
||||
{% include scroll-button %}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,27 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="{{currentLanguage}}" data-color-mode="$COLORMODE$" data-dark-theme="$DARKTHEME$" data-light-theme="$LIGHTTHEME$">
|
||||
{% include head %}
|
||||
<body>
|
||||
{% include header %}
|
||||
|
||||
<div class="container-xl p-responsive py-9">
|
||||
<article class="markdown-body col-md-10 col-lg-7 mx-auto">
|
||||
<h1>{% data ui.errors.oops %}</h1>
|
||||
|
||||
<div class="lead-mktg mb-5">
|
||||
{% data ui.errors.something_went_wrong %}
|
||||
{% data ui.errors.we_track_errors %}
|
||||
</div>
|
||||
|
||||
{% if error %}
|
||||
<pre><code>{{ error.stack }}</code></pre>
|
||||
{% endif %}
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<!-- {{ content }} -->
|
||||
{% include support-section %}
|
||||
{% include small-footer %}
|
||||
{% include scroll-button %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,23 +0,0 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import crypto from 'crypto'
|
||||
|
||||
// Get an MD4 Digest Hex content hash, loosely based on Webpack `[contenthash]`
|
||||
function getContentHash(absFilePath) {
|
||||
const buffer = fs.readFileSync(absFilePath)
|
||||
const hash = crypto.createHash('md4')
|
||||
hash.update(buffer)
|
||||
return hash.digest('hex')
|
||||
}
|
||||
|
||||
function getUrl(relFilePath) {
|
||||
const absFilePath = path.join(process.cwd(), relFilePath)
|
||||
return `/${relFilePath}?hash=${getContentHash(absFilePath)}`
|
||||
}
|
||||
|
||||
export default {
|
||||
main: {
|
||||
js: getUrl('dist/index.js'),
|
||||
css: getUrl('dist/index.css'),
|
||||
},
|
||||
}
|
||||
@@ -3,7 +3,6 @@ import renderContent from './renderContent.js'
|
||||
import { ExtendedMarkdown, tags } from '../liquid-tags/extended-markdown.js'
|
||||
import xLink from '../liquid-tags/link.js'
|
||||
import xLinkWithIntro from '../liquid-tags/link-with-intro.js'
|
||||
import xHomepageLinkWithIntro from '../liquid-tags/homepage-link-with-intro.js'
|
||||
import xLinkInList from '../liquid-tags/link-in-list.js'
|
||||
import xTopicLinkInList from '../liquid-tags/topic-link-in-list.js'
|
||||
import xIndentedDataReference from '../liquid-tags/indented-data-reference.js'
|
||||
@@ -15,7 +14,6 @@ import xIfversion from '../liquid-tags/ifversion.js'
|
||||
// Include custom tags like {% link_with_intro /article/foo %}
|
||||
renderContent.liquid.registerTag('link', xLink('link'))
|
||||
renderContent.liquid.registerTag('link_with_intro', xLinkWithIntro)
|
||||
renderContent.liquid.registerTag('homepage_link_with_intro', xHomepageLinkWithIntro)
|
||||
renderContent.liquid.registerTag('link_in_list', xLinkInList)
|
||||
renderContent.liquid.registerTag('topic_link_in_list', xTopicLinkInList)
|
||||
renderContent.liquid.registerTag('indented_data_reference', xIndentedDataReference)
|
||||
|
||||
@@ -6,7 +6,6 @@ import xPathUtils from '../lib/path-utils.js'
|
||||
import productNames from '../lib/product-names.js'
|
||||
import warmServer from '../lib/warm-server.js'
|
||||
import readJsonFile from '../lib/read-json-file.js'
|
||||
import builtAssets from '../lib/built-asset-urls.js'
|
||||
import searchVersions from '../lib/search/versions.js'
|
||||
import nonEnterpriseDefaultVersion from '../lib/non-enterprise-default-version.js'
|
||||
const activeProducts = Object.values(productMap).filter(
|
||||
@@ -58,9 +57,6 @@ export default async function contextualize(req, res, next) {
|
||||
req.context.siteTree = siteTree
|
||||
req.context.pages = pageMap
|
||||
|
||||
// JS + CSS asset paths
|
||||
req.context.builtAssets = builtAssets
|
||||
|
||||
// Object exposing selected variables to client
|
||||
req.context.expose = JSON.stringify({
|
||||
// Languages and versions for search
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import FailBot from '../lib/failbot.js'
|
||||
import loadSiteData from '../lib/site-data.js'
|
||||
import builtAssets from '../lib/built-asset-urls.js'
|
||||
import { nextApp } from './next.js'
|
||||
|
||||
function shouldLogException(error) {
|
||||
@@ -42,9 +41,8 @@ export default async function handleError(error, req, res, next) {
|
||||
// set req.context.site here so we can pass data/ui.yml text to the 500 layout
|
||||
if (!req.context) {
|
||||
const site = await loadSiteData()
|
||||
req.context = { site: site[req.language || 'en'].site, builtAssets }
|
||||
req.context = { site: site[req.language || 'en'].site }
|
||||
}
|
||||
|
||||
// display error on the page in development and staging, but not in production
|
||||
if (req.context && process.env.HEROKU_PRODUCTION_APP !== 'true') {
|
||||
req.context.error = error
|
||||
|
||||
@@ -3,17 +3,30 @@
|
||||
|
||||
const fs = require('fs')
|
||||
const frontmatter = require('gray-matter')
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
const path = require('path')
|
||||
const homepage = path.posix.join(process.cwd(), 'content/index.md')
|
||||
const { data } = frontmatter(fs.readFileSync(homepage, 'utf8'))
|
||||
const productIds = data.children
|
||||
|
||||
module.exports = {
|
||||
webpack: (config, { isServer }) => {
|
||||
if (isServer) {
|
||||
config.plugins.push(
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: path.join(__dirname, 'node_modules/@primer/css/fonts'),
|
||||
to: path.join(__dirname, 'assets/fonts/inter'),
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
}
|
||||
return config
|
||||
},
|
||||
// speed up production `next build` by ignoring typechecking during that step of build.
|
||||
// type-checking still occurs in the Dockerfile build
|
||||
future: {
|
||||
webpack: 5,
|
||||
},
|
||||
typescript: {
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
|
||||
682
package-lock.json
generated
682
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -26,6 +26,7 @@
|
||||
"connect-datadog": "0.0.9",
|
||||
"connect-slashes": "^1.4.0",
|
||||
"cookie-parser": "^1.4.5",
|
||||
"copy-webpack-plugin": "^9.0.1",
|
||||
"cors": "^2.8.5",
|
||||
"csurf": "^1.11.0",
|
||||
"dayjs": "^1.10.4",
|
||||
@@ -118,7 +119,6 @@
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"chalk": "^4.1.1",
|
||||
"commander": "^7.2.0",
|
||||
"copy-webpack-plugin": "^8.1.1",
|
||||
"count-array-values": "^1.2.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"csp-parse": "0.0.2",
|
||||
@@ -173,8 +173,6 @@
|
||||
"ts-loader": "^9.2.3",
|
||||
"typescript": "^4.3.2",
|
||||
"url-template": "^2.0.8",
|
||||
"webpack": "^5.37.0",
|
||||
"webpack-cli": "^4.7.0",
|
||||
"website-scraper": "^4.2.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -196,7 +194,7 @@
|
||||
"browser-test": "start-server-and-test browser-test-server 4001 browser-test-tests",
|
||||
"browser-test-server": "cross-env NODE_ENV=production WEB_CONCURRENCY=1 PORT=4001 node server.mjs",
|
||||
"browser-test-tests": "cross-env BROWSER=1 NODE_OPTIONS=--experimental-vm-modules jest tests/browser/browser.js",
|
||||
"build": "npm run webpack-build && next build",
|
||||
"build": "next build",
|
||||
"debug": "cross-env NODE_ENV=development ENABLED_LANGUAGES='en,ja' nodemon --inspect server.mjs",
|
||||
"dev": "npm start",
|
||||
"heroku-postbuild": "node script/early-access/clone-for-build.js && npm run build",
|
||||
@@ -220,8 +218,7 @@
|
||||
"sync-search-indices": "script/sync-search-indices.js",
|
||||
"sync-search-server": "cross-env NODE_ENV=production WEB_CONCURRENCY=1 PORT=4002 node server.mjs",
|
||||
"test": "npm run lint && cross-env NODE_OPTIONS=--experimental-vm-modules jest",
|
||||
"test-watch": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --watch --notify --notifyMode=change --coverage",
|
||||
"webpack-build": "cross-env NODE_ENV=production npx webpack --mode production"
|
||||
"test-watch": "cross-env NODE_OPTIONS=--experimental-vm-modules jest --watch --notify --notifyMode=change --coverage"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,mjs,ts,tsx}": "eslint --cache --fix",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
@import "@primer/css/labels/index.scss";
|
||||
@import "@primer/css/avatars/avatar.scss";
|
||||
|
||||
$marketing-font-path: "/dist/fonts/";
|
||||
$marketing-font-path: "/assets/fonts/inter/";
|
||||
@import "@primer/css/marketing/index.scss";
|
||||
@import "font-mktg.scss";
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import enterpriseServerReleases from '../../lib/enterprise-server-releases.js'
|
||||
import { get, getDOM, head, post } from '../helpers/supertest.js'
|
||||
import { describeViaActionsOnly } from '../helpers/conditional-runs.js'
|
||||
import { loadPages } from '../../lib/page-data.js'
|
||||
import builtAssets from '../../lib/built-asset-urls.js'
|
||||
import CspParse from 'csp-parse'
|
||||
import { productMap } from '../../lib/all-products.js'
|
||||
import { jest } from '@jest/globals'
|
||||
@@ -177,7 +176,11 @@ describe('server', () => {
|
||||
const $ = await getDOM('/not-a-real-page')
|
||||
expect($('h1').text()).toBe('Ooops!')
|
||||
expect($.text().includes("It looks like this page doesn't exist.")).toBe(true)
|
||||
expect($.text().includes('We track these errors automatically, but if the problem persists please feel free to contact us.')).toBe(true)
|
||||
expect(
|
||||
$.text().includes(
|
||||
'We track these errors automatically, but if the problem persists please feel free to contact us.'
|
||||
)
|
||||
).toBe(true)
|
||||
expect($.res.statusCode).toBe(404)
|
||||
})
|
||||
|
||||
@@ -200,8 +203,12 @@ describe('server', () => {
|
||||
test('renders a 500 page when errors are thrown', async () => {
|
||||
const $ = await getDOM('/_500')
|
||||
expect($('h1').text()).toBe('Ooops!')
|
||||
expect($.text().includes("It looks like something went wrong.")).toBe(true)
|
||||
expect($.text().includes("We track these errors automatically, but if the problem persists please feel free to contact us.")).toBe(true)
|
||||
expect($.text().includes('It looks like something went wrong.')).toBe(true)
|
||||
expect(
|
||||
$.text().includes(
|
||||
'We track these errors automatically, but if the problem persists please feel free to contact us.'
|
||||
)
|
||||
).toBe(true)
|
||||
expect($.res.statusCode).toBe(500)
|
||||
})
|
||||
|
||||
@@ -498,9 +505,7 @@ describe('server', () => {
|
||||
const $ = await getDOM(
|
||||
`${latestEnterprisePath}/github/getting-started-with-github/set-up-git`
|
||||
)
|
||||
expect(
|
||||
$('a[href="/en/desktop/installing-and-configuring-github-desktop"]').length
|
||||
).toBe(1)
|
||||
expect($('a[href="/en/desktop/installing-and-configuring-github-desktop"]').length).toBe(1)
|
||||
})
|
||||
|
||||
test('admin articles that link to non-admin articles have Enterprise user links', async () => {
|
||||
@@ -526,16 +531,16 @@ describe('server', () => {
|
||||
`${latestEnterprisePath}/admin/installation/upgrading-github-enterprise-server`
|
||||
)
|
||||
expect(
|
||||
$(
|
||||
'a[href="https://docs.microsoft.com/azure/backup/backup-azure-vms-first-look-arm"]'
|
||||
).length
|
||||
$('a[href="https://docs.microsoft.com/azure/backup/backup-azure-vms-first-look-arm"]')
|
||||
.length
|
||||
).toBe(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('article versions', () => {
|
||||
test('includes links to all versions of each article', async () => {
|
||||
const articlePath = 'github/setting-up-and-managing-your-github-user-account/managing-user-account-settings/about-your-personal-dashboard'
|
||||
const articlePath =
|
||||
'github/setting-up-and-managing-your-github-user-account/managing-user-account-settings/about-your-personal-dashboard'
|
||||
const $ = await getDOM(
|
||||
`/en/enterprise-server@${enterpriseServerReleases.latest}/${articlePath}`
|
||||
)
|
||||
@@ -545,7 +550,10 @@ describe('server', () => {
|
||||
).length
|
||||
).toBe(2)
|
||||
// 2.13 predates this feature, so it should be excluded:
|
||||
expect($(`[data-testid=article-version-picker] a[href="/en/enterprise/2.13/user/${articlePath}"]`).length).toBe(0)
|
||||
expect(
|
||||
$(`[data-testid=article-version-picker] a[href="/en/enterprise/2.13/user/${articlePath}"]`)
|
||||
.length
|
||||
).toBe(0)
|
||||
})
|
||||
|
||||
test('is not displayed if article has only one version', async () => {
|
||||
@@ -852,8 +860,9 @@ describe('GitHub Desktop URLs', () => {
|
||||
|
||||
describe('static assets', () => {
|
||||
test('fonts', async () => {
|
||||
expect((await get('/dist/fonts/Inter-Medium.woff')).statusCode).toBe(200)
|
||||
expect((await get('/dist/fonts/Inter-Regular.woff')).statusCode).toBe(200)
|
||||
expect((await get('/assets/fonts/inter/Inter-Bold.woff')).statusCode).toBe(200)
|
||||
expect((await get('/assets/fonts/inter/Inter-Medium.woff')).statusCode).toBe(200)
|
||||
expect((await get('/assets/fonts/inter/Inter-Regular.woff')).statusCode).toBe(200)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -948,37 +957,6 @@ describe('?json query param for context debugging', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('stylesheets', () => {
|
||||
it('compiles and sets the right content-type header', async () => {
|
||||
const stylesheetUrl = builtAssets.main.css
|
||||
const res = await get(stylesheetUrl)
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(res.headers['content-type']).toBe('text/css; charset=UTF-8')
|
||||
})
|
||||
})
|
||||
|
||||
describe('client-side JavaScript bundle', () => {
|
||||
let res
|
||||
beforeAll(async () => {
|
||||
const scriptUrl = builtAssets.main.js
|
||||
res = await get(scriptUrl)
|
||||
})
|
||||
|
||||
it('returns a 200 response', async () => {
|
||||
expect(res.statusCode).toBe(200)
|
||||
})
|
||||
|
||||
it('sets the right content-type header', async () => {
|
||||
expect(res.headers['content-type']).toBe('application/javascript; charset=UTF-8')
|
||||
})
|
||||
|
||||
// TODO: configure webpack to create production bundle in the test env
|
||||
// it('is not too big', async () => {
|
||||
// const tooBig = 10 * 1000
|
||||
// expect(res.text.length).toBeLessThan(tooBig)
|
||||
// })
|
||||
})
|
||||
|
||||
describe('static routes', () => {
|
||||
it('serves content from the /assets directory', async () => {
|
||||
expect((await get('/assets/images/site/be-social.gif')).statusCode).toBe(200)
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
const path = require('path')
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
const { EnvironmentPlugin, ProvidePlugin } = require('webpack')
|
||||
|
||||
module.exports = {
|
||||
mode: 'development',
|
||||
devtool: process.env.NODE_ENV === 'development' ? 'eval' : 'source-map', // no 'eval' outside of development
|
||||
entry: './javascripts/index.ts',
|
||||
output: {
|
||||
filename: 'index.js',
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
publicPath: '/dist',
|
||||
},
|
||||
stats: 'errors-only',
|
||||
resolve: {
|
||||
extensions: ['.tsx', '.ts', '.js', '.css', '.scss'],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
{
|
||||
test: /\.css$/i,
|
||||
use: ['style-loader', 'css-loader'],
|
||||
},
|
||||
{
|
||||
test: /\.s[ac]ss$/i,
|
||||
use: [
|
||||
MiniCssExtractPlugin.loader,
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
sourceMap: true,
|
||||
url: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
// Needed to resolve image url()s within @primer/css
|
||||
loader: 'resolve-url-loader',
|
||||
options: {},
|
||||
},
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
sassOptions: {
|
||||
quietDeps: true,
|
||||
includePaths: ['./stylesheets', './node_modules'],
|
||||
options: {
|
||||
sourceMap: true,
|
||||
sourceMapContents: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new MiniCssExtractPlugin({
|
||||
filename: 'index.css',
|
||||
}),
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [{ from: 'node_modules/@primer/css/fonts', to: 'fonts' }],
|
||||
}),
|
||||
new EnvironmentPlugin({
|
||||
NODE_ENV: 'development', // use 'development' unless process.env.NODE_ENV is defined
|
||||
DEBUG: false,
|
||||
}),
|
||||
new ProvidePlugin({
|
||||
process: 'process/browser',
|
||||
}),
|
||||
],
|
||||
}
|
||||
Reference in New Issue
Block a user