ragerender 0.1.5 → 0.1.7

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: aedbeb388659f01299aa3694a3f30bd1368e09e9f729caa31334fff4300c6b7c
4
- data.tar.gz: 11e966fefecb160bf9992f6697ae8fc23dbe299095c1bc458af6395fb29903c8
3
+ metadata.gz: 152c14ba01c06c930b537a0e1c72a994e673fa8aea1176b82f92adfc912e5e77
4
+ data.tar.gz: bfb9fa9e0e5d4e1dae45c3bdc7fc09fd7de45fdcbb3043fd45c26e11404b0d1d
5
5
  SHA512:
6
- metadata.gz: cbdffe416a3edb68d95c256456341c19199d09d71cbc0ef8d3a2f3b41b362048ec81d67848982beca07d2560d4a164758861c152d9b24d71f76010ee45bfc793
7
- data.tar.gz: 266d915e6744dd58bf1f5db3fe795105190f9a09081ed03b596d7c58420e258d0f3ff7e1546c209c051bfb936654583d528c5555835ce5340b2863f0158baf67
6
+ metadata.gz: 78e6690eb005da5ac9604c4111612b3e3dcf2d68c52bb8b351dc6165d3fecf9acc8267cfb69d551a6ee8374bb6b660d812465e2bb14875e2d95199c5b1a81a6e
7
+ data.tar.gz: 841b860abb04b124f88aa730ece8650fce559f08a889598f4121b503eb6d5c3ef03bb396f6afffe860ded22cffb92d887750cd597b170369dbc74e7660d8f1ea
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,9 @@ 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
+ genres:
96
+ - Comedy
97
+ - Romance
95
98
 
96
99
  defaults:
97
100
  - scope:
@@ -105,18 +108,17 @@ Your webcomic now has its basic information set up.
105
108
 
106
109
  === Adding your layouts
107
110
 
108
- RageRender will use a ComicFury default layout if you don't supply your own
109
- files.
111
+ If you want to use your own layout code, then create a <tt>_layouts</tt>
112
+ directory and put the contents of each of your ComicFury layout tabs in there,
113
+ and then put your CSS in the main folder.
110
114
 
111
- If you want to keep using the "simple" layout, you can add the details into your
112
- <tt>_config.yml</tt> too:
115
+ The easiest way is to go to your Webcomic Management, click "Edit Layout", then
116
+ in the box labelled "Useful", click "Download Layout Backup". Pass this file to
117
+ RageRender, which will <tt>unpack</tt> it for you:
113
118
 
114
- layout:
119
+ bundle exec jekyll unpack mycomic-2025-09-13.cflxml
115
120
 
116
- If you want to use your own layout code, then create a <tt>_layouts</tt>
117
- directory and put the contents of each of your ComicFury layout tabs in there,
118
- and then put your CSS in the main folder. You should end up with a full set of
119
- files like:
121
+ You should end up with a full set of files like:
120
122
 
121
123
  _layouts
122
124
  archive.html
@@ -134,7 +136,7 @@ instead.
134
136
 
135
137
  === Adding blogs
136
138
 
137
- Add your blogs into a folder called `_posts`:
139
+ Add your blogs into a folder called <tt>_posts</tt>:
138
140
 
139
141
  cat _posts/2025-05-29-my-new-comic.md
140
142
  Hey guys, welcome to my new comic! It's gonna be so sick!
@@ -201,6 +203,16 @@ You control this by setting a <tt>frontpage</tt> key in your site config.
201
203
  - anything else will display the extra page that has the matching
202
204
  <tt>slug</tt> in its Front Matter
203
205
 
206
+ === Putting changes on ComicFury
207
+
208
+ Once you're done making changes, you can <tt>pack</tt> your layout:
209
+
210
+ bundle exec jekyll pack
211
+
212
+ The resulting file can be uploaded to ComicFury by going to your Webcomic
213
+ Management, clicking "Edit Layout", then in the box labelled "Useful", click
214
+ "Restore Layout Backup".
215
+
204
216
  === Stuff that doesn't work
205
217
 
206
218
  Here is a probably incomplete list of things you can expect to be different
@@ -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,105 @@
1
+ require 'pathname'
2
+ require 'rexml'
3
+ require 'base64'
4
+
5
+ module RageRender
6
+ HTML_FILES = {
7
+ overall: 'overall',
8
+ overview: 'overview',
9
+ viewblog: 'blog-display',
10
+ comic: 'comic-page',
11
+ archive: 'archive',
12
+ blogarchive: 'blog-archive',
13
+ error: 'error-page',
14
+ search: 'search',
15
+ }
16
+
17
+ CSS_FILES = {
18
+ layoutcss: 'layout'
19
+ }
20
+
21
+ def self.unpack src, html_dest, css_dest
22
+ doc = REXML::Document.new(src).elements
23
+ html_dir = Pathname.new(html_dest)
24
+ css_dir = Pathname.new(css_dest)
25
+
26
+ HTML_FILES.each do |(tag, filename)|
27
+ File.write html_dir.join(filename + '.html'), Base64.decode64(doc["/layout/ldata/#{tag.to_s}/text()"].value)
28
+ end
29
+
30
+ CSS_FILES.each do |(tag, filename)|
31
+ File.write css_dir.join(filename + '.css'), Base64.decode64(doc["/layout/ldata/#{tag.to_s}/text()"].value)
32
+ end
33
+ end
34
+
35
+ def self.pack html_srcdir, css_srcdir, dest
36
+ layout = REXML::Element.new('layout')
37
+
38
+ name = REXML::Element.new('name')
39
+ name.text = "Downloaded ComicFury layout"
40
+ layout.add name
41
+
42
+ version = REXML::Element.new('cfxml')
43
+ version.text = '1.2'
44
+ layout.add version
45
+
46
+ spage = REXML::Element.new('spage')
47
+ spage.text = '1'
48
+ layout.add spage
49
+
50
+ ldata = REXML::Element.new('ldata')
51
+ html_dir = Pathname.new(html_srcdir)
52
+ css_dir = Pathname.new(css_srcdir)
53
+
54
+ HTML_FILES.each do |(tag, filename)|
55
+ elem = REXML::Element.new tag.to_s
56
+ elem.text = Base64.strict_encode64 File.read html_dir.join(filename + '.html')
57
+ ldata.add elem
58
+ end
59
+
60
+ CSS_FILES.each do |(tag, filename)|
61
+ elem = REXML::Element.new tag.to_s
62
+ elem.text = Base64.strict_encode64 File.read css_dir.join(filename + '.css')
63
+ ldata.add elem
64
+ end
65
+ layout.add ldata
66
+
67
+ # ComicFury will only accept backup files that have:
68
+ # - A valid XML declaration using double quotes
69
+ # – A comment as per below
70
+ # – Tab indentation
71
+ doc = REXML::Document.new(nil, prologue_quote: :quote)
72
+ doc.add REXML::XMLDecl.new REXML::XMLDecl::DEFAULT_VERSION, REXML::XMLDecl::DEFAULT_ENCODING
73
+ doc.add REXML::Comment.new 'This is a ComicFury layout backup, use the import layout function to restore this to a webcomic site. You can access this as follows: Go to your Webcomic Management, click "Edit Layout", then in the Box labelled "Useful", click "Restore Layout Backup"'
74
+ doc.add layout
75
+
76
+ # Pretty print, but *always* make text nodes take up a single line, no
77
+ # matter how long they are
78
+ formatter = REXML::Formatters::Pretty.new(1)
79
+ formatter.compact = true
80
+ formatter.width = Float::INFINITY
81
+
82
+ # Replace the space indentation with tab indentation
83
+ buf = StringIO.new
84
+ formatter.write(doc, buf)
85
+ buf.string.each_line do |line|
86
+ dest << line.gsub(/^ +/) {|sp| "\t" * sp.size }
87
+ end
88
+ end
89
+ end
90
+
91
+ if __FILE__ == $0
92
+ case ARGV.shift
93
+ when 'pack'
94
+ html_srcdir, css_srcdir, *dest = ARGV
95
+ RageRender.pack(html_srcdir, css_srcdir, dest.any? ? File.open(dest.first, 'w') : $stdout)
96
+ when 'unpack'
97
+ src, html_dest, css_dest = ARGV
98
+ RageRender.unpack(src == '-' ? $stdin : File.open(src, 'r'), html_dest, css_dest)
99
+ else
100
+ raise <<~USAGE
101
+ Usage: pack html_srcdir css_srcdir dest
102
+ Usage: unpack src html_dest css_dest
103
+ USAGE
104
+ end
105
+ end
@@ -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
 
@@ -8,6 +9,7 @@ require_relative 'pipettes'
8
9
  # chapter pages because they are not "pages"
9
10
  Jekyll::Hooks.register :pages, :pre_render do |page, payload|
10
11
  if page.data['layout'] == 'archive'
12
+ RageRender::Pipettes.clean_payload payload
11
13
  payload.merge! RageRender::ArchiveDrop.new(page).to_liquid
12
14
  end
13
15
  end
@@ -112,6 +114,7 @@ module RageRender
112
114
  !show_comic_list
113
115
  end
114
116
 
117
+ def_loop :chapters, *(RageRender::ChapterDrop.invokable_methods - Jekyll::Drops::DocumentDrop.invokable_methods)
115
118
  def chapters
116
119
  unless show_chapter_overview
117
120
  @obj.site.collections['chapters'].docs.reject do |page|
@@ -122,13 +125,14 @@ module RageRender
122
125
  end
123
126
  end
124
127
 
128
+ def_loop :comics_paginated, :number, :newchapter, :chapterend, *ComicDrop::PAGINATION_FIELDS, *ChapterDrop::PAGINATION_FIELDS
125
129
  def comics_paginated
126
130
  number = @obj.data['number']
127
131
  comics = if number
128
132
  selected_comics.to_a[number - 1]
129
133
  else
130
134
  selected_comics.to_a.flatten
131
- end.group_by {|c| c.data['chapter'] }
135
+ end&.group_by {|c| c.data['chapter'] } || []
132
136
 
133
137
  comics.map do |chapter, comics|
134
138
  chapter_data = ChapterDrop.new(@obj.site.collections['chapters'].docs.detect {|c| c.data['slug'] == chapter })
@@ -4,6 +4,7 @@ require_relative '../date_formats'
4
4
  require_relative 'pipettes'
5
5
 
6
6
  Jekyll::Hooks.register :posts, :pre_render do |post, payload|
7
+ RageRender::Pipettes.clean_payload payload
7
8
  payload.merge! RageRender::BlogDrop.new(post).to_liquid
8
9
  end
9
10
 
@@ -8,6 +8,7 @@ require_relative 'pipettes'
8
8
  # Pass the right variables to blog archive pages.
9
9
  Jekyll::Hooks.register :pages, :pre_render do |page, payload|
10
10
  if page.data['layout'] == 'blog-archive'
11
+ RageRender::Pipettes.clean_payload payload
11
12
  payload.merge! RageRender::BlogArchiveDrop.new(page).to_liquid
12
13
  end
13
14
  end
@@ -51,11 +52,40 @@ module RageRender
51
52
  end
52
53
  end
53
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
+
54
81
  # Data to pass to a blog archive page.
55
82
  class BlogArchiveDrop < Jekyll::Drops::Drop
83
+ extend Pipettes
84
+
56
85
  private delegate_method_as :data, :fallback_data
57
86
  data_delegator 'number'
58
87
 
88
+ def_loop :blogs_paginated, *(RageRender::PaginatedBlogDrop.invokable_methods - Jekyll::Drops::DocumentDrop.invokable_methods)
59
89
  def blogs_paginated
60
90
  all_blogs[number-1]&.map {|blog| PaginatedBlogDrop.new(blog).to_liquid } || []
61
91
  end
@@ -80,36 +110,11 @@ module RageRender
80
110
  }
81
111
  end
82
112
  end
113
+ def_loop :pages, :page, :pagelink, :is_current, :skipped_ahead
83
114
 
84
115
  private
85
116
  def all_blogs
86
117
  @all_blogs = @obj.site.posts.docs.each_slice(BLOGS_PER_PAGE).to_a
87
118
  end
88
119
  end
89
-
90
- # Data representing a single paginated blog entry, as available from
91
- # [l:blogs_paginated].
92
- class PaginatedBlogDrop < Jekyll::Drops::DocumentDrop
93
- extend Pipettes
94
-
95
- def_data_delegator :title, :blogtitle
96
- def_delegator :@obj, :url, :bloglink
97
- def_data_delegator :author, :authorname
98
- def_delegator :@obj, :content, :blog
99
- # TODO profilelink
100
-
101
- private delegate_method_as :data, :fallback_data
102
-
103
- def posttime
104
- comicfury_date(@obj.date)
105
- end
106
-
107
- def allowcomments
108
- @obj.site.config['allowcomments']
109
- end
110
-
111
- def comments
112
- (@obj.data['comments'] || []).size
113
- end
114
- end
115
120
  end
@@ -21,6 +21,7 @@ Jekyll::Hooks.register :site, :after_init do |site|
21
21
  end
22
22
 
23
23
  Jekyll::Hooks.register :chapters, :pre_render do |chapter, payload|
24
+ RageRender::Pipettes.clean_payload payload
24
25
  payload.merge! RageRender::ChapterDrop.new(chapter).to_liquid
25
26
  payload.merge! RageRender::ArchiveDrop.new(chapter).to_liquid
26
27
  end
@@ -73,21 +74,22 @@ module RageRender
73
74
  COVER_MAX_HEIGHT = 420
74
75
  COVER_MAX_WIDTH = 300
75
76
 
76
- PAGINATION_FIELDS = %w[ chaptername chapterdescription ]
77
+ PAGINATION_FIELDS = %w[ chaptername chapterdescription chapterid ]
77
78
 
78
79
  delegate_method_as :data, :fallback_data
79
80
  extend Pipettes
80
81
  extend Forwardable
81
82
 
82
83
  def_data_delegator :description, :chapterdescription
84
+ def_data_delegator :image, :image
83
85
  def_delegator :@obj, :url, :chapterarchiveurl
84
86
 
85
- def chaptername
86
- escape @obj.data['title']
87
+ def chapterid
88
+ @obj.collection.docs.index @obj
87
89
  end
88
90
 
89
- def cover
90
- cover_obj.url
91
+ def chaptername
92
+ escape @obj.data['title']
91
93
  end
92
94
 
93
95
  def cover_width_small
@@ -117,24 +119,16 @@ module RageRender
117
119
  end
118
120
 
119
121
  private
120
- def cover_width
121
- cover_obj.data['width'] ||= Dimensions.width cover_obj.path
122
- end
123
-
124
- def cover_height
125
- cover_obj.data['height'] ||= Dimensions.height cover_obj.path
126
- end
127
-
128
- def cover_obj
129
- @cover_obj ||= @obj.site.static_files.detect {|f| f.relative_path == cover_relative_path }
130
- end
131
-
132
- def cover_relative_path
133
- Pathname.new('/').join(@obj.data['image']).to_s
134
- end
122
+ def_image_metadata :image
123
+ private :image_url, :image_width, :image_height
135
124
 
136
125
  def first_comic
137
126
  @obj.site.collections['comics'].docs.select {|c| c.data['chapter'] == @obj.data['slug'] }.first
138
127
  end
128
+
129
+ public
130
+ alias cover image_url
131
+ alias cover_height image_height
132
+ alias cover_width image_width
139
133
  end
140
134
  end
@@ -1,16 +1,24 @@
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'
5
10
  require_relative 'pipettes'
6
11
 
7
12
  Jekyll::Hooks.register :comics, :pre_render do |page, payload|
13
+ RageRender::Pipettes.clean_payload payload
8
14
  payload.merge! RageRender::ComicDrop.new(page).to_liquid
9
15
  end
10
16
 
11
17
  module RageRender
12
18
  SPECIAL_COMIC_SLUGS = %w{frontpage index}
13
19
 
20
+ BASE_DIR = File.join(File.dirname(__FILE__), '..', '..', '..')
21
+
14
22
  # Creates comics for each file found in the 'images' directory
15
23
  # that does not already have an associated comic object.
16
24
  class ComicFromImageGenerator < Jekyll::Generator
@@ -41,9 +49,14 @@ module RageRender
41
49
  def generate site
42
50
  comics = site.collections['comics']
43
51
  index = comics.docs.last.dup
52
+ collection = comics.docs
53
+ if index.nil?
54
+ index = site.pages.detect {|p| p.data["title"] == "Comic not found" }.dup
55
+ collection = site.pages
56
+ end
44
57
  index.instance_variable_set(:"@data", index.data.dup)
45
58
  index.data['slug'] = 'index'
46
- comics.docs << index
59
+ collection << index
47
60
  end
48
61
  end
49
62
 
@@ -82,6 +95,7 @@ module RageRender
82
95
 
83
96
  delegate_method_as :id, :comicid
84
97
  def_delegator :@obj, :url, :comicurl
98
+ def_delegator :@obj, :url, :permalink
85
99
  data_delegator 'rating'
86
100
  data_delegator 'votecount'
87
101
 
@@ -89,10 +103,26 @@ module RageRender
89
103
  escape @obj.data['title']
90
104
  end
91
105
 
106
+ def comicnumber
107
+ 1 + all_comics.index(@obj)
108
+ end
109
+
110
+ def comicsnum
111
+ all_comics.size
112
+ end
113
+
92
114
  def posttime
93
115
  comicfury_date(@obj.date)
94
116
  end
95
117
 
118
+ def postyear
119
+ @obj.date.year
120
+ end
121
+
122
+ def postmonth
123
+ @obj.date.month
124
+ end
125
+
96
126
  def usechapters
97
127
  all_comics.any? {|comic| comic.data.include? 'chapter' }
98
128
  end
@@ -109,6 +139,7 @@ module RageRender
109
139
  chapter&.url
110
140
  end
111
141
 
142
+ def_loop :dropdown, :is_selected, :is_disabled, :title, :grouplabel, :newgroup, :endgroup, :url
112
143
  def dropdown
113
144
  all_comics.each_with_object([]) do |c, dropdown|
114
145
  new_group = dropdown.last.nil? ? true : dropdown.last['grouplabel'] != c.data['chapter']
@@ -141,6 +172,7 @@ module RageRender
141
172
  end
142
173
  end
143
174
 
175
+ def_loop :authornotes, :is_reply, :comment, :isguest, :avatar, :authorname, :commentanchor, :posttime, :profilelink
144
176
  def authornotes
145
177
  @obj.data['authornotes'] || [{
146
178
  'is_reply' => false,
@@ -176,10 +208,22 @@ module RageRender
176
208
  @obj.previous_doc&.url
177
209
  end
178
210
 
211
+ def prevcomicpermalink
212
+ unless @obj.previous_doc.nil?
213
+ URI.join(@obj.site.config["url"], @obj.site.baseurl || '/', @obj.previous_doc&.url).to_s
214
+ end
215
+ end
216
+
179
217
  def nextcomic
180
218
  @obj.next_doc&.url
181
219
  end
182
220
 
221
+ def nextcomicpermalink
222
+ unless @obj.next_doc.nil?
223
+ URI.join(@obj.site.config["url"], @obj.site.baseurl || '/', @obj.next_doc&.url).to_s
224
+ end
225
+ end
226
+
183
227
  # An HTML tag to print for the comic image. If there is a future image, then
184
228
  # this is also a link to the next comic page.
185
229
  def comicimage
@@ -195,6 +239,10 @@ module RageRender
195
239
  [linkopen, image, linkclose].join
196
240
  end
197
241
 
242
+ def keys
243
+ super.reject {|k| private_methods.include? k.to_sym }
244
+ end
245
+
198
246
  def to_liquid
199
247
  super.reject do |k, v|
200
248
  Jekyll::Drops::DocumentDrop::NESTED_OBJECT_FIELD_BLACKLIST.include? k
@@ -212,6 +260,7 @@ module RageRender
212
260
 
213
261
  data_delegator 'image'
214
262
  def_image_metadata :image
263
+ private :image, :image_url, :image_width, :image_height
215
264
 
216
265
  public
217
266
  alias comicimageurl image_url
@@ -2,6 +2,7 @@ require_relative 'pipettes'
2
2
 
3
3
  Jekyll::Hooks.register :pages, :pre_render do |page, payload|
4
4
  if page.data['layout'] == 'error-page'
5
+ RageRender::Pipettes.clean_payload payload
5
6
  payload.merge! RageRender::ErrorDrop.new(page).to_liquid
6
7
  end
7
8
  end
@@ -6,17 +6,23 @@ require_relative 'blog_archive'
6
6
  # Pass the right variables to overview pages.
7
7
  Jekyll::Hooks.register :pages, :pre_render do |page, payload|
8
8
  if page.data['layout'] == 'overview'
9
- payload.merge! RageRender::ComicDrop.new(page.site.collections['comics'].docs.last).to_liquid
9
+ RageRender::Pipettes.clean_payload payload
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
10
14
  payload.merge! RageRender::OverviewDrop.new(page).to_liquid
11
15
  end
12
16
  end
13
17
 
14
18
  module RageRender
15
19
  class OverviewDrop < Jekyll::Drops::Drop
20
+ extend Pipettes
16
21
  private delegate_method_as :data, :fallback_data
17
22
 
23
+ def_loop :latestblogs, *(RageRender::PaginatedBlogDrop.invokable_methods - Jekyll::Drops::DocumentDrop.invokable_methods)
18
24
  def latestblogs
19
- @obj.site.posts.docs[-5..].map {|post| RageRender::PaginatedBlogDrop.new(post) }
25
+ @obj.site.posts.docs[-5..]&.map {|post| RageRender::PaginatedBlogDrop.new(post) } || []
20
26
  end
21
27
  end
22
28
  end
@@ -1,14 +1,30 @@
1
1
  # Pipettes help you make drops.
2
2
  require 'cgi'
3
+ require 'dimensions'
3
4
 
4
5
  module RageRender
5
6
  module Pipettes
7
+ def self.clean_payload payload
8
+ Jekyll.logger.debug("Cleaning payload")
9
+ sets = Jekyll::Drops::DocumentDrop.subclasses.map(&:invokable_methods)
10
+ methods = sets.reduce(Set.new) {|s,acc| acc.merge(s)} - Set.new(Jekyll::Drops::DocumentDrop.invokable_methods)
11
+ payload.send(:fallback_data).delete_if {|k| methods.include? k}
12
+ end
13
+
6
14
  def def_data_delegator key, aliaz
7
15
  define_method(aliaz.to_sym) do
8
16
  @obj.data[key.to_s]
9
17
  end
10
18
  end
11
19
 
20
+ def def_loop method, *fields
21
+ (@loops ||= {})[method.to_sym] = fields
22
+ end
23
+
24
+ def loops
25
+ @loops || {}
26
+ end
27
+
12
28
  def self.extended mod
13
29
  mod.define_method(:escape) do |str|
14
30
  str.nil? ? nil : CGI.escapeHTML(str)
@@ -25,6 +41,7 @@ module RageRender
25
41
  define_method(:"#{prefix}_relative_path") do
26
42
  Pathname.new('/').join(send(prefix.to_sym)).to_path
27
43
  end
44
+ private :"#{prefix}_relative_path"
28
45
 
29
46
  define_method(:"#{prefix}_url") do
30
47
  File.join (@obj.site.baseurl || ''), send(:"#{prefix}_relative_path")
@@ -36,17 +53,19 @@ module RageRender
36
53
  end
37
54
  instance_variable_get(:"@#{prefix}_obj")
38
55
  end
56
+ private :"#{prefix}_obj"
39
57
 
40
58
  define_method(:"#{prefix}_path") do
41
59
  send(:"#{prefix}_obj").path
42
60
  end
61
+ private :"#{prefix}_path"
43
62
 
44
63
  define_method(:"#{prefix}_width") do
45
- send(:"#{prefix}_obj").data['width'] ||= Dimensions.width(send(:"#{prefix}_path")) rescue nil
64
+ send(:"#{prefix}_obj") && (send(:"#{prefix}_obj").data['width'] ||= Dimensions.width(send(:"#{prefix}_path")) rescue nil)
46
65
  end
47
66
 
48
67
  define_method(:"#{prefix}_height") do
49
- send(:"#{prefix}_obj").data['height'] ||= Dimensions.height(send(:"#{prefix}_path")) rescue nil
68
+ send(:"#{prefix}_obj") && (send(:"#{prefix}_obj").data['height'] ||= Dimensions.height(send(:"#{prefix}_path")) rescue nil)
50
69
  end
51
70
  end
52
71
  end
@@ -1,11 +1,14 @@
1
1
  Jekyll::Hooks.register :pages, :pre_render do |page, payload|
2
2
  if page.data['layout'] == 'search'
3
+ RageRender::Pipettes.clean_payload payload
3
4
  payload.merge! RageRender::SearchDrop.new(page).to_liquid
4
5
  end
5
6
  end
6
7
 
7
8
  module RageRender
8
9
  class SearchDrop < Jekyll::Drops::Drop
10
+ extend Pipettes
11
+
9
12
  private delegate_method_as :data, :fallback_data
10
13
  data_delegator 'searchterm'
11
14
 
@@ -13,6 +16,7 @@ module RageRender
13
16
  !searchterm.nil?
14
17
  end
15
18
 
19
+ def_loop :searchresults, :number, *ComicDrop::PAGINATION_FIELDS
16
20
  def searchresults
17
21
  return [] unless searched
18
22
  @results ||= @obj.site.collections['comics'].docs.select do |comic|
@@ -1,11 +1,15 @@
1
1
  require 'etc'
2
2
  require 'stringio'
3
- require 'jekyll'
4
- require 'dimensions'
3
+ require 'jekyll/command'
4
+ require 'jekyll/hooks'
5
+ require 'jekyll/plugin'
6
+ require 'jekyll/generator'
7
+ require 'liquid/drop'
5
8
  require_relative 'language'
6
9
  require_relative 'functions'
7
10
  require_relative 'to_liquid'
8
11
  require_relative 'date_formats'
12
+ require_relative 'cflxml'
9
13
  require_relative 'jekyll/archive'
10
14
  require_relative 'jekyll/blog'
11
15
  require_relative 'jekyll/blog_archive'
@@ -17,6 +21,39 @@ require_relative 'jekyll/search'
17
21
  require_relative 'jekyll/pipettes'
18
22
  require_relative 'jekyll/setup_collection'
19
23
 
24
+ module RageRender
25
+ class CFLXMLCommands < Jekyll::Command
26
+ class << self
27
+ def init_with_program(jekyll)
28
+ jekyll.command :pack do |pack|
29
+ pack.syntax 'pack'
30
+ pack.description 'Create a ComicFury layout backup from this site'
31
+ pack.action do |args, options|
32
+ config = configuration_from_options(options)
33
+ filename = "#{(config['title'] || File.basename(config['source']))} #{Time.now.to_s}.cflxml"
34
+ puts "Outputting backup file #{filename}"
35
+ File.open(filename, 'w') do |file|
36
+ RageRender.pack config['layouts_dir'], config['source'], file
37
+ end
38
+ end
39
+ end
40
+
41
+ jekyll.command :unpack do |unpack|
42
+ unpack.syntax 'unpack <file>'
43
+ unpack.description 'Overwrite HTML and CSS from a ComicFury layout backup'
44
+ unpack.action do |args, options|
45
+ raise ArgumentError, 'unpack requires one cflxml file' unless args.one?
46
+ config = configuration_from_options(options)
47
+ File.open(args.first, 'r') do |file|
48
+ RageRender.unpack file, config['layouts_dir'], config['source']
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+
20
57
  Jekyll::Hooks.register :site, :after_init do |site|
21
58
  # This is obviously quite naughty for many reasons,
22
59
  # but it's the only way to get the theme selected
@@ -24,6 +61,7 @@ Jekyll::Hooks.register :site, :after_init do |site|
24
61
  site.config['theme'] ||= 'ragerender'
25
62
  site.config['title'] ||= File.basename(site.source)
26
63
  site.config['search'] ||= true
64
+ site.config['url'] ||= "https://#{File.basename(site.source)}.thecomicseries.com"
27
65
  site.config = site.config
28
66
 
29
67
  setup_collection site, :comics, '/:collection/:slug/', layout: 'comic-page', chapter: '0'
@@ -72,6 +110,10 @@ class RageRender::FrontpageGenerator < Jekyll::Generator
72
110
  collection = site.pages
73
111
  site.pages.detect {|p| p.data["slug"] == frontpage }
74
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
75
117
  index.instance_variable_set(:"@data", index.data.dup)
76
118
  index.data['permalink'] = '/index.html'
77
119
  index.data['slug'] = 'frontpage'
@@ -84,6 +126,7 @@ Jekyll::Hooks.register :documents, :pre_render do |doc, payload|
84
126
  end
85
127
 
86
128
  Jekyll::Hooks.register :pages, :pre_render do |page, payload|
129
+ RageRender::Pipettes.clean_payload payload
87
130
  payload.merge! RageRender::WebcomicDrop.new(page).to_liquid
88
131
  end
89
132
 
@@ -108,6 +151,20 @@ class RageRender::WebcomicDrop < Jekyll::Drops::Drop
108
151
  escape @obj.site.config['description']
109
152
  end
110
153
 
154
+ def_loop :webcomicgenres, :genre_link, :genre_name
155
+ def webcomicgenres
156
+ (@obj.site.config['genres'] || []).map do |g|
157
+ {
158
+ 'genre_name' => escape(g),
159
+ 'genre_link' => "https://comicfury.com/search.php?vr=1&query=&tags=#{g.downcase.gsub(/[^a-z]/, '')}"
160
+ }
161
+ end
162
+ end
163
+
164
+ def webcomicgenre
165
+ (webcomicgenres.first || {})['genre_name']
166
+ end
167
+
111
168
  def webcomicurl
112
169
  @obj.site.baseurl
113
170
  end
@@ -117,7 +174,7 @@ class RageRender::WebcomicDrop < Jekyll::Drops::Drop
117
174
  end
118
175
 
119
176
  def copyrights
120
- escape @obj.site.config['copyrights'].gsub('[year]', Date.today.year.to_s)
177
+ escape @obj.site.config.fetch('copyrights', '').gsub('[year]', Date.today.year.to_s)
121
178
  end
122
179
 
123
180
  def banner
@@ -141,6 +198,7 @@ class RageRender::WebcomicDrop < Jekyll::Drops::Drop
141
198
  false
142
199
  end
143
200
 
201
+ def_loop :extrapages, :link, :title
144
202
  def extrapages
145
203
  @obj.site.pages.reject {|page| page.data['hidden'] }.map do |page|
146
204
  {'link' => page.url, 'title' => escape(page.data['title'])}
@@ -160,6 +218,7 @@ class RageRender::WebcomicDrop < Jekyll::Drops::Drop
160
218
  css_files << Pathname.new(@obj.site.theme.includes_path).join('layout.css') unless css_files.any?
161
219
  css_files.map {|f| File.read f }.join
162
220
  end
221
+ private :css
163
222
 
164
223
  def layoutcss
165
224
  <<~HTML
@@ -169,8 +228,6 @@ class RageRender::WebcomicDrop < Jekyll::Drops::Drop
169
228
  HTML
170
229
  end
171
230
 
172
- delegate_method_as :url, :permalink
173
-
174
231
  def pagetitle
175
232
  escape @obj.data['title']
176
233
  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.5
4
+ version: 0.1.7
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-06 00:00:00.000000000 Z
11
+ date: 2026-02-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rsec
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rexml
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: rake
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -127,7 +141,7 @@ description: |-
127
141
  mkdir mycomic && cd mycomic
128
142
  bundle init
129
143
  bundle add jekyll
130
- bundle add ragerender
144
+ bundle add ragerender --group=jekyll_plugins
131
145
 
132
146
  Now you can add comics! Add the image into an <tt>images</tt> folder:
133
147
 
@@ -170,6 +184,9 @@ description: |-
170
184
  description: >
171
185
  My epic story about how him and her
172
186
  fell into a romantic polycule with they and them
187
+ genres:
188
+ - Comedy
189
+ - Romance
173
190
 
174
191
  defaults:
175
192
  - scope:
@@ -185,8 +202,15 @@ description: |-
185
202
 
186
203
  If you want to use your own layout code, then create a <tt>_layouts</tt>
187
204
  directory and put the contents of each of your ComicFury layout tabs in there,
188
- and then put your CSS in the main folder. You should end up with a full set of
189
- files like:
205
+ and then put your CSS in the main folder.
206
+
207
+ The easiest way is to go to your Webcomic Management, click "Edit Layout", then
208
+ in the box labelled "Useful", click "Download Layout Backup". Pass this file to
209
+ RageRender, which will <tt>unpack</tt> it for you:
210
+
211
+ bundle exec jekyll unpack mycomic-2025-09-13.cflxml
212
+
213
+ You should end up with a full set of files like:
190
214
 
191
215
  _layouts
192
216
  archive.html
@@ -204,7 +228,7 @@ description: |-
204
228
 
205
229
  === Adding blogs
206
230
 
207
- Add your blogs into a folder called `_posts`:
231
+ Add your blogs into a folder called <tt>_posts</tt>:
208
232
 
209
233
  cat _posts/2025-05-29-my-new-comic.md
210
234
  Hey guys, welcome to my new comic! It's gonna be so sick!
@@ -271,6 +295,16 @@ description: |-
271
295
  - anything else will display the extra page that has the matching
272
296
  <tt>slug</tt> in its Front Matter
273
297
 
298
+ === Putting changes on ComicFury
299
+
300
+ Once you're done making changes, you can <tt>pack</tt> your layout:
301
+
302
+ bundle exec jekyll pack
303
+
304
+ The resulting file can be uploaded to ComicFury by going to your Webcomic
305
+ Management, clicking "Edit Layout", then in the box labelled "Useful", click
306
+ "Restore Layout Backup".
307
+
274
308
  === Stuff that doesn't work
275
309
 
276
310
  Here is a probably incomplete list of things you can expect to be different
@@ -319,9 +353,11 @@ files:
319
353
  - assets/archive-comics.html
320
354
  - assets/archive.html
321
355
  - assets/blog.html
356
+ - assets/comic-not-found.html
322
357
  - assets/overview.html
323
358
  - assets/search.html
324
359
  - lib/ragerender.rb
360
+ - lib/ragerender/cflxml.rb
325
361
  - lib/ragerender/date_formats.rb
326
362
  - lib/ragerender/functions.rb
327
363
  - lib/ragerender/jekyll.rb