jekyll-page-asset 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +674 -0
- data/README.md +139 -0
- data/_includes/page_asset/fountain.html +13 -0
- data/_includes/page_asset/gallery.html +306 -0
- data/_includes/page_asset/scroll_scrub_video.html +242 -0
- data/_lib/page_asset/fountain_renderer.rb +15 -0
- data/_lib/page_asset/gallery_renderer.rb +87 -0
- data/_lib/page_asset/image_renderer.rb +18 -0
- data/_lib/page_asset/renderer.rb +113 -0
- data/_lib/page_asset/scroll_scrub_video_renderer.rb +24 -0
- data/_lib/page_asset/video_renderer.rb +53 -0
- data/_plugins/page_asset.rb +103 -0
- data/_sass/_page-asset.scss +309 -0
- data/lib/jekyll-page-asset/version.rb +5 -0
- data/lib/jekyll-page-asset.rb +12 -0
- metadata +98 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Jekyll
|
|
3
|
+
module PageAsset
|
|
4
|
+
class FountainRenderer < Renderer
|
|
5
|
+
register_type 'fountain'
|
|
6
|
+
|
|
7
|
+
def render(context)
|
|
8
|
+
include_vars = build_include_vars(
|
|
9
|
+
"script_text" => File.read(@target_asset_path)
|
|
10
|
+
)
|
|
11
|
+
render_template(context, include_vars)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cgi"
|
|
4
|
+
|
|
5
|
+
module Jekyll
|
|
6
|
+
module PageAsset
|
|
7
|
+
class GalleryRenderer < Renderer
|
|
8
|
+
register_type 'gallery'
|
|
9
|
+
|
|
10
|
+
SUPPORTED_IMAGE_EXTENSIONS = %w[.jpg .jpeg .png .webp .avif .gif].freeze
|
|
11
|
+
|
|
12
|
+
def render(context)
|
|
13
|
+
return "" if @target_asset_paths.empty?
|
|
14
|
+
|
|
15
|
+
gallery_asset_root = @asset_path_arg
|
|
16
|
+
images = discover_images(@target_asset_paths)
|
|
17
|
+
gallery_template = render_template(context, {})
|
|
18
|
+
|
|
19
|
+
if images.empty?
|
|
20
|
+
return empty_state_html(@asset_path_arg) + gallery_template
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
thumbnails = images.each_with_index.map do |filename, index|
|
|
24
|
+
asset_path = File.join(gallery_asset_root, filename)
|
|
25
|
+
src = "/#{asset_path}"
|
|
26
|
+
alt = alt_text_for(filename, index)
|
|
27
|
+
picture_tag = render_thumbnail(context, asset_path)
|
|
28
|
+
|
|
29
|
+
<<~HTML
|
|
30
|
+
<button
|
|
31
|
+
class="gallery-thumb"
|
|
32
|
+
type="button"
|
|
33
|
+
data-gallery-thumb="true"
|
|
34
|
+
data-gallery-id="#{html_escape(@asset_path_arg)}"
|
|
35
|
+
data-gallery-index="#{index}"
|
|
36
|
+
data-gallery-src="#{html_escape(src)}"
|
|
37
|
+
data-gallery-alt="#{html_escape(alt)}"
|
|
38
|
+
aria-label="Open image #{index + 1}">
|
|
39
|
+
#{picture_tag}
|
|
40
|
+
</button>
|
|
41
|
+
HTML
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
<<~HTML + gallery_template
|
|
45
|
+
<section class="project-gallery" data-gallery="#{html_escape(@asset_path_arg)}" aria-label="Image gallery">
|
|
46
|
+
<div class="project-gallery-grid">
|
|
47
|
+
#{thumbnails.join("\n")}
|
|
48
|
+
</div>
|
|
49
|
+
</section>
|
|
50
|
+
HTML
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def discover_images(paths)
|
|
56
|
+
paths.select { |entry| SUPPORTED_IMAGE_EXTENSIONS.include?(File.extname(entry).downcase) }
|
|
57
|
+
.sort_by(&:downcase)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def render_thumbnail(context, path)
|
|
61
|
+
escaped_path = path.gsub('"', '\\"')
|
|
62
|
+
liquid = "{% picture \"#{escaped_path}\" alt=\"thumbnail\" %}"
|
|
63
|
+
Liquid::Template.parse(liquid).render(context)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def alt_text_for(filename, index)
|
|
67
|
+
stem = File.basename(filename, File.extname(filename)).tr("_-", " ").strip
|
|
68
|
+
return "Gallery image #{index + 1}" if stem.empty?
|
|
69
|
+
|
|
70
|
+
stem.gsub(/\s+/, " ")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def empty_state_html(gallery_id)
|
|
74
|
+
<<~HTML
|
|
75
|
+
<section class="project-gallery" data-gallery="#{html_escape(gallery_id)}" aria-label="Image gallery">
|
|
76
|
+
<p class="project-gallery-empty">No gallery images found.</p>
|
|
77
|
+
</section>
|
|
78
|
+
HTML
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def html_escape(value)
|
|
82
|
+
CGI.escape_html(value.to_s)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "base64"
|
|
5
|
+
|
|
6
|
+
module Jekyll
|
|
7
|
+
module PageAsset
|
|
8
|
+
class ImageRenderer < Renderer
|
|
9
|
+
register_type 'image'
|
|
10
|
+
|
|
11
|
+
def render(context)
|
|
12
|
+
augmented_liquid = "{% picture #{@target_asset_path} #{@rest} %}"
|
|
13
|
+
Liquid::Template.parse(augmented_liquid).render(context)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jekyll
|
|
4
|
+
module PageAsset
|
|
5
|
+
class Renderer
|
|
6
|
+
class << self
|
|
7
|
+
attr_reader :type
|
|
8
|
+
|
|
9
|
+
def type_renderers
|
|
10
|
+
@type_renderers ||= {}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def inherited(subclass)
|
|
14
|
+
unregistered_subclasses << subclass
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def unregistered_subclasses
|
|
18
|
+
@unregistered_subclasses ||= []
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def register_type(value)
|
|
22
|
+
@type = value.to_s.strip
|
|
23
|
+
raise "Render helper #{name} has no registered type" if @type.empty?
|
|
24
|
+
|
|
25
|
+
Renderer.type_renderers[@type] = self
|
|
26
|
+
Renderer.unregistered_subclasses.delete(self)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def assert_all_registered!
|
|
30
|
+
missing = unregistered_subclasses.reject { |klass| klass == self }
|
|
31
|
+
return if missing.empty?
|
|
32
|
+
|
|
33
|
+
names = missing.map { |klass| klass.name || klass.to_s }.sort.join(", ")
|
|
34
|
+
raise "PageAsset helpers need to register a type. Check subclass(es): #{names}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def new_by_type(type:, asset_path_arg:, rest:, params:)
|
|
38
|
+
renderer = type_renderers[type.to_s] || self
|
|
39
|
+
renderer.new(
|
|
40
|
+
type: type,
|
|
41
|
+
asset_path_arg: asset_path_arg,
|
|
42
|
+
rest: rest,
|
|
43
|
+
params: params
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def initialize(type:, asset_path_arg:, rest:, params: )
|
|
49
|
+
@type = type.to_s
|
|
50
|
+
@asset_path_arg = asset_path_arg
|
|
51
|
+
@target_asset_paths = target_asset_paths(asset_path_arg)
|
|
52
|
+
@target_asset_path = @target_asset_paths[0]
|
|
53
|
+
@rest = rest.to_s
|
|
54
|
+
@params = params || {}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def render(context)
|
|
58
|
+
include_vars = build_include_vars
|
|
59
|
+
render_template(context, include_vars)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
protected
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def target_asset_paths(target)
|
|
66
|
+
if File.file?(target)
|
|
67
|
+
[target]
|
|
68
|
+
elsif File.directory?(target)
|
|
69
|
+
Dir.children(target)
|
|
70
|
+
.select { |entry| File.file?(File.join(target, entry)) }
|
|
71
|
+
.sort_by(&:downcase)
|
|
72
|
+
else
|
|
73
|
+
# assume the path includes a tag and pass it. This may cause issues
|
|
74
|
+
raise 'target page_asset file not found'
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def build_include_vars(extra={})
|
|
79
|
+
{
|
|
80
|
+
"asset_paths" => @target_asset_paths,
|
|
81
|
+
"asset_path" => @target_asset_path,
|
|
82
|
+
"rest" => @rest,
|
|
83
|
+
"params" => @params,
|
|
84
|
+
}.merge(extra)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def load_template(context)
|
|
88
|
+
site = context.registers[:site]
|
|
89
|
+
include_path = File.join(site.source, "_includes", "page_asset", "#{@type}.html")
|
|
90
|
+
|
|
91
|
+
unless File.exist?(include_path)
|
|
92
|
+
gem_include_path = File.expand_path("../../_includes/page_asset/#{@type}.html", __dir__)
|
|
93
|
+
include_path = gem_include_path if File.exist?(gem_include_path)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
unless File.exist?(include_path)
|
|
97
|
+
raise IOError, "page_asset: include file not found at #{include_path}"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
Liquid::Template.parse(File.read(include_path))
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def render_template(context, include_vars)
|
|
104
|
+
template = load_template(context)
|
|
105
|
+
context.stack do
|
|
106
|
+
context["include"] = include_vars
|
|
107
|
+
template.render(context)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jekyll
|
|
4
|
+
module PageAsset
|
|
5
|
+
class ScrollScrubVideoRenderer < Renderer
|
|
6
|
+
register_type 'scroll_scrub_video'
|
|
7
|
+
|
|
8
|
+
DEFAULTS = {
|
|
9
|
+
"pixels_per_second" => 50,
|
|
10
|
+
"sticky_top" => 120
|
|
11
|
+
}.freeze
|
|
12
|
+
|
|
13
|
+
def render(context)
|
|
14
|
+
include_vars = build_include_vars({
|
|
15
|
+
"video_path" => @target_asset_path,
|
|
16
|
+
"pixels_per_second" => (@params["pixels_per_second"] || DEFAULTS["pixels_per_second"]).to_i,
|
|
17
|
+
"sticky_top" => (@params["sticky_top"] || DEFAULTS["sticky_top"]).to_i
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
render_template(context, include_vars)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cgi"
|
|
4
|
+
|
|
5
|
+
module Jekyll
|
|
6
|
+
module PageAsset
|
|
7
|
+
class VideoRenderer < Renderer
|
|
8
|
+
register_type "video"
|
|
9
|
+
|
|
10
|
+
DISPLAY_BACKGROUND = "background"
|
|
11
|
+
DISPLAY_FOREGROUND = "foreground"
|
|
12
|
+
|
|
13
|
+
def render(context)
|
|
14
|
+
video_path = @target_asset_path
|
|
15
|
+
|
|
16
|
+
display = normalized_display(@params["display"])
|
|
17
|
+
classes = display == DISPLAY_BACKGROUND ? "background-video" : "foreground-video"
|
|
18
|
+
playback_attrs = display == DISPLAY_BACKGROUND ? "autoplay muted loop playsinline" : "controls playsinline"
|
|
19
|
+
|
|
20
|
+
<<~HTML
|
|
21
|
+
<video #{playback_attrs} preload="auto" class="#{classes}">
|
|
22
|
+
<source src="/#{html_escape(video_path)}" type="#{html_escape(mime_type_for(video_path))}">
|
|
23
|
+
Your browser does not support the video tag.
|
|
24
|
+
</video>
|
|
25
|
+
HTML
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def normalized_display(value)
|
|
31
|
+
normalized = value.to_s.strip.downcase
|
|
32
|
+
return DISPLAY_BACKGROUND if normalized == DISPLAY_BACKGROUND
|
|
33
|
+
|
|
34
|
+
DISPLAY_FOREGROUND
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def mime_type_for(path)
|
|
38
|
+
case File.extname(path).downcase
|
|
39
|
+
when ".webm"
|
|
40
|
+
"video/webm"
|
|
41
|
+
when ".ogv"
|
|
42
|
+
"video/ogg"
|
|
43
|
+
else
|
|
44
|
+
"video/mp4"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def html_escape(value)
|
|
49
|
+
CGI.escape_html(value.to_s)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../_lib/page_asset/renderer"
|
|
4
|
+
|
|
5
|
+
Dir[File.join(__dir__, "../_lib","page_asset", "*_renderer.rb")].sort.each do |renderer_file|
|
|
6
|
+
require renderer_file
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
Jekyll::PageAsset::Renderer.assert_all_registered!
|
|
10
|
+
|
|
11
|
+
module Jekyll
|
|
12
|
+
module PageAsset
|
|
13
|
+
module TagHelpers
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def parse_tag_markup(markup, context)
|
|
17
|
+
normalized_markup = markup.to_s.strip
|
|
18
|
+
return [nil, nil, "", {}] if normalized_markup.empty?
|
|
19
|
+
|
|
20
|
+
# expand {{tags}}
|
|
21
|
+
normalized_markup = normalized_markup.gsub(/\{\{\s*([^}]+)\s*\}\}/) do
|
|
22
|
+
variable_path = $1.strip
|
|
23
|
+
# Dynamically lookup the variable in Jekyll's context hierarchy
|
|
24
|
+
context[variable_path].to_s
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
type, target, rest = normalized_markup.split(/\s+/, 3)
|
|
28
|
+
rest ||= ""
|
|
29
|
+
|
|
30
|
+
params = {}
|
|
31
|
+
rest.scan(/(\w+)=(?:'([^']*)'|"([^"]*)"|(\S+))/) do |key, sq, dq, bare|
|
|
32
|
+
params[key] = sq || dq || bare
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
[type, target, rest, params]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def tag_site(context)
|
|
39
|
+
context.registers[:site]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def tag_page(context)
|
|
43
|
+
context.registers[:page]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def project_slug_from_context(context)
|
|
47
|
+
page = tag_page(context)
|
|
48
|
+
page_url = page["url"] || page.url
|
|
49
|
+
page_url.to_s.split("/").reject(&:empty?).last
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def page_asset_root
|
|
53
|
+
Jekyll.configuration.dig("page_asset", "asset_root") || "assets/project_media"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def page_asset_relative_path(context, *segments)
|
|
57
|
+
project_slug = project_slug_from_context(context)
|
|
58
|
+
return nil if project_slug.nil? || project_slug.empty?
|
|
59
|
+
|
|
60
|
+
File.join(page_asset_root, project_slug, *segments.compact)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def page_asset_source_path(context, *segments)
|
|
64
|
+
relative_path = page_asset_relative_path(context, *segments)
|
|
65
|
+
return nil if relative_path.nil?
|
|
66
|
+
|
|
67
|
+
File.join(tag_site(context).source, relative_path)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
class PageAssetTag < Liquid::Tag
|
|
72
|
+
include TagHelpers
|
|
73
|
+
|
|
74
|
+
def initialize(tag_name, markup, options)
|
|
75
|
+
super
|
|
76
|
+
@markup = markup
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def render(context)
|
|
80
|
+
# Split the markup into file name and opts.
|
|
81
|
+
@type, @target, @rest, @params = parse_tag_markup(@markup, context)
|
|
82
|
+
full_target_path = page_asset_relative_path(context, @target)
|
|
83
|
+
return "" if full_target_path.nil? || full_target_path.empty?
|
|
84
|
+
|
|
85
|
+
renderer = PageAsset::Renderer.new_by_type(
|
|
86
|
+
type: @type,
|
|
87
|
+
asset_path_arg: full_target_path,
|
|
88
|
+
rest: @rest,
|
|
89
|
+
params: @params
|
|
90
|
+
)
|
|
91
|
+
ren = renderer.render(context)
|
|
92
|
+
|
|
93
|
+
<<~HTML
|
|
94
|
+
<div class="page-asset-container">
|
|
95
|
+
#{ren}
|
|
96
|
+
</div>
|
|
97
|
+
HTML
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
Liquid::Template.register_tag("page_asset", Jekyll::PageAsset::PageAssetTag)
|