Skip to content

Commit 66cc7ab

Browse files
authored
Merge pull request #291 from hmrc/TRG-128-for-GDS-review
Support sites deployed on paths other than "/" (by generating relative links)
2 parents 9715d10 + d49fef0 commit 66cc7ab

File tree

13 files changed

+278
-44
lines changed

13 files changed

+278
-44
lines changed

lib/assets/javascripts/_modules/collapsible-navigation.js

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,25 +57,31 @@
5757
$toggleLabel.text(setOpen ? 'Collapse ' + $heading.text() : 'Expand ' + $heading.text())
5858
}
5959

60+
/**
61+
* Returns an absolute pathname to $target by combining it with window.location.href
62+
* @param $target The target whose pathname to return. This may be an anchor with an absolute or relative href.
63+
* @returns {string} The absolute pathname of $target
64+
*/
65+
function getAbsolutePath ($target) {
66+
return new URL($target.attr('href'), window.location.href).pathname
67+
}
68+
6069
function openActiveHeading () {
6170
var $activeElement
6271
var currentPath = window.location.pathname
63-
var isActiveTrail = '[href*="' + currentPath + '"]'
64-
// Add an exception for the root page, as every href includes /
65-
if (currentPath === '/') {
66-
isActiveTrail = '[href="' + currentPath + window.location.hash + '"]'
67-
}
6872
for (var i = $topLevelItems.length - 1; i >= 0; i--) {
6973
var $element = $($topLevelItems[i])
7074
var $heading = $element.find('> a')
7175
// Check if this item href matches
72-
if ($heading.is(isActiveTrail)) {
76+
if (getAbsolutePath($heading) === currentPath) {
7377
$activeElement = $element
7478
break
7579
}
7680
// Otherwise check the children
7781
var $children = $element.find('li > a')
78-
var $matchingChildren = $children.filter(isActiveTrail)
82+
var $matchingChildren = $children.filter(function (_) {
83+
return getAbsolutePath($(this)) === currentPath
84+
})
7985
if ($matchingChildren.length) {
8086
$activeElement = $element
8187
break

lib/assets/javascripts/_modules/in-page-navigation.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,18 @@
7070
function highlightActiveItemInToc (fragment) {
7171
// Navigation items for single page navigation don't necessarily include the path name, but
7272
// navigation items for multipage navigation items do include it. This checks for either case.
73-
var $activeTocItem = $tocItems.filter(
74-
'[href="' + window.location.pathname + fragment + '"],[href="' + fragment + '"]'
75-
)
73+
var $activeTocItem = $tocItems.filter(function (_) {
74+
var url = new URL($(this).attr('href'), window.location.href)
75+
return url.href === window.location.href
76+
})
77+
7678
// Navigation items with children don't contain fragments in their url
7779
// Check to see if any nav items contain just the path name.
7880
if (!$activeTocItem.get(0)) {
79-
$activeTocItem = $tocItems.filter('[href="' + window.location.pathname + '"]')
81+
$activeTocItem = $tocItems.filter(function (_) {
82+
var url = new URL($(this).attr('href'), window.location.href)
83+
return url.hash === '' && url.pathname === window.location.pathname
84+
})
8085
}
8186
if ($activeTocItem.get(0)) {
8287
$tocItems.removeClass('toc-link--in-view')

lib/assets/javascripts/_modules/search.js

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
var results
1717
var query
1818
var maxSearchEntries = 20
19-
var searchIndexPath
19+
var pathToSiteRoot
2020

2121
this.start = function start ($element) {
2222
$searchForm = $element.find('form')
@@ -26,7 +26,7 @@
2626
$searchResults = $searchResultsWrapper.find('.search-results__content')
2727
$searchResultsTitle = $searchResultsWrapper.find('.search-results__title')
2828
$searchHelp = $('#search-help')
29-
searchIndexPath = $element.data('searchIndexPath')
29+
pathToSiteRoot = $element.data('pathToSiteRoot')
3030

3131
changeSearchAction()
3232
changeSearchLabel()
@@ -40,7 +40,7 @@
4040
query = s.getQuery()
4141
if (query) {
4242
$searchInput.val(query)
43-
doSearch(query)
43+
doSearch(query, pathToSiteRoot)
4444
doAnalytics()
4545
document.title = query + ' - ' + document.title
4646
}
@@ -51,7 +51,7 @@
5151
this.downloadSearchIndex = function downloadSearchIndex () {
5252
updateTitle('Loading search results')
5353
$.ajax({
54-
url: searchIndexPath,
54+
url: pathToSiteRoot + 'search.json',
5555
cache: true,
5656
method: 'GET',
5757
success: function (data) {
@@ -67,7 +67,7 @@
6767
// We need JavaScript to do search, so if JS is not available the search
6868
// input sends the query string to Google. This JS function changes the
6969
// input to instead send it to the search page.
70-
$searchForm.prop('action', '/search')
70+
$searchForm.prop('action', pathToSiteRoot + 'search/index.html')
7171
$searchForm.find('input[name="as_sitesearch"]').remove()
7272
}
7373

@@ -88,10 +88,10 @@
8888
return query
8989
}
9090

91-
function doSearch (query) {
91+
function doSearch (query, pathToSiteRoot) {
9292
s.search(query, function (r) {
9393
results = r
94-
renderResults(query)
94+
renderResults(query, pathToSiteRoot)
9595
updateTitle()
9696
})
9797
}
@@ -140,7 +140,7 @@
140140
callback(getResults(query))
141141
}
142142

143-
function renderResults (query) {
143+
function renderResults (query, pathToSiteRoot) {
144144
var output = ''
145145
if (results.length === 0) {
146146
output += '<p>Nothing found</p>'
@@ -151,7 +151,9 @@
151151
var content = s.processContent(result.content, query)
152152
output += '<li class="search-result">'
153153
output += '<h3 class="search-result__title">'
154-
output += '<a href="' + result.url + '">'
154+
var pagePathWithoutLeadingSlash = result.url.startsWith('/') ? result.url.slice(1) : result.url
155+
var url = pathToSiteRoot.startsWith('.') ? pathToSiteRoot + pagePathWithoutLeadingSlash : '/' + pagePathWithoutLeadingSlash
156+
output += '<a href="' + url + '">'
155157
output += result.title
156158
output += '</a>'
157159
output += '</h3>'

lib/govuk_tech_docs.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ def self.configure(context, options = {})
6767
context.activate :api_reference
6868

6969
context.helpers do
70+
include GovukTechDocs::PathHelpers
7071
include GovukTechDocs::TableOfContents::Helpers
7172
include GovukTechDocs::ContributionBanner
7273

lib/govuk_tech_docs/pages.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
module GovukTechDocs
22
class Pages
3+
include GovukTechDocs::PathHelpers
34
attr_reader :sitemap
45

5-
def initialize(sitemap, config)
6+
def initialize(sitemap, config, current_page)
67
@sitemap = sitemap
78
@config = config
9+
@current_page = current_page
810
end
911

1012
def to_json(*_args)
@@ -18,7 +20,7 @@ def as_json
1820
review = PageReview.new(page, @config)
1921
{
2022
title: page.data.title,
21-
url: "#{@config[:tech_docs][:host]}#{page.url}",
23+
url: get_path_to_resource(@config, page, @current_page).to_s,
2224
review_by: review.review_by,
2325
owner_slack: review.owner_slack,
2426
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
module GovukTechDocs
2+
module PathHelpers
3+
def get_path_to_resource(config, resource, current_page)
4+
if config[:relative_links]
5+
resource_path_segments = resource.path.split("/").reject(&:empty?)[0..-2]
6+
resource_file_name = resource.path.split("/")[-1]
7+
8+
path_to_site_root = path_to_site_root config, current_page.path
9+
resource_path = path_to_site_root + resource_path_segments
10+
.push(resource_file_name)
11+
.join("/")
12+
else
13+
resource_path = resource.url
14+
end
15+
resource_path
16+
end
17+
18+
def path_to_site_root(config, page_path)
19+
if config[:relative_links]
20+
number_of_ascents_to_site_root = page_path.to_s.split("/").reject(&:empty?)[0..-2].length
21+
ascents = number_of_ascents_to_site_root.zero? ? ["."] : number_of_ascents_to_site_root.times.collect { ".." }
22+
path_to_site_root = ascents.join("/").concat("/")
23+
else
24+
middleman_http_prefix = config[:http_prefix]
25+
path_to_site_root = middleman_http_prefix.end_with?("/") ? middleman_http_prefix : "#{middleman_http_prefix}/"
26+
end
27+
path_to_site_root
28+
end
29+
end
30+
end

lib/govuk_tech_docs/table_of_contents/helpers.rb

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
require "govuk_tech_docs/path_helpers"
12
require "govuk_tech_docs/table_of_contents/heading_tree_builder"
23
require "govuk_tech_docs/table_of_contents/heading_tree_renderer"
34
require "govuk_tech_docs/table_of_contents/heading_tree"
@@ -7,6 +8,8 @@
78
module GovukTechDocs
89
module TableOfContents
910
module Helpers
11+
include GovukTechDocs::PathHelpers
12+
1013
def single_page_table_of_contents(html, url: "", max_level: nil)
1114
output = "<ul>\n"
1215
output += list_items_from_headings(html, url: url, max_level: max_level)
@@ -48,11 +51,7 @@ def render_page_tree(resources, current_page, config, current_page_html)
4851

4952
# Reuse the generated content for the active page
5053
# If we generate it twice it increments the heading ids
51-
content = if current_page.url == resource.url && current_page_html
52-
current_page_html
53-
else
54-
resource.render(layout: false)
55-
end
54+
content = current_page.url == resource.url && current_page_html ? current_page_html : resource.render(layout: false)
5655
# Avoid redirect pages
5756
next if content.include? "http-equiv=refresh"
5857

@@ -71,15 +70,16 @@ def render_page_tree(resources, current_page, config, current_page_html)
7170
config[:http_prefix] + "/"
7271
end
7372

73+
link_value = get_path_to_resource(config, resource, current_page)
7474
if resource.children.any? && resource.url != home_url
75-
output += %{<li><a href="#{resource.url}"><span>#{resource.data.title}</span></a>\n}
75+
output += %{<li><a href="#{link_value}"><span>#{resource.data.title}</span></a>\n}
7676
output += render_page_tree(resource.children, current_page, config, current_page_html)
7777
output += "</li>\n"
7878
else
7979
output +=
8080
list_items_from_headings(
8181
content,
82-
url: resource.url,
82+
url: link_value,
8383
max_level: config[:tech_docs][:max_toc_heading_level],
8484
)
8585
end

lib/source/api/pages.json.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<%= GovukTechDocs::Pages.new(sitemap, config).to_json %>
1+
<%= GovukTechDocs::Pages.new(sitemap, config, current_page).to_json %>

lib/source/layouts/_search.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<% if config[:tech_docs][:enable_search] %>
2-
<div class="search" data-module="search" data-search-index-path="<%= search_index_path %>">
2+
<div class="search" data-module="search" data-path-to-site-root="<%= path_to_site_root config, current_page.path %>">
33
<form action="https://www.google.co.uk/search" method="get" role="search" class="search__form govuk-!-margin-bottom-4">
44
<input type="hidden" name="as_sitesearch" value="<%= config[:tech_docs][:host] %>"/>
55
<label class="govuk-label search__label" for="search" aria-hidden="true">

spec/govuk_tech_docs/pages_spec.rb

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,33 @@
11
RSpec.describe GovukTechDocs::Pages do
22
describe "#to_json" do
3-
it "returns the pages as JSON" do
3+
it "returns the pages as JSON when using absolute links" do
4+
current_page = double(path: "/api/pages.json")
45
sitemap = double(resources: [
56
double(url: "/a.html", data: double(title: "A thing", owner_slack: "#2ndline", last_reviewed_on: Date.yesterday, review_in: "0 days")),
67
double(url: "/b.html", data: double(title: "B thing", owner_slack: "#2ndline", last_reviewed_on: Date.yesterday, review_in: "2 days")),
78
])
89

9-
json = described_class.new(sitemap, tech_docs: {}).to_json
10+
json = described_class.new(sitemap, {}, current_page).to_json
1011

1112
expect(JSON.parse(json)).to eql([
1213
{ "title" => "A thing", "url" => "/a.html", "review_by" => Date.yesterday.to_s, "owner_slack" => "#2ndline" },
1314
{ "title" => "B thing", "url" => "/b.html", "review_by" => Date.tomorrow.to_s, "owner_slack" => "#2ndline" },
1415
])
1516
end
17+
18+
it "returns the pages as JSON when using relative links" do
19+
current_page = double(path: "/api/pages.json")
20+
sitemap = double(resources: [
21+
double(url: "/a.html", path: "/a.html", data: double(title: "A thing", owner_slack: "#2ndline", last_reviewed_on: Date.yesterday, review_in: "0 days")),
22+
double(url: "/b/c.html", path: "/b/c.html", data: double(title: "B thing", owner_slack: "#2ndline", last_reviewed_on: Date.yesterday, review_in: "2 days")),
23+
])
24+
25+
json = described_class.new(sitemap, { relative_links: true }, current_page).to_json
26+
27+
expect(JSON.parse(json)).to eql([
28+
{ "title" => "A thing", "url" => "../a.html", "review_by" => Date.yesterday.to_s, "owner_slack" => "#2ndline" },
29+
{ "title" => "B thing", "url" => "../b/c.html", "review_by" => Date.tomorrow.to_s, "owner_slack" => "#2ndline" },
30+
])
31+
end
1632
end
1733
end

0 commit comments

Comments
 (0)