├── lib
├── jekyll-spark.rb
└── jekyll
│ ├── spark
│ ├── version.rb
│ ├── tag.rb
│ ├── block.rb
│ └── base.rb
│ └── spark.rb
├── test
├── source
│ ├── _posts
│ │ └── 2016-01-01-base.md
│ ├── _plugins
│ │ ├── jekyll-spark.rb
│ │ └── components
│ │ │ ├── blocks
│ │ │ ├── nested.rb
│ │ │ ├── container.rb
│ │ │ └── wistia_popover.rb
│ │ │ └── tags
│ │ │ ├── wistia.rb
│ │ │ └── image.rb
│ ├── _config.yml
│ └── components
│ │ ├── wistia-markdown.md
│ │ ├── image-markdown.md
│ │ ├── wistia.html
│ │ ├── test-nested.md
│ │ ├── wistia-popover.md
│ │ ├── image.html
│ │ └── container.md
├── test_component_props.rb
├── test_component_container.rb
├── test_component_test_nested.rb
├── test_unit_spark_base.rb
├── test_component_wistia_popover.rb
├── test_component_wistia.rb
├── helper.rb
└── test_component_image.rb
├── Gemfile
├── .gitignore
├── .travis.yml
├── Rakefile
├── examples
├── tag_component.rb
└── block_component.rb
├── LICENSE.txt
├── docs
├── introduction.md
└── creating-a-component.md
├── README.md
└── jekyll-spark.gemspec
/lib/jekyll-spark.rb:
--------------------------------------------------------------------------------
1 | require "jekyll/spark"
2 |
--------------------------------------------------------------------------------
/test/source/_posts/2016-01-01-base.md:
--------------------------------------------------------------------------------
1 | Hello
2 |
--------------------------------------------------------------------------------
/test/source/_plugins/jekyll-spark.rb:
--------------------------------------------------------------------------------
1 | ../../../lib/jekyll-spark.rb
--------------------------------------------------------------------------------
/lib/jekyll/spark/version.rb:
--------------------------------------------------------------------------------
1 | module Jekyll
2 | module Spark
3 | VERSION = "0.6.0"
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/test/source/_config.yml:
--------------------------------------------------------------------------------
1 | defaults:
2 | -
3 | scope:
4 | path: ""
5 | values:
6 | layout: null
7 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gem 'coveralls', require: false
4 | gem 'simplecov', require: false
5 |
6 | gemspec
7 |
--------------------------------------------------------------------------------
/lib/jekyll/spark.rb:
--------------------------------------------------------------------------------
1 | require "jekyll"
2 | require "jekyll/spark/version"
3 | require "jekyll/spark/block"
4 | require "jekyll/spark/tag"
5 |
--------------------------------------------------------------------------------
/test/source/components/wistia-markdown.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Wistia Markdown"
3 | type: "Component"
4 | ---
5 |
6 | {% Wistia id: "123" %}
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.bundle/
3 | /.yardoc
4 | /Gemfile.lock
5 | /_yardoc/
6 | /coverage/
7 | /doc/
8 | /pkg/
9 | /spec/reports/
10 | /tmp/
11 |
--------------------------------------------------------------------------------
/lib/jekyll/spark/tag.rb:
--------------------------------------------------------------------------------
1 | require "jekyll"
2 | require_relative "./base"
3 |
4 | module Jekyll
5 | class ComponentTag < Liquid::Tag
6 | include Jekyll::ComponentBase
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/lib/jekyll/spark/block.rb:
--------------------------------------------------------------------------------
1 | require "jekyll"
2 | require_relative "./base"
3 |
4 | module Jekyll
5 | class ComponentBlock < Liquid::Block
6 | include Jekyll::ComponentBase
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: ruby
3 | rvm:
4 | - 2.3.3
5 | before_install: gem install bundler -v 1.13.7
6 |
7 | install:
8 | - bundle install
9 |
10 | script:
11 | - bundle exec rake test
12 |
--------------------------------------------------------------------------------
/test/source/components/image-markdown.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Image Markdown"
3 | type: "Component"
4 | ---
5 |
6 | {% img
7 | alt: "alt"
8 | class: "one"
9 | height: "200"
10 | src: "test.png"
11 | style: "background: red;"
12 | title: "title"
13 | width: "400"
14 | %}
15 |
--------------------------------------------------------------------------------
/test/source/components/wistia.html:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Wistia"
3 | type: "Component"
4 | ---
5 |
6 |
7 | {% Wistia id: "123" %}
8 |
9 |
10 |
11 | {% Wistia %}
12 |
13 |
14 |
15 | {% Wistia id: "wistia_123" %}
16 |
17 |
18 |
19 | {% Wistia id: "wistia_123" %}
20 |
21 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'bundler/gem_tasks'
2 | require 'rubygems'
3 | require 'rake'
4 | require 'rdoc'
5 | require 'date'
6 | require 'yaml'
7 | require 'rake/testtask'
8 |
9 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), *%w[lib]))
10 | require 'jekyll/version'
11 |
12 | Rake::TestTask.new(:test) do |test|
13 | test.libs << 'lib' << 'test'
14 | test.pattern = 'test/**/test_*.rb'
15 | test.verbose = true
16 | end
17 |
--------------------------------------------------------------------------------
/test/source/components/test-nested.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Test Nested"
3 | type: "Component"
4 | ---
5 |
6 |
7 | {% TestNested %}{% endTestNested %}
8 |
9 |
10 |
11 | {% TestNested %}
12 | Hello
13 | {% endTestNested %}
14 |
15 |
16 |
17 | {% TestNested %}
18 | {% TestNested %}
19 | Yo dawg…
20 | {% endTestNested %}
21 | {% endTestNested %}
22 |
23 |
--------------------------------------------------------------------------------
/examples/tag_component.rb:
--------------------------------------------------------------------------------
1 | require "jekyll-spark"
2 |
3 | module Jekyll
4 | class ExampleTagComponent < ComponentTag
5 | def template(context)
6 | # Declare props as variables here
7 |
8 | # Output rendered markup
9 | render = %Q[
10 |
11 | Example
12 |
13 | ]
14 | end
15 | end
16 | end
17 |
18 | Liquid::Template.register_tag(
19 | "ExampleTag",
20 | Jekyll::ExampleTagComponent,
21 | )
22 |
--------------------------------------------------------------------------------
/examples/block_component.rb:
--------------------------------------------------------------------------------
1 | require "jekyll-spark"
2 |
3 | module Jekyll
4 | class ExampleBlockComponent < ComponentBlock
5 | def template(context)
6 | # Declare props as variables here
7 | content = @props["content"]
8 |
9 | # Output rendered markup
10 | render = %Q[
11 |
12 | #{content}
13 |
14 | ]
15 | end
16 | end
17 | end
18 |
19 | Liquid::Template.register_tag(
20 | "ExampleBlock",
21 | Jekyll::ExampleBlockComponent,
22 | )
23 |
--------------------------------------------------------------------------------
/test/source/_plugins/components/blocks/nested.rb:
--------------------------------------------------------------------------------
1 | require "jekyll-spark"
2 |
3 | module Jekyll
4 | class TestNestedComponent < ComponentBlock
5 | def template(context)
6 | content = @props["content"]
7 |
8 | render = %Q[
9 |
10 | {% img
11 | src: "test.jpg"
12 | %}
13 | #{content}
14 |
15 | ]
16 | end
17 | end
18 | end
19 |
20 | Liquid::Template.register_tag(
21 | "TestNested",
22 | Jekyll::TestNestedComponent,
23 | )
24 |
--------------------------------------------------------------------------------
/test/source/_plugins/components/blocks/container.rb:
--------------------------------------------------------------------------------
1 | require "jekyll-spark"
2 |
3 | module Jekyll
4 | class ContainerComponent < ComponentBlock
5 | def template(context)
6 | content = @props["content"]
7 | class_name = @props["class"]
8 |
9 | render = %Q[
10 |
11 |
12 |
13 | #{content}
14 |
15 |
16 |
17 | ]
18 | end
19 | end
20 | end
21 |
22 | Liquid::Template.register_tag(
23 | "Container",
24 | Jekyll::ContainerComponent,
25 | )
26 |
--------------------------------------------------------------------------------
/test/source/components/wistia-popover.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Wistia Popover"
3 | type: "Component"
4 | text: "wut"
5 | ---
6 |
7 | {% WistiaPopover id: "hello" class: "first" %}
8 | Hello
9 | {% endWistiaPopover %}
10 |
11 |
12 |
13 | {% WistiaPopover id: "linky" class: "linky" %}
14 | Help Scout
15 | {% endWistiaPopover %}
16 |
17 |
18 |
19 | {% WistiaPopover id: "linky" class: "eval" %}
20 | {% if page.text %}
21 | {{ page.text }}
22 | {% else %}
23 | Nope!
24 | {% endif %}
25 | {% endWistiaPopover %}
26 |
27 |
28 |
29 | {% WistiaPopover
30 | id: "mov123"
31 | class: "stylin"
32 | style: "display: inline-block"
33 | %}
34 | Watch the video
35 | {% endWistiaPopover %}
36 |
--------------------------------------------------------------------------------
/test/test_component_props.rb:
--------------------------------------------------------------------------------
1 | require "helper"
2 |
3 | class PropsComponent < JekyllUnitTest
4 | should "render with nil props" do
5 | @joule.site.data["props"] = Hash.new
6 | @joule.site.data["props"]["src"] = "hello.jpg"
7 | @joule.site.data["props"]["post"] = {
8 | "nope_title" => "Title"
9 | }
10 |
11 | @joule.render(%Q[
12 | {% assign thumb = site.data.props.src %}
13 | {% img
14 | src: thumb
15 | alt: post.title
16 | height: 196
17 | title: post.title
18 | width: 350
19 | skip_lazy: true
20 | %}
21 | ])
22 |
23 | el = @joule.find("img")
24 |
25 | assert_equal(el.prop("src"), "hello.jpg")
26 | assert_equal(el.prop("width"), "350")
27 | assert_equal(el.prop("height"), "196")
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/test/source/components/image.html:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Image"
3 | type: "Component"
4 | ---
5 |
6 | {% img
7 | alt: "alt"
8 | class: "one"
9 | height: "200"
10 | src: "test.png"
11 | style: "background: red;"
12 | title: "title"
13 | width: "400"
14 | %}
15 |
16 | {% Image class: "nope" %}
17 |
18 | {% img
19 | class: "default"
20 | alt: "alt"
21 | crossorigin: "crossorigin"
22 | id: "id"
23 | ismap: "ismap"
24 | longdesc: "longdesc"
25 | usemap: "usemap"
26 | %}
27 |
28 | {% img
29 | class: "data-attr"
30 | data_jared: "cupcake"
31 | %}
32 |
33 | {% img
34 | class: "no-lazy"
35 | src: "test.png"
36 | skip_lazy: true
37 | %}
38 |
39 | {% img
40 | class: "responsive"
41 | srcset: "test.png"
42 | width: "600"
43 | %}
44 |
45 | {% img
46 | class: "number-dimension"
47 | srcset: "test.png"
48 | width: 600
49 | height: 400
50 | %}
51 |
--------------------------------------------------------------------------------
/test/source/components/container.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Container"
3 | type: "Component"
4 | ---
5 |
6 | {% Container class: "pink-hot" %}
7 |
8 | Hot Pink
9 |
10 |
11 | Alright
12 |
13 |
18 |
19 | {% endContainer %}
20 |
21 | {% Container class: "nested" %}
22 |
23 | {% Wistia id: "awesome" class: "awesome" %}
24 |
25 |
26 |
27 | {% img src: "001.png" class: "picture" %}
28 |
29 |
30 | {% img src: "002.png" %}
31 |
32 |
33 | {% img src: "003.png" %}
34 |
35 |
36 |
37 | {% Container class: "inner" %}
38 | {% WistiaPopover id: "pop" class: "pop" %}
39 | Popped!
40 | {% endWistiaPopover %}
41 | {% endContainer %}
42 | {% endContainer %}
43 |
--------------------------------------------------------------------------------
/test/source/_plugins/components/tags/wistia.rb:
--------------------------------------------------------------------------------
1 | require "jekyll-spark"
2 |
3 | module Jekyll
4 | class WistiaComponent < ComponentTag
5 | def template(context)
6 | unless @props["id"]
7 | return context
8 | end
9 |
10 | id = @props["id"].gsub("wistia_", "").gsub("Wistia_", "")
11 | class_name = @props["class"]
12 |
13 | render = %Q[
14 |
15 |
22 | ]
23 | end
24 | end
25 | end
26 |
27 | Liquid::Template.register_tag('Wistia', Jekyll::WistiaComponent)
28 |
--------------------------------------------------------------------------------
/test/test_component_container.rb:
--------------------------------------------------------------------------------
1 | require "helper"
2 |
3 | class ContainerComponent < JekyllUnitTest
4 | should "render inner HTML tag(s) and content" do
5 | markup = get_component_page("Container")
6 | doc = Nokogiri::HTML(markup)
7 | o = doc.css(".pink-hot")[0]
8 | t = doc.css(".text")[0]
9 | script = doc.css("script")[0]
10 | style = doc.css("style")[0]
11 |
12 | assert(o)
13 | assert(t.text.downcase.include?("hot pink"))
14 | assert(script)
15 | assert(style)
16 | end
17 |
18 | should "render a series of inner components and HTML tags" do
19 | markup = get_component_page("Container")
20 | doc = Nokogiri::HTML(markup)
21 | o = doc.css(".nested")[0]
22 | wistia = doc.css(".awesome")[0]
23 | pop = doc.css(".pop")[0]
24 | inner = doc.css(".inner")[0]
25 | picture = doc.css(".picture")[0]
26 | img = doc.css("img")
27 |
28 | assert(o)
29 | assert(wistia)
30 | assert(pop)
31 | assert(inner)
32 | assert(picture)
33 | assert_equal(img.length, 6)
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/test/source/_plugins/components/blocks/wistia_popover.rb:
--------------------------------------------------------------------------------
1 | require "jekyll-spark"
2 |
3 | module Jekyll
4 | class WistiaPopoverComponent < ComponentBlock
5 | def template(context)
6 | unless @props["id"]
7 | return context
8 | end
9 |
10 | id = @props["id"].gsub("wistia_", "").gsub("Wistia_", "")
11 | class_name = @props["class"]
12 | content = @props["content"]
13 | style = @props["style"] || "display:inline-block;height:80px;width:150px"
14 |
15 | render = %Q[
16 |
17 |
18 |
26 | #{content}
27 |
28 |
29 | ]
30 | end
31 | end
32 | end
33 |
34 | Liquid::Template.register_tag('WistiaPopover', Jekyll::WistiaPopoverComponent)
35 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017 Help Scout
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_component_test_nested.rb:
--------------------------------------------------------------------------------
1 | require "helper"
2 |
3 | class TestNestedComponent < JekyllUnitTest
4 | should "render inner component tag and content" do
5 | markup = get_component_page("Test Nested")
6 | doc = Nokogiri::HTML(markup)
7 | div = doc.css(".basic div")[0]
8 | image = doc.css("img")[0]
9 |
10 | assert(div)
11 | assert(div.text.include?("Hello"))
12 | assert(image, "Renders inner {% img %} component")
13 | end
14 |
15 | should "render without content" do
16 | markup = get_component_page("Test Nested")
17 | doc = Nokogiri::HTML(markup)
18 | div = doc.css(".no-content div")[0]
19 | image = doc.css("img")[0]
20 |
21 | assert(div)
22 | assert(image, "Renders inner {% img %} component")
23 | end
24 |
25 | should "render inner recursive component block and content" do
26 | markup = get_component_page("Test Nested")
27 | doc = Nokogiri::HTML(markup)
28 | div = doc.css(".recursive")[0]
29 | img = doc.css("img")[0]
30 | repeat = doc.css(".c-test-nested .c-test-nested")
31 |
32 | assert(div)
33 | assert(img, "Renders inner {% img %} component")
34 | assert(repeat)
35 | assert(repeat.text.include?("Yo dawg"))
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/docs/introduction.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | Components are `.rb` files that are added to your Jekyll project's default `_plugins/` directory:
4 |
5 | ```shell
6 | my-jekyll/
7 | └── _plugins/
8 | └── *Add components here*
9 | ```
10 |
11 | There are two types are Components:
12 |
13 | **Tags**
14 |
15 | These components are created using Liquid [Tags](http://www.rubydoc.info/github/Shopify/liquid/Liquid/Tag), and they do not contain content when used.
16 |
17 | Example:
18 | ```html
19 | {% Napolean id: "skillz" class: "nunchucks bow staff computer-hacking" %}
20 | ```
21 |
22 | **Blocks**
23 |
24 | These components are created using Liquid [Blocks](http://www.rubydoc.info/github/Shopify/liquid/Liquid/Block), and they **do** contain content when used.
25 |
26 | Example:
27 | ```html
28 | {% Napolean class: "chapstick" %}
29 | But my lips hurt real bad!
30 | {% endNapolean %}
31 | ```
32 |
33 | Because of these types, we recommend you organize your components in your `_plugins/` directory into `tags` and `blocks` directories:
34 |
35 | ```shell
36 | my-jekyll/
37 | └── _plugins/
38 | ├── blocks/
39 | └── tags/
40 | ```
41 |
42 |
43 | ### Up next
44 |
45 | Learn how to [create a component](creating-a-component.md).
46 |
--------------------------------------------------------------------------------
/test/test_unit_spark_base.rb:
--------------------------------------------------------------------------------
1 | require "helper"
2 | require "jekyll/spark"
3 |
4 | class SparkComponentBase < JekyllUnitTest
5 | should "have blank? return false for Liquid" do
6 | o = Object.new
7 | o.extend(Jekyll::ComponentBase)
8 |
9 | assert(!o.blank?)
10 | end
11 |
12 | class PropsMethod < JekyllUnitTest
13 | should "be able to set props" do
14 | o = Object.new
15 | o.extend(Jekyll::ComponentBase)
16 |
17 | h = Hash.new
18 | h["name"] = "Name"
19 | o.props = h
20 |
21 | assert_equal(o.props["name"], "Name")
22 | end
23 |
24 | should "handle null key/values" do
25 | o = Object.new
26 | o.extend(Jekyll::ComponentBase)
27 | o.props = Hash.new
28 |
29 | h = Hash.new
30 | h["name"] = "Name"
31 | h["nulls"]
32 | h["nil"] = nil
33 |
34 | o.set_props(h)
35 |
36 | assert_equal(o.props["name"], "Name")
37 | end
38 | end
39 |
40 | class TemplateMethod < JekyllUnitTest
41 | should "return context arg by default" do
42 | o= Struct.new(:foo).new
43 | class << o
44 | include Jekyll::ComponentBase
45 | end
46 |
47 | assert_equal(o.template("default"), "default")
48 | end
49 | end
50 | end
51 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Spark ✨ [](https://travis-ci.org/helpscout/jekyll-spark) [](https://badge.fury.io/rb/jekyll-spark) [](https://coveralls.io/github/helpscout/jekyll-spark?branch=master)
2 |
3 | A Jekyll library for building fast component-based UI.
4 |
5 | This library was heavily inspired by view/component creation from modern Javascript libraries like [React](https://facebook.github.io/react/) and [Vue](https://vuejs.org/).
6 |
7 | **Table of Contents**
8 |
9 | - [Install](#install)
10 | - [Documentation](#documenation)
11 | - [Examples](#examples)
12 |
13 | ## Install
14 |
15 | Add this line to your application's Gemfile:
16 |
17 | ```ruby
18 | gem 'jekyll-spark'
19 | ```
20 |
21 | And then execute:
22 | ```
23 | bundle
24 | ```
25 |
26 | Or install it yourself as:
27 | ```
28 | gem install jekyll-spark
29 | ```
30 |
31 |
32 |
33 | ## Documentation
34 |
35 | **[View the docs](https://github.com/helpscout/jekyll-spark/blob/master/docs/introduction.md)** to get started with Jekyll Components!
36 |
37 |
38 | ## Examples
39 |
40 | **[View the starter](https://github.com/helpscout/jekyll-spark/tree/master/examples)** Component view files.
41 |
--------------------------------------------------------------------------------
/jekyll-spark.gemspec:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | lib = File.expand_path('../lib', __FILE__)
3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4 | require 'jekyll/spark/version'
5 |
6 | Gem::Specification.new do |spec|
7 | spec.name = "jekyll-spark"
8 | spec.version = Jekyll::Spark::VERSION
9 | spec.authors = ["ItsJonQ"]
10 | spec.email = ["itsjonq@gmail.com"]
11 |
12 | spec.summary = "A Jekyll library for building component-based UI"
13 | spec.homepage = "https://github.com/helpscout/jekyll-spark"
14 | spec.license = "MIT"
15 |
16 | spec.files = `git ls-files -z`.split("\x0").reject do |f|
17 | f.match(%r{^(test|spec|features)/})
18 | end
19 | spec.bindir = "exe"
20 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21 | spec.require_paths = ["lib"]
22 |
23 | spec.add_runtime_dependency("jekyll", ">= 3.1.2")
24 | spec.add_runtime_dependency("htmlcompressor", "~> 0.3.1")
25 |
26 | spec.add_development_dependency "bundler", "~> 1.13"
27 | spec.add_development_dependency "rake", "~> 10.0"
28 | spec.add_development_dependency "minitest-reporters"
29 | spec.add_development_dependency "minitest-profile"
30 | spec.add_development_dependency "minitest", "~> 5.8"
31 | spec.add_development_dependency "rspec-mocks"
32 | spec.add_development_dependency "jekyll-joule", "~> 0.2.0"
33 | spec.add_development_dependency "shoulda"
34 | spec.add_development_dependency "kramdown"
35 | end
36 |
--------------------------------------------------------------------------------
/test/test_component_wistia_popover.rb:
--------------------------------------------------------------------------------
1 | require "helper"
2 |
3 | class WistiaPopoverComponent < JekyllUnitTest
4 | should "generate an async Wistia popover embed code with an ID" do
5 | markup = get_component_page("Wistia Popover")
6 | doc = Nokogiri::HTML(markup)
7 | span = doc.css(".first")[0]
8 |
9 | assert(span)
10 | assert(span.text.downcase.include?("hello"))
11 | assert(span["class"].include?("wistia_async_hello"))
12 | end
13 |
14 | should "generate a block with markup-based content" do
15 | markup = get_component_page("Wistia Popover")
16 | doc = Nokogiri::HTML(markup)
17 | span = doc.css(".linky")[0]
18 | link = doc.css(".linky a")[0]
19 |
20 | assert(span)
21 | assert(span["class"].include?("wistia_async_linky"))
22 | assert(link)
23 | assert(link["href"].include?("helpscout.net"))
24 | assert(link.text.include?("Help Scout"))
25 | end
26 |
27 | should "render style prop" do
28 | markup = get_component_page("Wistia Popover")
29 | doc = Nokogiri::HTML(markup)
30 | span = doc.css(".stylin")[0]
31 | link = doc.css(".stylin a")[0]
32 |
33 | assert(span)
34 | assert(span["style"].include?("inline-block"))
35 | assert(link)
36 | assert(link["class"].include?("text-link"))
37 | assert(link["href"].include?("#"))
38 | assert(link.text.include?("video"))
39 | end
40 |
41 | should "evaluate variables within content" do
42 | markup = get_component_page("Wistia Popover")
43 | doc = Nokogiri::HTML(markup)
44 | span = doc.css(".eval")[0]
45 |
46 | assert(span)
47 | assert(span.text.include?("wut"))
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/test/test_component_wistia.rb:
--------------------------------------------------------------------------------
1 | require "helper"
2 |
3 | class WistiaComponent < JekyllUnitTest
4 | should "generate an async Wistia embed code with an ID" do
5 | markup = get_component_page("Wistia")
6 | doc = Nokogiri::HTML(markup)
7 | script = doc.css(".default script")[0]
8 | embed = doc.css(".default .wistia_embed")[0]
9 |
10 | assert(script)
11 | assert(script.key?("async"))
12 | assert(script["src"].include?("jsonp"), "Has jsonp callback")
13 | assert(embed)
14 | end
15 |
16 | should "correctly be parsed by markdown" do
17 | markup = get_component_page("Wistia Markdown")
18 | doc = Nokogiri::HTML(markup)
19 | script = doc.css("script")[0]
20 | embed = doc.css(".wistia_embed")[0]
21 |
22 | assert(script)
23 | assert(embed)
24 | assert(!doc.css("code")[0])
25 | assert(!doc.css(".highlighter-rouge")[0])
26 | end
27 |
28 | should "not generate an embed without an ID" do
29 | markup = get_component_page("Wistia")
30 | doc = Nokogiri::HTML(markup)
31 | script = doc.css(".nope script")[0]
32 | embed = doc.css(".nope .wistia_embed")[0]
33 |
34 | assert(!script)
35 | assert(!embed)
36 | end
37 |
38 | should "strip wistia- (hyphen) from the ID" do
39 | markup = get_component_page("Wistia")
40 | doc = Nokogiri::HTML(markup)
41 | script = doc.css(".wistia-hyphen-id script")[0]
42 |
43 | assert(!script["src"].include?("wistia-"))
44 | end
45 |
46 | should "strip wistia_ (underscore) from the ID" do
47 | markup = get_component_page("Wistia")
48 | doc = Nokogiri::HTML(markup)
49 | script = doc.css(".wistia-underscore-id script")[0]
50 |
51 | assert(!script["src"].include?("wistia_"))
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/test/helper.rb:
--------------------------------------------------------------------------------
1 | require "simplecov"
2 | require "coveralls"
3 |
4 | SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
5 | SimpleCov::Formatter::HTMLFormatter,
6 | Coveralls::SimpleCov::Formatter
7 | ]
8 |
9 | SimpleCov.start do
10 | add_filter "/source/"
11 | # add_filter "/test/"
12 | end
13 |
14 | require 'rubygems'
15 | require 'minitest/autorun'
16 | require 'minitest/reporters'
17 | require 'minitest/profile'
18 | require 'nokogiri'
19 | require 'ostruct'
20 | require 'rspec/mocks'
21 |
22 | require 'jekyll'
23 | require 'jekyll/joule'
24 |
25 | Jekyll.logger = Logger.new(StringIO.new)
26 |
27 | require 'kramdown'
28 | require 'shoulda'
29 |
30 | include Jekyll
31 |
32 | # Report with color.
33 | Minitest::Reporters.use! [
34 | Minitest::Reporters::SpecReporter.new(
35 | :color => true
36 | )
37 | ]
38 |
39 | class JekyllUnitTest < Minitest::Test
40 | include ::RSpec::Mocks::ExampleMethods
41 |
42 | def setup
43 | @site = Site.new(site_configuration)
44 | @site.read
45 | @site.generate
46 | @site.render
47 | @pages = @site.pages
48 | @joule = Jekyll::Joule::Site.new(@site)
49 | end
50 |
51 | def get_component_page(title)
52 | page = @pages.find { |p|
53 | p.data["title"].downcase === title.downcase and p.data["type"].downcase === "component"
54 | }
55 |
56 | return page ? page["content"] : false
57 | end
58 |
59 | def mocks_expect(*args)
60 | RSpec::Mocks::ExampleMethods::ExpectHost.instance_method(:expect).\
61 | bind(self).call(*args)
62 | end
63 |
64 | def before_setup
65 | ::RSpec::Mocks.setup
66 | super
67 | end
68 |
69 | def after_teardown
70 | super
71 | ::RSpec::Mocks.verify
72 | ensure
73 | ::RSpec::Mocks.teardown
74 | end
75 |
76 | def fixture_site(overrides = {})
77 | Jekyll::Site.new(site_configuration(overrides))
78 | end
79 |
80 | def build_configs(overrides, base_hash = Jekyll::Configuration::DEFAULTS)
81 | Utils.deep_merge_hashes(base_hash, overrides)
82 | .fix_common_issues.backwards_compatibilize.add_default_collections
83 | end
84 |
85 | def site_configuration(overrides = {})
86 | full_overrides = build_configs(overrides, build_configs({
87 | "destination" => dest_dir,
88 | "incremental" => false
89 | }))
90 | build_configs({
91 | "source" => source_dir
92 | }, full_overrides)
93 | end
94 |
95 | def dest_dir(*subdirs)
96 | root_dir('dest', *subdirs)
97 | end
98 |
99 | def source_dir(*subdirs)
100 | root_dir('source', *subdirs)
101 | end
102 |
103 | def clear_dest
104 | FileUtils.rm_rf(dest_dir)
105 | FileUtils.rm_rf(source_dir('.jekyll-metadata'))
106 | end
107 |
108 | def root_dir(*subdirs)
109 | File.join(File.dirname(__FILE__), *subdirs)
110 | end
111 |
112 | def directory_with_contents(path)
113 | FileUtils.rm_rf(path)
114 | FileUtils.mkdir(path)
115 | File.open("#{path}/index.html", "w"){ |f| f.write("I was previously generated.") }
116 | end
117 |
118 | def with_env(key, value)
119 | old_value = ENV[key]
120 | ENV[key] = value
121 | yield
122 | ENV[key] = old_value
123 | end
124 |
125 | def capture_output
126 | stderr = StringIO.new
127 | Jekyll.logger = Logger.new stderr
128 | yield
129 | stderr.rewind
130 | return stderr.string.to_s
131 | end
132 | alias_method :capture_stdout, :capture_output
133 | alias_method :capture_stderr, :capture_output
134 | end
135 |
--------------------------------------------------------------------------------
/lib/jekyll/spark/base.rb:
--------------------------------------------------------------------------------
1 | require "htmlcompressor"
2 | require "jekyll"
3 | require "liquid"
4 |
5 | module Jekyll
6 | module ComponentBase
7 | attr_accessor :props, :content
8 | include Liquid::StandardFilters
9 |
10 | @@compressor = HtmlCompressor::Compressor.new({
11 | :remove_comments => true,
12 | :remove_intertag_spaces => true,
13 | :preserve_line_breaks => false,
14 | }).freeze
15 |
16 | def initialize(tag_name, markup, tokens)
17 | super
18 |
19 | @attributes = {}
20 | @context = false
21 | @context_name = self.name.to_s.gsub("::", "_").downcase
22 | @content = ''
23 | @default_selector_attr = []
24 | @props = Hash.new
25 | @site = false
26 |
27 | if markup =~ /(#{Liquid::QuotedFragment}+)?/
28 | # Parse parameters
29 | # Source: https://gist.github.com/jgatjens/8925165
30 | markup.scan(Liquid::TagAttributes) do |key, value|
31 | @attributes[key] = Liquid::Expression.parse(value)
32 | end
33 | end
34 | end
35 |
36 | # blank?
37 | # Description: Override's Liquid's default blank checker. This allows
38 | # for templates to be used without passing inner content.
39 | def blank?
40 | false
41 | end
42 |
43 | def selector_default_props(attr = @default_selector_attr)
44 | template = ""
45 | attr.each { |prop|
46 | if @props.key?(prop)
47 | template += "#{prop}='#{@props[prop]}' "
48 | end
49 | }
50 |
51 | return template
52 | end
53 |
54 | def selector_data_props(attr = @attributes)
55 | template = ""
56 | attr.keys.each { |key|
57 | if key.include? "data"
58 | template += "#{key.gsub("_", "-")}='#{@props[key]}' "
59 | end
60 | }
61 |
62 | return template
63 | end
64 |
65 | def selector_props(attr = @default_selector_attr)
66 | template = ""
67 | template += selector_data_props
68 | template += selector_default_props(attr)
69 |
70 | return template
71 | end
72 |
73 | def set_props(props = Hash.new)
74 | @props = @props.merge(props)
75 | end
76 |
77 | def serialize_data
78 | data = Hash.new
79 | @attributes["children"] = @content
80 | @attributes["content"] = @content
81 | if @attributes.length
82 | @attributes.each do |key, value|
83 | if @context
84 | value = @context.evaluate(value)
85 | end
86 | data[key] = value
87 | end
88 | end
89 |
90 | return set_props(data)
91 | end
92 |
93 | def unindent(content)
94 | # Remove initial whitespace
95 | content.gsub!(/\A^\s*\n/, "")
96 | # Remove indentations
97 | if content =~ %r!^\s*!m
98 | indentation = Regexp.last_match(0).length
99 | content.gsub!(/^\ {#{indentation}}/, "")
100 | end
101 |
102 | return content
103 | end
104 |
105 | def render(context)
106 | @context = context
107 | @site = @context.registers[:site]
108 | @content = super
109 | serialize_data
110 | output = template(context)
111 |
112 | if (output.instance_of?(String))
113 | output = Liquid::Template.parse(output).render()
114 | output = @@compressor.compress(unindent(output))
115 | else
116 | output = ""
117 | end
118 |
119 | return output
120 | end
121 |
122 | def template(context = @context)
123 | return context
124 | end
125 |
126 | end
127 | end
128 |
--------------------------------------------------------------------------------
/test/source/_plugins/components/tags/image.rb:
--------------------------------------------------------------------------------
1 | require "jekyll-spark"
2 |
3 | module Jekyll
4 | class ImageComponent < ComponentTag
5 | DEFAULT_TAG_PROPS = [
6 | "alt",
7 | "crossorigin",
8 | "id",
9 | "ismap",
10 | "longdesc",
11 | "style",
12 | "title",
13 | "usemap",
14 | ]
15 | BLANK_IMG = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
16 |
17 | def template(context)
18 | prop = @props
19 |
20 | blazy_class_name = "b-lazy"
21 | class_name = prop["class"]
22 | height = prop["height"]
23 | data_src = BLANK_IMG
24 | src = prop["src"]
25 | srcset = prop["srcset"]
26 | title = prop["title"]
27 | width = prop["width"]
28 | responsive = ""
29 | default_props = selector_props(DEFAULT_TAG_PROPS)
30 |
31 | # Responsive
32 | # Adds new responsive-based image attributes: srcset and sizes.
33 | # The numbers are based on @attribute["width"].
34 | # At the moment, this plugin only supports 1x and 2x pixel-density sizes.
35 | #
36 | # Learn more about srcset
37 | # https://ericportis.com/posts/2014/srcset-sizes/
38 | #
39 | if srcset
40 | srcset_2x = srcset.to_s.gsub(/\.(?=[^.]*$)/, "@2x.")
41 | srcset_1x = srcset.to_s.gsub(/\.(?=[^.]*$)/, "@1x.")
42 | # Width-based srcset (Recommended)
43 | if (width)
44 | # Ensure that width is just a number (just in case)
45 | w = width.to_s.gsub("px", "").gsub("%", "").to_i
46 | responsive = "
47 | srcset='
48 | #{srcset_1x} #{w}w,
49 | #{srcset_2x} #{w*2}w
50 | '
51 | "
52 | responsive += "
53 | sizes='
54 | (min-width: 40em) #{w}px,
55 | 100vw
56 | '
57 | "
58 | # Fallback to pixel-density based (if width is not available)
59 | else
60 | responsive = "
61 | srcset='
62 | #{srcset_1x} 1x,
63 | #{srcset_2x} 2x
64 | '
65 | "
66 | end
67 | src = srcset_1x
68 | end
69 |
70 | # Set the src for