diff --git a/lib/assets/javascripts/_modules/collapsible-navigation.js b/lib/assets/javascripts/_modules/collapsible-navigation.js index 4e4f6426..0d8baad4 100644 --- a/lib/assets/javascripts/_modules/collapsible-navigation.js +++ b/lib/assets/javascripts/_modules/collapsible-navigation.js @@ -57,25 +57,31 @@ $toggleLabel.text(setOpen ? 'Collapse ' + $heading.text() : 'Expand ' + $heading.text()) } + /** + * Returns an absolute pathname to $target by combining it with window.location.href + * @param $target The target whose pathname to return. This may be an anchor with an absolute or relative href. + * @returns {string} The absolute pathname of $target + */ + function getAbsolutePath ($target) { + return new URL($target.attr('href'), window.location.href).pathname + } + function openActiveHeading () { var $activeElement var currentPath = window.location.pathname - var isActiveTrail = '[href*="' + currentPath + '"]' - // Add an exception for the root page, as every href includes / - if (currentPath === '/') { - isActiveTrail = '[href="' + currentPath + window.location.hash + '"]' - } for (var i = $topLevelItems.length - 1; i >= 0; i--) { var $element = $($topLevelItems[i]) var $heading = $element.find('> a') // Check if this item href matches - if ($heading.is(isActiveTrail)) { + if (getAbsolutePath($heading) === currentPath) { $activeElement = $element break } // Otherwise check the children var $children = $element.find('li > a') - var $matchingChildren = $children.filter(isActiveTrail) + var $matchingChildren = $children.filter(function (_) { + return getAbsolutePath($(this)) === currentPath + }) if ($matchingChildren.length) { $activeElement = $element break diff --git a/lib/assets/javascripts/_modules/in-page-navigation.js b/lib/assets/javascripts/_modules/in-page-navigation.js index 8274c876..0848bfb6 100644 --- a/lib/assets/javascripts/_modules/in-page-navigation.js +++ b/lib/assets/javascripts/_modules/in-page-navigation.js @@ -70,13 +70,18 @@ function highlightActiveItemInToc (fragment) { // Navigation items for single page navigation don't necessarily include the path name, but // navigation items for multipage navigation items do include it. This checks for either case. - var $activeTocItem = $tocItems.filter( - '[href="' + window.location.pathname + fragment + '"],[href="' + fragment + '"]' - ) + var $activeTocItem = $tocItems.filter(function (_) { + var url = new URL($(this).attr('href'), window.location.href) + return url.href === window.location.href + }) + // Navigation items with children don't contain fragments in their url // Check to see if any nav items contain just the path name. if (!$activeTocItem.get(0)) { - $activeTocItem = $tocItems.filter('[href="' + window.location.pathname + '"]') + $activeTocItem = $tocItems.filter(function (_) { + var url = new URL($(this).attr('href'), window.location.href) + return url.hash === '' && url.pathname === window.location.pathname + }) } if ($activeTocItem.get(0)) { $tocItems.removeClass('toc-link--in-view') diff --git a/lib/assets/javascripts/_modules/search.js b/lib/assets/javascripts/_modules/search.js index 48f6a0b0..0fa20ecf 100644 --- a/lib/assets/javascripts/_modules/search.js +++ b/lib/assets/javascripts/_modules/search.js @@ -16,7 +16,7 @@ var results var query var maxSearchEntries = 20 - var searchIndexPath + var pathToSiteRoot this.start = function start ($element) { $searchForm = $element.find('form') @@ -26,7 +26,7 @@ $searchResults = $searchResultsWrapper.find('.search-results__content') $searchResultsTitle = $searchResultsWrapper.find('.search-results__title') $searchHelp = $('#search-help') - searchIndexPath = $element.data('searchIndexPath') + pathToSiteRoot = $element.data('pathToSiteRoot') changeSearchAction() changeSearchLabel() @@ -40,7 +40,7 @@ query = s.getQuery() if (query) { $searchInput.val(query) - doSearch(query) + doSearch(query, pathToSiteRoot) doAnalytics() document.title = query + ' - ' + document.title } @@ -51,7 +51,7 @@ this.downloadSearchIndex = function downloadSearchIndex () { updateTitle('Loading search results') $.ajax({ - url: searchIndexPath, + url: pathToSiteRoot + 'search.json', cache: true, method: 'GET', success: function (data) { @@ -67,7 +67,7 @@ // We need JavaScript to do search, so if JS is not available the search // input sends the query string to Google. This JS function changes the // input to instead send it to the search page. - $searchForm.prop('action', '/search') + $searchForm.prop('action', pathToSiteRoot + 'search/index.html') $searchForm.find('input[name="as_sitesearch"]').remove() } @@ -88,10 +88,10 @@ return query } - function doSearch (query) { + function doSearch (query, pathToSiteRoot) { s.search(query, function (r) { results = r - renderResults(query) + renderResults(query, pathToSiteRoot) updateTitle() }) } @@ -140,7 +140,7 @@ callback(getResults(query)) } - function renderResults (query) { + function renderResults (query, pathToSiteRoot) { var output = '' if (results.length === 0) { output += '

Nothing found

' @@ -151,7 +151,9 @@ var content = s.processContent(result.content, query) output += '
  • ' output += '

    ' - output += '' + var pagePathWithoutLeadingSlash = result.url.startsWith('/') ? result.url.slice(1) : result.url + var url = pathToSiteRoot.startsWith('.') ? pathToSiteRoot + pagePathWithoutLeadingSlash : '/' + pagePathWithoutLeadingSlash + output += '' output += result.title output += '' output += '

    ' diff --git a/lib/govuk_tech_docs.rb b/lib/govuk_tech_docs.rb index cd19219f..4cacd978 100644 --- a/lib/govuk_tech_docs.rb +++ b/lib/govuk_tech_docs.rb @@ -67,6 +67,7 @@ def self.configure(context, options = {}) context.activate :api_reference context.helpers do + include GovukTechDocs::PathHelpers include GovukTechDocs::TableOfContents::Helpers include GovukTechDocs::ContributionBanner diff --git a/lib/govuk_tech_docs/pages.rb b/lib/govuk_tech_docs/pages.rb index dfe9bcf7..32445d6b 100644 --- a/lib/govuk_tech_docs/pages.rb +++ b/lib/govuk_tech_docs/pages.rb @@ -1,10 +1,12 @@ module GovukTechDocs class Pages + include GovukTechDocs::PathHelpers attr_reader :sitemap - def initialize(sitemap, config) + def initialize(sitemap, config, current_page) @sitemap = sitemap @config = config + @current_page = current_page end def to_json(*_args) @@ -18,7 +20,7 @@ def as_json review = PageReview.new(page, @config) { title: page.data.title, - url: "#{@config[:tech_docs][:host]}#{page.url}", + url: get_path_to_resource(@config, page, @current_page).to_s, review_by: review.review_by, owner_slack: review.owner_slack, } diff --git a/lib/govuk_tech_docs/path_helpers.rb b/lib/govuk_tech_docs/path_helpers.rb new file mode 100644 index 00000000..8fdad288 --- /dev/null +++ b/lib/govuk_tech_docs/path_helpers.rb @@ -0,0 +1,30 @@ +module GovukTechDocs + module PathHelpers + def get_path_to_resource(config, resource, current_page) + if config[:relative_links] + resource_path_segments = resource.path.split("/").reject(&:empty?)[0..-2] + resource_file_name = resource.path.split("/")[-1] + + path_to_site_root = path_to_site_root config, current_page.path + resource_path = path_to_site_root + resource_path_segments + .push(resource_file_name) + .join("/") + else + resource_path = resource.url + end + resource_path + end + + def path_to_site_root(config, page_path) + if config[:relative_links] + number_of_ascents_to_site_root = page_path.to_s.split("/").reject(&:empty?)[0..-2].length + ascents = number_of_ascents_to_site_root.zero? ? ["."] : number_of_ascents_to_site_root.times.collect { ".." } + path_to_site_root = ascents.join("/").concat("/") + else + middleman_http_prefix = config[:http_prefix] + path_to_site_root = middleman_http_prefix.end_with?("/") ? middleman_http_prefix : "#{middleman_http_prefix}/" + end + path_to_site_root + end + end +end diff --git a/lib/govuk_tech_docs/table_of_contents/helpers.rb b/lib/govuk_tech_docs/table_of_contents/helpers.rb index d2cb3730..b83c7109 100644 --- a/lib/govuk_tech_docs/table_of_contents/helpers.rb +++ b/lib/govuk_tech_docs/table_of_contents/helpers.rb @@ -1,3 +1,4 @@ +require "govuk_tech_docs/path_helpers" require "govuk_tech_docs/table_of_contents/heading_tree_builder" require "govuk_tech_docs/table_of_contents/heading_tree_renderer" require "govuk_tech_docs/table_of_contents/heading_tree" @@ -7,6 +8,8 @@ module GovukTechDocs module TableOfContents module Helpers + include GovukTechDocs::PathHelpers + def single_page_table_of_contents(html, url: "", max_level: nil) output = " + } + + expect(subject.multi_page_table_of_contents(resources, current_page, config, current_page_html).strip).to eq(expected_multi_page_table_of_contents.strip) + end + + it "builds a table of contents using relative links from several page resources" do + resources = [] + resources[0] = FakeResource.new("/index.html", '

    Heading one

    Heading two

    ', 10, "Index"); + resources[1] = FakeResource.new("/1/2/a.html", '

    Heading one

    Heading two

    ', 20, "Sub page A", resources[0]); + resources[2] = FakeResource.new("/1/2/3/b.html", '

    Heading one

    Heading two

    ', 30, "Sub page A", resources[0]); + resources[3] = FakeResource.new("/1/2/3/4/c.html", '

    Heading one

    Heading two

    ', 40, "Sub page B", resources[0]); + resources[4] = FakeResource.new("/1/5/d.html", '

    Heading one

    Heading two

    ', 50, "Sub page A", resources[0]); + resources[5] = FakeResource.new("/1/5/6/e.html", '

    Heading one

    Heading two

    ', 60, "Sub page A", resources[0]); + resources[0].add_children [resources[1], resources[2], resources[3], resources[4], resources[5]] + + current_page = double("current_page", + data: double("page_frontmatter", description: "The description.", title: "The Title"), + url: "/1/2/3/index.html", + path: "/1/2/3/index.html", + metadata: { locals: {} }) + + current_page_html = '

    Heading one

    Heading two

    '; + + config = { + http_prefix: "/", + relative_links: true, + tech_docs: { + max_toc_heading_level: 3, + }, + } + + expected_multi_page_table_of_contents = %{ +