commit
30f2bdff5b
|
@ -25,6 +25,7 @@ env:
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
- master
|
- master
|
||||||
|
- themes
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
irc:
|
irc:
|
||||||
|
|
1
Gemfile
1
Gemfile
|
@ -22,6 +22,7 @@ group :test do
|
||||||
gem "rspec-mocks"
|
gem "rspec-mocks"
|
||||||
gem "nokogiri"
|
gem "nokogiri"
|
||||||
gem "rspec"
|
gem "rspec"
|
||||||
|
gem "test-theme", path: File.expand_path("./test/fixtures/test-theme", File.dirname(__FILE__))
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
Feature: Writing themes
|
||||||
|
As a hacker who likes to share my expertise
|
||||||
|
I want to be able to make a gemified theme
|
||||||
|
In order to share my awesome style skillz with other Jekyllites
|
||||||
|
|
||||||
|
Scenario: A theme with SCSS
|
||||||
|
Given I have a configuration file with "theme" set to "test-theme"
|
||||||
|
And I have a css directory
|
||||||
|
And I have a "css/main.scss" page that contains "@import 'style';"
|
||||||
|
When I run jekyll build
|
||||||
|
Then I should get a zero exit status
|
||||||
|
And the _site directory should exist
|
||||||
|
And I should see ".sample {\n color: black; }" in "_site/css/main.css"
|
||||||
|
|
||||||
|
Scenario: A theme with an include
|
||||||
|
Given I have a configuration file with "theme" set to "test-theme"
|
||||||
|
And I have an _includes directory
|
||||||
|
And I have an "_includes/in_project.html" file that contains "I'm in the project."
|
||||||
|
And I have an "index.html" page that contains "{% include in_project.html %} {% include include.html %}"
|
||||||
|
When I run jekyll build
|
||||||
|
Then I should get a zero exit status
|
||||||
|
And the _site directory should exist
|
||||||
|
And I should see "I'm in the project." in "_site/index.html"
|
||||||
|
And I should see "<span class=\"sample\">include.html from test-theme</span>" in "_site/index.html"
|
||||||
|
|
||||||
|
Scenario: A theme with a layout
|
||||||
|
Given I have a configuration file with "theme" set to "test-theme"
|
||||||
|
And I have an _layouts directory
|
||||||
|
And I have an "_layouts/post.html" file that contains "post.html from the project: {{ content }}"
|
||||||
|
And I have an "index.html" page with layout "default" that contains "I'm content."
|
||||||
|
And I have a "post.html" page with layout "post" that contains "I'm more content."
|
||||||
|
When I run jekyll build
|
||||||
|
Then I should get a zero exit status
|
||||||
|
And the _site directory should exist
|
||||||
|
And I should see "default.html from test-theme: I'm content." in "_site/index.html"
|
||||||
|
And I should see "post.html from the project: I'm more content." in "_site/post.html"
|
||||||
|
|
||||||
|
Scenario: Complicated site that puts it all together
|
||||||
|
Given I have a configuration file with "theme" set to "test-theme"
|
||||||
|
And I have a _posts directory
|
||||||
|
And I have the following posts:
|
||||||
|
| title | date | layout | content |
|
||||||
|
| entry1 | 2016-04-21 | post | I am using a local layout. {% include include.html %} |
|
||||||
|
| entry2 | 2016-04-21 | default | I am using a themed layout. {% include include.html %} {% include in_project.html %} |
|
||||||
|
And I have a _layouts directory
|
||||||
|
And I have a "_layouts/post.html" page with layout "default" that contains "I am a post layout! {{ content }}"
|
||||||
|
And I have an _includes directory
|
||||||
|
And I have an "_includes/in_project.html" file that contains "I am in the project, not the theme."
|
||||||
|
When I run jekyll build
|
||||||
|
Then I should get a zero exit status
|
||||||
|
And the _site directory should exist
|
||||||
|
And I should see "I am in the project, not the theme." in "_site/2016/04/21/entry2.html"
|
||||||
|
And I should see "<span class=\"sample\">include.html from test-theme</span>" in "_site/2016/04/21/entry2.html"
|
||||||
|
And I should see "default.html from test-theme:" in "_site/2016/04/21/entry2.html"
|
||||||
|
And I should see "I am using a themed layout." in "_site/2016/04/21/entry2.html"
|
||||||
|
And I should not see "I am a post layout!" in "_site/2016/04/21/entry2.html"
|
||||||
|
And I should not see "I am in the project, not the theme." in "_site/2016/04/21/entry1.html"
|
||||||
|
And I should see "<span class=\"sample\">include.html from test-theme</span>" in "_site/2016/04/21/entry1.html"
|
||||||
|
And I should see "default.html from test-theme:" in "_site/2016/04/21/entry1.html"
|
||||||
|
And I should see "I am using a local layout." in "_site/2016/04/21/entry1.html"
|
||||||
|
And I should see "I am a post layout!" in "_site/2016/04/21/entry1.html"
|
|
@ -66,6 +66,7 @@ module Jekyll
|
||||||
autoload :Site, 'jekyll/site'
|
autoload :Site, 'jekyll/site'
|
||||||
autoload :StaticFile, 'jekyll/static_file'
|
autoload :StaticFile, 'jekyll/static_file'
|
||||||
autoload :Stevenson, 'jekyll/stevenson'
|
autoload :Stevenson, 'jekyll/stevenson'
|
||||||
|
autoload :Theme, 'jekyll/theme'
|
||||||
autoload :URL, 'jekyll/url'
|
autoload :URL, 'jekyll/url'
|
||||||
autoload :Utils, 'jekyll/utils'
|
autoload :Utils, 'jekyll/utils'
|
||||||
autoload :VERSION, 'jekyll/version'
|
autoload :VERSION, 'jekyll/version'
|
||||||
|
|
|
@ -39,7 +39,7 @@ module Jekyll
|
||||||
filename = File.join(base, name)
|
filename = File.join(base, name)
|
||||||
|
|
||||||
begin
|
begin
|
||||||
self.content = File.read(site.in_source_dir(base, name),
|
self.content = File.read(@path || site.in_source_dir(base, name),
|
||||||
Utils.merged_file_read_opts(site, opts))
|
Utils.merged_file_read_opts(site, opts))
|
||||||
if content =~ Document::YAML_FRONT_MATTER_REGEXP
|
if content =~ Document::YAML_FRONT_MATTER_REGEXP
|
||||||
self.content = $POSTMATCH
|
self.content = $POSTMATCH
|
||||||
|
@ -217,7 +217,7 @@ module Jekyll
|
||||||
self.output = render_liquid(layout.content,
|
self.output = render_liquid(layout.content,
|
||||||
payload,
|
payload,
|
||||||
info,
|
info,
|
||||||
File.join(site.config['layouts_dir'], layout.name))
|
layout.relative_path)
|
||||||
|
|
||||||
# Add layout to dependency tree
|
# Add layout to dependency tree
|
||||||
site.regenerator.add_dependency(
|
site.regenerator.add_dependency(
|
||||||
|
|
|
@ -29,7 +29,14 @@ module Jekyll
|
||||||
@site = site
|
@site = site
|
||||||
@base = base
|
@base = base
|
||||||
@name = name
|
@name = name
|
||||||
|
|
||||||
|
if site.theme && site.theme.layouts_path.eql?(base)
|
||||||
|
@base_dir = site.theme.root
|
||||||
|
@path = site.in_theme_dir(base, name)
|
||||||
|
else
|
||||||
|
@base_dir = site.source
|
||||||
@path = site.in_source_dir(base, name)
|
@path = site.in_source_dir(base, name)
|
||||||
|
end
|
||||||
|
|
||||||
self.data = {}
|
self.data = {}
|
||||||
|
|
||||||
|
@ -45,5 +52,13 @@ module Jekyll
|
||||||
def process(name)
|
def process(name)
|
||||||
self.ext = File.extname(name)
|
self.ext = File.extname(name)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# The path to the layout, relative to the site source.
|
||||||
|
#
|
||||||
|
# Returns a String path which represents the relative path
|
||||||
|
# from the site source to this layout
|
||||||
|
def relative_path
|
||||||
|
@relative_path ||= Pathname.new(path).relative_path_from(Pathname.new(@base_dir)).to_s
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -40,6 +40,7 @@ module Jekyll
|
||||||
@base = base
|
@base = base
|
||||||
@dir = dir
|
@dir = dir
|
||||||
@name = name
|
@name = name
|
||||||
|
@path = site.in_source_dir(base, dir, name)
|
||||||
|
|
||||||
process(name)
|
process(name)
|
||||||
read_yaml(File.join(base, dir), name)
|
read_yaml(File.join(base, dir), name)
|
||||||
|
|
|
@ -7,8 +7,12 @@ module Jekyll
|
||||||
end
|
end
|
||||||
|
|
||||||
def read
|
def read
|
||||||
layout_entries.each do |f|
|
layout_entries.each do |layout_file|
|
||||||
@layouts[layout_name(f)] = Layout.new(site, layout_directory, f)
|
@layouts[layout_name(layout_file)] = Layout.new(site, layout_directory, layout_file)
|
||||||
|
end
|
||||||
|
|
||||||
|
theme_layout_entries.each do |layout_file|
|
||||||
|
@layouts[layout_name(layout_file)] ||= Layout.new(site, theme_layout_directory, layout_file)
|
||||||
end
|
end
|
||||||
|
|
||||||
@layouts
|
@layouts
|
||||||
|
@ -18,11 +22,23 @@ module Jekyll
|
||||||
@layout_directory ||= (layout_directory_in_cwd || layout_directory_inside_source)
|
@layout_directory ||= (layout_directory_in_cwd || layout_directory_inside_source)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def theme_layout_directory
|
||||||
|
@theme_layout_directory ||= site.theme.layouts_path if site.theme
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def layout_entries
|
def layout_entries
|
||||||
|
entries_in layout_directory
|
||||||
|
end
|
||||||
|
|
||||||
|
def theme_layout_entries
|
||||||
|
theme_layout_directory ? entries_in(theme_layout_directory) : []
|
||||||
|
end
|
||||||
|
|
||||||
|
def entries_in(dir)
|
||||||
entries = []
|
entries = []
|
||||||
within(layout_directory) do
|
within(dir) do
|
||||||
entries = EntryFilter.new(site).filter(Dir['**/*.*'])
|
entries = EntryFilter.new(site).filter(Dir['**/*.*'])
|
||||||
end
|
end
|
||||||
entries
|
entries
|
||||||
|
|
|
@ -145,7 +145,7 @@ module Jekyll
|
||||||
layout.content,
|
layout.content,
|
||||||
payload,
|
payload,
|
||||||
info,
|
info,
|
||||||
File.join(site.config['layouts_dir'], layout.name)
|
layout.relative_path
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add layout to dependency tree
|
# Add layout to dependency tree
|
||||||
|
|
|
@ -8,10 +8,10 @@ module Jekyll
|
||||||
:exclude, :include, :lsi, :highlighter, :permalink_style,
|
:exclude, :include, :lsi, :highlighter, :permalink_style,
|
||||||
:time, :future, :unpublished, :safe, :plugins, :limit_posts,
|
:time, :future, :unpublished, :safe, :plugins, :limit_posts,
|
||||||
:show_drafts, :keep_files, :baseurl, :data, :file_read_opts,
|
:show_drafts, :keep_files, :baseurl, :data, :file_read_opts,
|
||||||
:gems, :plugin_manager
|
:gems, :plugin_manager, :theme
|
||||||
|
|
||||||
attr_accessor :converters, :generators, :reader
|
attr_accessor :converters, :generators, :reader
|
||||||
attr_reader :regenerator, :liquid_renderer
|
attr_reader :regenerator, :liquid_renderer, :includes_load_paths
|
||||||
|
|
||||||
# Public: Initialize a new Site.
|
# Public: Initialize a new Site.
|
||||||
#
|
#
|
||||||
|
@ -52,6 +52,12 @@ module Jekyll
|
||||||
self.plugin_manager = Jekyll::PluginManager.new(self)
|
self.plugin_manager = Jekyll::PluginManager.new(self)
|
||||||
self.plugins = plugin_manager.plugins_path
|
self.plugins = plugin_manager.plugins_path
|
||||||
|
|
||||||
|
self.theme = nil
|
||||||
|
self.theme = Jekyll::Theme.new(config["theme"]) if config["theme"]
|
||||||
|
|
||||||
|
@includes_load_paths = Array(in_source_dir(config["includes_dir"].to_s))
|
||||||
|
@includes_load_paths << theme.includes_path if self.theme
|
||||||
|
|
||||||
self.file_read_opts = {}
|
self.file_read_opts = {}
|
||||||
self.file_read_opts[:encoding] = config['encoding'] if config['encoding']
|
self.file_read_opts[:encoding] = config['encoding'] if config['encoding']
|
||||||
|
|
||||||
|
@ -367,6 +373,19 @@ module Jekyll
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Public: Prefix a given path with the theme directory.
|
||||||
|
#
|
||||||
|
# paths - (optional) path elements to a file or directory within the
|
||||||
|
# theme directory
|
||||||
|
#
|
||||||
|
# Returns a path which is prefixed with the theme root directory.
|
||||||
|
def in_theme_dir(*paths)
|
||||||
|
return nil unless theme
|
||||||
|
paths.reduce(theme.root) do |base, path|
|
||||||
|
Jekyll.sanitized_path(base, path)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Public: Prefix a given path with the destination directory.
|
# Public: Prefix a given path with the destination directory.
|
||||||
#
|
#
|
||||||
# paths - (optional) path elements to a file or directory within the
|
# paths - (optional) path elements to a file or directory within the
|
||||||
|
|
|
@ -12,8 +12,6 @@ module Jekyll
|
||||||
end
|
end
|
||||||
|
|
||||||
class IncludeTag < Liquid::Tag
|
class IncludeTag < Liquid::Tag
|
||||||
attr_reader :includes_dir
|
|
||||||
|
|
||||||
VALID_SYNTAX = /([\w-]+)\s*=\s*(?:"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|([\w\.-]+))/
|
VALID_SYNTAX = /([\w-]+)\s*=\s*(?:"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|([\w\.-]+))/
|
||||||
VARIABLE_SYNTAX = /(?<variable>[^{]*(\{\{\s*[\w\-\.]+\s*(\|.*)?\}\}[^\s{}]*)+)(?<params>.*)/
|
VARIABLE_SYNTAX = /(?<variable>[^{]*(\{\{\s*[\w\-\.]+\s*(\|.*)?\}\}[^\s{}]*)+)(?<params>.*)/
|
||||||
|
|
||||||
|
@ -98,20 +96,29 @@ eos
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def tag_includes_dir(context)
|
def tag_includes_dirs(context)
|
||||||
context.registers[:site].config['includes_dir'].freeze
|
context.registers[:site].includes_load_paths.freeze
|
||||||
|
end
|
||||||
|
|
||||||
|
def locate_include_file(context, file, safe)
|
||||||
|
includes_dirs = tag_includes_dirs(context)
|
||||||
|
includes_dirs.each do |dir|
|
||||||
|
path = File.join(dir, file)
|
||||||
|
return path if valid_include_file?(path, dir, safe)
|
||||||
|
end
|
||||||
|
raise IOError, "Could not locate the included file '#{file}' in any of #{includes_dirs}." \
|
||||||
|
" Ensure it exists in one of those directories and, if it is a symlink, " \
|
||||||
|
"does not point outside your site source."
|
||||||
end
|
end
|
||||||
|
|
||||||
def render(context)
|
def render(context)
|
||||||
site = context.registers[:site]
|
site = context.registers[:site]
|
||||||
@includes_dir = tag_includes_dir(context)
|
|
||||||
dir = resolved_includes_dir(context)
|
|
||||||
|
|
||||||
file = render_variable(context) || @file
|
file = render_variable(context) || @file
|
||||||
validate_file_name(file)
|
validate_file_name(file)
|
||||||
|
|
||||||
path = File.join(dir, file)
|
path = locate_include_file(context, file, site.safe)
|
||||||
validate_path(path, dir, site.safe)
|
return unless path
|
||||||
|
|
||||||
# Add include to dependency tree
|
# Add include to dependency tree
|
||||||
if context.registers[:page] && context.registers[:page].key?("path")
|
if context.registers[:page] && context.registers[:page].key?("path")
|
||||||
|
@ -121,16 +128,16 @@ eos
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
begin
|
#begin
|
||||||
partial = load_cached_partial(path, context)
|
partial = load_cached_partial(path, context)
|
||||||
|
|
||||||
context.stack do
|
context.stack do
|
||||||
context['include'] = parse_params(context) if @params
|
context['include'] = parse_params(context) if @params
|
||||||
partial.render!(context)
|
partial.render!(context)
|
||||||
end
|
end
|
||||||
rescue => e
|
#rescue => e
|
||||||
raise IncludeTagError.new e.message, File.join(@includes_dir, @file)
|
#raise IncludeTagError.new e.message, path
|
||||||
end
|
#end
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_cached_partial(path, context)
|
def load_cached_partial(path, context)
|
||||||
|
@ -144,24 +151,18 @@ eos
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def resolved_includes_dir(context)
|
def valid_include_file?(path, dir, safe)
|
||||||
context.registers[:site].in_source_dir(@includes_dir)
|
!(outside_site_source?(path, dir, safe) || !File.exist?(path))
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_path(path, dir, safe)
|
def outside_site_source?(path, dir, safe)
|
||||||
if safe && !realpath_prefixed_with?(path, dir)
|
safe && !realpath_prefixed_with?(path, dir)
|
||||||
raise IOError.new "The included file '#{path}' should exist and should not be a symlink"
|
|
||||||
elsif !File.exist?(path)
|
|
||||||
raise IOError.new "Included file '#{path_relative_to_source(dir, path)}' not found"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def path_relative_to_source(dir, path)
|
|
||||||
File.join(@includes_dir, path.sub(Regexp.new("^#{dir}"), ""))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def realpath_prefixed_with?(path, dir)
|
def realpath_prefixed_with?(path, dir)
|
||||||
File.exist?(path) && File.realpath(path).start_with?(dir)
|
File.exist?(path) && File.realpath(path).start_with?(dir)
|
||||||
|
rescue
|
||||||
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
# This method allows to modify the file content by inheriting from the class.
|
# This method allows to modify the file content by inheriting from the class.
|
||||||
|
@ -171,16 +172,17 @@ eos
|
||||||
end
|
end
|
||||||
|
|
||||||
class IncludeRelativeTag < IncludeTag
|
class IncludeRelativeTag < IncludeTag
|
||||||
def tag_includes_dir(context)
|
def tag_includes_dirs(context)
|
||||||
'.'.freeze
|
Array(page_path(context)).freeze
|
||||||
end
|
end
|
||||||
|
|
||||||
def page_path(context)
|
def page_path(context)
|
||||||
context.registers[:page].nil? ? includes_dir : File.dirname(context.registers[:page]["path"])
|
if context.registers[:page].nil?
|
||||||
|
context.registers[:site].source
|
||||||
|
else
|
||||||
|
current_doc_dir = File.dirname(context.registers[:page]["path"])
|
||||||
|
context.registers[:site].in_source_dir current_doc_dir
|
||||||
end
|
end
|
||||||
|
|
||||||
def resolved_includes_dir(context)
|
|
||||||
context.registers[:site].in_source_dir(page_path(context))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
module Jekyll
|
||||||
|
class Theme
|
||||||
|
extend Forwardable
|
||||||
|
attr_reader :name
|
||||||
|
def_delegator :gemspec, :version, :version
|
||||||
|
|
||||||
|
def initialize(name)
|
||||||
|
@name = name.downcase.strip
|
||||||
|
configure_sass
|
||||||
|
end
|
||||||
|
|
||||||
|
def root
|
||||||
|
@root ||= gemspec.full_gem_path
|
||||||
|
end
|
||||||
|
|
||||||
|
def includes_path
|
||||||
|
path_for :includes
|
||||||
|
end
|
||||||
|
|
||||||
|
def layouts_path
|
||||||
|
path_for :layouts
|
||||||
|
end
|
||||||
|
|
||||||
|
def sass_path
|
||||||
|
path_for :sass
|
||||||
|
end
|
||||||
|
|
||||||
|
def configure_sass
|
||||||
|
return unless sass_path
|
||||||
|
require 'sass'
|
||||||
|
Sass.load_paths << sass_path
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def path_for(folder)
|
||||||
|
resolved_dir = realpath_for(folder)
|
||||||
|
return unless resolved_dir
|
||||||
|
|
||||||
|
path = Jekyll.sanitized_path(root, resolved_dir)
|
||||||
|
path if Dir.exists?(path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def realpath_for(folder)
|
||||||
|
File.realpath(Jekyll.sanitized_path(root, "_#{folder}"))
|
||||||
|
rescue Errno::ENOENT, Errno::EACCES, Errno::ELOOP
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def gemspec
|
||||||
|
@gemspec ||= Gem::Specification.find_by_name(name)
|
||||||
|
rescue Gem::LoadError
|
||||||
|
raise Jekyll::Errors::MissingDependencyException, "The #{name} theme could not be found."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -103,7 +103,7 @@ namespace :site do
|
||||||
|
|
||||||
desc "Create a nicely formatted history page for the jekyll site based on the repo history."
|
desc "Create a nicely formatted history page for the jekyll site based on the repo history."
|
||||||
task :history do
|
task :history do
|
||||||
siteify_file('History.markdown')
|
siteify_file('History.markdown', { "title" => "History" })
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "Copy the Code of Conduct"
|
desc "Copy the Code of Conduct"
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
- permalinks
|
- permalinks
|
||||||
- pagination
|
- pagination
|
||||||
- plugins
|
- plugins
|
||||||
|
- themes
|
||||||
- extras
|
- extras
|
||||||
|
|
||||||
- title: Deployment
|
- title: Deployment
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
---
|
||||||
|
layout: docs
|
||||||
|
title: Themes
|
||||||
|
permalink: /docs/themes/
|
||||||
|
---
|
||||||
|
|
||||||
|
Jekyll has an extensive theme system, which allows you to leverage community-maintained templates and styles to customize your site's presentation. Jekyll themes package layouts, includes, and stylesheets in a way that can be overridden by your site's content.
|
||||||
|
|
||||||
|
## Installing a theme
|
||||||
|
|
||||||
|
1. To install a theme, first, add the theme to your site's `Gemfile`:
|
||||||
|
|
||||||
|
gem 'my-awesome-jekyll-theme'
|
||||||
|
|
||||||
|
2. Save the changes to your `Gemfile`
|
||||||
|
3. Run the command `bundle install` to install the theme
|
||||||
|
4. Finally, activate the theme by adding the following to your site's `_config.yml`:
|
||||||
|
|
||||||
|
theme: my-awesome-jekyll-theme
|
||||||
|
|
||||||
|
You can have multiple themes listed in your site's Gemfile, but only one theme can be selected in your site's `_config.yml`.
|
||||||
|
{: .note .info }
|
||||||
|
|
||||||
|
## Overriding theme defaults
|
||||||
|
|
||||||
|
Jekyll themes set default layouts, includes, and stylesheets, that can be overridden by your site's content. For example, if your selected theme has a `page` layout, you can override the theme's layout by creating your own `page` layout in the `_layouts` folder (e.g., `_layouts/page.html`).
|
||||||
|
|
||||||
|
Jekyll will look first to your site's content, before looking to the theme's defaults, for any requested file in the following folders:
|
||||||
|
|
||||||
|
* `/_layouts`
|
||||||
|
* `/_includes`
|
||||||
|
* `/_sass`
|
||||||
|
|
||||||
|
Refer to your selected theme's documentation and source repository for more information on what files you can override.
|
||||||
|
{: .note .info}
|
||||||
|
|
||||||
|
## Creating a theme
|
||||||
|
|
||||||
|
Jekyll themes are distributed as Ruby gems. The only required file is the [Ruby Gemspec](http://guides.rubygems.org/specification-reference/). Here's an example of a minimal Gemspec for the `my-awesome-jekyll-theme` theme, saved as `/my-awsome-jekyll-theme.gemspec`:
|
||||||
|
|
||||||
|
{% highlight ruby %}
|
||||||
|
Gem::Specification.new do |s|
|
||||||
|
s.name = '<THEME TITLE>'
|
||||||
|
s.version = '0.1.0'
|
||||||
|
s.license = 'MIT'
|
||||||
|
s.summary = '<THEME DESCRIPTION>'
|
||||||
|
s.author = '<YOUR NAME>'
|
||||||
|
s.email = '<YOUR EMAIL>'
|
||||||
|
s.homepage = 'https://github.com/jekyll/my-awesome-jekyll-theme'
|
||||||
|
s.files = `git ls-files -z`.split("\x0").grep(%r{^_(sass|includes|layouts)/})
|
||||||
|
end
|
||||||
|
{% endhighlight %}
|
||||||
|
|
||||||
|
### Layouts and includes
|
||||||
|
|
||||||
|
Theme layouts and includes work just like they work in any Jekyll site. Place layouts in your theme's `/_layouts` folder, and place includes in your themes `/_includes` folder.
|
||||||
|
|
||||||
|
For example, if your theme has a `/_layouts/page.html` file, and a page has `layout: page` in its YAML front matter, Jekyll will first look to the site's `_layouts` folder for a the `page` layout, and if none exists, will use your theme's `page` layout.
|
||||||
|
|
||||||
|
### Stylesheets
|
||||||
|
|
||||||
|
Your theme's stylesheets should be placed in your theme's `/_sass` folder, again, just as you would when authoring a Jekyll site. Your theme's styles can be included in the user's stylesheet using the `@import` directive.
|
||||||
|
|
||||||
|
### Documenting your theme
|
||||||
|
|
||||||
|
Your theme should include a `/README.md` file, which explains how site authors can install and use your theme. What layouts are included? What includes? Do they need to add anything special to their site's configuration file?
|
||||||
|
|
||||||
|
### Adding a screenshot
|
||||||
|
|
||||||
|
Themes are visual. Show users what your theme looks like by including a screenshot as `/screenshot.png` within your theme's repository where it can be retrieved programatically. You can also include this screenshot within your theme's documentation.
|
||||||
|
|
||||||
|
### Previewing your theme
|
||||||
|
|
||||||
|
To preview your theme as you're authoring it, it may be helpful to add dummy content in, for example, `/index.html` and `/page.html` files. This will allow you to use the `jekyll build` and `jekyll serve` commands to preview your theme, just as you'd preview a Jekyll site.
|
||||||
|
|
||||||
|
If you do preview your theme locally, be sure to add `/_site` to your theme's `.gitignore` file to prevent the compiled site from also being included when you distribute your theme.
|
||||||
|
{: .info .note}
|
||||||
|
|
||||||
|
### Publishing your theme
|
||||||
|
|
||||||
|
Themes are published via [RubyGems.org](https://rubygems.org). You'll need a RubyGems account, which you can [create for free](https://rubygems.org/sign_up).
|
||||||
|
|
||||||
|
1. First, package your theme, by running the following command, replacing `my-awesome-jekyll-theme` with the name of your theme:
|
||||||
|
|
||||||
|
gem build my-awesome-jekyll-theme.gemspec
|
||||||
|
|
||||||
|
2. Next, push your packaged theme up to the RubyGems service, by running the following command, again replacing `my-awesome-jekyll-theme` with the name of your theme:
|
||||||
|
|
||||||
|
gem push my-awesome-jekyll-theme-*.gem
|
|
@ -0,0 +1 @@
|
||||||
|
<span class="sample">include.html from test-theme</span>
|
|
@ -0,0 +1 @@
|
||||||
|
default.html from test-theme: {{ content }}
|
|
@ -0,0 +1,3 @@
|
||||||
|
.sample {
|
||||||
|
color: black;
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
_layouts
|
|
@ -0,0 +1,9 @@
|
||||||
|
Gem::Specification.new do |s|
|
||||||
|
s.name = 'test-theme'
|
||||||
|
s.version = '0.1.0'
|
||||||
|
s.licenses = ['MIT']
|
||||||
|
s.summary = "This is a theme used to test Jekyll"
|
||||||
|
s.authors = ["Jekyll"]
|
||||||
|
s.files = ["lib/example.rb"]
|
||||||
|
s.homepage = 'https://github.com/jekyll/jekyll'
|
||||||
|
end
|
|
@ -616,7 +616,7 @@ title: Include symlink
|
||||||
CONTENT
|
CONTENT
|
||||||
create_post(content, {'permalink' => 'pretty', 'source' => source_dir, 'destination' => dest_dir, 'read_posts' => true, 'safe' => true })
|
create_post(content, {'permalink' => 'pretty', 'source' => source_dir, 'destination' => dest_dir, 'read_posts' => true, 'safe' => true })
|
||||||
end
|
end
|
||||||
assert_match(/should exist and should not be a symlink/, ex.message)
|
assert_match "Could not locate the included file 'tmp/pages-test-does-not-exist' in any of [\"#{source_dir}/_includes\"].", ex.message
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -757,7 +757,7 @@ CONTENT
|
||||||
exception = assert_raises IOError do
|
exception = assert_raises IOError do
|
||||||
create_post(@content, {'permalink' => 'pretty', 'source' => source_dir, 'destination' => dest_dir, 'read_posts' => true})
|
create_post(@content, {'permalink' => 'pretty', 'source' => source_dir, 'destination' => dest_dir, 'read_posts' => true})
|
||||||
end
|
end
|
||||||
assert_equal 'Included file \'_includes/missing.html\' not found', exception.message
|
assert_match "Could not locate the included file 'missing.html' in any of [\"#{source_dir}/_includes\"].", exception.message
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -839,7 +839,7 @@ CONTENT
|
||||||
exception = assert_raises IOError do
|
exception = assert_raises IOError do
|
||||||
create_post(@content, {'permalink' => 'pretty', 'source' => source_dir, 'destination' => dest_dir, 'read_posts' => true})
|
create_post(@content, {'permalink' => 'pretty', 'source' => source_dir, 'destination' => dest_dir, 'read_posts' => true})
|
||||||
end
|
end
|
||||||
assert_equal 'Included file \'./missing.html\' not found', exception.message
|
assert_match "Could not locate the included file 'missing.html' in any of [\"#{source_dir}\"].", exception.message
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -894,7 +894,7 @@ title: Include symlink
|
||||||
CONTENT
|
CONTENT
|
||||||
create_post(content, {'permalink' => 'pretty', 'source' => source_dir, 'destination' => dest_dir, 'read_posts' => true, 'safe' => true })
|
create_post(content, {'permalink' => 'pretty', 'source' => source_dir, 'destination' => dest_dir, 'read_posts' => true, 'safe' => true })
|
||||||
end
|
end
|
||||||
assert_match(/should exist and should not be a symlink/, ex.message)
|
assert_match /Ensure it exists in one of those directories and, if it is a symlink, does not point outside your site source./, ex.message
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
require 'helper'
|
||||||
|
|
||||||
|
class TestTheme < JekyllUnitTest
|
||||||
|
def setup
|
||||||
|
@theme = Theme.new('test-theme')
|
||||||
|
@expected_root = File.expand_path "./fixtures/test-theme", File.dirname(__FILE__)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "initializing" do
|
||||||
|
should "normalize the theme name" do
|
||||||
|
theme = Theme.new(' Test-Theme ')
|
||||||
|
assert_equal "test-theme", theme.name
|
||||||
|
end
|
||||||
|
|
||||||
|
should "know the theme root" do
|
||||||
|
assert_equal @expected_root, @theme.root
|
||||||
|
end
|
||||||
|
|
||||||
|
should "know the theme version" do
|
||||||
|
assert_equal Gem::Version.new("0.1.0"), @theme.version
|
||||||
|
end
|
||||||
|
|
||||||
|
should "raise an error for invalid themes" do
|
||||||
|
assert_raises Jekyll::Errors::MissingDependencyException do
|
||||||
|
Theme.new("foo").version
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
should "add itself to sass's load path" do
|
||||||
|
@theme.configure_sass
|
||||||
|
assert Sass.load_paths.include?(@theme.sass_path), "Sass load paths should include the theme sass dir"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "path generation" do
|
||||||
|
[:layouts, :includes, :sass].each do |folder|
|
||||||
|
should "know the #{folder} path" do
|
||||||
|
expected = File.expand_path("_#{folder}", @expected_root)
|
||||||
|
assert_equal expected, @theme.public_send("#{folder}_path")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
should "generate folder paths" do
|
||||||
|
expected = File.expand_path("./_sass", @expected_root)
|
||||||
|
assert_equal expected, @theme.send(:path_for, :sass)
|
||||||
|
end
|
||||||
|
|
||||||
|
should "not allow paths outside of the theme root" do
|
||||||
|
assert_equal nil, @theme.send(:path_for, "../../source")
|
||||||
|
end
|
||||||
|
|
||||||
|
should "return nil for paths that don't exist" do
|
||||||
|
assert_equal nil, @theme.send(:path_for, "foo")
|
||||||
|
end
|
||||||
|
|
||||||
|
should "return the resolved path when a symlink & resolved path exists" do
|
||||||
|
expected = File.expand_path("./_layouts", @expected_root)
|
||||||
|
assert_equal expected, @theme.send(:path_for, :symlink)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
should "retrieve the gemspec" do
|
||||||
|
assert_equal "test-theme-0.1.0", @theme.send(:gemspec).full_name
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue