├── .gitignore ├── LICENSE.txt ├── jekyll_v1-2 └── multipost.rb ├── README.md └── jekyll_v3 └── multipost.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .todo 3 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Scott A. Clark 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /jekyll_v1-2/multipost.rb: -------------------------------------------------------------------------------- 1 | require 'cgi' 2 | 3 | module Jekyll 4 | class MultiPostGenerator < Generator 5 | safe true 6 | 7 | def generate(site) 8 | generate_views(site, :posts) 9 | generate_views(site, :pages) 10 | end 11 | 12 | private 13 | 14 | def generate_views(site, resource_type) 15 | klass = resource_klass(resource_type) 16 | 17 | site.send(resource_type).map! do |resource| 18 | resource.data["layout"].is_a?(Array) ? generate_layouts(resource, klass) : resource 19 | end.flatten! 20 | end 21 | 22 | def resource_klass(resource_type) 23 | klasses = { 24 | :posts => Post, 25 | :pages => Page 26 | } 27 | 28 | klasses[resource_type] 29 | end 30 | 31 | def generate_layouts(resource, klass) 32 | resource.data["layout"].map do |layout| 33 | view = klass.new(resource.site, resource.site.source, resource_dir(resource), resource.name) 34 | view.data["layout"] = layout 35 | view.data["permalink"] = view_permalink(resource, layout) 36 | view 37 | end 38 | end 39 | 40 | def resource_dir(resource) 41 | # TODO: This. Don't ever do it. But for now...Eh, screw it... 42 | resource.instance_variable_get(:@dir) 43 | end 44 | 45 | def view_permalink(resource, layout) 46 | layout_path = CGI.escape(layout) 47 | url = resource.url 48 | ext = File.extname(url) 49 | 50 | if url.include?(':layout') 51 | return url.gsub(/:layout/, layout_path) 52 | end 53 | 54 | if ext.empty? 55 | "#{url}/#{layout_path}/" 56 | else 57 | url.gsub(/\/$|#{ext}$/) { |url_end| "/#{layout_path}#{url_end}" } 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MultiPost 2 | MultiPost is a [Jekyll](https://github.com/mojombo/jekyll) plugin that allows you to specify multiple layouts in which to render a given post, page, or collection document. 3 | 4 | ## Installation 5 | Simply clone/download/copy+paste the appropriate `multipost.rb` file to a `_plugins` directory in your jekyll site source root (see option #1 from the [jekyll documentation](http://jekyllrb.com/docs/plugins/#installing-a-plugin)). 6 | 7 | **Jekyll version >= 3** 8 | - Use the `mutlipost.rb` file from the `jekyll_v3` directory. 9 | 10 | **Jekyll version < 3** 11 | - Use the `mutlipost.rb` file from the `jekyll_v1-2` directory. 12 | 13 | ## Usage 14 | This plugin gives you the option to list more than one layout in the YAML front matter of your posts and pages (entries with a single layout (e.g. `layout: layout_a`) will still behave as usual). 15 | 16 | Example: 17 | ```yaml 18 | --- 19 | layout: [layout_a, layout_b] 20 | title: "My Awesome Post" 21 | --- 22 | ``` 23 | 24 | The post will then be rendered in each of the given layouts. 25 | 26 | To ensure unique urls, each rendering of the post will have the name of the layout appended to it by default. For example: 27 | 28 | - `/my-awesome-post/` becomes `/my-awesome-post/layout_a/`, or 29 | - `/my-awesome-post.html` becomes `/my-awesome-post/layout_a.html` 30 | 31 | 32 | However, you can customize this behavior by using a `:layout` variable in your permalink templates. 33 | 34 | ```yaml 35 | --- 36 | layout: [layout_a, layout_b] 37 | permalink: ":layout/:title/" 38 | title: "My Awesome Post" 39 | --- 40 | ``` 41 | 42 | ## Why Would I Want This? 43 | This is useful for any situation where you have a single content base that you wish to display in different views (e.g. prototyping designs, A/B testing, tailoring content presentation for different audiences). 44 | -------------------------------------------------------------------------------- /jekyll_v3/multipost.rb: -------------------------------------------------------------------------------- 1 | require 'cgi' 2 | 3 | module Jekyll 4 | module PermalinkBuilder 5 | extend self 6 | 7 | def get_adjusted_permalink(resource, layout) 8 | layout_path = CGI.escape(layout) 9 | url = resource.url 10 | ext = File.extname(url) 11 | 12 | if url.include?(':layout') 13 | return url.gsub(/:layout/, layout_path) 14 | end 15 | 16 | if ext.empty? 17 | "#{url}/#{layout_path}/" 18 | else 19 | url.gsub(/\/$|#{ext}$/) { |url_end| "/#{layout_path}#{url_end}" } 20 | end 21 | end 22 | end 23 | 24 | class PageLayoutsGenerator 25 | def generate(site) 26 | pages = site.pages.map! do |page| 27 | if page.data["layout"].is_a?(Array) 28 | create_layout_views(page) 29 | else 30 | page 31 | end 32 | end 33 | 34 | pages.flatten! 35 | end 36 | 37 | private 38 | 39 | def create_layout_views(page) 40 | page.data["layout"].map do |layout| 41 | dir = File.dirname(page.relative_path) 42 | Page.new(page.site, page.site.source, dir, page.name).tap do |new_page| 43 | new_page.data["layout"] = layout 44 | new_page.data["permalink"] = PermalinkBuilder.get_adjusted_permalink(page, layout) 45 | end 46 | end 47 | end 48 | end 49 | 50 | class CollectionLayoutsGenerator 51 | def generate(site) 52 | site.collections.each do |_, collection| 53 | docs = collection.docs.map! do |doc| 54 | if doc.data["layout"].is_a?(Array) 55 | create_layout_views(site, collection, doc) 56 | else 57 | doc 58 | end 59 | end 60 | 61 | docs.flatten! 62 | end 63 | end 64 | 65 | private 66 | 67 | def create_layout_views(site, collection, doc) 68 | doc.data["layout"].map do |layout| 69 | Document.new(doc.path, :site => site, :collection => collection).tap do |new_doc| 70 | new_doc.read 71 | new_doc.data["layout"] = layout 72 | new_doc.data["permalink"] = PermalinkBuilder.get_adjusted_permalink(doc, layout) 73 | end 74 | end 75 | end 76 | end 77 | 78 | class MultiPostGenerator < Generator 79 | safe true 80 | 81 | def generate(site) 82 | PageLayoutsGenerator.new.generate(site) 83 | CollectionLayoutsGenerator.new.generate(site) 84 | end 85 | end 86 | end 87 | --------------------------------------------------------------------------------