Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-docs.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid O'Regan <doregan@gitlab.com>2023-08-15 01:05:37 +0300
committerDavid O'Regan <doregan@gitlab.com>2023-08-15 01:05:37 +0300
commit1fd18d39e1a4103211d57a7b8dcf1f26fa014a25 (patch)
tree04d77206140895348c9eb1d34ea785bb5f1a9e5f
parentfb97a56db704f205050a150e05176e2af4990b4b (diff)
parentcd05967b7f674a4a778b7c6955ede163df5aecb4 (diff)
Merge branch '1690-badge-refactor' into 'main'
Refactor tier badges See merge request https://gitlab.com/gitlab-org/gitlab-docs/-/merge_requests/4153 Merged-by: David O'Regan <doregan@gitlab.com> Approved-by: David O'Regan <doregan@gitlab.com> Approved-by: Suzanne Selhorn <sselhorn@gitlab.com> Co-authored-by: Suzanne Selhorn <sselhorn@gitlab.com> Co-authored-by: Sarah German <sgerman@gitlab.com>
-rw-r--r--content/_data/badges.yaml44
-rw-r--r--content/assets/javascripts/badges.js182
-rw-r--r--content/assets/stylesheets/_tables.scss4
-rw-r--r--content/assets/stylesheets/_typography.scss19
-rw-r--r--content/assets/stylesheets/stylesheet.scss114
-rw-r--r--content/frontend/default/components/docs_badges.vue71
-rw-r--r--content/frontend/default/default.js49
-rw-r--r--content/frontend/search/recently_viewed.js6
-rw-r--r--content/frontend/shared/dom.js16
-rw-r--r--layouts/default.html1
-rw-r--r--layouts/header.html2
-rw-r--r--layouts/home.html1
-rw-r--r--lib/filters/badges.rb88
-rw-r--r--lib/filters/gitlab_kramdown.rb17
-rw-r--r--package.json1
-rw-r--r--rollup.config.js2
-rw-r--r--spec/frontend/search/google_search_spec.js17
-rw-r--r--spec/lib/filters/badges_spec.rb108
-rw-r--r--yarn.lock14
19 files changed, 311 insertions, 445 deletions
diff --git a/content/_data/badges.yaml b/content/_data/badges.yaml
new file mode 100644
index 00000000..d27eabba
--- /dev/null
+++ b/content/_data/badges.yaml
@@ -0,0 +1,44 @@
+badgeIndex:
+ # Tiers
+ - id: free
+ label: All tiers
+ type: tier
+ text: Free, Premium, and Ultimate
+ - id: premium
+ label: Premium
+ type: tier
+ link: https://about.gitlab.com/pricing/?glm_source=docs.gitlab.com&glm_content=badges-docs
+ - id: ultimate
+ label: Ultimate
+ type: tier
+ link: https://about.gitlab.com/pricing/?glm_source=docs.gitlab.com&glm_content=badges-docs
+
+ # Offerings
+ - id: self
+ label: Self-managed
+ type: offering
+ - id: saas
+ label: SaaS
+ type: offering
+ - id: all
+ label: All offerings
+ type: offering
+ text: Self-managed and SaaS
+
+ # Statuses
+ - id: experiment
+ label: Experiment
+ text: This feature is in the process of being developed. It's not production-ready but we encourage you to try it and provide feedback.
+ type: status
+ link: https://docs.gitlab.com/ee/policy/experiment-beta-support.html#experiment
+ - id: beta
+ label: Beta
+ text: This feature is not production-ready, but is unlikely to change drastically before it's released. We encourage you to try it and provide feedback.
+ type: status
+ link: https://docs.gitlab.com/ee/policy/experiment-beta-support.html#beta
+
+ # Content tags
+ - id: contribute
+ label: Contribute
+ text: This content is about development of the GitLab product.
+ type: content
diff --git a/content/assets/javascripts/badges.js b/content/assets/javascripts/badges.js
deleted file mode 100644
index 9244526f..00000000
--- a/content/assets/javascripts/badges.js
+++ /dev/null
@@ -1,182 +0,0 @@
-(function() {
- const classes = ['core-only', 'core', 'starter-only', 'premium-only', 'ultimate-only', 'starter', 'premium', 'ultimate', 'free-only' , 'bronze-only', 'silver-only', 'gold-only', 'free', 'free-saas', 'free-self', 'premium-saas', 'premium-self', 'ultimate-saas', 'ultimate-self', 'contribute'];
-
- const BADGES_TITLES = {
- // Free
- free: 'On GitLab self-managed and GitLab SaaS, available in all tiers.',
- 'free-self':
- 'On GitLab self-managed, available in all tiers. Not available on GitLab SaaS.',
- 'free-only':
- 'On GitLab SaaS, available in all tiers. Not available on self-managed.',
- 'free-saas':
- 'On GitLab SaaS, available in all tiers. Not available on self-managed.',
- // Premium
- premium: 'On GitLab self-managed and GitLab SaaS, available in Premium and Ultimate.',
- 'premium-saas':
- 'On GitLab SaaS, available in Premium and Ultimate. Not available on self-managed.',
- 'silver-only':
- 'On GitLab SaaS, available in Premium and Ultimate. Not available on self-managed.',
- 'premium-only':
- 'On GitLab self-managed, available in Premium and Ultimate. Not available on GitLab SaaS.',
- 'premium-self':
- 'On GitLab self-managed, available in Premium and Ultimate. Not available on GitLab SaaS.',
- // Ultimate
- ultimate: 'On GitLab self-managed and GitLab SaaS, available in Ultimate.',
- 'ultimate-only':
- 'On GitLab self-managed, available in Ultimate. Not available on GitLab SaaS.',
- 'ultimate-self':
- 'On GitLab self-managed, available in Ultimate. Not available on GitLab SaaS.',
- 'ultimate-saas':
- 'On GitLab SaaS, available in Ultimate. Not available on self-managed.',
- 'gold-only':
- 'On GitLab SaaS, available in Ultimate. Not available on self-managed.',
- // Deprecated badges
- core: 'On GitLab self-managed and GitLab SaaS, available in all tiers.',
- 'core-only':
- 'On GitLab self-managed, available in all tiers. Not available on GitLab SaaS.',
- starter: 'Available on GitLab Starter, GitLab.com Bronze, and higher tiers.',
- 'starter-only':
- 'Available on GitLab Starter and higher tiers. Not available on GitLab.com.',
- 'bronze-only':
- 'Available on GitLab Bronze and higher tiers. Not available on self-managed.',
- // Contributor page badge
- 'contribute':
- 'Use this content to contribute to GitLab development.'
- };
-
- const BADGES_MAPPING = {
- // Free
- core: ['all tiers'],
- free: ['all tiers'],
- 'core-only': ['all tiers', 'self-managed'],
- 'free-self': ['all tiers', 'self-managed'],
- 'free-only': ['all tiers', 'saas'],
- 'free-saas': ['all tiers', 'saas'],
- // Premium
- premium: ['premium'],
- 'silver-only': ['premium', 'saas'],
- 'premium-only': ['premium', 'self-managed'],
- 'premium-self': ['premium', 'self-managed'],
- 'premium-saas': ['premium', 'saas'],
- // Ultimate
- ultimate: ['ultimate'],
- 'ultimate-only': ['ultimate', 'self-managed'],
- 'gold-only': ['ultimate', 'saas'],
- 'ultimate-self': ['ultimate', 'self-managed'],
- 'ultimate-saas': ['ultimate', 'saas'],
- // Deprecated badges
- starter: ['starter', 'bronze'],
- 'starter-only': ['starter'],
- 'bronze-only': ['bronze'],
- // Contributor badge
- contribute: ['contribute']
- };
-
- const BADGES_CLASS = {
- // Tier class
- core: 'tier',
- starter: 'tier',
- premium: 'tier',
- ultimate: 'tier',
- 'all tiers': 'tier',
- // Contributors
- 'contribute': 'contribute',
- // GitLab SaaS
- bronze: 'saas',
- silver: 'saas',
- gold: 'saas',
- saas: 'saas',
- // GitLab self-managed
- 'self-managed': 'self-managed',
- };
-
- // Check if the currently-viewed page is GitLab contributor documentation.
- const isContributorDocs = () => {
- const paths = ['/ee/development/', '/omnibus/development/', '/runner/development/', '/charts/development/'];
- return paths.some(substr => window.location.pathname.startsWith(substr))
- }
-
- function init() {
- // Insert markup for the "Contribute" badge if this is a contributor doc page.
- if (isContributorDocs()) {
- $('<span class="badge-trigger contribute"></span>').insertBefore('h1 a');
- }
-
- var $badges = $('.badge-trigger');
-
- $badges.each(function() {
- convertBadge($(this));
- });
-
- $('[data-toggle="tooltip"]').tooltip();
- }
-
- function convertBadge($badge) {
- var small = isSmall($badge);
- var badgeType = retrieveBadgeType($badge);
-
- var smallBadgeTag = function(title) {
- return $('<span>', {
- class: 'badge-small',
- html: '<%= icon("information-o", 14) %>',
- 'data-title': title,
- });
- };
-
- var largeBadgeTag = function(badge, badgeClass) {
- return $('<div>', {
- class: 'badge-display badge-' + badgeClass,
- text: badge,
- });
- };
-
- var template = function(title, badges) {
- let container = $('<a>', {
- class: 'badges-drop',
- 'data-toggle': 'tooltip',
- 'data-placement': 'top',
- 'target': '_blank',
- title: title,
- href: 'https://about.gitlab.com/pricing/?glm_source=docs.gitlab.com&glm_content=badges-docs'
- });
- if (isContributorDocs()) {
- container.attr({href: "https://docs.gitlab.com/ee/development/"})
- }
- container.append($('<span>').append(badges));
- return container;
- };
-
- var tags = [];
-
- if (small) {
- tags.push(smallBadgeTag(BADGES_MAPPING[badgeType].join(' | ')));
- } else {
- $.each(BADGES_MAPPING[badgeType], function(i, badge) {
- tags.push(largeBadgeTag(badge, BADGES_CLASS[badge]));
- });
- }
-
- $badge.append($(template(BADGES_TITLES[badgeType], tags)));
- }
-
- // Get the badge type from a specific list of expected values in element class
- function retrieveBadgeType($badge) {
- const classType = $badge.attr('class').split(' ');
- const match = classes.filter(matchingClass => classType.includes(matchingClass));
- if (match) {
- return match.pop();
- }
- }
-
- // When badge is not in a HTML header, we use the small version
- function isSmall($badge) {
- return !$badge
- .parent()
- .prop('tagName')
- .match(/H1|H2|H3|H4|H5/);
- }
-
- $(function() {
- init();
- });
-})();
diff --git a/content/assets/stylesheets/_tables.scss b/content/assets/stylesheets/_tables.scss
index 1414859b..0f8a9ce0 100644
--- a/content/assets/stylesheets/_tables.scss
+++ b/content/assets/stylesheets/_tables.scss
@@ -57,10 +57,6 @@ table {
border-bottom: 0 !important;
/* stylelint-enable declaration-no-important */
}
-
- .badge-drop {
- display: none;
- }
}
}
diff --git a/content/assets/stylesheets/_typography.scss b/content/assets/stylesheets/_typography.scss
index 7a7fc7c4..6e1ce8a0 100644
--- a/content/assets/stylesheets/_typography.scss
+++ b/content/assets/stylesheets/_typography.scss
@@ -172,20 +172,6 @@
}
}
- // Badge tier
-
- .badge-tier {
- color: $red-500;
- font-size: 0.75rem;
- font-weight: 800;
- text-transform: uppercase;
-
- &:hover {
- border-bottom-color: $red-700;
- color: $red-700;
- }
- }
-
.introduced-in {
border-left: 4px solid $theme-indigo-200;
padding-left: 0.75rem;
@@ -354,11 +340,6 @@
}
}
- .sm {
- @include gl-font-size-markdown-sm;
- @include gl-line-height-20;
- }
-
.monospace {
@include gl-font-monospace;
@include gl-font-size-monospace;
diff --git a/content/assets/stylesheets/stylesheet.scss b/content/assets/stylesheets/stylesheet.scss
index ebaa60bd..3a1d074a 100644
--- a/content/assets/stylesheets/stylesheet.scss
+++ b/content/assets/stylesheets/stylesheet.scss
@@ -374,115 +374,25 @@ p.result-snippet {
font-size: 0.875rem;
}
-//badges
-.badges-drop {
- display: inline-block;
- cursor: pointer;
- transition: background-color 0.5s ease-in-out;
- /* stylelint-disable declaration-no-important */
- border-bottom: 0 !important;
- /* stylelint-enable declaration-no-important */
-
- &:hover {
+// Badges
+.docs-badges-wrapper {
+ a.gl-link {
border-bottom: 0;
+ color: inherit;
}
-
- .badge-tier,
- .badge-self-managed,
- .badge-saas,
- .badge-contribute {
- &::after {
- content: '';
- display: block;
- width: 0;
- height: 1px;
- transition: width 0.3s;
- opacity: 0.5;
- }
- }
-
- .badge-tier,
- .badge-contribute {
- &::after {
- background: $badge-tier;
- }
- }
-
- .badge-self-managed {
- &::after {
- background: $badge-self-managed;
- }
- }
-
- .badge-saas {
- &::after {
- background: $badge-saas;
- }
- }
- &:hover {
- .badge-tier,
- .badge-self-managed,
- .badge-saas,
- .badge-contribute {
- &::after {
- width: 100%;
- }
- }
- }
-}
-
-.badge-tier {
- color: $badge-tier;
-}
-
-.badge-self-managed {
- color: $badge-self-managed;
- padding-left: 5px;
-}
-
-.badge-saas {
- color: $badge-saas;
- padding-left: 5px;
}
-
-.badge-contribute {
- color: $badge-tier;
- padding-left: 5px;
+// Badges in headings
+.badge-heading-true span.gl-badge {
+ vertical-align: super;
}
-
-.badge-small {
- color: $badge-self-managed;
- opacity: 0.6;
- transition: opacity 0.3s;
- &:hover {
- opacity: 1;
- &::after {
- content: attr(data-title);
- text-transform: uppercase;
- font-weight: 700;
- font-size: 0.7em;
- display: inline;
- position: absolute;
- padding: 1px 5px;
- opacity: 1;
- background-color: $white;
- white-space: nowrap;
- border-radius: 2px;
- max-width: 150px;
- }
- }
+// Badges in regular content
+.badge-heading-false span.gl-badge {
+ vertical-align: middle;
}
-.badge-display {
- text-transform: uppercase;
- text-align: center;
- display: inline-block;
- margin-left: 2px;
- border-radius: 2px;
+// Tooltips
+.tooltip {
font-size: 0.75rem;
- font-weight: 700;
- letter-spacing: 0.02em;
- line-height: 1.4;
}
//in-page styles
diff --git a/content/frontend/default/components/docs_badges.vue b/content/frontend/default/components/docs_badges.vue
new file mode 100644
index 00000000..d8722ac3
--- /dev/null
+++ b/content/frontend/default/components/docs_badges.vue
@@ -0,0 +1,71 @@
+<script>
+import { GlBadge, GlTooltipDirective, GlLink } from '@gitlab/ui';
+import { badgeIndex } from '../../../_data/badges.yaml';
+
+export default {
+ components: {
+ GlBadge,
+ GlLink,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ badgesData: {
+ type: Array,
+ required: true,
+ },
+ isHeading: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ badges: [],
+ };
+ },
+ created() {
+ // Pull in more badge info from the badges.yaml file
+ this.badgesData.forEach((badge) => {
+ this.badges.push(badgeIndex.find((obj) => obj.id.includes(badge.text)));
+ });
+ },
+ methods: {
+ getBadgeVariant(type) {
+ const variantMapping = {
+ tier: 'tier',
+ offering: 'info',
+ status: 'neutral',
+ content: 'warning',
+ };
+ return variantMapping[type] || '';
+ },
+ getBadgeSize(isHeading) {
+ return isHeading ? 'md' : 'sm';
+ },
+ },
+};
+</script>
+
+<template>
+ <span :class="`docs-badges-wrapper badge-heading-${isHeading} gl-ml-2`">
+ <gl-badge
+ v-for="badge in badges"
+ :key="badge"
+ v-gl-tooltip.top
+ :variant="getBadgeVariant(badge.type)"
+ :title="badge.text"
+ class="gl-mr-2"
+ :size="getBadgeSize(isHeading)"
+ >
+ <component
+ :is="badge.link ? 'GlLink' : 'span'"
+ :href="badge.link"
+ class="docs-badge gl-font-sm"
+ >
+ {{ badge.label }}
+ </component>
+ </gl-badge>
+ </span>
+</template>
diff --git a/content/frontend/default/default.js b/content/frontend/default/default.js
index 9b4b6053..f4c92c54 100644
--- a/content/frontend/default/default.js
+++ b/content/frontend/default/default.js
@@ -1,16 +1,63 @@
/* global Vue */
-import { getNextUntil } from '../shared/dom';
+import { getNextUntil, isContainedInHeading } from '../shared/dom';
import NavigationToggle from './components/navigation_toggle.vue';
import VersionBanner from './components/version_banner.vue';
import { setupTableOfContents } from './setup_table_of_contents';
import VersionsMenu from './components/versions_menu.vue';
import TabsSection from './components/tabs_section.vue';
+import DocsBadges from './components/docs_badges.vue';
/* eslint-disable no-new */
document.addEventListener('DOMContentLoaded', () => {
setupTableOfContents();
/**
+ * Badge components
+ *
+ * Badges are typically added in markdown and rendered by Nanoc as spans.
+ * Contributor docs have a section-wide badge added here.
+ */
+ const isContributorDocs = () => {
+ const paths = [
+ '/ee/development/',
+ '/omnibus/development/',
+ '/runner/development/',
+ '/charts/development/',
+ ];
+ return paths.some((substr) => window.location.pathname.startsWith(substr));
+ };
+ // Inject markup for our Contributor docs badge.
+ if (isContributorDocs()) {
+ document
+ .querySelector('h1 a')
+ .insertAdjacentHTML(
+ 'beforebegin',
+ ' <span data-component="docs-badges"><span data-type="content" data-value="contribute"></span></span>',
+ );
+ }
+ document.querySelectorAll('[data-component="docs-badges"]').forEach((badgeSet) => {
+ const badges = badgeSet.querySelectorAll('span');
+
+ // Get badges that were added to the heading
+ const badgesData = Array.from(badges).map((badge) => ({
+ type: badge.getAttribute('data-type'),
+ text: badge.getAttribute('data-value'),
+ }));
+
+ new Vue({
+ el: badgeSet,
+ components: {
+ DocsBadges,
+ },
+ render(createElement) {
+ return createElement(DocsBadges, {
+ props: { badgesData, isHeading: isContainedInHeading(badgeSet) },
+ });
+ },
+ });
+ });
+
+ /**
* Banner components
*/
const versionBanner = document.querySelector('#js-version-banner');
diff --git a/content/frontend/search/recently_viewed.js b/content/frontend/search/recently_viewed.js
index 5a69ee3b..a16e0f52 100644
--- a/content/frontend/search/recently_viewed.js
+++ b/content/frontend/search/recently_viewed.js
@@ -31,10 +31,6 @@ export const getCookie = (name) => {
// Writes page URLs to a cookie
export const trackPageHistory = () => {
- if (document.querySelector('h1') === null) {
- return;
- }
-
let pageHistory = [];
const currentPath = window.location.pathname;
const cookieValue = getCookie('pageHistory');
@@ -52,7 +48,7 @@ export const trackPageHistory = () => {
// Add the current page URL to the beginning of the history array
pageHistory.unshift({
path: currentPath,
- title: document.querySelector('h1').textContent,
+ title: document.title.replace(' | GitLab', ''),
});
// Keep only the designated amount of pages in history
diff --git a/content/frontend/shared/dom.js b/content/frontend/shared/dom.js
index 4e285a01..f632fe0d 100644
--- a/content/frontend/shared/dom.js
+++ b/content/frontend/shared/dom.js
@@ -29,3 +29,19 @@ export const getNextUntil = (el, selector) => {
return siblings.join('');
};
+
+/**
+ * Check if an element is contained within a heading.
+ *
+ * @param {Element} el
+ * @returns {Boolean}
+ */
+export const isContainedInHeading = (el) => {
+ // Loop upwards in the DOM through all parent elements, checking for headers.
+ for (let parent = el.parentNode; parent !== null; parent = parent.parentNode) {
+ if (parent.tagName && /^H\d$/.test(parent.tagName)) {
+ return true;
+ }
+ }
+ return false;
+};
diff --git a/layouts/default.html b/layouts/default.html
index f87e945f..ccc36f4b 100644
--- a/layouts/default.html
+++ b/layouts/default.html
@@ -82,7 +82,6 @@
<% end %>
<script src="<%= @items['/assets/javascripts/toggle_popover.*'].path %>"></script>
<script src="<%= @items['/frontend/shared/clipboardjs.*'].path %>"></script>
- <script src="<%= @items['/assets/javascripts/badges.*'].path %>"></script>
<% if @item.compiled_content.include? "load-mermaid" %>
<script src="/assets/vendor/mermaid.min.js"></script>
diff --git a/layouts/header.html b/layouts/header.html
index cc951de1..1aaed16a 100644
--- a/layouts/header.html
+++ b/layouts/header.html
@@ -1,4 +1,4 @@
-<nav class="navbar navbar-expand-md navbar-dark gl-py-0 gl-px-5 gl-sticky gl-top-0 gl-z-index-9999">
+<nav class="navbar navbar-expand-md navbar-dark gl-py-0 gl-px-5 gl-sticky gl-top-0 gl-z-index-200">
<a class="navbar-brand d-flex align-items-center justify-content-center mr-3" href="/">
<img src="<%= @items['/assets/images/gitlab-logo-header.svg'].path %>" alt="GitLab documentation home" class="logo" />
<span class="border-left border-light ml-2 pl-2">Docs</strong>
diff --git a/layouts/home.html b/layouts/home.html
index 4e57fe2e..eae9a047 100644
--- a/layouts/home.html
+++ b/layouts/home.html
@@ -16,7 +16,6 @@
<script src="<%= @items['/frontend/header/index.*'].path %>"></script>
<script src="<%= @items['/frontend/default/default.*'].path %>"></script>
- <script src="<%= @items['/assets/javascripts/badges.*'].path %>"></script>
<% if @config[:search_backend] == "google" %>
<script src="<%= @items['/frontend/search/google.*'].path %>"></script>
<script src="<%= @items['/frontend/search/recently_viewed.*'].path %>"></script>
diff --git a/lib/filters/badges.rb b/lib/filters/badges.rb
index 9fd8540b..3c4503bb 100644
--- a/lib/filters/badges.rb
+++ b/lib/filters/badges.rb
@@ -1,51 +1,93 @@
# frozen_string_literal: true
-# GitLab price / tiers badge
+# GitLab subscription tiers and offerings badges
#
# This allows us to add visual Badges to our documentation using standard Markdown
# that will render in any markdown editor.
#
-# The available pattern is either:
-# - `**(<BADGE_TYPE> <MODIFIER>)**` (preferred)
-# - `**[<BADGE_TYPE> <MODIFIER>]**` (deprecated)
+# Badges are defined in content/_data/badges.yaml.
#
-# The following TIERS are supported: CORE, STARTER, PREMIUM, ULTIMATE
-# The following MODIFIERS are supported: ONLY
+# The available pattern is:
+# - `**(<TIER BADGE> <OFFERING BADGE> <STATUS BADGE>)**`
#
-# When you have ONLY as MODIFIER, it means, it applies only for on premise instances
-# so we are not going to expand "STARTER" to "BRONZE" as well.
class BadgesFilter < Nanoc::Filter
identifier :badges
+ badge_data = YAML.load_file('content/_data/badges.yaml')
+ badge_types = %w[tier offering status].freeze
+
+ id_mapping = {}
+ badge_types.each do |type|
+ ids_with_given_type = badge_data["badgeIndex"].select { |item| item["type"] == type }.map { |item| item["id"].upcase }
+ id_mapping[type.upcase] = ids_with_given_type.join("|")
+ end
+
+ TIERS = id_mapping["TIER"]
+ OFFERINGS = id_mapping["OFFERING"]
+ STATUSES = id_mapping["STATUS"]
+
BADGES_HTML_PATTERN = %r{
<strong>
- [\[|(]
- (?<tier>CORE|STARTER|PREMIUM|ULTIMATE|FREE|BRONZE|SILVER|GOLD)(?:\s+(?<type>ONLY|SAAS|SELF))?
- [\]|)]
+ \(
+ (?:
+ (?:(?<tier>#{TIERS})(?:\s+(?<offering>#{OFFERINGS}))?(?:\s+(?<status>#{STATUSES}))?) # All three badge types
+ |
+ (?:(?<tier>#{TIERS})(?:\s+(?<offering>#{OFFERINGS}))?) # Tier and offering
+ |
+ (?:(?<tier>#{TIERS})(?:\s+(?<status>#{STATUSES}))?) # Tier and status
+ |
+ (?:(?<offering>#{OFFERINGS})(?:\s+(?<status>#{STATUSES}))?) # Offering and status
+ |
+ (?<tier>#{TIERS}) # Only tier
+ |
+ (?<offering>#{OFFERINGS}) # Only offering
+ |
+ (?<status>#{STATUSES}) # Only status
+ )
+ \)
</strong>
}x.freeze
BADGES_MARKDOWN_PATTERN = %r{
- (?:^|[^`]) # must be start of the line or anything except backtick
+ (?:^|[^`])
\*\*(?:\[|\()
- (?<tier>CORE|STARTER|PREMIUM|ULTIMATE|FREE|BRONZE|SILVER|GOLD)(?:\s+(?<type>ONLY|SAAS|SELF))
- ?(?:\]|\))\*\*
- (?:$|[^`]) # must end of line or anything except backtick
+ (?:
+ (?:(?<tier>#{TIERS})(?:\s+(?<offering>#{OFFERINGS}))?(?:\s+(?<status>#{STATUSES}))?) # All three badge types
+ |
+ (?:(?<tier>#{TIERS})(?:\s+(?<offering>#{OFFERINGS}))?) # Tier and offering
+ |
+ (?:(?<tier>#{TIERS})(?:\s+(?<status>#{STATUSES}))?) # Tier and status
+ |
+ (?:(?<offering>#{OFFERINGS})(?:\s+(?<status>#{STATUSES}))?) # Offering and status
+ |
+ (?<tier>#{TIERS}) # Only tier
+ |
+ (?<offering>#{OFFERINGS}) # Only offering
+ |
+ (?<status>#{STATUSES}) # Only status
+ )
+ (?:\]|\))\*\*
+ (?:$|[^`])
}x.freeze
def run(content, _params = {})
- content.gsub(BADGES_HTML_PATTERN) { generate(Regexp.last_match[:tier].downcase, Regexp.last_match[:type]) }
+ content.gsub(BADGES_HTML_PATTERN) { generate(Regexp.last_match[:tier], Regexp.last_match[:offering], Regexp.last_match[:status]) }
end
def run_from_markdown(content)
- content.gsub(BADGES_MARKDOWN_PATTERN) { generate(Regexp.last_match[:tier].downcase, Regexp.last_match[:type]) }
+ content.gsub(BADGES_MARKDOWN_PATTERN) { generate(Regexp.last_match[:tier], Regexp.last_match[:offering], Regexp.last_match[:status]) }
end
- def generate(tier, type)
- if type.nil?
- %(<span class="badge-trigger #{tier}"></span>)
- else
- %(<span class="badge-trigger #{tier}-#{type.downcase}"></span>)
- end
+ def generate(tier, offering, status)
+ badges = { tier: tier, offering: offering, status: status }
+ return "" if badges.empty?
+
+ badges_markup = badges.map do |key, value|
+ next if value.nil?
+
+ %(<span data-type="#{key}" data-value="#{value.downcase}"></span>)
+ end.join
+
+ %(<span data-component="docs-badges">#{badges_markup}</span>)
end
end
diff --git a/lib/filters/gitlab_kramdown.rb b/lib/filters/gitlab_kramdown.rb
index 63f4309f..3a872ffe 100644
--- a/lib/filters/gitlab_kramdown.rb
+++ b/lib/filters/gitlab_kramdown.rb
@@ -13,11 +13,16 @@ module Nanoc::Filters
PATCH
- PRODUCT_TIERS = %w[core starter premium ultimate free bronze silver gold].freeze
- PRODUCT_TYPES = %w[only saas self].freeze
- PRODUCT_SUFFIX = %r{-(#{PRODUCT_TIERS.join('|')})(?:-(#{PRODUCT_TYPES.join('|')}))?\z}.freeze
+ # Drop tier badges from the heading permalinks provided by GitLab Kramdown
+ badge_index = YAML.load_file('content/_data/badges.yaml')
+ badges = []
+ badge_index["badgeIndex"].each do |badge|
+ id = badge["id"]
+ badges << id unless badges.include?(id)
+ end
+ BADGE_SUFFIX = %r{(#{badges.join('|')})(?:-+(#{badges.join('|')}))?(?:-+(#{badges.join('|')}))?$}.freeze
- # Runs the content through [GitLab Kramdown](https://gitlab.com/brodock/gitlab_kramdown).
+ # Runs the content through [GitLab Kramdown](https://gitlab.com/gitlab-org/ruby/gems/gitlab_kramdown).
# Parameters passed to this filter will be passed on to Kramdown.
#
# @param [String] raw_content The content to filter
@@ -56,7 +61,7 @@ module Nanoc::Filters
headers = find_type_elements(:header, elements)
headers.each do |header|
- next unless header.attr['id'].match(PRODUCT_SUFFIX)
+ next unless header.attr['id'].match(BADGE_SUFFIX)
remove_product_suffix!(header, 'id')
@@ -67,7 +72,7 @@ module Nanoc::Filters
end
def remove_product_suffix!(element, attr)
- element.attr[attr] = element.attr[attr].gsub(PRODUCT_SUFFIX, '')
+ element.attr[attr] = element.attr[attr].gsub(BADGE_SUFFIX, '').gsub(%r{-+$}, '')
end
def find_type_elements(type, elements)
diff --git a/package.json b/package.json
index c19d7804..f906f7f6 100644
--- a/package.json
+++ b/package.json
@@ -27,6 +27,7 @@
"@rollup/plugin-replace": "^5.0.2",
"@rollup/plugin-terser": "^0.4.3",
"@rollup/plugin-url": "^8.0.1",
+ "@rollup/plugin-yaml": "^4.1.1",
"@vitejs/plugin-vue2": "^2.2.0",
"@vue/compat": "^3.2.47",
"@vue/compiler-sfc": "^3.2.47",
diff --git a/rollup.config.js b/rollup.config.js
index eb8c48a7..807aa462 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -9,6 +9,7 @@ const url = require('@rollup/plugin-url');
const vue = require('@vitejs/plugin-vue2');
const copy = require('rollup-plugin-copy');
const terser = require('@rollup/plugin-terser');
+const yaml = require('@rollup/plugin-yaml');
function mapDirectory(file) {
return file.replace('content/', 'public/');
@@ -60,6 +61,7 @@ module.exports = globSync('content/frontend/**/*.js')
'process.env.NODE_ENV': JSON.stringify('production'),
}),
terser(),
+ yaml(),
copy({
copyOnce: true,
hook: 'closeBundle',
diff --git a/spec/frontend/search/google_search_spec.js b/spec/frontend/search/google_search_spec.js
index 431ca0c0..cbf7f53f 100644
--- a/spec/frontend/search/google_search_spec.js
+++ b/spec/frontend/search/google_search_spec.js
@@ -152,7 +152,7 @@ describe('content/frontend/search/recently_viewed.js', () => {
it('should set a cookie with the current page URL and title', () => {
// Set up the DOM
- document.body.innerHTML = '<h1>Test Page</h1>';
+ document.title = 'Test Page | GitLab';
const location = {
...window.location,
pathname: '/test-page',
@@ -174,7 +174,7 @@ describe('content/frontend/search/recently_viewed.js', () => {
});
it('should limit the number of items in the history to RECENT_HISTORY_ITEMS', () => {
- document.body.innerHTML = '<h1>Test Page</h1>';
+ document.title = 'Test Page | GitLab';
// Set a cookie with RECENT_HISTORY_ITEMS pages in it, then track this page
setCookie('pageHistory', JSON.stringify(mockHistoryCookie), 365);
@@ -188,7 +188,7 @@ describe('content/frontend/search/recently_viewed.js', () => {
});
it('should not add duplicate history items', () => {
- document.body.innerHTML = '<h1>Test Page</h1>';
+ document.title = 'Test Page | GitLab';
// Set a cookie with the current page URL
const initialPageHistory = [{ path: '/test-page', title: 'Test Page' }];
@@ -204,15 +204,4 @@ describe('content/frontend/search/recently_viewed.js', () => {
expect(pageHistory[0].path).toBe('/test-page');
expect(pageHistory[0].title).toBe('Test Page');
});
-
- it('should not set a cookie if the page does not have a title', () => {
- // Set up the DOM without a h1 element
- document.body.innerHTML = '<p>Test Page</p>';
-
- trackPageHistory();
-
- // Check that the cookie was not set
- const cookieValue = getCookie('pageHistory');
- expect(cookieValue).toBeNull();
- });
});
diff --git a/spec/lib/filters/badges_spec.rb b/spec/lib/filters/badges_spec.rb
index aefbdefa..09d34353 100644
--- a/spec/lib/filters/badges_spec.rb
+++ b/spec/lib/filters/badges_spec.rb
@@ -12,23 +12,7 @@ describe BadgesFilter do
let(:content) { '<strong>(FREE)</strong>' }
it 'returns correct HTML' do
- expect(run).to eq('<span class="badge-trigger free"></span>')
- end
- end
-
- context 'when <strong>(PREMIUM)</strong> badge' do
- let(:content) { '<strong>(PREMIUM)</strong>' }
-
- it 'returns correct HTML' do
- expect(run).to eq('<span class="badge-trigger premium"></span>')
- end
- end
-
- context 'when <strong>(ULTIMATE)</strong> badge' do
- let(:content) { '<strong>(ULTIMATE)</strong>' }
-
- it 'returns correct HTML' do
- expect(run).to eq('<span class="badge-trigger ultimate"></span>')
+ expect(run).to eq('<span data-component="docs-badges"><span data-type="tier" data-value="free"></span></span>')
end
end
@@ -36,47 +20,31 @@ describe BadgesFilter do
let(:content) { '<strong>(FREE SELF)</strong>' }
it 'returns correct HTML' do
- expect(run).to eq('<span class="badge-trigger free-self"></span>')
- end
- end
-
- context 'when <strong>(PREMIUM SELF)</strong> badge' do
- let(:content) { '<strong>(PREMIUM SELF)</strong>' }
-
- it 'returns correct HTML' do
- expect(run).to eq('<span class="badge-trigger premium-self"></span>')
- end
- end
-
- context 'when <strong>(ULTIMATE SELF)</strong> badge' do
- let(:content) { '<strong>(ULTIMATE SELF)</strong>' }
-
- it 'returns correct HTML' do
- expect(run).to eq('<span class="badge-trigger ultimate-self"></span>')
+ expect(run).to eq('<span data-component="docs-badges"><span data-type="tier" data-value="free"></span><span data-type="offering" data-value="self"></span></span>')
end
end
- context 'when <strong>(FREE SAAS)</strong> badge' do
- let(:content) { '<strong>(FREE SAAS)</strong>' }
+ context 'when <strong>(FREE EXPERIMENT)</strong> badge' do
+ let(:content) { '<strong>(FREE EXPERIMENT)</strong>' }
it 'returns correct HTML' do
- expect(run).to eq('<span class="badge-trigger free-saas"></span>')
+ expect(run).to eq('<span data-component="docs-badges"><span data-type="tier" data-value="free"></span><span data-type="status" data-value="experiment"></span></span>')
end
end
- context 'when <strong>(PREMIUM SAAS)</strong> badge' do
- let(:content) { '<strong>(PREMIUM SAAS)</strong>' }
+ context 'when <strong>(SAAS EXPERIMENT)</strong> badge' do
+ let(:content) { '<strong>(SAAS EXPERIMENT)</strong>' }
it 'returns correct HTML' do
- expect(run).to eq('<span class="badge-trigger premium-saas"></span>')
+ expect(run).to eq('<span data-component="docs-badges"><span data-type="offering" data-value="saas"></span><span data-type="status" data-value="experiment"></span></span>')
end
end
- context 'when <strong>(ULTIMATE SAAS)</strong> badge' do
- let(:content) { '<strong>(ULTIMATE SAAS)</strong>' }
+ context 'when <strong>(BETA)</strong> badge' do
+ let(:content) { '<strong>(BETA)</strong>' }
it 'returns correct HTML' do
- expect(run).to eq('<span class="badge-trigger ultimate-saas"></span>')
+ expect(run).to eq('<span data-component="docs-badges"><span data-type="status" data-value="beta"></span></span>')
end
end
end
@@ -90,23 +58,7 @@ describe BadgesFilter do
let(:content) { '**(FREE)**' }
it 'returns correct HTML' do
- expect(run_from_markdown).to eq('<span class="badge-trigger free"></span>')
- end
- end
-
- context 'when **(PREMIUM)** badge' do
- let(:content) { '**(PREMIUM)**' }
-
- it 'returns correct HTML' do
- expect(run_from_markdown).to eq('<span class="badge-trigger premium"></span>')
- end
- end
-
- context 'when **(ULTIMATE)** badge' do
- let(:content) { '**(ULTIMATE)**' }
-
- it 'returns correct HTML' do
- expect(run_from_markdown).to eq('<span class="badge-trigger ultimate"></span>')
+ expect(run_from_markdown).to eq('<span data-component="docs-badges"><span data-type="tier" data-value="free"></span></span>')
end
end
@@ -114,47 +66,31 @@ describe BadgesFilter do
let(:content) { '**(FREE SELF)**' }
it 'returns correct HTML' do
- expect(run_from_markdown).to eq('<span class="badge-trigger free-self"></span>')
- end
- end
-
- context 'when **(PREMIUM SELF)** badge' do
- let(:content) { '**(PREMIUM SELF)**' }
-
- it 'returns correct HTML' do
- expect(run_from_markdown).to eq('<span class="badge-trigger premium-self"></span>')
- end
- end
-
- context 'when **(ULTIMATE SELF)** badge' do
- let(:content) { '**(ULTIMATE SELF)**' }
-
- it 'returns correct HTML' do
- expect(run_from_markdown).to eq('<span class="badge-trigger ultimate-self"></span>')
+ expect(run_from_markdown).to eq('<span data-component="docs-badges"><span data-type="tier" data-value="free"></span><span data-type="offering" data-value="self"></span></span>')
end
end
- context 'when **(FREE SAAS)** badge' do
- let(:content) { '**(FREE SAAS)**' }
+ context 'when **(FREE EXPERIMENT)** badge' do
+ let(:content) { '**(FREE EXPERIMENT)**' }
it 'returns correct HTML' do
- expect(run_from_markdown).to eq('<span class="badge-trigger free-saas"></span>')
+ expect(run_from_markdown).to eq('<span data-component="docs-badges"><span data-type="tier" data-value="free"></span><span data-type="status" data-value="experiment"></span></span>')
end
end
- context 'when **(PREMIUM SAAS)** badge' do
- let(:content) { '**(PREMIUM SAAS)**' }
+ context 'when **(SAAS EXPERIMENT)** badge' do
+ let(:content) { '**(SAAS EXPERIMENT)**' }
it 'returns correct HTML' do
- expect(run_from_markdown).to eq('<span class="badge-trigger premium-saas"></span>')
+ expect(run_from_markdown).to eq('<span data-component="docs-badges"><span data-type="offering" data-value="saas"></span><span data-type="status" data-value="experiment"></span></span>')
end
end
- context 'when **(ULTIMATE SAAS)** badge' do
- let(:content) { '**(ULTIMATE SAAS)**' }
+ context 'when **(BETA)** badge' do
+ let(:content) { '**(BETA)**' }
it 'returns correct HTML' do
- expect(run_from_markdown).to eq('<span class="badge-trigger ultimate-saas"></span>')
+ expect(run_from_markdown).to eq('<span data-component="docs-badges"><span data-type="status" data-value="beta"></span></span>')
end
end
end
diff --git a/yarn.lock b/yarn.lock
index 1ba1d5c5..de372d50 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1619,6 +1619,15 @@
make-dir "^3.1.0"
mime "^3.0.0"
+"@rollup/plugin-yaml@^4.1.1":
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/@rollup/plugin-yaml/-/plugin-yaml-4.1.1.tgz#c965acb5b4b707f424106a45e906eb9835b834d6"
+ integrity sha512-firWc3X2Ea5CWx2tOh/MzrsdxoX8ZUtG8RC+NQ7T04/cOereC64tDo4v1L+adTIJlmNko/Oaqn+uCol/t0qvbw==
+ dependencies:
+ "@rollup/pluginutils" "^5.0.1"
+ js-yaml "^4.1.0"
+ tosource "^2.0.0-alpha.3"
+
"@rollup/pluginutils@^5.0.1", "@rollup/pluginutils@^5.0.2":
version "5.0.2"
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.0.2.tgz#012b8f53c71e4f6f9cb317e311df1404f56e7a33"
@@ -6580,6 +6589,11 @@ to-regex-range@^5.0.1:
dependencies:
is-number "^7.0.0"
+tosource@^2.0.0-alpha.3:
+ version "2.0.0-alpha.3"
+ resolved "https://registry.yarnpkg.com/tosource/-/tosource-2.0.0-alpha.3.tgz#ef385dac9092e009bf25c018838ddaae436daeb6"
+ integrity sha512-KAB2lrSS48y91MzFPFuDg4hLbvDiyTjOVgaK7Erw+5AmZXNq4sFRVn8r6yxSLuNs15PaokrDRpS61ERY9uZOug==
+
tough-cookie@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874"