├── test
├── source
│ ├── index.html
│ └── _posts
│ │ ├── 2015-06-01-bleh.html
│ │ ├── 2015-06-02-humor.html
│ │ ├── 2015-06-03-hey-there.html
│ │ ├── 2015-12-31-whateva.html
│ │ ├── 2016-01-01-oh-yes.html
│ │ └── 2015-05-01-blah.html
├── jekyll-tagging-related_posts_test.rb
└── test_helper.rb
├── CHANGELOG.md
├── bin
├── setup
└── console
├── .github
├── dependabot.yml
└── workflows
│ └── test.yml
├── lib
├── jekyll
│ └── tagging
│ │ ├── related_posts
│ │ └── version.rb
│ │ └── related_posts.rb
└── jekyll-tagging-related_posts.rb
├── .gitignore
├── Gemfile
├── Appraisals
├── Rakefile
├── gemfiles
├── jekyll_3.9.gemfile
├── jekyll_4.0.gemfile
├── jekyll_4.1.gemfile
├── jekyll_4.2.gemfile
└── jekyll_4.3.gemfile
├── LICENSE.txt
├── jekyll-tagging-related_posts.gemspec
└── README.md
/test/source/index.html:
--------------------------------------------------------------------------------
1 | ---
2 | ---
--------------------------------------------------------------------------------
/test/source/_posts/2015-06-01-bleh.html:
--------------------------------------------------------------------------------
1 | ---
2 | tags: bar
3 | ---
4 |
--------------------------------------------------------------------------------
/test/source/_posts/2015-06-02-humor.html:
--------------------------------------------------------------------------------
1 | ---
2 | tags: baz
3 | ---
4 |
--------------------------------------------------------------------------------
/test/source/_posts/2015-06-03-hey-there.html:
--------------------------------------------------------------------------------
1 | ---
2 | tags: qux
3 | ---
4 |
--------------------------------------------------------------------------------
/test/source/_posts/2015-12-31-whateva.html:
--------------------------------------------------------------------------------
1 | ---
2 | tags: foo bar
3 | ---
4 |
--------------------------------------------------------------------------------
/test/source/_posts/2016-01-01-oh-yes.html:
--------------------------------------------------------------------------------
1 | ---
2 | tags: foo baz
3 | ---
4 |
--------------------------------------------------------------------------------
/test/source/_posts/2015-05-01-blah.html:
--------------------------------------------------------------------------------
1 | ---
2 | title: blah
3 | tags: foo
4 | ---
5 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Changelog is maintained under [Github Releases](https://github.com/toshimaru/jekyll-tagging-related_posts/releases).
2 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 | IFS=$'\n\t'
4 | set -vx
5 |
6 | bundle install
7 |
8 | # Do any other automated setup that you need to do here
9 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: github-actions
4 | directory: "/"
5 | schedule:
6 | interval: monthly
7 | reviewers:
8 | - toshimaru
9 |
--------------------------------------------------------------------------------
/lib/jekyll/tagging/related_posts/version.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Jekyll
4 | module Tagging
5 | module RelatedPosts
6 | VERSION = '1.3.1'
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle/
2 | /.yardoc
3 | /Gemfile.lock
4 | /_yardoc/
5 | /coverage/
6 | /doc/
7 | /pkg/
8 | /spec/reports/
9 | /tmp/
10 | /test/dest
11 | /test/source
12 | /gemfiles/.bundle/
13 | *.gemfile.lock
14 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source 'https://rubygems.org'
4 |
5 | gemspec
6 |
7 | gem "appraisal"
8 | gem "bundler"
9 | gem "minitest-reporters"
10 | gem "minitest"
11 | gem "pry"
12 | gem "rake"
13 | gem "simplecov"
14 |
--------------------------------------------------------------------------------
/Appraisals:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | SUPPORTED_JEKYLL_VERSIONS = %w[3.9 4.0 4.1 4.2 4.3].freeze
4 |
5 | SUPPORTED_JEKYLL_VERSIONS.each do |version|
6 | appraise "jekyll-#{version}" do
7 | gem "jekyll", "~> #{version}.0"
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "bundler/gem_tasks"
4 | require "rake/testtask"
5 |
6 | Rake::TestTask.new(:test) do |t|
7 | t.libs << "test"
8 | t.libs << "lib"
9 | t.test_files = FileList['test/**/*_test.rb']
10 | end
11 |
12 | task default: :test
13 |
--------------------------------------------------------------------------------
/gemfiles/jekyll_3.9.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "appraisal"
6 | gem "bundler"
7 | gem "minitest-reporters"
8 | gem "minitest"
9 | gem "pry"
10 | gem "rake"
11 | gem "simplecov"
12 | gem "jekyll", "~> 3.9.0"
13 |
14 | gemspec path: "../"
15 |
--------------------------------------------------------------------------------
/gemfiles/jekyll_4.0.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "appraisal"
6 | gem "bundler"
7 | gem "minitest-reporters"
8 | gem "minitest"
9 | gem "pry"
10 | gem "rake"
11 | gem "simplecov"
12 | gem "jekyll", "~> 4.0.0"
13 |
14 | gemspec path: "../"
15 |
--------------------------------------------------------------------------------
/gemfiles/jekyll_4.1.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "appraisal"
6 | gem "bundler"
7 | gem "minitest-reporters"
8 | gem "minitest"
9 | gem "pry"
10 | gem "rake"
11 | gem "simplecov"
12 | gem "jekyll", "~> 4.1.0"
13 |
14 | gemspec path: "../"
15 |
--------------------------------------------------------------------------------
/gemfiles/jekyll_4.2.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "appraisal"
6 | gem "bundler"
7 | gem "minitest-reporters"
8 | gem "minitest"
9 | gem "pry"
10 | gem "rake"
11 | gem "simplecov"
12 | gem "jekyll", "~> 4.2.0"
13 |
14 | gemspec path: "../"
15 |
--------------------------------------------------------------------------------
/gemfiles/jekyll_4.3.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "appraisal"
6 | gem "bundler"
7 | gem "minitest-reporters"
8 | gem "minitest"
9 | gem "pry"
10 | gem "rake"
11 | gem "simplecov"
12 | gem "jekyll", "~> 4.3.0"
13 |
14 | gemspec path: "../"
15 |
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | require "bundler/setup"
5 | require "jekyll-tagging-related_posts"
6 |
7 | # You can add fixtures and/or initialization code here to make experimenting
8 | # with your gem easier. You can also use a different console, if you like.
9 |
10 | require "irb"
11 | IRB.start(__FILE__)
12 |
--------------------------------------------------------------------------------
/lib/jekyll-tagging-related_posts.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Require `jekyll/document` to override `Jekyll::Document`
4 | require "jekyll/document"
5 |
6 | require "jekyll/tagging/related_posts/version"
7 | require "jekyll/tagging/related_posts"
8 |
9 | module Jekyll
10 | class Document
11 | include ::Jekyll::Tagging::RelatedPosts
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/test/jekyll-tagging-related_posts_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 |
3 | class TestPage < JekyllUnitTest
4 | def setup
5 | @site = fixture_site
6 | @site.process
7 | @document = @site.posts.docs.first
8 | end
9 |
10 | def test_tag_name
11 | assert_equal 'foo', @document.data["tags"].first
12 | end
13 |
14 | def test_related_posts
15 | assert_instance_of Array, @document.related_posts
16 | end
17 |
18 | def test_related_posts
19 | assert_instance_of Jekyll::Document, @document.related_posts.first
20 | end
21 |
22 | def test_related_posts_count
23 | assert_equal 2, @document.related_posts.count
24 | end
25 |
26 | def test_related_post_tag
27 | assert_includes @document.related_posts.first.data["tags"], 'foo'
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016-2024 Toshimaru
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
4 | require 'simplecov'
5 | SimpleCov.start
6 |
7 | require 'jekyll-tagging-related_posts'
8 | require 'jekyll'
9 |
10 | require 'minitest/autorun'
11 | require 'minitest/reporters'
12 | Minitest::Reporters.use!
13 |
14 | class JekyllUnitTest < Minitest::Test
15 | include Jekyll
16 |
17 | def fixture_site(overrides = {})
18 | Jekyll::Site.new(site_configuration(overrides))
19 | end
20 |
21 | def build_configs(overrides, base_hash = default_configuration)
22 | Utils.deep_merge_hashes(base_hash, overrides)
23 | end
24 |
25 | def default_configuration
26 | Marshal.load(Marshal.dump(Jekyll::Configuration::DEFAULTS))
27 | end
28 |
29 | def site_configuration(overrides = {})
30 | full_overrides = build_configs(overrides, build_configs(
31 | "destination" => dest_dir,
32 | "incremental" => false
33 | ))
34 | Configuration.from(full_overrides.merge("source" => source_dir))
35 | end
36 |
37 | def dest_dir(*subdirs)
38 | test_dir('dest', *subdirs)
39 | end
40 |
41 | def source_dir(*subdirs)
42 | test_dir('source', *subdirs)
43 | end
44 |
45 | def test_dir(*subdirs)
46 | File.join(File.dirname(__FILE__), *subdirs)
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 |
9 | jobs:
10 | test:
11 | strategy:
12 | matrix:
13 | ruby: ['2.7', '3.0', '3.1', '3.2', '3.3']
14 | gemfile:
15 | - gemfiles/jekyll_3.9.gemfile
16 | - gemfiles/jekyll_4.0.gemfile
17 | - gemfiles/jekyll_4.1.gemfile
18 | - gemfiles/jekyll_4.2.gemfile
19 | - gemfiles/jekyll_4.3.gemfile
20 | env:
21 | BUNDLE_GEMFILE: ${{ matrix.gemfile }}
22 | runs-on: ubuntu-latest
23 | steps:
24 | - uses: actions/checkout@v4
25 | - name: Set up Ruby ${{ matrix.ruby }}
26 | uses: ruby/setup-ruby@v1
27 | with:
28 | ruby-version: ${{ matrix.ruby }}
29 | bundler-cache: true
30 | - name: Run Test
31 | run: bundle exec rake
32 |
33 | coverage:
34 | runs-on: ubuntu-latest
35 | steps:
36 | - uses: actions/checkout@v4
37 | - uses: ruby/setup-ruby@v1
38 | with:
39 | ruby-version: 3.2
40 | bundler-cache: true
41 | - uses: paambaati/codeclimate-action@v8.0.0
42 | env:
43 | CC_TEST_REPORTER_ID: 03d64532821ff06fd0cf9ae57dacc81b2322ac37ed14c5cfbb90ffea40e9c7fb
44 | with:
45 | coverageCommand: bundle exec rake
46 |
--------------------------------------------------------------------------------
/jekyll-tagging-related_posts.gemspec:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative 'lib/jekyll/tagging/related_posts/version'
4 |
5 | Gem::Specification.new do |spec|
6 | spec.name = "jekyll-tagging-related_posts"
7 | spec.version = Jekyll::Tagging::RelatedPosts::VERSION
8 | spec.authors = ["toshimaru"]
9 | spec.email = ["me@toshimaru.net"]
10 |
11 | spec.summary = %q{Jekyll `related_posts` function based on tags (works on Jekyll3)}
12 | spec.description = %q{Jekyll `related_posts` function based on tags (works on Jekyll3). It replaces original Jekyll's `related_posts` function to use tags to calculate relationships.}
13 | spec.homepage = "https://github.com/toshimaru/jekyll-tagging-related_posts"
14 | spec.license = "MIT"
15 | spec.required_ruby_version = ">= 2.7.0"
16 |
17 | spec.metadata["homepage_uri"] = spec.homepage
18 | spec.metadata["source_code_uri"] = spec.homepage
19 | spec.metadata["changelog_uri"] = "https://github.com/toshimaru/jekyll-tagging-related_posts/releases"
20 |
21 | # Specify which files should be added to the gem when it is released.
22 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23 | spec.files = Dir.chdir(__dir__) do
24 | `git ls-files -z`.split("\x0").reject do |f|
25 | (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
26 | end
27 | end
28 | spec.bindir = "exe"
29 | spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30 | spec.require_paths = ["lib"]
31 |
32 | spec.add_runtime_dependency "jekyll", ">= 3.9", "< 5.0"
33 | end
34 |
--------------------------------------------------------------------------------
/lib/jekyll/tagging/related_posts.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Jekyll
4 | module Tagging
5 | module RelatedPosts
6 | # Used to remove #related_posts so that it can be overridden
7 | def self.included(klass)
8 | klass.class_eval do
9 | remove_method :related_posts
10 | end
11 | end
12 |
13 | # Calculate related posts.
14 | # Returns []
15 | def related_posts
16 | return [] unless docs.count > 1
17 |
18 | highest_freq = tag_freq.values.max
19 | related_scores = Hash.new(0)
20 |
21 | docs.each do |doc|
22 | doc.data["tags"].each do |tag|
23 | if self.data["tags"].include?(tag) && doc != self
24 | cat_freq = tag_freq[tag]
25 | related_scores[doc] += (1 + highest_freq - cat_freq)
26 | end
27 | end
28 | end
29 |
30 | sort_related_posts(related_scores)
31 | end
32 |
33 | private
34 |
35 | # Calculate the frequency of each tag.
36 | # Returns {tag => freq, tag => freq, ...}
37 | def tag_freq
38 | @tag_freq ||= docs.inject(Hash.new(0)) do |tag_freq, doc|
39 | doc.data["tags"].each { |tag| tag_freq[tag] += 1 }
40 | tag_freq
41 | end
42 | end
43 |
44 | # Sort the related posts in order of their score and date
45 | # and return just the posts
46 | def sort_related_posts(related_scores)
47 | related_scores.sort do |a, b|
48 | if a[1] < b[1]
49 | 1
50 | elsif a[1] > b[1]
51 | -1
52 | else
53 | b[0].date <=> a[0].date
54 | end
55 | end.collect { |post, _freq| post }
56 | end
57 |
58 | def docs
59 | @docs ||= site.posts.docs
60 | end
61 | end
62 | end
63 | end
64 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # jekyll-tagging-related_posts
2 |
3 | [](https://github.com/toshimaru/jekyll-tagging-related_posts/actions/workflows/test.yml)
4 | [](https://badge.fury.io/rb/jekyll-tagging-related_posts)
5 | [](https://codeclimate.com/github/toshimaru/jekyll-tagging-related_posts/coverage)
6 | [](https://codeclimate.com/github/toshimaru/jekyll-tagging-related_posts)
7 |
8 | Jekyll `related_posts` function based on tags (works on Jekyll3). It replaces original Jekyll's `related_posts` function to use tags to calculate relationships.
9 |
10 | The calculation algorithm is based on [related\_posts-jekyll\_plugin](https://github.com/LawrenceWoodman/related_posts-jekyll_plugin) by [@LawrenceWoodman](https://github.com/LawrenceWoodman).
11 |
12 | ## Installation
13 |
14 | Add this line to your application's `Gemfile`:
15 |
16 | ```ruby
17 | gem 'jekyll-tagging-related_posts'
18 | ```
19 |
20 | And then execute:
21 |
22 | $ bundle
23 |
24 | Or install it yourself as:
25 |
26 | $ gem install jekyll-tagging-related_posts
27 |
28 | ## Usage
29 |
30 | Edit `_config.yml` to use the plug-in:
31 |
32 | ```yml
33 | gems:
34 | - jekyll-tagging-related_posts
35 | ```
36 |
37 | Then, add `site.related_posts` in your post layout page. For example:
38 |
39 | ```liquid
40 | {% if site.related_posts.size >= 1 %}
41 |
42 |
Related Posts
43 |
48 |
49 | {% endif %}
50 | ```
51 |
52 | ## Development
53 |
54 | After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
55 |
56 | ## Contributing
57 |
58 | Bug reports and pull requests are welcome on GitHub at https://github.com/toshimaru/jekyll-tagging-related_posts/issues. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
59 |
60 | ## License
61 |
62 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
63 |
--------------------------------------------------------------------------------