Compare commits
10 Commits
01781355ef
...
176fd9425c
Author | SHA1 | Date |
---|---|---|
|
176fd9425c | |
|
40ac06ed3e | |
|
76982c73c0 | |
|
55024b37ae | |
|
59d5d9ae62 | |
|
a2330bb3b3 | |
|
79a8e16f22 | |
|
e2e1ee8eaa | |
|
2a37caac83 | |
|
84437a5052 |
|
@ -3,6 +3,8 @@
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* Avoid caching resource when called via `include_relative` tag (#9784)
|
* Avoid caching resource when called via `include_relative` tag (#9784)
|
||||||
|
* Fix logs containing IPv6 URLs (#9813)
|
||||||
|
* Do not treat colons in `url_placeholders` as URI delimiters (#9850)
|
||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
|
|
||||||
|
@ -11,6 +13,15 @@
|
||||||
* Add Supranode to third-party deployment guide (#9786)
|
* Add Supranode to third-party deployment guide (#9786)
|
||||||
* Document the need for a `Gemfile` in deployment step of step-by-step walkthrough (#9805)
|
* Document the need for a `Gemfile` in deployment step of step-by-step walkthrough (#9805)
|
||||||
* Add Azion to the 3rd party deployment docs (#9811)
|
* Add Azion to the 3rd party deployment docs (#9811)
|
||||||
|
* Add ruby-erb prerequisite for Arch Linux installations (#9832)
|
||||||
|
|
||||||
|
### Development Fixes
|
||||||
|
|
||||||
|
* Improve readability of `post_url` tag (#9829)
|
||||||
|
|
||||||
|
### Minor Enhancements
|
||||||
|
|
||||||
|
* feat: Allowing post_url tag to receive liquid variables (#9776)
|
||||||
|
|
||||||
## 4.4.1 / 2025-01-29
|
## 4.4.1 / 2025-01-29
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ sudo emerge --ask --verbose jekyll
|
||||||
### ArchLinux
|
### ArchLinux
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo pacman -S ruby base-devel
|
sudo pacman -S ruby base-devel ruby-erb
|
||||||
```
|
```
|
||||||
|
|
||||||
### OpenSUSE
|
### OpenSUSE
|
||||||
|
|
|
@ -188,3 +188,25 @@ You can also use this tag to create a link to a post in Markdown as follows:
|
||||||
[Name of Link]({% post_url 2010-07-21-name-of-post %})
|
[Name of Link]({% post_url 2010-07-21-name-of-post %})
|
||||||
```
|
```
|
||||||
{% endraw %}
|
{% endraw %}
|
||||||
|
|
||||||
|
Now lets say you have a [datafile]({{ '/docs/datafiles/' | relative_url }}) `_data/cool_posts.yaml` used to keep track
|
||||||
|
of certain posts that you intend to be listed as say *Cool Posts*:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- title: "An Awesome Post"
|
||||||
|
slug: "2010-07-21-name-of-post"
|
||||||
|
- title: "Another Awesome Post"
|
||||||
|
slug: "2016-07-26-name-of-post"
|
||||||
|
```
|
||||||
|
|
||||||
|
You may list such posts using the `post_url` tag as well (from {%- include docs_version_badge.html version="4.5.0" -%}):
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
```liquid
|
||||||
|
Cool posts:
|
||||||
|
|
||||||
|
{%- for cool_post in site.data.cool_posts %}
|
||||||
|
- [{{ cool_post.title }}]({% post_url {{ cool_post.slug }} %})
|
||||||
|
{%- endfor %}
|
||||||
|
```
|
||||||
|
{% endraw %}
|
||||||
|
|
|
@ -157,3 +157,37 @@ Feature: PostUrl Tag
|
||||||
But the _site directory should exist
|
But the _site directory should exist
|
||||||
And I should see "<p><a href=\"/cats%20and%20dogs/2019/02/04/hello-world.html\">Post 1</a></p>" in "_site/index.html"
|
And I should see "<p><a href=\"/cats%20and%20dogs/2019/02/04/hello-world.html\">Post 1</a></p>" in "_site/index.html"
|
||||||
And I should see "<p><a href=\"/2019/02/05/hello-again.html\">Post 2</a></p>" in "_site/index.html"
|
And I should see "<p><a href=\"/2019/02/05/hello-again.html\">Post 2</a></p>" in "_site/index.html"
|
||||||
|
|
||||||
|
Scenario: Calling for a post via a liquid variable
|
||||||
|
Given I have a _posts directory
|
||||||
|
And I have the following post:
|
||||||
|
| title | date | content |
|
||||||
|
| Hello World | 2019-02-04 | Lorem ipsum dolor |
|
||||||
|
And I have an "index.md" page with content:
|
||||||
|
"""
|
||||||
|
{% assign value='2019-02-04-hello-world' %}
|
||||||
|
[Welcome]({% post_url {{ value }} %})
|
||||||
|
"""
|
||||||
|
When I run jekyll build
|
||||||
|
Then I should get a zero exit status
|
||||||
|
And the _site directory should exist
|
||||||
|
And I should see "<p><a href=\"/2019/02/04/hello-world.html\">Welcome</a></p>" in "_site/index.html"
|
||||||
|
|
||||||
|
Scenario: Calling for posts via a liquid variable in a for tag
|
||||||
|
Given I have a _posts directory
|
||||||
|
And I have the following post:
|
||||||
|
| title | date | content |
|
||||||
|
| Hello World | 2019-02-04 | Lorem ipsum dolor |
|
||||||
|
| We Meet Again | 2019-02-05 | Alpha beta gamma |
|
||||||
|
And I have an "index.md" page with content:
|
||||||
|
"""
|
||||||
|
{% assign posts = '2019-02-04-hello-world;2019-02-05-we-meet-again' | split: ';' %}
|
||||||
|
{%- for slug in posts -%}
|
||||||
|
[{{ slug }}]({% post_url {{ slug }} %})
|
||||||
|
{%- endfor %}
|
||||||
|
"""
|
||||||
|
When I run jekyll build
|
||||||
|
Then I should get a zero exit status
|
||||||
|
And the _site directory should exist
|
||||||
|
And I should see "<a href=\"/2019/02/04/hello-world.html\">2019-02-04-hello-world</a>" in "_site/index.html"
|
||||||
|
And I should see "<a href=\"/2019/02/05/we-meet-again.html\">2019-02-05-we-meet-again</a>" in "_site/index.html"
|
||||||
|
|
|
@ -185,7 +185,8 @@ module Jekyll
|
||||||
#
|
#
|
||||||
# Returns true if the 'write' metadata is true, false otherwise.
|
# Returns true if the 'write' metadata is true, false otherwise.
|
||||||
def write?
|
def write?
|
||||||
!!metadata.fetch("output", false)
|
!!metadata.fetch("output", false) &&
|
||||||
|
(site.target == metadata.fetch("target", site.target))
|
||||||
end
|
end
|
||||||
|
|
||||||
# The URL template to render collection's documents at.
|
# The URL template to render collection's documents at.
|
||||||
|
|
|
@ -30,6 +30,7 @@ module Jekyll
|
||||||
"limit_posts" => 0,
|
"limit_posts" => 0,
|
||||||
"future" => false,
|
"future" => false,
|
||||||
"unpublished" => false,
|
"unpublished" => false,
|
||||||
|
"target" => "default",
|
||||||
|
|
||||||
# Plugins
|
# Plugins
|
||||||
"whitelist" => [],
|
"whitelist" => [],
|
||||||
|
|
|
@ -348,17 +348,20 @@ module Jekyll
|
||||||
end
|
end
|
||||||
|
|
||||||
# Determine whether this document should be written.
|
# Determine whether this document should be written.
|
||||||
# Based on the Collection to which it belongs.
|
# Based on the Collection to which it belongs
|
||||||
|
# and site and document target.
|
||||||
#
|
#
|
||||||
# True if the document has a collection and if that collection's #write?
|
# True if the document has a collection and if that collection's #write?
|
||||||
# method returns true, and if the site's Publisher will publish the document.
|
# method returns true, and if the site's Publisher will publish the document,
|
||||||
|
# and if the document's target matches the site target or is undefined.
|
||||||
# False otherwise.
|
# False otherwise.
|
||||||
#
|
#
|
||||||
# rubocop:disable Naming/MemoizedInstanceVariableName
|
# rubocop:disable Naming/MemoizedInstanceVariableName
|
||||||
def write?
|
def write?
|
||||||
return @write_p if defined?(@write_p)
|
return @write_p if defined?(@write_p)
|
||||||
|
|
||||||
@write_p = collection&.write? && site.publisher.publish?(self)
|
@write_p = collection&.write? && site.publisher.publish?(self) &&
|
||||||
|
(site.target == data.fetch("target", site.target))
|
||||||
end
|
end
|
||||||
# rubocop:enable Naming/MemoizedInstanceVariableName
|
# rubocop:enable Naming/MemoizedInstanceVariableName
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ module Jekyll
|
||||||
private delegate_method_as :data, :fallback_data
|
private delegate_method_as :data, :fallback_data
|
||||||
|
|
||||||
delegate_methods :id, :output, :content, :to_s, :relative_path, :url, :date
|
delegate_methods :id, :output, :content, :to_s, :relative_path, :url, :date
|
||||||
data_delegators "title", "categories", "tags"
|
data_delegators "title", "categories", "tags", :target
|
||||||
|
|
||||||
def collection
|
def collection
|
||||||
@obj.collection.label
|
@obj.collection.label
|
||||||
|
|
|
@ -180,7 +180,7 @@ module Jekyll
|
||||||
end
|
end
|
||||||
|
|
||||||
def write?
|
def write?
|
||||||
true
|
site.config["target"] == data.fetch("target", site.config["target"])
|
||||||
end
|
end
|
||||||
|
|
||||||
def excerpt_separator
|
def excerpt_separator
|
||||||
|
|
|
@ -6,7 +6,7 @@ module Jekyll
|
||||||
:file_read_opts, :future, :gems, :generators, :highlighter,
|
:file_read_opts, :future, :gems, :generators, :highlighter,
|
||||||
:include, :inclusions, :keep_files, :layouts, :limit_posts,
|
:include, :inclusions, :keep_files, :layouts, :limit_posts,
|
||||||
:lsi, :pages, :permalink_style, :plugin_manager, :plugins,
|
:lsi, :pages, :permalink_style, :plugin_manager, :plugins,
|
||||||
:reader, :safe, :show_drafts, :static_files, :theme, :time,
|
:reader, :safe, :target, :show_drafts, :static_files, :theme, :time,
|
||||||
:unpublished
|
:unpublished
|
||||||
|
|
||||||
attr_reader :cache_dir, :config, :dest, :filter_cache, :includes_load_paths,
|
attr_reader :cache_dir, :config, :dest, :filter_cache, :includes_load_paths,
|
||||||
|
@ -47,7 +47,7 @@ module Jekyll
|
||||||
def config=(config)
|
def config=(config)
|
||||||
@config = config.clone
|
@config = config.clone
|
||||||
|
|
||||||
%w(safe lsi highlighter baseurl exclude include future unpublished
|
%w(safe lsi highlighter baseurl exclude include future unpublished target
|
||||||
show_drafts limit_posts keep_files).each do |opt|
|
show_drafts limit_posts keep_files).each do |opt|
|
||||||
send(:"#{opt}=", config[opt])
|
send(:"#{opt}=", config[opt])
|
||||||
end
|
end
|
||||||
|
@ -360,7 +360,7 @@ module Jekyll
|
||||||
end
|
end
|
||||||
|
|
||||||
def each_site_file
|
def each_site_file
|
||||||
pages.each { |page| yield page }
|
pages.each { |page| yield (page) if page.write? }
|
||||||
static_files.each { |file| yield(file) if file.write? }
|
static_files.each { |file| yield(file) if file.write? }
|
||||||
collections.each_value { |coll| coll.docs.each { |doc| yield(doc) if doc.write? } }
|
collections.each_value { |coll| coll.docs.each { |doc| yield(doc) if doc.write? } }
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,34 +3,39 @@
|
||||||
module Jekyll
|
module Jekyll
|
||||||
module Tags
|
module Tags
|
||||||
class PostComparer
|
class PostComparer
|
||||||
|
# Deprecated (soft; No interpreter warnings).
|
||||||
|
# To be removed in v5.0.
|
||||||
|
# Use private constant `POST_PATH_MATCHER` instead.
|
||||||
MATCHER = %r!^(.+/)*(\d+-\d+-\d+)-(.*)$!.freeze
|
MATCHER = %r!^(.+/)*(\d+-\d+-\d+)-(.*)$!.freeze
|
||||||
|
|
||||||
|
POST_PATH_MATCHER = %r!\A(.+/)*?(\d{2,4}-\d{1,2}-\d{1,2})-([^/]*)\z!.freeze
|
||||||
|
private_constant :POST_PATH_MATCHER
|
||||||
|
|
||||||
attr_reader :path, :date, :slug, :name
|
attr_reader :path, :date, :slug, :name
|
||||||
|
|
||||||
def initialize(name)
|
def initialize(name)
|
||||||
@name = name
|
@name = name
|
||||||
|
|
||||||
all, @path, @date, @slug = *name.sub(%r!^/!, "").match(MATCHER)
|
all, @path, @date, @slug = *name.delete_prefix("/").match(POST_PATH_MATCHER)
|
||||||
unless all
|
unless all
|
||||||
raise Jekyll::Errors::InvalidPostNameError,
|
raise Jekyll::Errors::InvalidPostNameError,
|
||||||
"'#{name}' does not contain valid date and/or title."
|
"'#{name}' does not contain valid date and/or title."
|
||||||
end
|
end
|
||||||
|
|
||||||
basename_pattern = "#{date}-#{Regexp.escape(slug)}\\.[^.]+"
|
basename_pattern = "#{date}-#{Regexp.escape(slug)}\\.[^.]+"
|
||||||
@name_regex = %r!^_posts/#{path}#{basename_pattern}|^#{path}_posts/?#{basename_pattern}!
|
@name_regex = %r!\A_posts/#{path}#{basename_pattern}|\A#{path}_posts/?#{basename_pattern}!
|
||||||
end
|
end
|
||||||
|
|
||||||
def post_date
|
def post_date
|
||||||
@post_date ||= Utils.parse_date(
|
@post_date ||= Utils.parse_date(date, "Path '#{name}' does not contain valid date.")
|
||||||
date,
|
|
||||||
"'#{date}' does not contain valid date and/or title."
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Returns `MatchData` or `nil`.
|
||||||
def ==(other)
|
def ==(other)
|
||||||
other.relative_path.match(@name_regex)
|
other.relative_path.match(@name_regex)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Deprecated. To be removed in v5.0.
|
||||||
def deprecated_equality(other)
|
def deprecated_equality(other)
|
||||||
slug == post_slug(other) &&
|
slug == post_slug(other) &&
|
||||||
post_date.year == other.date.year &&
|
post_date.year == other.date.year &&
|
||||||
|
@ -40,9 +45,9 @@ module Jekyll
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# Construct the directory-aware post slug for a Jekyll::Post
|
# Construct the directory-aware post slug for a Jekyll::Document object.
|
||||||
#
|
#
|
||||||
# other - the Jekyll::Post
|
# other - the Jekyll::Document object.
|
||||||
#
|
#
|
||||||
# Returns the post slug with the subdirectory (relative to _posts)
|
# Returns the post slug with the subdirectory (relative to _posts)
|
||||||
def post_slug(other)
|
def post_slug(other)
|
||||||
|
@ -58,47 +63,71 @@ module Jekyll
|
||||||
class PostUrl < Liquid::Tag
|
class PostUrl < Liquid::Tag
|
||||||
include Jekyll::Filters::URLFilters
|
include Jekyll::Filters::URLFilters
|
||||||
|
|
||||||
def initialize(tag_name, post, tokens)
|
def initialize(tag_name, markup, tokens)
|
||||||
super
|
super
|
||||||
@orig_post = post.strip
|
@markup = markup.strip
|
||||||
begin
|
@template = Liquid::Template.parse(@markup) if @markup.include?("{{")
|
||||||
@post = PostComparer.new(@orig_post)
|
|
||||||
rescue StandardError => e
|
# Deprecated instance_variables.
|
||||||
raise Jekyll::Errors::PostURLError, <<~MSG
|
# To be removed in Jekyll v5.0.
|
||||||
Could not parse name of post "#{@orig_post}" in tag 'post_url'.
|
@orig_post = @markup
|
||||||
Make sure the post exists and the name is correct.
|
@post = nil
|
||||||
#{e.class}: #{e.message}
|
|
||||||
MSG
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
@context = context
|
@context = context
|
||||||
|
@resolved_markup = @template&.render(@context) || @markup
|
||||||
site = context.registers[:site]
|
site = context.registers[:site]
|
||||||
|
|
||||||
site.posts.docs.each do |document|
|
begin
|
||||||
return relative_url(document) if @post == document
|
@post_comparer = PostComparer.new(@resolved_markup)
|
||||||
|
rescue StandardError
|
||||||
|
raise_markup_parse_error
|
||||||
|
end
|
||||||
|
# For backwards compatibility only; deprecated instance_variable.
|
||||||
|
# To be removed in Jekyll v5.0.
|
||||||
|
@post = @post_comparer
|
||||||
|
|
||||||
|
# First pass-through.
|
||||||
|
site.posts.docs.each do |post|
|
||||||
|
return relative_url(post) if @post_comparer == post
|
||||||
end
|
end
|
||||||
|
|
||||||
# New matching method did not match, fall back to old method
|
# First pass-through did not yield the requested post. Search again using legacy matching
|
||||||
# with deprecation warning if this matches
|
# method. Log deprecation warning if a post is detected via this round.
|
||||||
|
site.posts.docs.each do |post|
|
||||||
|
next unless @post_comparer.deprecated_equality(post)
|
||||||
|
|
||||||
site.posts.docs.each do |document|
|
log_legacy_usage_deprecation
|
||||||
next unless @post.deprecated_equality document
|
return relative_url(post)
|
||||||
|
|
||||||
Jekyll::Deprecator.deprecation_message(
|
|
||||||
"A call to '{% post_url #{@post.name} %}' did not match a post using the new " \
|
|
||||||
"matching method of checking name (path-date-slug) equality. Please make sure " \
|
|
||||||
"that you change this tag to match the post's name exactly."
|
|
||||||
)
|
|
||||||
return relative_url(document)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
raise_post_not_found_error
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def raise_markup_parse_error
|
||||||
raise Jekyll::Errors::PostURLError, <<~MSG
|
raise Jekyll::Errors::PostURLError, <<~MSG
|
||||||
Could not find post "#{@orig_post}" in tag 'post_url'.
|
Could not parse name of post #{@resolved_markup.inspect} in tag 'post_url'.
|
||||||
Make sure the post exists and the name is correct.
|
Make sure the correct name is given to the tag.
|
||||||
MSG
|
MSG
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def raise_post_not_found_error
|
||||||
|
raise Jekyll::Errors::PostURLError, <<~MSG
|
||||||
|
Could not find post #{@resolved_markup.inspect} in tag 'post_url'.
|
||||||
|
Make sure the post exists and the correct name is given to the tag.
|
||||||
|
MSG
|
||||||
|
end
|
||||||
|
|
||||||
|
def log_legacy_usage_deprecation
|
||||||
|
Jekyll::Deprecator.deprecation_message(
|
||||||
|
"A call to '{% post_url #{@resolved_markup} %}' did not match a post using the new " \
|
||||||
|
"matching method of checking name (path-date-slug) equality. Please make sure that " \
|
||||||
|
"you change this tag to match the post's name exactly."
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -144,7 +144,13 @@ module Jekyll
|
||||||
# pct-encoded = "%" HEXDIG HEXDIG
|
# pct-encoded = "%" HEXDIG HEXDIG
|
||||||
# sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
# sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
|
||||||
# / "*" / "+" / "," / ";" / "="
|
# / "*" / "+" / "," / ";" / "="
|
||||||
Addressable::URI.encode(path).encode("utf-8").sub("#", "%23")
|
#
|
||||||
|
# `Addressable::URI::CharacterClassesRegexps::PATH` is used to encode
|
||||||
|
# non-alphanumeric characters such as "[", "]", etc.
|
||||||
|
Addressable::URI.encode_component(
|
||||||
|
path,
|
||||||
|
Addressable::URI::CharacterClassesRegexps::PATH
|
||||||
|
).encode("utf-8").sub("#", "%23")
|
||||||
end
|
end
|
||||||
|
|
||||||
# Unescapes a URL path segment
|
# Unescapes a URL path segment
|
||||||
|
|
|
@ -80,5 +80,16 @@ class TestURL < JekyllUnitTest
|
||||||
).to_s
|
).to_s
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
should "not treat colons in placeholders as uri delimiters" do
|
||||||
|
assert_equal "/foo/foo%20bar:foobar/", URL.new(
|
||||||
|
:template => "/:x/:y/",
|
||||||
|
:placeholders => { :x => "foo", :y => "foo bar:foobar" }
|
||||||
|
).to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
should "unescape urls with colons" do
|
||||||
|
assert_equal "/foo/foo bar:foobar/", Jekyll::URL.unescape_path("/foo/foo%20bar:foobar/")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue