├── .prettierignore ├── _config.yml ├── .rspec ├── lib ├── jekyll-postfiles.rb └── jekyll │ ├── postfiles │ └── version.rb │ └── postfiles.rb ├── Gemfile ├── Rakefile ├── spec ├── fixtures │ ├── assets │ │ └── jekyll.png │ ├── _posts │ │ └── 2016-06-09-example │ │ │ ├── cloudflare.png │ │ │ └── 2016-06-09-example.md │ ├── _layouts │ │ └── default.html │ └── page.md ├── jekyll_postfiles_spec.rb └── spec_helper.rb ├── .travis.yml ├── .rubocop.yml ├── .github └── dependabot.yml ├── script └── fmt ├── _release.sh ├── RELEASES.md ├── .gitignore ├── .rubocop_todo.yml ├── LICENSE ├── jekyll-postfiles.gemspec ├── CONTRIBUTING.md └── README.md /.prettierignore: -------------------------------------------------------------------------------- 1 | *.md 2 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | -------------------------------------------------------------------------------- /lib/jekyll-postfiles.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "jekyll/postfiles" 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gemspec 6 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler/gem_tasks" 4 | task :default => :spec 5 | -------------------------------------------------------------------------------- /spec/fixtures/assets/jekyll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhoizey/jekyll-postfiles/master/spec/fixtures/assets/jekyll.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.3.0 4 | before_install: 5 | - gem install bundler 6 | script: 7 | - bundle exec rspec 8 | cache: bundler -------------------------------------------------------------------------------- /lib/jekyll/postfiles/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jekyll 4 | module PostFiles 5 | VERSION = "3.1.0" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/fixtures/_posts/2016-06-09-example/cloudflare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhoizey/jekyll-postfiles/master/spec/fixtures/_posts/2016-06-09-example/cloudflare.png -------------------------------------------------------------------------------- /spec/fixtures/_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ page.title }} 4 | 5 | 6 | {{ content }} 7 | 8 | 9 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: .rubocop_todo.yml 2 | 3 | inherit_gem: 4 | jekyll: .rubocop.yml 5 | 6 | AllCops: 7 | TargetRubyVersion: 2.3 8 | Exclude: 9 | - vendor/**/* 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: bundler 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "04:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /spec/fixtures/page.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: blah "blah" & blah 3 | description: Some description 4 | layout: default 5 | --- 6 | 7 | # Test 8 | 9 | This contains a [link](http://contentlink1.com). And [another link](https://contentlink2.org). -------------------------------------------------------------------------------- /script/fmt: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Rubocop $(bundle exec rubocop --version)" 4 | bundle exec rubocop -S -D -E $@ 5 | success=$? 6 | if ((success != 0)); then 7 | echo -e "\nTry running \`script/fmt -a\` to automatically fix errors" 8 | fi 9 | exit $success 10 | -------------------------------------------------------------------------------- /_release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # https://github.com/svenfuchs/gem-release 3 | 4 | if [ -z "$1" ]; then 5 | echo "Usage: provide the release type (patch, minor, major)." 6 | exit -1 7 | else 8 | release_type="$@" 9 | fi 10 | 11 | gem bump --version "$release_type" --tag --release 12 | -------------------------------------------------------------------------------- /RELEASES.md: -------------------------------------------------------------------------------- 1 | # Releases 2 | 3 | ## v3.1.0 4 | 5 | - [Supports Jekyll 4](https://github.com/nhoizey/jekyll-postfiles/commit/9dbb9778fb5cc8036aa819a8476a348a72c10e69) 6 | 7 | ## v3.0.0 8 | 9 | - [Rewrite for Jekyll 3.8.0](https://github.com/nhoizey/jekyll-postfiles/pull/12) by [@Birch-san](https://github.com/Birch-san) 10 | - Match jekyll's coding style thanks to [Rubocop](http://rubocop.readthedocs.io/) 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Because this is a gem, ignore Gemfile.lock: 2 | 3 | Gemfile.lock 4 | 5 | # And because this is Ruby, ignore the following 6 | # (source: https://github.com/github/gitignore/blob/master/Ruby.gitignore): 7 | 8 | *.gem 9 | *.rbc 10 | .bundle 11 | .config 12 | coverage 13 | InstalledFiles 14 | lib/bundler/man 15 | pkg 16 | rdoc 17 | spec/reports 18 | test/tmp 19 | test/version_tmp 20 | tmp 21 | spec/fixtures/.jekyll-cache 22 | -------------------------------------------------------------------------------- /spec/jekyll_postfiles_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Jekyll::PostFiles do 4 | let(:page) { make_page } 5 | let(:site) { make_site } 6 | let(:post) { make_post } 7 | let(:context) { make_context(:page => page, :site => site) } 8 | let(:url) { "" } 9 | 10 | before do 11 | Jekyll.logger.log_level = :error 12 | site.process 13 | end 14 | 15 | it "copies image from global assets folder" do 16 | expect(Pathname.new(File.expand_path('assets/jekyll.png', dest_dir))).to exist 17 | end 18 | 19 | it "copies image from post folder" do 20 | expect(Pathname.new(File.expand_path('2016/06/09/cloudflare.png', dest_dir))).to exist 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config` 3 | # on 2018-10-31 10:17:53 +0100 using RuboCop version 0.60.0. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | # Offense count: 1 10 | Metrics/AbcSize: 11 | Max: 43 12 | 13 | # Offense count: 11 14 | # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. 15 | # URISchemes: http, https 16 | Metrics/LineLength: 17 | Max: 135 18 | 19 | # Offense count: 1 20 | # Configuration parameters: CountComments, ExcludedMethods. 21 | Metrics/MethodLength: 22 | Max: 29 23 | 24 | # Offense count: 1 25 | # Configuration parameters: CountKeywordArgs. 26 | Metrics/ParameterLists: 27 | Max: 5 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Nicolas Hoizey 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 | -------------------------------------------------------------------------------- /jekyll-postfiles.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH.unshift(File.expand_path("lib", __dir__)) 4 | require "jekyll/postfiles/version" 5 | 6 | Gem::Specification.new do |spec| 7 | spec.version = Jekyll::PostFiles::VERSION 8 | spec.homepage = "https://nhoizey.github.io/jekyll-postfiles/" 9 | spec.authors = ["Nicolas Hoizey"] 10 | spec.email = ["nicolas@hoizey.com"] 11 | spec.files = %w(Rakefile Gemfile README.md RELEASES.md LICENSE) + Dir["lib/**/*"] 12 | spec.summary = "A Jekyll plugin to keep posts assets alongside their Markdown files" 13 | spec.name = "jekyll-postfiles" 14 | spec.license = "MIT" 15 | spec.require_paths = ["lib"] 16 | spec.description = <<-DESC 17 | This plugin takes any file that is in posts folders, and copy them to the folder in which the post HTML page will be created. 18 | DESC 19 | 20 | spec.required_ruby_version = ">= 2.3.0" 21 | 22 | spec.add_runtime_dependency "jekyll", ">= 3.8.6", "< 5" 23 | 24 | spec.add_development_dependency "bundler", "~> 2.1.4" 25 | spec.add_development_dependency "rake", "~> 13.0" 26 | spec.add_development_dependency "rubocop", "~> 0.76.0" 27 | spec.add_development_dependency "rspec", "~> 3.5" 28 | spec.add_development_dependency "kramdown-parser-gfm", "~> 1.1.0" 29 | end 30 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path("../lib", __dir__) 2 | require "jekyll" 3 | require "jekyll-postfiles" 4 | 5 | ENV["JEKYLL_LOG_LEVEL"] = "error" 6 | 7 | def dest_dir 8 | File.expand_path("../tmp/dest", __dir__) 9 | end 10 | 11 | def source_dir 12 | File.expand_path("fixtures", __dir__) 13 | end 14 | 15 | CONFIG_DEFAULTS = { 16 | "source" => source_dir, 17 | "destination" => dest_dir, 18 | "gems" => ["jekyll-postfiles"], 19 | }.freeze 20 | 21 | def make_page(options = {}) 22 | page = Jekyll::Page.new site, CONFIG_DEFAULTS["source"], "", "page.md" 23 | page.data = options 24 | page 25 | end 26 | 27 | def make_post(options = {}) 28 | filename = File.expand_path("_posts/2016-06-09-cloudflare/2016-06-09-so-long-cloudflare-and-thanks-for-all-the-fissh.md", CONFIG_DEFAULTS["source"]) 29 | config = { :site => site, :collection => site.collections["posts"] } 30 | page = Jekyll::Document.new filename, config 31 | page.merge_data!(options) 32 | page 33 | end 34 | 35 | def make_site(options = {}) 36 | config = Jekyll.configuration CONFIG_DEFAULTS.merge(options) 37 | Jekyll::Site.new(config) 38 | end 39 | 40 | def make_context(registers = {}, environments = {}) 41 | Liquid::Context.new(environments, {}, { :site => site, :page => page }.merge(registers)) 42 | end 43 | -------------------------------------------------------------------------------- /spec/fixtures/_posts/2016-06-09-example/2016-06-09-example.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | title: Example 4 | --- 5 | 6 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam nec lorem arcu. Duis ac interdum mauris, id ultricies neque. Phasellus sagittis viverra dui, vitae pellentesque libero consequat commodo. Pellentesque sodales augue eu tincidunt lacinia. Mauris ligula leo, dictum non enim nec, efficitur consectetur dui. Donec vitae eros augue. Sed ex ex, scelerisque eget nunc ut, sodales pharetra quam. 7 | 8 | ![Jekyll logo](/assets/jekyll.png) 9 | 10 | Mauris sed elementum velit, nec suscipit diam. In varius ultricies urna, et ornare lorem consectetur ac. Maecenas sed urna tortor. Sed sed risus laoreet, sodales mi in, condimentum sapien. Etiam consequat fermentum luctus. Duis porttitor mattis euismod. Morbi ullamcorper et sem eu tristique. 11 | 12 | ![alt text](cloudflare.png) 13 | 14 | Integer lorem tellus, luctus sed feugiat a, efficitur eu orci. Proin accumsan lorem et hendrerit fermentum. Donec egestas dolor ac rutrum volutpat. Donec quam dolor, mattis at interdum non, convallis at nulla. Integer ultrices turpis ut scelerisque malesuada. Suspendisse quis rutrum lacus. Suspendisse vel eros tempus, bibendum magna a, tincidunt metus. Etiam id ante non nisl rutrum pulvinar. Curabitur turpis lorem, euismod at accumsan nec, aliquet at ligula. Proin iaculis diam bibendum felis ultrices, in accumsan nibh porttitor. Mauris pretium odio purus, ut vestibulum odio sodales vitae. Donec iaculis ex vel consectetur lacinia. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribute 2 | 3 | ## Introduction 4 | 5 | First, thank you for considering contributing to jekyll-postfiles! It's people like you that make the open source community such a great community! 😊 6 | 7 | We welcome any type of contribution, not only code. You can help with 8 | 9 | * **QA**: file bug reports, the more details you can give the better (e.g. screenshots with the console open) 10 | * **Marketing**: writing blog posts, howto's, printing stickers, ... 11 | * **Community**: presenting the project at meetups, organizing a dedicated meetup for the local community, ... 12 | * **Code**: take a look at the [open issues](issues). Even if you can't write code, commenting on them, showing that you care about a given issue matters. It helps us triage them. 13 | 14 | ## Your First Contribution 15 | 16 | Working on your first Pull Request? You can learn how from this _free_ series, [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github). 17 | 18 | ## Submitting code 19 | 20 | Any code change should be submitted as a pull request. The description should explain what the code does and give steps to execute it. The pull request should also contain tests. 21 | 22 | ## Code review process 23 | 24 | The bigger the pull request, the longer it will take to review and merge. Try to [break down large pull requests in smaller chunks](https://oncletom.io/2013/the-55-commits-syndrome/) that are easier to review and merge. 25 | 26 | It is also always helpful to have some context for your pull request. What was the purpose? Why does it matter to you? 27 | 28 | ## Questions 29 | 30 | If you have any questions, create an [issue](issue) (protip: do a quick search first to see if someone else didn't ask the same question before!). 31 | 32 | ## Credits 33 | 34 | ### Contributors 35 | 36 | Thank you to [all the people who have already contributed](https://github.com/nhoizey/jekyll-postfiles/graphs/contributors) to jekyll-postfiles! 37 | 38 | 39 | -------------------------------------------------------------------------------- /lib/jekyll/postfiles.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "jekyll" 4 | require "pathname" 5 | 6 | module Jekyll 7 | module PostFiles 8 | 9 | # there's a bug in the regex Document::DATE_FILENAME_MATCHER: 10 | # %r!^(?:.+/)*(\d{2,4}-\d{1,2}-\d{1,2})-(.*)(\.[^.]+)$! 11 | # used by: 12 | # jekyll/lib/jekyll/readers/post_reader.rb#read_posts 13 | # which ultimately populates: 14 | # site.posts.docs 15 | # 16 | # the original code's intention was to match: 17 | # all files with a date in the name 18 | # but it accidentally matches also: 19 | # all files immediately within a directory whose name contains a date 20 | # 21 | # our plugin changes the regex, to: 22 | # avoid false positive when directory name matches date regex 23 | Hooks.register :site, :after_reset do |_site| 24 | # Suppress warning messages. 25 | original_verbose = $VERBOSE 26 | $VERBOSE = nil 27 | Document.const_set("DATE_FILENAME_MATCHER", PostFileGenerator::FIXED_DATE_FILENAME_MATCHER) 28 | # Activate warning messages again. 29 | $VERBOSE = original_verbose 30 | end 31 | 32 | class PostFile < StaticFile 33 | # Initialize a new PostFile. 34 | # 35 | # site - The Site. 36 | # base - The String path to the - /srv/jekyll 37 | # dir - The String path between and the file - _posts/somedir 38 | # name - The String filename of the file - cool.svg 39 | # dest - The String path to the containing folder of the document which is output - /dist/blog/[:tag/]*:year/:month/:day 40 | def initialize(site, base, dir, name, dest) 41 | super(site, base, dir, name) 42 | @name = name 43 | @dest = dest 44 | end 45 | 46 | # Obtain destination path. 47 | # 48 | # dest - The String path to the destination dir. 49 | # 50 | # Returns destination file path. 51 | def destination(_dest) 52 | File.join(@dest, @name) 53 | end 54 | end 55 | 56 | class PostFileGenerator < Generator 57 | FIXED_DATE_FILENAME_MATCHER = %r!^(?:.+/)*(\d{2,4}-\d{1,2}-\d{1,2})-([^/]*)(\.[^.]+)$!.freeze 58 | 59 | # _posts/ 60 | # 2018-01-01-whatever.md # there's a date on this filename, so it will be treated as a post 61 | # # it's a direct descendant of _posts, so we do not treat it as an asset root 62 | # somedir/ 63 | # 2018-05-01-some-post.md # there's a date on this filename, so it will be treated as a post. 64 | # # moreover, we will treat its dir as an asset root 65 | # cool.svg # there's no date on this filename, so it will be treated as an asset 66 | # undated.md # there's no date on this filename, so it will be treated as an asset 67 | # img/ 68 | # cool.png # yes, even deeply-nested files are eligible to be copied. 69 | def generate(site) 70 | site_srcroot = Pathname.new site.source 71 | posts_src_dir = site_srcroot + "_posts" 72 | drafts_src_dir = site_srcroot + "_drafts" 73 | 74 | # Jekyll.logger.warn("[PostFiles]", "_posts: #{posts_src_dir}") 75 | # Jekyll.logger.warn("[PostFiles]", "docs: #{site.posts.docs.map(&:path)}") 76 | 77 | docs_with_dirs = site.posts.docs 78 | .reject do |doc| 79 | Pathname.new(doc.path).dirname.instance_eval do |dirname| 80 | [posts_src_dir, drafts_src_dir].reduce(false) do |acc, dir| 81 | acc || dirname.eql?(dir) 82 | end 83 | end 84 | end 85 | 86 | # Jekyll.logger.warn("[PostFiles]", "postdirs: #{docs_with_dirs.map{|doc| Pathname.new(doc.path).dirname}}") 87 | 88 | assets = docs_with_dirs.map do |doc| 89 | dest_dir = Pathname.new(doc.destination("")).dirname 90 | Pathname.new(doc.path).dirname.instance_eval do |postdir| 91 | Dir[postdir + "**/*"] 92 | .reject { |fname| fname =~ FIXED_DATE_FILENAME_MATCHER } 93 | .reject { |fname| File.directory? fname } 94 | .map do |fname| 95 | asset_abspath = Pathname.new fname 96 | srcroot_to_asset = asset_abspath.relative_path_from(site_srcroot) 97 | srcroot_to_assetdir = srcroot_to_asset.dirname 98 | asset_basename = srcroot_to_asset.basename 99 | 100 | assetdir_abs = site_srcroot + srcroot_to_assetdir 101 | postdir_to_assetdir = assetdir_abs.relative_path_from(postdir) 102 | PostFile.new(site, site_srcroot, srcroot_to_assetdir.to_path, asset_basename, (dest_dir + postdir_to_assetdir).to_path) 103 | end 104 | end 105 | end.flatten 106 | 107 | site.static_files.concat(assets) 108 | end 109 | end 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jekyll-postfiles 2 | 3 | [![Gem Version](https://badge.fury.io/rb/jekyll-postfiles.svg)](https://badge.fury.io/rb/jekyll-postfiles) 4 | [![Gem Downloads](https://img.shields.io/gem/dt/jekyll-postfiles.svg?style=flat)](http://rubygems.org/gems/jekyll-postfiles) 5 | [![Build Status](https://travis-ci.org/nhoizey/jekyll-postfiles.svg?branch=master)](https://travis-ci.org/nhoizey/jekyll-postfiles) 6 | 7 | 8 | 9 | 10 | 11 | - [Easing the management of images (and other files) attached to Markdown posts](#easing-the-management-of-images-and-other-files-attached-to-markdown-posts) 12 | - [The pain of Jekyll's recommended posts assets management](#the-pain-of-jekylls-recommended-posts-assets-management) 13 | - [There must be another way](#there-must-be-another-way) 14 | - [Not every assets need this](#not-every-assets-need-this) 15 | - [How does it work?](#how-does-it-work) 16 | - [Installation](#installation) 17 | - [Usage](#usage) 18 | - [Contributing](#contributing) 19 | - [License](#license) 20 | - [Thanks](#thanks) 21 | 22 | 23 | 24 | ## Easing the management of images (and other files) attached to Markdown posts 25 | 26 | ### The pain of Jekyll's recommended posts assets management 27 | 28 | Jekyll's natural way to deal with static files attached to posts, like images or PDFs, is to put them all in a global `assets/` (or `downloads/`) folder at the site root. Read "[Including images and resources](https://jekyllrb.com/docs/posts/#including-images-and-resources)" in Jekyll's documentation. 29 | 30 | You can of course put files in subfolders of `assets/`, but it will be really cumbersome to manage posts' Markdown files in `_posts/` or a subfolder, and images elsewhere, and then use the good hierarchy in all Markdown image tags. 31 | 32 | Imagine you have these files: 33 | 34 | ``` 35 | _posts/ 36 | 2016-06/ 37 | 2016-06-09-so-long-cloudflare-and-thanks-for-all-the-fissh.md 38 | … 39 | assets/ 40 | 2016-06-09-cloudflare/ 41 | cloudflare-architecture.png 42 | performance-report-sample.pdf 43 | ``` 44 | 45 | To use the image and PDF files in the post's Markdown, you will have to write this: 46 | 47 | ```markdown 48 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod 49 | tempor incididunt ut labore et dolore magna aliqua. 50 | 51 | ![Cloudflare architecture](/assets/2016-06-09-cloudflare/cloudflare-architecture.png) 52 | 53 | Ut enim ad minim veniam, 54 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo 55 | consequat. 56 | 57 | Here is [an example of performance report](/assets/2016-06-09-cloudflare/performance-report-sample.pdf). 58 | 59 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod 60 | tempor incididunt ut labore et dolore magna aliqua. 61 | ``` 62 | 63 | Painful to write. 64 | 65 | Imagine you want to change the post's publication date, or one of the file names? 66 | 67 | Painful to update. 68 | 69 | What if you want to put new WIP Markdown files in `_drafts/`, and the attached assets somewhere in a way they won't be copied to the destination `_site/` folder next time you build the site? You can't put the files in the `assets/` folder, so when you will publish the draft, you will have to change the assets location in the Markdown file. 70 | 71 | Painful, and prone to errors. 72 | 73 | And what about previewing the content while editing? If you use an editor like [MacDown](http://macdown.uranusjr.com/) with live preview, how will it find the actual path to the images? What means `/assets/…` for the editor? 74 | 75 | Painful to preview. 76 | 77 | ### There must be another way 78 | 79 | What if instead, you could have the files stored like that: 80 | 81 | ``` 82 | _posts/ 83 | 2016-06-09-cloudflare/ 84 | 2016-06-09-so-long-cloudflare-and-thanks-for-all-the-fissh.md 85 | cloudflare-architecture.png 86 | performance-report-sample.pdf 87 | ``` 88 | 89 | And if you could write your Markdown like this: 90 | 91 | ```markdown 92 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod 93 | tempor incididunt ut labore et dolore magna aliqua. 94 | 95 | ![Cloudflare architecture](cloudflare-architecture.png) 96 | 97 | Ut enim ad minim veniam, 98 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo 99 | consequat. 100 | 101 | Here is [an example of performance report](performance-report-sample.pdf). 102 | 103 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod 104 | tempor incididunt ut labore et dolore magna aliqua. 105 | ``` 106 | 107 | Much easier! 108 | 109 | - Easy to store, everything is in one single folder. 110 | - Easy to write, no path to add to file links 111 | - Easy to update 112 | - Easy to move from `_drafts/` to `_posts/`, without anything to change in the Markdown content 113 | - Easy to edit in any editor with live preview 114 | 115 | ### Not every assets need this 116 | 117 | [Some Jekyll users will try to convince you](http://stackoverflow.com/a/10366173/717195) it's a bad idea, because it means the asset is tightly linked to the post. 118 | 119 | In my own experience, 95% of assets, at least, are used in one single post. And this is pretty common to find such requests from users of other static generators, like [Hugo](https://github.com/spf13/hugo/issues/147) ([fixed in May 2015](https://github.com/spf13/hugo/issues/147#issuecomment-104067783)), [Nikola](https://github.com/getnikola/nikola/issues/2266) ([already there, but not obvious or user friendly](https://github.com/getnikola/nikola/issues/2266#issuecomment-189211387)), [Octopress](http://stackoverflow.com/questions/17052468/insert-local-image-into-a-blog-post-with-octopress), etc. 120 | 121 | But it's true this might not be ideal for all assets (the remaining 5%), so you can of course continue using full assets paths with `/assets/…` to have a few assets shared by several posts. 122 | 123 | ## How does it work? 124 | 125 | This plugin takes any file that is in posts folders, and copy them to the folder in which the post HTML page will be created. 126 | 127 | Let's say you have these files: 128 | 129 | ``` 130 | _posts/ 131 | 2016-06-09-cloudflare/ 132 | 2016-06-09-so-long-cloudflare-and-thanks-for-all-the-fissh.md 133 | cloudflare-architecture.png 134 | performance-report-sample.pdf 135 | ``` 136 | 137 | And your Jekyll settings for permalinks are these: 138 | 139 | ```yaml 140 | # Permalinks 141 | permalink: /:year/:month/:day/:title/ 142 | ``` 143 | 144 | Jekyll with this plugin will generate the site content like this: 145 | 146 | ``` 147 | 2016/ 148 | 06/ 149 | 09/ 150 | so-long-cloudflare-and-thanks-for-all-the-fissh/ 151 | index.html 152 | cloudflare-logo.png 153 | performance-report-sample.pdf 154 | ``` 155 | 156 | If you change your Jekyll settings for permalinks like these: 157 | 158 | ```yaml 159 | # Permalinks 160 | permalink: /:year/:month/:day/:title.html 161 | ``` 162 | 163 | Jekyll with this plugin will generate the site content like this: 164 | 165 | ``` 166 | 2016/ 167 | 06/ 168 | 09/ 169 | so-long-cloudflare-and-thanks-for-all-the-fissh.html 170 | cloudflare-logo.png 171 | performance-report-sample.pdf 172 | ``` 173 | 174 | Handy, isn't it? 175 | 176 | ## Installation 177 | 178 | Add `gem 'jekyll-postfiles'` to the `jekyll_plugin` group in your `Gemfile`: 179 | 180 | ```ruby 181 | source 'https://rubygems.org' 182 | 183 | gem 'jekyll' 184 | 185 | group :jekyll_plugins do 186 | gem 'jekyll-postfiles' 187 | end 188 | ``` 189 | 190 | Then run `bundle` to install the gem. 191 | 192 | ## Usage 193 | 194 | You don't have anything to do. 195 | 196 | Just put the images (and PDFs, etc.) in the same folder as your Markdown files, or even subfolders, and use the standard Markdown image syntax, with a relative path. 197 | 198 | ## Compatibility 199 | 200 | :warning: This plugin is not supported by Github Pages, host your site on services like https://netlify.com which support third party plugins. 201 | 202 | ## Contributing 203 | 204 | Thanks for your interest in contributing! There are many ways to contribute to this project. [Get started here](https://github.com/nhoizey/jekyll-postfiles/blob/master/CONTRIBUTING.md). 205 | 206 | ## License 207 | 208 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 209 | 210 | ## Thanks 211 | 212 | Inspired by [this old Gist](https://gist.github.com/kevinoid/3131752) by [@kevinoid](https://github.com/kevinoid/). 213 | 214 | --------------------------------------------------------------------------------