Replace JavaScript link fix with custom plugin

This replaces the JavaScript link fix script with a custom plugin,
based on the jekyll-relative-link plugin, and modified so that it
can be used as Liquid "filter".

While it borrows from the jekyll-relative-links plugin, it takes some shortcuts;

- We use the code from jekyll-relative-links plugin to find/extract
  links on the page
- Relative links are converted to absolute links, using the path of
  the markdown source file that's passed as argument
- After conversion to an absolute link, we strip the ".md" extension;
  no attempt is made to resolve the file that's linked to. This is
  different from the jekyll-relative-links plugin, which _does_ resolve
  the linked file. This functionality could be added in future by
  someone who has more experience with Ruby.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn 2020-04-25 16:48:09 +02:00
parent ecdbded6b2
commit 9cab4195c0
No known key found for this signature in database
GPG Key ID: 76698F39D527CE8C
2 changed files with 117 additions and 58 deletions

View File

@ -4,7 +4,7 @@
## Description
{{ controller_data.short }}
{{ controller_data.short | replace_relative_links: page.path }}
{% if controller_data.min_api_version %}
@ -89,7 +89,7 @@ your client and daemon API versions.
## Extended description
{{ controller_data.long }}
{{ controller_data.long | replace_relative_links: page.path }}
{% endunless %}
@ -137,7 +137,7 @@ For example uses of this command, refer to the [examples section](#examples) bel
## Examples
{{ controller_data.examples }}
{{ controller_data.examples | replace_relative_links: page.path }}
{% endif %}
@ -203,58 +203,3 @@ For example uses of this command, refer to the [examples section](#examples) bel
</table>
{% endunless %}
<script>
// This is a horrible hack, and cute little kittens are sacrificed every time it's run, so we
// need to remove it as soon as possible.
//
// Fix up links to markdown pages that weren't resolved by Jekyll (or the "jekyll-relative-links"
// plugin). This is a horrible hack, and should not rely on JavaScript (perhaps be re-implemented
// using Liquid). We need this hack to work around two bugs in the "jekyll-relative-links" plugin;
// 1. As reported in https://github.com/benbalter/jekyll-relative-links/issues/54, (relative) links
// to markdown pages in includes are not processed by Jekyll. This means that (for example) our
// reference pages (which use includes) contain broken links. We can work around this by modifying
// the markdown for those pages to use "absolute" "html" links (/link/to/other/page/#some-anchor),
// but doing so would render the links broken when viewed on GitHub. Instead, we're fixing them up
// here, hoping the bug will be fixed, and it's only temporarily.
// 2. As reported in https://github.com/benbalter/jekyll-relative-links/issues/61, (relative) links
// to markdown pages are not resolved if the link's caption/title is wrapped.
//
Array.prototype.forEach.call(document.querySelectorAll("section.section a:not(.nomunge)"), function (el) {
let href = el.getAttribute("href");
if (href.startsWith("/") || href.startsWith("#") || href.includes("://") || !href.includes('.md')) {
// Don't modify anchor links, absolute links, links to external websites,
// or links not pointing to a .md file; we assume those were
// resolved successfully by Jekyll.
return
}
if (href.startsWith("./")) {
href = href.substr(2)
}
if ("{{ page.name }}" !== "index.md") {
// For non-index pages, things are a bit hairy. For example, for /foo/bar/mypage.md, Jekyll
// will generate a page named /foo/bar/mypage/index.html. This means that all links relative
// to mypage.md expect those links to be relative to the /foo/bar/ directory, but end up
// being relative to /foo/bar/mypage/.
//
// For files "next to", or "below" this file, such as "file.md" or "nested/dir/file.md" we
// prepend the "parent-dir" to the URL.
//
// For links to files "up" the directory tree, we prepend
// "../" to the URL and have the browser handle this. For example, "../file.md" and "../../file.md"
// become "../../file.md" and "../../../file.md".
if (href.startsWith("../")) {
href = "../" + href
} else {
// Generate "parentPath" with Liquid, which is used below. Liquid's page.path (and page.dir)
// are relative to the _generated_ HTML page, not the source page, so we have to remove the
// last part of the path:
// {% raw %}{% assign parentPath = page.path | prepend: "/" | remove: page.name %}{% endraw %}
// {% assign parentPath = page.path | prepend: "/" | remove: page.name %}
href = "{{ parentPath}}" + href
}
}
// finally, we replace the .md extension for a slash, and update the link's href
el.setAttribute("href", href.replace(".md", "/"))
});
</script>

View File

@ -0,0 +1,114 @@
module Jekyll
# This custom Filter is used to fix up links to markdown pages that weren't
# resolved by Jekyll (or the "jekyll-relative-links" plugin). We need this hack
# to work around a bug in the "jekyll-relative-links" plugin;
#
# As reported in https://github.com/benbalter/jekyll-relative-links/issues/54,
# (relative) links to markdown pages in includes are not processed by Jekyll.
# This means that our reference pages (which use includes) have broken links.
# We could work around this by modifying the markdown for those pages to use
# "absolute" "html" links (/link/to/other/page/#some-anchor), but doing so
# would render the links broken when viewed on GitHub. Instead, we're fixing
# them up here, until the bug is fixed upstream.
#
# A second bug (https://github.com/benbalter/jekyll-relative-links/issues/61),
# causes (relative) links to markdown pages to not be resolved if the link's
# caption/title is wrapped. This bug is currently not handled by this plugin,
# but could possibly be addressed by modifying the TITLE_REGEX.
#
# This plugin is based on code in the jekyll-relative-links plugin, but takes
# some shortcuts;
#
# - We use the code from jekyll-relative-links plugin to find/extract links
# on the page
# - Relative links are converted to absolute links, using the path of the
# markdown source file that's passed as argument
# - After conversion to an absolute link, we strip the ".md" extension; no
# attempt is made to resolve the file that's linked to. This is different
# from the jekyll-relative-links plugin, which _does_ resolve the linked
# file. This functionality could be added in future by someone who has
# more experience with Ruby.
module RelativeLinksFilter
attr_accessor :site, :config
# Use Jekyll's native relative_url filter
include Jekyll::Filters::URLFilters
LINK_TEXT_REGEX = %r!(.*?)!.freeze
FRAGMENT_REGEX = %r!(#.+?)?!.freeze
TITLE_REGEX = %r{(\s+"(?:\\"|[^"])*(?<!\\)"|\s+"(?:\\'|[^'])*(?<!\\)')?}.freeze
FRAG_AND_TITLE_REGEX = %r!#{FRAGMENT_REGEX}#{TITLE_REGEX}!.freeze
INLINE_LINK_REGEX = %r!\[#{LINK_TEXT_REGEX}\]\(([^\)]+?)#{FRAG_AND_TITLE_REGEX}\)!.freeze
REFERENCE_LINK_REGEX = %r!^\s*?\[#{LINK_TEXT_REGEX}\]: (.+?)#{FRAG_AND_TITLE_REGEX}\s*?$!.freeze
LINK_REGEX = %r!(#{INLINE_LINK_REGEX}|#{REFERENCE_LINK_REGEX})!.freeze
def replace_relative_links(input, source_path)
url_base = File.dirname("/" + source_path)
input = input.dup.gsub(LINK_REGEX) do |original|
link = link_parts(Regexp.last_match)
next original unless replaceable_link?(link.path)
path = path_from_root(link.path, url_base)
url = path.gsub(".md", "/")
next original unless url
link.path = url
replacement_text(link)
end
end
private
# Stores info on a Markdown Link (avoid rubocop's Metrics/ParameterLists warning)
Link = Struct.new(:link_type, :text, :path, :fragment, :title)
def link_parts(matches)
last_inline = 5
link_type = matches[2] ? :inline : :reference
link_text = matches[link_type == :inline ? 2 : last_inline + 1]
relative_path = matches[link_type == :inline ? 3 : last_inline + 2]
fragment = matches[link_type == :inline ? 4 : last_inline + 3]
title = matches[link_type == :inline ? 5 : last_inline + 4]
Link.new(link_type, link_text, relative_path, fragment, title)
end
def path_from_root(relative_path, url_base)
relative_path.sub!(%r!\A/!, "")
absolute_path = File.expand_path(relative_path, url_base)
absolute_path.sub(%r!\A#{Regexp.escape(Dir.pwd)}/!, "")
end
# @param link [Link] A Link object describing the markdown link to make
def replacement_text(link)
link.path << link.fragment if link.fragment
if link.link_type == :inline
"[#{link.text}](#{link.path}#{link.title})"
else
"\n[#{link.text}]: #{link.path}#{link.title}"
end
end
def absolute_url?(string)
return unless string
Addressable::URI.parse(string).absolute?
rescue Addressable::URI::InvalidURIError
nil
end
def fragment?(string)
string&.start_with?("#")
end
def replaceable_link?(string)
!fragment?(string) && !absolute_url?(string)
end
def global_entry_filter
@global_entry_filter ||= Jekyll::EntryFilter.new(site)
end
end
end
Liquid::Template.register_filter(Jekyll::RelativeLinksFilter)