ragerender 0.1.6 → 0.1.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 313e3cd9c0ccd4c11ea547a565e2a1cfa77af1fda5e2fc2219c0da8046c6f27a
4
- data.tar.gz: 3911407ca12e719f71396099f73d70c6b19aeee29ccc38f796ef4c45b684d80d
3
+ metadata.gz: c3398336663640ad12e5ca290c96e61fd8fbb19e9851b8944a6434a3eca2c2d2
4
+ data.tar.gz: d00c31c1051eba512321b80e524c3d117f8d8970ebfd3fc8e9ad7ca4030b358d
5
5
  SHA512:
6
- metadata.gz: 4ca0ef393683e910b68e9ffe41a5909511ec66da604b50bcb99fbd758cad371bdeafdd594e1a27394e461beb3e0e8f41c4c3033a28f4c7692a492b470251f8b2
7
- data.tar.gz: 5509097f24aa3df45416be46acb9d2f31710318b4227b190f9413dcec287d6c473793bff0fd08b3ecf7b1cc49617b9437739fc991d1c9bd2a309fcbd61eef199
6
+ metadata.gz: 5d7674505e282530b8e19ad5f085b8516888e206bf7a28fb1c9b3e27b127665ca5be9cc056841aca784b382a1eedf8c60d566003a27a851ce227698d55fd2647
7
+ data.tar.gz: d092d5d375481b8662ea97baa4cbf89e6c1d50347e132654a66af39599750a40dfc9531e1197d2e77761468666e78bd914a65472554254a4cd98e46772011bf7
data/README.rdoc CHANGED
@@ -49,7 +49,7 @@ To set up a new site, open a terminal and type:
49
49
  mkdir mycomic && cd mycomic
50
50
  bundle init
51
51
  bundle add jekyll
52
- bundle add ragerender
52
+ bundle add ragerender --group=jekyll_plugins
53
53
 
54
54
  Now you can add comics! Add the image into an <tt>images</tt> folder:
55
55
 
@@ -92,6 +92,10 @@ Put something like this in your webcomic folder and call it
92
92
  description: >
93
93
  My epic story about how him and her
94
94
  fell into a romantic polycule with they and them
95
+ status: active
96
+ genres:
97
+ - Comedy
98
+ - Romance
95
99
 
96
100
  defaults:
97
101
  - scope:
@@ -151,6 +155,8 @@ details of your author notes and blogs manually:
151
155
  date: "2025-03-05 16:20"
152
156
  image: "images/ghost.png"
153
157
  author: "Jane doe"
158
+ description: "Some spooky mouseover text"
159
+ keywords: [excellent, comic page, spooky]
154
160
  custom:
155
161
  # use yes and no for tickbox settings
156
162
  spooky: yes
@@ -161,6 +167,9 @@ details of your author notes and blogs manually:
161
167
  Testing webcomics
162
168
  Now easier than ever
163
169
  Thanks to RageRender
170
+ transcript: >
171
+ The transcript contains a machine-readable version
172
+ of all the text in your comic image.
164
173
  comments:
165
174
  - author: "Skippy"
166
175
  date: "13 Mar 2025, 3.45 PM"
@@ -0,0 +1,7 @@
1
+ ---
2
+ title: Comic not found
3
+ layout: error-page
4
+ hidden: true
5
+ published: false
6
+ ---
7
+ This webcomic does not have any comic pages yet!
@@ -0,0 +1,2 @@
1
+ _layouts
2
+ test-layout.cflxml
@@ -0,0 +1,51 @@
1
+ require 'forwardable'
2
+ require 'liquid'
3
+ require_relative '../lib/ragerender/cflxml'
4
+ require_relative '../lib/ragerender/jekyll'
5
+
6
+ TEMPLATE_DROPS = {
7
+ 'archive' => RageRender::ArchiveDrop,
8
+ 'comic-page' => RageRender::ComicDrop,
9
+ 'error-page' => RageRender::ErrorDrop,
10
+ 'blog-archive' => RageRender::BlogArchiveDrop,
11
+ 'blog-display' => RageRender::BlogDrop,
12
+ 'overview' => RageRender::OverviewDrop,
13
+ 'search' => RageRender::SearchDrop,
14
+ }
15
+
16
+ Liquid::Template.error_mode = :strict
17
+ Liquid::Template.file_system = Liquid::LocalFileSystem.new('_includes', '%s.html')
18
+
19
+ directory '_layouts'
20
+
21
+ TEMPLATE_DROPS.each do |layout, drop|
22
+ file "_layouts/#{layout}.html" => ['_layouts', RageRender.const_source_location(drop.name).first, "_templates/#{layout}.html", __FILE__] do |t|
23
+ fields = Set.new(drop.invokable_methods) -
24
+ Set.new(Jekyll::Drops::DocumentDrop.invokable_methods)
25
+ all_fields = Set.new(RageRender::WebcomicDrop.invokable_methods) -
26
+ Set.new(Jekyll::Drops::DocumentDrop.invokable_methods)
27
+ template_name = "_templates/#{layout}.html"
28
+ template = Liquid::Template.parse File::read template_name, environment: ENV
29
+
30
+ rake_output_message "liquid ... >#{t.name} <#{template_name}"
31
+ File::write t.name, template.render!({
32
+ 'title' => "#{drop.name.rpartition(/[A-Z]/).first.split('::').last} Page",
33
+ 'all_fields' => all_fields.to_a.sort,
34
+ 'fields' => fields.to_a.sort,
35
+ 'loops' => [*drop.loops, *RageRender::WebcomicDrop.loops].map {|k, v| {"name" => k.to_s, "fields" => v.map(&:to_s)} },
36
+ }, strict_variables: true, strict_filters: true)
37
+ end
38
+ end
39
+
40
+ file '_layouts/overall.html' => '_templates/overall.html' do |t|
41
+ cp t.source, t.name
42
+ end
43
+
44
+ file 'test-layout.cflxml' => ['layout.css', '_layouts/overall.html', *TEMPLATE_DROPS.keys.map {|n| "_layouts/#{n}.html"}] do |t|
45
+ File.open t.name, 'w' do |f|
46
+ rake_output_message 'bundle exec jekyll pack'
47
+ RageRender.pack '_layouts', '.', f
48
+ end
49
+ end
50
+
51
+ task :default => 'test-layout.cflxml'
@@ -0,0 +1,8 @@
1
+ <tr>
2
+ <td>{{ name }}</td>
3
+ <td>[v:{{field}}]</td>
4
+ <td>
5
+ <script type="text/javascript">document.currentScript.parentElement.append(document.createTextNode([f:js|v:{{field}}]))</script>
6
+ </td>
7
+ <td>[c:{{field}}]true[/][c:!{{field}}]false[/]</td>
8
+ </tr>
@@ -0,0 +1,15 @@
1
+ {% assign looped_fields = loops | map: "name" %}
2
+ {% for field in fields -%}
3
+ {%- include "field_row", name: field, field: field -%}
4
+ {% if looped_fields contains field %}
5
+ {% assign field_obj = loops | where: "name", field %}
6
+ {% assign field_fields = field_obj[0].fields %}
7
+ [l:{{field}}]
8
+ {% for field_field in field_fields %}
9
+ {% capture name %}{{field}}&lbrack;[v:l.aiteration]&rbrack; → {{ field_field }}{% endcapture %}
10
+ {% assign field_name = field_field | prepend: "l." %}
11
+ {%- include "field_row", name: name, field: field_name %}
12
+ {% endfor %}
13
+ [/]
14
+ {% endif %}
15
+ {% endfor -%}
@@ -0,0 +1,22 @@
1
+ <table>
2
+ <thead>
3
+ <tr>
4
+ <th>Variable name</th>
5
+ <th>Value on this page</th>
6
+ <th>Value (escaped)</th>
7
+ <th>True or false in conditional</th>
8
+ </tr>
9
+ </thead>
10
+ <tbody>
11
+ <tr>
12
+ <th colspan=4>Unique to this page</th>
13
+ </tr>
14
+ {%- include "field_rows", fields: fields, loops: loops -%}
15
+ </tbody>
16
+ <tbody>
17
+ <tr>
18
+ <th colspan=4>On all pages</th>
19
+ </tr>
20
+ {%- include "field_rows", fields: all_fields, loops: loops -%}
21
+ </tbody>
22
+ </table>
@@ -0,0 +1,3 @@
1
+ <h2>{{ title }}</h2>
2
+
3
+ {%- include "field_table", all_fields: all_fields, fields: fields, loops: loops -%}
@@ -0,0 +1,3 @@
1
+ <h2>{{ title }}</h2>
2
+
3
+ {%- include "field_table", all_fields: all_fields, fields: fields, loops: loops -%}
@@ -0,0 +1,3 @@
1
+ <h2>{{ title }}</h2>
2
+
3
+ {%- include "field_table", all_fields: all_fields, fields: fields, loops: loops -%}
@@ -0,0 +1,3 @@
1
+ <h2>{{ title }}</h2>
2
+
3
+ {%- include "field_table", all_fields: all_fields, fields: fields, loops: loops -%}
@@ -0,0 +1,3 @@
1
+ <h2>{{ title }}</h2>
2
+
3
+ {%- include "field_table", all_fields: all_fields, fields: fields, loops: loops -%}
@@ -0,0 +1,90 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
6
+
7
+ <meta property="og:type" content="website" />
8
+ <meta property="og:title" content="[v:webcomicname]" />
9
+ <meta property="og:description" content="[v:pagetitle]" />
10
+ [c:permalink]
11
+ <meta property="og:url" content="[v:permalink]" />
12
+ [/]
13
+ <meta name="twitter:card" content="summary_large_image" />
14
+ <meta name="twitter:title" content="[v:webcomicname]" />
15
+ <meta name="twitter:description" content="[v:pagetitle]" />
16
+
17
+ [c:previewimageurl]
18
+ <meta property="og:image" content="[v:previewimageurl]" />
19
+ <meta property="og:image:width" content="[v:previewimagewidth]" />
20
+ <meta property="og:image:height" content="[v:previewimageheight]" />
21
+ <meta name="twitter:image" content="[v:previewimageurl]" />
22
+ [/]
23
+
24
+ <link rel="alternate" type="application/rss+xml" title="RSS" href="/rss/" />
25
+
26
+ <title>[v:webcomicname] - [v:pagetitle]</title>
27
+ [v:layoutcss]
28
+ </head>
29
+ <body>
30
+ <header>
31
+ <h1><a href="/">[v:webcomicname]</a></h1>
32
+ <div id="slogan">[v:webcomicslogan]</div>
33
+ </header>
34
+
35
+
36
+ <nav class="site-nav">
37
+ <ul id="navigation">
38
+ <li><a href="/">Home</a></li>
39
+ <li><a href="/comics/#content-start">Latest</a></li>
40
+ <li><a href="/comics/1/#content-start">First</a></li>
41
+ [c:searchon]
42
+ <li><a href="/search/">Search</a></li>
43
+ [/]
44
+ <li><a href="/archive/">Archive</a></li>
45
+ [c:infinitescrollenabled]
46
+ <li><a href="[v:infinitescrolllink]">Scroll View</a></li>
47
+ [/]
48
+ [c:hasblogs]
49
+ <li><a href="/blog/">Blog</a></li>
50
+ [/]
51
+ [c:!hidefromhost]
52
+ <li><a href="[v:addsubscriptionlink]">Subscribe</a></li>
53
+ [/]
54
+ [l:extrapages]
55
+ <li><a href="[v:l.link]">[v:l.title]</a></li>
56
+ [/]
57
+ </ul>
58
+ </nav>
59
+
60
+ <!--layout:[content]-->
61
+ <footer>
62
+ <div>
63
+ <ul>
64
+ <li>
65
+ <a href="/rss/">RSS</a>
66
+ </li>
67
+ [c:!hidefromhost]
68
+ <li>
69
+ <a href="[v:comicprofile]">Comic Profile</a>
70
+ </li>
71
+ [/]
72
+ <li>
73
+ <a href="#">Top</a>
74
+ </li>
75
+ </ul>
76
+ </div>
77
+
78
+ <div class="hostedby">
79
+ <span>Hosted by </span>
80
+ <a href="https://comicfury.com">
81
+ <img src="https://comicfury.com/images/gator-icon-black.png" height="22" style="height:22px; width:auto;" />
82
+ ComicFury
83
+ </a>
84
+ </div>
85
+ <div>
86
+ &copy; [v:copyrights]<br />
87
+ </div>
88
+ </footer>
89
+ </body>
90
+ </html>
@@ -0,0 +1,3 @@
1
+ <h2>{{ title }}</h2>
2
+
3
+ {%- include "field_table", all_fields: all_fields, fields: fields, loops: loops -%}
@@ -0,0 +1,10 @@
1
+ <h2>{{ title }}</h2>
2
+
3
+ <form method="post" action="/search/" id="searchbox">
4
+ <div class="searchbar">
5
+ <input type="text" name="search" value="[v:searchterm]" placeholder="Search Term" id="bigsearchbox" />
6
+ <input type="submit" value="Search!" class="submit" />
7
+ </div>
8
+ </form>
9
+
10
+ {%- include "field_table", all_fields: all_fields, fields: fields, loops: loops -%}
@@ -0,0 +1,50 @@
1
+ body {
2
+ max-width: 1024px;
3
+ margin: 2em auto 0;
4
+
5
+ font-family: monospace;
6
+ }
7
+
8
+ body::before {
9
+ content: "";
10
+ display: block;
11
+ position: absolute;
12
+ left: 0;
13
+ top: 0;
14
+ right: 0;
15
+ height: 0;
16
+ background: yellow;
17
+ border-top: 1em black dashed;
18
+ transform: skewX(-35deg);
19
+ max-width: 99vw;
20
+ }
21
+
22
+ table td, table th {
23
+ background: white;
24
+ padding: 1ch;
25
+ }
26
+
27
+ table td:nth-child(2) {
28
+ max-width: 35vw;
29
+ overflow: scroll;
30
+ }
31
+
32
+ table td:nth-child(3) {
33
+ word-wrap: anywhere;
34
+ }
35
+
36
+ table tr:nth-child(even) {
37
+ filter: invert(0.1);
38
+ }
39
+
40
+ table th {
41
+ filter: invert(0.2);
42
+ }
43
+
44
+ td:empty::before {
45
+ content: 'blank';
46
+ display: inline-block;
47
+ background: rgba(0, 0, 0, 0.15);
48
+ padding: 0px 4px;
49
+ border-radius: 5px;
50
+ }
@@ -1,6 +1,7 @@
1
1
  require 'jekyll/generator'
2
2
  require 'jekyll/drops/drop'
3
3
  require_relative 'comics'
4
+ require_relative 'chapter'
4
5
  require_relative 'pagination'
5
6
  require_relative 'pipettes'
6
7
 
@@ -113,6 +114,7 @@ module RageRender
113
114
  !show_comic_list
114
115
  end
115
116
 
117
+ def_loop :chapters, *(RageRender::ChapterDrop.invokable_methods - Jekyll::Drops::DocumentDrop.invokable_methods)
116
118
  def chapters
117
119
  unless show_chapter_overview
118
120
  @obj.site.collections['chapters'].docs.reject do |page|
@@ -123,13 +125,14 @@ module RageRender
123
125
  end
124
126
  end
125
127
 
128
+ def_loop :comics_paginated, :number, :newchapter, :chapterend, *ComicDrop::PAGINATION_FIELDS, *ChapterDrop::PAGINATION_FIELDS
126
129
  def comics_paginated
127
130
  number = @obj.data['number']
128
131
  comics = if number
129
132
  selected_comics.to_a[number - 1]
130
133
  else
131
134
  selected_comics.to_a.flatten
132
- end.group_by {|c| c.data['chapter'] }
135
+ end&.group_by {|c| c.data['chapter'] } || []
133
136
 
134
137
  comics.map do |chapter, comics|
135
138
  chapter_data = ChapterDrop.new(@obj.site.collections['chapters'].docs.detect {|c| c.data['slug'] == chapter })
@@ -52,11 +52,40 @@ module RageRender
52
52
  end
53
53
  end
54
54
 
55
+ # Data representing a single paginated blog entry, as available from
56
+ # [l:blogs_paginated].
57
+ class PaginatedBlogDrop < Jekyll::Drops::DocumentDrop
58
+ extend Pipettes
59
+
60
+ def_data_delegator :title, :blogtitle
61
+ def_delegator :@obj, :url, :bloglink
62
+ def_data_delegator :author, :authorname
63
+ def_delegator :@obj, :content, :blog
64
+ # TODO profilelink
65
+
66
+ private delegate_method_as :data, :fallback_data
67
+
68
+ def posttime
69
+ comicfury_date(@obj.date)
70
+ end
71
+
72
+ def allowcomments
73
+ @obj.site.config['allowcomments']
74
+ end
75
+
76
+ def comments
77
+ (@obj.data['comments'] || []).size
78
+ end
79
+ end
80
+
55
81
  # Data to pass to a blog archive page.
56
82
  class BlogArchiveDrop < Jekyll::Drops::Drop
83
+ extend Pipettes
84
+
57
85
  private delegate_method_as :data, :fallback_data
58
86
  data_delegator 'number'
59
87
 
88
+ def_loop :blogs_paginated, *(RageRender::PaginatedBlogDrop.invokable_methods - Jekyll::Drops::DocumentDrop.invokable_methods)
60
89
  def blogs_paginated
61
90
  all_blogs[number-1]&.map {|blog| PaginatedBlogDrop.new(blog).to_liquid } || []
62
91
  end
@@ -81,36 +110,11 @@ module RageRender
81
110
  }
82
111
  end
83
112
  end
113
+ def_loop :pages, :page, :pagelink, :is_current, :skipped_ahead
84
114
 
85
115
  private
86
116
  def all_blogs
87
117
  @all_blogs = @obj.site.posts.docs.each_slice(BLOGS_PER_PAGE).to_a
88
118
  end
89
119
  end
90
-
91
- # Data representing a single paginated blog entry, as available from
92
- # [l:blogs_paginated].
93
- class PaginatedBlogDrop < Jekyll::Drops::DocumentDrop
94
- extend Pipettes
95
-
96
- def_data_delegator :title, :blogtitle
97
- def_delegator :@obj, :url, :bloglink
98
- def_data_delegator :author, :authorname
99
- def_delegator :@obj, :content, :blog
100
- # TODO profilelink
101
-
102
- private delegate_method_as :data, :fallback_data
103
-
104
- def posttime
105
- comicfury_date(@obj.date)
106
- end
107
-
108
- def allowcomments
109
- @obj.site.config['allowcomments']
110
- end
111
-
112
- def comments
113
- (@obj.data['comments'] || []).size
114
- end
115
- end
116
120
  end
@@ -74,9 +74,8 @@ module RageRender
74
74
  COVER_MAX_HEIGHT = 420
75
75
  COVER_MAX_WIDTH = 300
76
76
 
77
- PAGINATION_FIELDS = %w[ chaptername chapterdescription ]
77
+ PAGINATION_FIELDS = %w[ chaptername chapterdescription chapterid ]
78
78
 
79
- delegate_method_as :data, :fallback_data
80
79
  extend Pipettes
81
80
  extend Forwardable
82
81
 
@@ -84,11 +83,17 @@ module RageRender
84
83
  def_data_delegator :image, :image
85
84
  def_delegator :@obj, :url, :chapterarchiveurl
86
85
 
86
+ def chapterid
87
+ @obj.collection.docs.index @obj
88
+ end
89
+
87
90
  def chaptername
88
91
  escape @obj.data['title']
89
92
  end
90
93
 
91
94
  def cover_width_small
95
+ return nil if cover_width.nil?
96
+
92
97
  if (cover_height.to_f / COVER_MAX_HEIGHT) > (cover_width.to_f / COVER_MAX_WIDTH)
93
98
  (cover_height_small * cover_width) / cover_height
94
99
  else
@@ -97,6 +102,8 @@ module RageRender
97
102
  end
98
103
 
99
104
  def cover_height_small
105
+ return nil if cover_height.nil?
106
+
100
107
  if (cover_height.to_f / COVER_MAX_HEIGHT) > (cover_width.to_f / COVER_MAX_WIDTH)
101
108
  [COVER_MAX_HEIGHT, cover_height].min
102
109
  else
@@ -116,9 +123,18 @@ module RageRender
116
123
 
117
124
  private
118
125
  def_image_metadata :image
126
+ private :image_url, :image_width, :image_height
127
+
128
+ def comics
129
+ @obj.site.collections['comics'].docs.select {|c| c.data['chapter'] == @obj.data['slug'] }
130
+ end
119
131
 
120
132
  def first_comic
121
- @obj.site.collections['comics'].docs.select {|c| c.data['chapter'] == @obj.data['slug'] }.first
133
+ comics.first
134
+ end
135
+
136
+ def fallback_data
137
+ {}
122
138
  end
123
139
 
124
140
  public
@@ -1,7 +1,13 @@
1
+ require 'uri'
2
+ require 'liquid/drop'
3
+ require 'jekyll/hooks'
4
+ require 'jekyll/plugin'
1
5
  require 'jekyll/generator'
2
6
  require 'jekyll/document'
7
+ require 'jekyll/drops/drop'
3
8
  require 'jekyll/drops/document_drop'
4
9
  require_relative '../date_formats'
10
+ require_relative 'chapter'
5
11
  require_relative 'pipettes'
6
12
 
7
13
  Jekyll::Hooks.register :comics, :pre_render do |page, payload|
@@ -12,6 +18,8 @@ end
12
18
  module RageRender
13
19
  SPECIAL_COMIC_SLUGS = %w{frontpage index}
14
20
 
21
+ BASE_DIR = File.join(File.dirname(__FILE__), '..', '..', '..')
22
+
15
23
  # Creates comics for each file found in the 'images' directory
16
24
  # that does not already have an associated comic object.
17
25
  class ComicFromImageGenerator < Jekyll::Generator
@@ -42,9 +50,14 @@ module RageRender
42
50
  def generate site
43
51
  comics = site.collections['comics']
44
52
  index = comics.docs.last.dup
53
+ collection = comics.docs
54
+ if index.nil?
55
+ index = site.pages.detect {|p| p.data["title"] == "Comic not found" }.dup
56
+ collection = site.pages
57
+ end
45
58
  index.instance_variable_set(:"@data", index.data.dup)
46
59
  index.data['slug'] = 'index'
47
- comics.docs << index
60
+ collection << index
48
61
  end
49
62
  end
50
63
 
@@ -53,12 +66,16 @@ module RageRender
53
66
 
54
67
  def generate site
55
68
  site.collections['comics'].docs.each do |comic|
56
- comic.data['image'] ||= default_image_path(comic)
69
+ comic.data['image'] ||= default_image_path(site, comic)
57
70
  end
58
71
  end
59
72
 
60
- def default_image_path comic
61
- "images/#{comic.data['slug']}.jpg"
73
+ def images site
74
+ @images ||= site.static_files.select {|f| f.relative_path.start_with? '/images' }.map {|f| [f.basename, f] }.to_h
75
+ end
76
+
77
+ def default_image_path site, comic
78
+ images(site)[comic.data['slug']].relative_path
62
79
  end
63
80
  end
64
81
 
@@ -86,31 +103,65 @@ module RageRender
86
103
  def_delegator :@obj, :url, :permalink
87
104
  data_delegator 'rating'
88
105
  data_delegator 'votecount'
106
+ data_delegator 'comments'
89
107
 
90
108
  def comictitle
91
109
  escape @obj.data['title']
92
110
  end
93
111
 
112
+ def comicdescription
113
+ escape @obj.data['description']
114
+ end
115
+
116
+ def transcript
117
+ escape @obj.data['transcript']
118
+ end
119
+
120
+ def comickeywords
121
+ (@obj.data['keywords'] || []).map {|k| escape k }.join(', ')
122
+ end
123
+
124
+ def comicnumber
125
+ 1 + all_comics.index(@obj)
126
+ end
127
+
128
+ def comicsnum
129
+ all_comics.size
130
+ end
131
+
94
132
  def posttime
95
133
  comicfury_date(@obj.date)
96
134
  end
97
135
 
98
- def usechapters
99
- all_comics.any? {|comic| comic.data.include? 'chapter' }
136
+ def postyear
137
+ @obj.date.year
138
+ end
139
+
140
+ def postmonth
141
+ @obj.date.month
100
142
  end
101
143
 
102
144
  def haschapter
103
145
  @obj.data.include? 'chapter'
104
146
  end
105
147
 
106
- def chaptername
107
- escape(chapter.data['title']) rescue nil
148
+ def_safe_delegator :chapterdrop, :chapterid, :chapterid
149
+ def_safe_delegator :chapterdrop, :chaptername, :chaptername
150
+ def_safe_delegator :chapterdrop, :chapterdescription, :chapterdescription
151
+ def_safe_delegator :chapter, :url, :chapterlink
152
+
153
+ def_safe_delegator :prevchapterdrop, :url, :prevchapter
154
+ def_safe_delegator :nextchapterdrop, :url, :nextchapter
155
+
156
+ def isfirstcomicinchapter
157
+ (chapterdrop&.send(:comics) || []).first == @obj
108
158
  end
109
159
 
110
- def chapterlink
111
- chapter&.url
160
+ def islastcomicinchapter
161
+ (chapterdrop&.send(:comics) || []).last == @obj
112
162
  end
113
163
 
164
+ def_loop :dropdown, :is_selected, :is_disabled, :title, :grouplabel, :newgroup, :endgroup, :url
114
165
  def dropdown
115
166
  all_comics.each_with_object([]) do |c, dropdown|
116
167
  new_group = dropdown.last.nil? ? true : dropdown.last['grouplabel'] != c.data['chapter']
@@ -143,6 +194,7 @@ module RageRender
143
194
  end
144
195
  end
145
196
 
197
+ def_loop :authornotes, :is_reply, :comment, :isguest, :avatar, :authorname, :commentanchor, :posttime, :profilelink
146
198
  def authornotes
147
199
  @obj.data['authornotes'] || [{
148
200
  'is_reply' => false,
@@ -174,12 +226,28 @@ module RageRender
174
226
  all_comics.last == @obj
175
227
  end
176
228
 
177
- def prevcomic
178
- @obj.previous_doc&.url
229
+ def_safe_delegator :prevcomicdrop, :url, :prevcomic
230
+ def_safe_delegator :prevcomicdrop, :permalink, :prevcomicpermalink
231
+ def_safe_delegator :prevcomicdrop, :title, :prevcomictitle
232
+
233
+ def_safe_delegator :nextcomicdrop, :url, :nextcomic
234
+ def_safe_delegator :nextcomicdrop, :permalink, :nextcomicpermalink
235
+ def_safe_delegator :nextcomicdrop, :title, :nextcomictitle
236
+
237
+ def prevcomicbychapter
238
+ if isfirstcomicinchapter
239
+ (prevchapterdrop&.send(:comics) || []).last
240
+ else
241
+ (chapterdrop&.send(:comics) || []).each_cons(2).detect {|_, this| this == @obj }&.first
242
+ end&.url
179
243
  end
180
244
 
181
- def nextcomic
182
- @obj.next_doc&.url
245
+ def nextcomicbychapter
246
+ if islastcomicinchapter
247
+ nextchapterdrop&.send(:first_comic)
248
+ else
249
+ (chapterdrop&.send(:comics) || []).each_cons(2).detect {|this, _| this == @obj }&.last
250
+ end&.url
183
251
  end
184
252
 
185
253
  # An HTML tag to print for the comic image. If there is a future image, then
@@ -189,7 +257,9 @@ module RageRender
189
257
  <a href="#{nextcomic}">
190
258
  HTML
191
259
  image = <<~HTML
192
- <img id="comicimage" src="#{comicimageurl}" width="#{comicwidth}" height="#{comicheight}">
260
+ <img id="comicimage" src="#{comicimageurl}" alt="#{comictitle}"
261
+ width="#{comicwidth}" height="#{comicheight}"
262
+ title="#{comicdescription}">
193
263
  HTML
194
264
  linkclose = nextcomic ? <<~HTML : ''
195
265
  </a>
@@ -197,6 +267,10 @@ module RageRender
197
267
  [linkopen, image, linkclose].join
198
268
  end
199
269
 
270
+ def keys
271
+ super.reject {|k| private_methods.include? k.to_sym }
272
+ end
273
+
200
274
  def to_liquid
201
275
  super.reject do |k, v|
202
276
  Jekyll::Drops::DocumentDrop::NESTED_OBJECT_FIELD_BLACKLIST.include? k
@@ -212,8 +286,29 @@ module RageRender
212
286
  @obj.site.collections['chapters'].docs.detect {|c| c.data['slug'] == @obj.data['chapter'] }
213
287
  end
214
288
 
289
+ def nextcomicdrop
290
+ @obj.next_doc.nil? ? nil : ComicDrop.new(@obj.next_doc)
291
+ end
292
+
293
+ def prevcomicdrop
294
+ @obj.previous_doc.nil? ? nil : ComicDrop.new(@obj.previous_doc)
295
+ end
296
+
297
+ def chapterdrop
298
+ chapter.nil? ? nil : ChapterDrop.new(chapter)
299
+ end
300
+
301
+ def prevchapterdrop
302
+ chapter&.previous_doc.nil? ? nil : ChapterDrop.new(chapter.previous_doc)
303
+ end
304
+
305
+ def nextchapterdrop
306
+ chapter&.next_doc.nil? ? nil : ChapterDrop.new(chapter.next_doc)
307
+ end
308
+
215
309
  data_delegator 'image'
216
310
  def_image_metadata :image
311
+ private :image, :image_url, :image_width, :image_height
217
312
 
218
313
  public
219
314
  alias comicimageurl image_url
@@ -7,17 +7,22 @@ require_relative 'blog_archive'
7
7
  Jekyll::Hooks.register :pages, :pre_render do |page, payload|
8
8
  if page.data['layout'] == 'overview'
9
9
  RageRender::Pipettes.clean_payload payload
10
- payload.merge! RageRender::ComicDrop.new(page.site.collections['comics'].docs.last).to_liquid
10
+ latest_comic = page.site.collections['comics'].docs.last
11
+ if latest_comic
12
+ payload.merge! RageRender::ComicDrop.new(latest_comic).to_liquid
13
+ end
11
14
  payload.merge! RageRender::OverviewDrop.new(page).to_liquid
12
15
  end
13
16
  end
14
17
 
15
18
  module RageRender
16
19
  class OverviewDrop < Jekyll::Drops::Drop
20
+ extend Pipettes
17
21
  private delegate_method_as :data, :fallback_data
18
22
 
23
+ def_loop :latestblogs, *(RageRender::PaginatedBlogDrop.invokable_methods - Jekyll::Drops::DocumentDrop.invokable_methods)
19
24
  def latestblogs
20
- @obj.site.posts.docs[-5..].map {|post| RageRender::PaginatedBlogDrop.new(post) }
25
+ @obj.site.posts.docs[-5..]&.map {|post| RageRender::PaginatedBlogDrop.new(post) } || []
21
26
  end
22
27
  end
23
28
  end
@@ -11,12 +11,26 @@ module RageRender
11
11
  payload.send(:fallback_data).delete_if {|k| methods.include? k}
12
12
  end
13
13
 
14
+ def def_safe_delegator obj, key, aliaz
15
+ define_method(aliaz.to_sym) do
16
+ send(obj.to_sym)&.send(key.to_sym)
17
+ end
18
+ end
19
+
14
20
  def def_data_delegator key, aliaz
15
21
  define_method(aliaz.to_sym) do
16
22
  @obj.data[key.to_s]
17
23
  end
18
24
  end
19
25
 
26
+ def def_loop method, *fields
27
+ (@loops ||= {})[method.to_sym] = fields
28
+ end
29
+
30
+ def loops
31
+ @loops || {}
32
+ end
33
+
20
34
  def self.extended mod
21
35
  mod.define_method(:escape) do |str|
22
36
  str.nil? ? nil : CGI.escapeHTML(str)
@@ -33,6 +47,7 @@ module RageRender
33
47
  define_method(:"#{prefix}_relative_path") do
34
48
  Pathname.new('/').join(send(prefix.to_sym)).to_path
35
49
  end
50
+ private :"#{prefix}_relative_path"
36
51
 
37
52
  define_method(:"#{prefix}_url") do
38
53
  File.join (@obj.site.baseurl || ''), send(:"#{prefix}_relative_path")
@@ -44,17 +59,19 @@ module RageRender
44
59
  end
45
60
  instance_variable_get(:"@#{prefix}_obj")
46
61
  end
62
+ private :"#{prefix}_obj"
47
63
 
48
64
  define_method(:"#{prefix}_path") do
49
65
  send(:"#{prefix}_obj").path
50
66
  end
67
+ private :"#{prefix}_path"
51
68
 
52
69
  define_method(:"#{prefix}_width") do
53
- send(:"#{prefix}_obj").data['width'] ||= Dimensions.width(send(:"#{prefix}_path")) rescue nil
70
+ send(:"#{prefix}_obj") && (send(:"#{prefix}_obj").data['width'] ||= Dimensions.width(send(:"#{prefix}_path")) rescue nil)
54
71
  end
55
72
 
56
73
  define_method(:"#{prefix}_height") do
57
- send(:"#{prefix}_obj").data['height'] ||= Dimensions.height(send(:"#{prefix}_path")) rescue nil
74
+ send(:"#{prefix}_obj") && (send(:"#{prefix}_obj").data['height'] ||= Dimensions.height(send(:"#{prefix}_path")) rescue nil)
58
75
  end
59
76
  end
60
77
  end
@@ -7,6 +7,8 @@ end
7
7
 
8
8
  module RageRender
9
9
  class SearchDrop < Jekyll::Drops::Drop
10
+ extend Pipettes
11
+
10
12
  private delegate_method_as :data, :fallback_data
11
13
  data_delegator 'searchterm'
12
14
 
@@ -14,6 +16,7 @@ module RageRender
14
16
  !searchterm.nil?
15
17
  end
16
18
 
19
+ def_loop :searchresults, :number, *ComicDrop::PAGINATION_FIELDS
17
20
  def searchresults
18
21
  return [] unless searched
19
22
  @results ||= @obj.site.collections['comics'].docs.select do |comic|
@@ -1,6 +1,10 @@
1
1
  require 'etc'
2
2
  require 'stringio'
3
- require 'jekyll'
3
+ require 'jekyll/command'
4
+ require 'jekyll/hooks'
5
+ require 'jekyll/plugin'
6
+ require 'jekyll/generator'
7
+ require 'liquid/drop'
4
8
  require_relative 'language'
5
9
  require_relative 'functions'
6
10
  require_relative 'to_liquid'
@@ -57,6 +61,7 @@ Jekyll::Hooks.register :site, :after_init do |site|
57
61
  site.config['theme'] ||= 'ragerender'
58
62
  site.config['title'] ||= File.basename(site.source)
59
63
  site.config['search'] ||= true
64
+ site.config['url'] ||= "https://#{File.basename(site.source)}.thecomicseries.com"
60
65
  site.config = site.config
61
66
 
62
67
  setup_collection site, :comics, '/:collection/:slug/', layout: 'comic-page', chapter: '0'
@@ -105,6 +110,10 @@ class RageRender::FrontpageGenerator < Jekyll::Generator
105
110
  collection = site.pages
106
111
  site.pages.detect {|p| p.data["slug"] == frontpage }
107
112
  end.dup
113
+ if index.nil?
114
+ collection = site.pages
115
+ index = site.pages.detect {|p| p.data['title'] == 'Comic not found' }.dup
116
+ end
108
117
  index.instance_variable_set(:"@data", index.data.dup)
109
118
  index.data['permalink'] = '/index.html'
110
119
  index.data['slug'] = 'frontpage'
@@ -125,8 +134,8 @@ class RageRender::WebcomicDrop < Jekyll::Drops::Drop
125
134
  extend Forwardable
126
135
  extend RageRender::Pipettes
127
136
 
128
- def self.def_config_delegator source, target
129
- define_method(target) { @obj.site.config[source.to_s] }
137
+ def self.def_config_delegator source, target, default=nil
138
+ define_method(target) { @obj.site.config.fetch(source.to_s, default) }
130
139
  end
131
140
 
132
141
  def_config_delegator :search, :searchon
@@ -134,16 +143,62 @@ class RageRender::WebcomicDrop < Jekyll::Drops::Drop
134
143
  def_config_delegator var, var
135
144
  end
136
145
 
146
+ def webcomicid
147
+ @obj.site.config['id'] || @obj.site.hash
148
+ end
149
+
137
150
  def webcomicname
138
151
  escape @obj.site.config['title']
139
152
  end
140
153
 
141
154
  def webcomicslogan
155
+ escape @obj.site.config['slogan']
156
+ end
157
+
158
+ def webcomicdescription
142
159
  escape @obj.site.config['description']
143
160
  end
144
161
 
162
+ def webcomicactivitystatus
163
+ escape @obj.site.config['status']&.capitalize
164
+ end
165
+
166
+ def webcomicsub
167
+ URI.parse(@obj.site.config['url']).host.split.first
168
+ end
169
+
170
+ def comicprofile
171
+ "https://comicfury.com/comicprofile.php?url=#{webcomicsub}"
172
+ end
173
+
174
+ def addsubscriptionlink
175
+ "https://comicfury.com/comic.php?action=addsubscription&url=#{webcomicsub}"
176
+ end
177
+
178
+ def_loop :webcomicgenres, :genre_link, :genre_name
179
+ def webcomicgenres
180
+ (@obj.site.config['genres'] || []).map do |g|
181
+ {
182
+ 'genre_name' => escape(g),
183
+ 'genre_link' => "https://comicfury.com/search.php?vr=1&query=&tags=#{g.downcase.gsub(/[^a-z]/, '')}"
184
+ }
185
+ end
186
+ end
187
+
188
+ def webcomicgenre
189
+ (webcomicgenres.first || {})['genre_name']
190
+ end
191
+
145
192
  def webcomicurl
146
- @obj.site.baseurl
193
+ @obj.site.config['url']
194
+ end
195
+
196
+ def webcomicrating
197
+ @obj.site.config['rating']
198
+ end
199
+
200
+ def lastupdate
201
+ comicfury_date Time.now
147
202
  end
148
203
 
149
204
  def lastupdatedmy
@@ -151,7 +206,7 @@ class RageRender::WebcomicDrop < Jekyll::Drops::Drop
151
206
  end
152
207
 
153
208
  def copyrights
154
- escape @obj.site.config['copyrights'].gsub('[year]', Date.today.year.to_s)
209
+ escape @obj.site.config.fetch('copyrights', '').gsub('[year]', Date.today.year.to_s)
155
210
  end
156
211
 
157
212
  def banner
@@ -171,13 +226,34 @@ class RageRender::WebcomicDrop < Jekyll::Drops::Drop
171
226
  @obj.site.posts.docs.any?
172
227
  end
173
228
 
229
+ def hascomics
230
+ comicsnum > 0
231
+ end
232
+
233
+ def comicsnum
234
+ @obj.site.collections['comics'].docs.size
235
+ end
236
+
237
+ def usechapters
238
+ @obj.site.collections['comics'].docs.any? {|comic| comic.data.include? 'chapter' }
239
+ end
240
+
241
+ %w{subscriptions pageviewsnum visitsnum}.each do |var|
242
+ def_config_delegator var, var, 0
243
+ end
244
+
174
245
  def hidefromhost
175
246
  false
176
247
  end
177
248
 
249
+ def_loop :extrapages, :link, :title, :foldername
178
250
  def extrapages
179
251
  @obj.site.pages.reject {|page| page.data['hidden'] }.map do |page|
180
- {'link' => page.url, 'title' => escape(page.data['title'])}
252
+ {
253
+ 'link' => page.url,
254
+ 'title' => escape(page.data['title']),
255
+ 'foldername' => File.basename(URI::parse(page.url).path, '.*'),
256
+ }
181
257
  end
182
258
  end
183
259
 
@@ -194,6 +270,7 @@ class RageRender::WebcomicDrop < Jekyll::Drops::Drop
194
270
  css_files << Pathname.new(@obj.site.theme.includes_path).join('layout.css') unless css_files.any?
195
271
  css_files.map {|f| File.read f }.join
196
272
  end
273
+ private :css
197
274
 
198
275
  def layoutcss
199
276
  <<~HTML
@@ -207,14 +284,58 @@ class RageRender::WebcomicDrop < Jekyll::Drops::Drop
207
284
  escape @obj.data['title']
208
285
  end
209
286
 
287
+ def isarchive
288
+ @obj.data['layout'] == 'archive'
289
+ end
290
+
291
+ def issearch
292
+ @obj.data['layout'] == 'search'
293
+ end
294
+
210
295
  def iscomicpage
211
- @obj.type == :comics
296
+ @obj.data['layout'] == 'comic-page'
297
+ end
298
+
299
+ def isblog
300
+ @obj.data['layout'] == 'blog-archive'
301
+ end
302
+
303
+ def isoverview
304
+ @obj.data['layout'] == 'overview'
305
+ end
306
+
307
+ def iserrorpage
308
+ @obj.data['layout'] == 'error-page'
309
+ end
310
+
311
+ def isfrontpage
312
+ @obj.data['slug'] == 'frontpage'
313
+ end
314
+
315
+ def iscomicrelatedpage
316
+ @obj.type == :comics || @obj.data['layout'] == 'overview'
212
317
  end
213
318
 
214
319
  def isextrapage
215
320
  @obj.type == :pages && @obj.data['hidden'] != true
216
321
  end
217
322
 
323
+ def currentdate
324
+ comicfury_date Date.today
325
+ end
326
+
327
+ def day
328
+ Date.today.day
329
+ end
330
+
331
+ def month
332
+ Date.today.month
333
+ end
334
+
335
+ def year
336
+ Date.today.year
337
+ end
338
+
218
339
  def fallback_data
219
340
  {}
220
341
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ragerender
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simon Worthington
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-09-21 00:00:00.000000000 Z
11
+ date: 2026-04-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rsec
@@ -141,7 +141,7 @@ description: |-
141
141
  mkdir mycomic && cd mycomic
142
142
  bundle init
143
143
  bundle add jekyll
144
- bundle add ragerender
144
+ bundle add ragerender --group=jekyll_plugins
145
145
 
146
146
  Now you can add comics! Add the image into an <tt>images</tt> folder:
147
147
 
@@ -184,6 +184,10 @@ description: |-
184
184
  description: >
185
185
  My epic story about how him and her
186
186
  fell into a romantic polycule with they and them
187
+ status: active
188
+ genres:
189
+ - Comedy
190
+ - Romance
187
191
 
188
192
  defaults:
189
193
  - scope:
@@ -243,6 +247,8 @@ description: |-
243
247
  date: "2025-03-05 16:20"
244
248
  image: "images/ghost.png"
245
249
  author: "Jane doe"
250
+ description: "Some spooky mouseover text"
251
+ keywords: [excellent, comic page, spooky]
246
252
  custom:
247
253
  # use yes and no for tickbox settings
248
254
  spooky: yes
@@ -253,6 +259,9 @@ description: |-
253
259
  Testing webcomics
254
260
  Now easier than ever
255
261
  Thanks to RageRender
262
+ transcript: >
263
+ The transcript contains a machine-readable version
264
+ of all the text in your comic image.
256
265
  comments:
257
266
  - author: "Skippy"
258
267
  date: "13 Mar 2025, 3.45 PM"
@@ -350,8 +359,23 @@ files:
350
359
  - assets/archive-comics.html
351
360
  - assets/archive.html
352
361
  - assets/blog.html
362
+ - assets/comic-not-found.html
353
363
  - assets/overview.html
354
364
  - assets/search.html
365
+ - inspector/.gitignore
366
+ - inspector/Rakefile
367
+ - inspector/_includes/field_row.html
368
+ - inspector/_includes/field_rows.html
369
+ - inspector/_includes/field_table.html
370
+ - inspector/_templates/archive.html
371
+ - inspector/_templates/blog-archive.html
372
+ - inspector/_templates/blog-display.html
373
+ - inspector/_templates/comic-page.html
374
+ - inspector/_templates/error-page.html
375
+ - inspector/_templates/overall.html
376
+ - inspector/_templates/overview.html
377
+ - inspector/_templates/search.html
378
+ - inspector/layout.css
355
379
  - lib/ragerender.rb
356
380
  - lib/ragerender/cflxml.rb
357
381
  - lib/ragerender/date_formats.rb