diff options
author | Sarah German <sgerman@gitlab.com> | 2023-08-15 01:05:37 +0300 |
---|---|---|
committer | David O'Regan <doregan@gitlab.com> | 2023-08-15 01:05:37 +0300 |
commit | cd05967b7f674a4a778b7c6955ede163df5aecb4 (patch) | |
tree | b21d4516fba812b08d862581e629167e0c12a04f | |
parent | 95a4eb4fa3920264599a2d53bd6a311d3ab5f8d8 (diff) |
Refactor tier badges
-rw-r--r-- | content/_data/badges.yaml | 44 | ||||
-rw-r--r-- | content/assets/javascripts/badges.js | 182 | ||||
-rw-r--r-- | content/assets/stylesheets/_tables.scss | 4 | ||||
-rw-r--r-- | content/assets/stylesheets/_typography.scss | 19 | ||||
-rw-r--r-- | content/assets/stylesheets/stylesheet.scss | 114 | ||||
-rw-r--r-- | content/frontend/default/components/docs_badges.vue | 71 | ||||
-rw-r--r-- | content/frontend/default/default.js | 49 | ||||
-rw-r--r-- | content/frontend/search/recently_viewed.js | 6 | ||||
-rw-r--r-- | content/frontend/shared/dom.js | 16 | ||||
-rw-r--r-- | layouts/default.html | 1 | ||||
-rw-r--r-- | layouts/header.html | 2 | ||||
-rw-r--r-- | layouts/home.html | 1 | ||||
-rw-r--r-- | lib/filters/badges.rb | 88 | ||||
-rw-r--r-- | lib/filters/gitlab_kramdown.rb | 17 | ||||
-rw-r--r-- | package.json | 1 | ||||
-rw-r--r-- | rollup.config.js | 2 | ||||
-rw-r--r-- | spec/frontend/search/google_search_spec.js | 17 | ||||
-rw-r--r-- | spec/lib/filters/badges_spec.rb | 108 | ||||
-rw-r--r-- | yarn.lock | 14 |
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 751c5b18..2890e5fd 100644 --- a/content/assets/stylesheets/stylesheet.scss +++ b/content/assets/stylesheets/stylesheet.scss @@ -369,115 +369,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 938b07b5..c179bf2d 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 @@ -1635,6 +1635,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" @@ -6596,6 +6605,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" |