├── 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 | [![Test](https://github.com/toshimaru/jekyll-tagging-related_posts/actions/workflows/test.yml/badge.svg)](https://github.com/toshimaru/jekyll-tagging-related_posts/actions/workflows/test.yml) 4 | [![Gem Version](https://badge.fury.io/rb/jekyll-tagging-related_posts.svg)](https://badge.fury.io/rb/jekyll-tagging-related_posts) 5 | [![Test Coverage](https://codeclimate.com/github/toshimaru/jekyll-tagging-related_posts/badges/coverage.svg)](https://codeclimate.com/github/toshimaru/jekyll-tagging-related_posts/coverage) 6 | [![Code Climate](https://codeclimate.com/github/toshimaru/jekyll-tagging-related_posts/badges/gpa.svg)](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 | --------------------------------------------------------------------------------