jekyll-paginate-content 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "jekyll/paginate/content"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,28 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "jekyll/paginate/content/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "jekyll-paginate-content"
8
+ spec.version = Jekyll::Paginate::Content::VERSION
9
+ spec.required_ruby_version = '>= 2.0.0'
10
+ spec.authors = ["Alex Ibrado"]
11
+ spec.email = ["alex@ibrado.org"]
12
+
13
+ spec.summary = %q{Jekyll::Paginate::Content: Easily split Jekyll pages, posts, etc. into multiple URLs}
14
+ spec.description = %q{Jekyll::Paginate::Content splits pages and posts (and other collections/content) into multiple parts/URLs automatically via h1-h6 headers, or manually by inserting something like <!--page--> where you want page breaks. Features: Automatic content splitting into several pages, single-page view, configurable permalinks, page trail/pager, SEO support, self-adjusting internal links, multipage-aware Table Of Contents.}
15
+ spec.homepage = "https://github.com/ibrado/jekyll-paginate-content"
16
+ spec.license = "MIT"
17
+
18
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
+ f.match(%r{^(test|spec|features)/})
20
+ end
21
+ spec.bindir = "exe"
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ["lib"]
24
+
25
+ spec.add_runtime_dependency "jekyll", "~> 3.0"
26
+
27
+ spec.add_development_dependency "bundler", "~> 1.16"
28
+ end
@@ -0,0 +1,795 @@
1
+ require "jekyll/paginate/content/version"
2
+
3
+ module Jekyll
4
+ module Paginate::Content
5
+
6
+ class Generator < Jekyll::Generator
7
+ safe true
8
+
9
+ def generate(site)
10
+ start_time = Time.now
11
+
12
+ sconfig = site.config['paginate_content'] || {}
13
+
14
+ return unless sconfig["enabled"].nil? || sconfig["enabled"]
15
+
16
+ @debug = sconfig["debug"]
17
+
18
+ sconfig['collection'] = sconfig['collection'].split(/,\s*/) if sconfig['collection'].is_a?(String)
19
+
20
+ collections = [ sconfig['collection'], sconfig["collections"] ].flatten.compact.uniq;
21
+ collections = [ "posts", "pages" ] if collections.empty?
22
+
23
+ # Use this hash syntax to facilite merging _config.yml overrides
24
+ properties = {
25
+ 'all' => {
26
+ 'autogen' => 'jekyll-paginate-content',
27
+ 'hidden' => true,
28
+ 'tag' => nil,
29
+ 'tags' => nil,
30
+ 'category' => nil,
31
+ 'categories'=> nil
32
+ },
33
+
34
+ 'first' => {
35
+ 'hidden' => false,
36
+ 'tag' => '$',
37
+ 'tags' => '$',
38
+ 'category' => '$',
39
+ 'categories'=> '$'
40
+ },
41
+
42
+ 'part' => {},
43
+
44
+ 'last' => {},
45
+
46
+ 'single' => {}
47
+ }
48
+
49
+ base_url = (sconfig['prepend_baseurl'].nil? || sconfig['prepend_baseurl']) ? site.config['baseurl'] : ''
50
+
51
+ @config = {
52
+ :collections => collections,
53
+ :title => sconfig['title'],
54
+ :permalink => sconfig['permalink'] || '/:num/',
55
+ :trail => sconfig['trail'] || {},
56
+ :auto => sconfig['auto'],
57
+ :base_url => base_url,
58
+
59
+ :separator => sconfig['separator'] || '<!--page-->',
60
+ :header => sconfig['header'] || '<!--page_header-->',
61
+ :footer => sconfig['footer'] || '<!--page_footer-->',
62
+
63
+ :single_page => sconfig['single_page'] || '/view-all/',
64
+ :seo_canonical => sconfig['seo_canonical'].nil? || sconfig['seo_canonical'],
65
+ :toc_exclude => sconfig['toc_exclude'],
66
+
67
+ :properties => properties,
68
+ :user_props => sconfig['properties'] || {}
69
+ }
70
+
71
+ # Run through each specified collection
72
+
73
+ collections.each do |collection|
74
+ if collection == "pages"
75
+ items = site.pages
76
+ else
77
+ next if !site.collections.has_key?(collection)
78
+ items = site.collections[collection].docs
79
+ end
80
+
81
+ new_items = []
82
+ old_items = []
83
+
84
+ total_parts = 0
85
+ total_copies = 0
86
+
87
+ if @config[:auto]
88
+ if m = /^h(\d)/i.match(@config[:separator])
89
+ process = items.select { |item| /(\n|)(<h#{m[1]}|-{4,}|={4,})/.match?(item.content) }
90
+ else
91
+ process = items.select { |item| item.content.include?(@config[:separator]) }
92
+ end
93
+ else
94
+ process = items.select { |item| item.data['paginate'] }
95
+ end
96
+
97
+ process.each do |item|
98
+ pager = Paginator.new(site, collection, item, @config)
99
+ next if pager.items.empty?
100
+
101
+ debug "[#{collection}] \"#{item.data['title']}\", #{pager.items.length-1}+1 pages"
102
+ total_parts += pager.items.length-1;
103
+ total_copies += 1
104
+ new_items << pager.items
105
+ old_items << item
106
+ end
107
+
108
+ if !new_items.empty?
109
+ # Remove the old items at the original URLs
110
+ old_items.each do |item|
111
+ items.delete(item)
112
+ end
113
+
114
+ # Add the new items in
115
+ new_items.flatten!.each do |new_item|
116
+ items << new_item
117
+ end
118
+
119
+ info "[#{collection}] Generated #{total_parts}+#{total_copies} pages"
120
+ end
121
+ end
122
+ runtime = "%.6f" % (Time.now - start_time).to_f
123
+ debug "Runtime: #{runtime}s"
124
+ end
125
+
126
+ private
127
+ def info(msg)
128
+ Jekyll.logger.info "PaginateContent:", msg
129
+ end
130
+
131
+ def warn(msg)
132
+ Jekyll.logger.warn "PaginateContent:", msg
133
+ end
134
+
135
+ def debug(msg)
136
+ Jekyll.logger.warn "PaginateContent:", msg if @debug
137
+ end
138
+ end
139
+
140
+ class Document < Jekyll::Document
141
+ attr_accessor :pager
142
+
143
+ def initialize(orig_doc, site, collection)
144
+ super(orig_doc.path, { :site => site,
145
+ :collection => site.collections[collection]})
146
+ self.merge_data!(orig_doc.data)
147
+ end
148
+
149
+ def data
150
+ @data ||= {}
151
+ end
152
+
153
+ end
154
+
155
+ class Page < Jekyll::Page
156
+ def initialize(orig_page, site, dirname, filename)
157
+ @site = site
158
+ @base = site.source
159
+ @dir = dirname
160
+ @name = filename
161
+
162
+ self.process(filename)
163
+ self.data ||= {}
164
+ self.data.merge!(orig_page.data)
165
+ end
166
+ end
167
+
168
+ class Pager
169
+ attr_accessor :activated, :first_page, :first_page_path,
170
+ :first_path, :has_next, :has_prev, :has_previous,
171
+ :is_first, :is_last, :last_page, :last_page_path,
172
+ :last_path, :next_is_last, :next_page, :next_page_path,
173
+ :next_path, :next_section, :page, :page_num, :page_path,
174
+ :page_trail, :pages, :paginated, :previous_is_first,
175
+ :prev_is_first, :previous_page, :prev_page, :previous_page_path,
176
+ :previous_path, :prev_path, :prev_section, :previous_section,
177
+ :section, :seo, :single_page, :toc, :total_pages, :view_all
178
+
179
+ def initialize(data)
180
+ data.each do |k,v|
181
+ instance_variable_set("@#{k}", v) if self.respond_to? k
182
+ end
183
+ end
184
+
185
+ def to_liquid
186
+ {
187
+ # Based on sverrir's jpv2
188
+ 'first_page' => first_page,
189
+ 'first_page_path' => first_page_path,
190
+ 'last_page' => last_page,
191
+ 'last_page_path' => last_page_path,
192
+ 'next_page' => next_page,
193
+ 'next_page_path' => next_page_path,
194
+ 'page' => page_num,
195
+ 'page_path' => page_path,
196
+ 'page_trail' => page_trail,
197
+ 'previous_page' => previous_page,
198
+ 'previous_page_path' => previous_page_path,
199
+ 'total_pages' => total_pages, # parts of the original page
200
+
201
+ # New stuff
202
+ 'has_next' => has_next,
203
+ 'has_previous' => has_previous,
204
+ 'is_first' => is_first,
205
+ 'is_last' => is_last,
206
+ 'next_is_last' => next_is_last,
207
+ 'previous_is_first' => previous_is_first,
208
+ 'paginated' => paginated,
209
+ 'seo' => seo,
210
+ 'single_page' => single_page,
211
+ 'section' => section,
212
+ 'toc' => toc,
213
+ 'next_section' => next_section,
214
+ 'previous_section' => previous_section,
215
+
216
+ # Aliases
217
+ 'activated' => paginated,
218
+ 'first_path' => first_page_path,
219
+ 'next_path' => next_page_path,
220
+ 'has_prev' => has_previous,
221
+ 'previous_path' => previous_page_path,
222
+ 'prev_path' => previous_page_path,
223
+ 'last_path' => last_page_path,
224
+ 'prev_page' => previous_page,
225
+ 'prev_is_first' => previous_is_first,
226
+ 'prev_section' => previous_section,
227
+ 'page_num' => page_num,
228
+ 'pages' => total_pages,
229
+ 'view_all' => single_page
230
+ }
231
+ end
232
+ end
233
+
234
+ class Paginator
235
+ def initialize(site, collection, item, config)
236
+ @site = site
237
+ @collection = collection
238
+
239
+ final_config = {}.merge(config)
240
+ if item.data.has_key?('paginate_content')
241
+ item.data['paginate_content'].each do |k,v|
242
+ s = k.downcase.strip.to_sym
243
+ final_config[s] = v
244
+ end
245
+ end
246
+
247
+ @config = final_config
248
+
249
+ @items = []
250
+ self.split(item)
251
+ end
252
+
253
+ def items
254
+ @items
255
+ end
256
+
257
+ def split(item)
258
+ sep = @config[:separator].downcase.strip
259
+
260
+ # Update the header IDs the original document
261
+ content = item.content
262
+
263
+ # Escape special characters inside code blocks
264
+ content.scan(/(```|~~~+)(.*?)\1/m).each do |e|
265
+ escaped = e[1].gsub(/([#<\-=])/, '~|\1|')
266
+ content.gsub!(e[1], escaped)
267
+ end
268
+
269
+ # Generate TOC
270
+ toc = ""
271
+
272
+ seen_anchors = {}
273
+ list_chars = ['-','*','+']
274
+
275
+ if m = /^h([1-6])$/.match(sep)
276
+ base_level = m[1].to_i - 1
277
+ else
278
+ base_level = 5
279
+ end
280
+
281
+ lowest_level = 5
282
+
283
+ # TODO: Optimize this regex
284
+ content.scan(/(^|\r?\n)((#+)\s*([^\r\n#]+)#*\r?\n|([^\r\n]+)\r?\n(=+|\-{4,})\s*\r?\n|<h([1-6])[^>]*>([^\r\n<]+)(\s*<\/h\7>))/mi).each do |m|
285
+ header = m[3] || m[4] || m[7]
286
+
287
+ next if @config[:toc_exclude] && @config[:toc_exclude].include?(header)
288
+
289
+ markup = m[1].strip
290
+
291
+ # Level is 0-based for convenience
292
+ if m[3]
293
+ level = m[2].length - 1
294
+ elsif m[4]
295
+ level = m[5][0] == '=' ? 0 : 1
296
+ elsif m[7]
297
+ level = m[6].to_i - 1
298
+ end
299
+
300
+ lowest_level = [level, lowest_level].min
301
+
302
+ orig_anchor = anchor = header.downcase.gsub(/[[:punct:]]/, '').gsub(/\s+/, '-')
303
+
304
+ ctr = 1
305
+ while seen_anchors[anchor]
306
+ anchor = "#{orig_anchor}-#{ctr}"
307
+ ctr += 1
308
+ end
309
+ seen_anchors[anchor] = 1
310
+
311
+ # Escape the header so we don't match again
312
+ # for the same header text in a different location
313
+ escaped = Regexp.escape(markup)
314
+ markup = "$$_#{markup}_$$"
315
+
316
+ content.sub!(/#{escaped}\s*(?=#|\r?\n)/, "#{markup}#{$/}{: id=\"#{anchor}\"}#{$/}")
317
+
318
+ # Markdown indent
319
+ char = list_chars[level % 3]
320
+ indent = ' ' * level
321
+ toc << "#{indent}#{char} [#{header}](##{anchor})#{$/}"
322
+ end
323
+
324
+ if lowest_level > 0
325
+ excess = ' ' * lowest_level
326
+ toc.gsub!(/^#{excess}/, '')
327
+ end
328
+
329
+ # Restore original header text
330
+ content.gsub!(/\$\$_(.*?)_\$\$/m, '\1')
331
+
332
+ @toc = toc.empty? ? nil : toc
333
+
334
+ # Handle splitting by headers, h1-h6
335
+ if m = /^h([1-6])$/.match(sep)
336
+ # Split on <h2> etc.
337
+
338
+ level = m[1].to_i
339
+
340
+ init_pages = []
341
+
342
+ # atx syntax: Prefixed by one or more '#'
343
+ atx = "#" * level
344
+ atx_parts = content.split(/(?=^#{atx} )/)
345
+
346
+ # HTML symtax <h1> to <h6>
347
+ htx_parts = []
348
+ atx_parts.each do |section|
349
+ htx_parts << section.split(/(?=<#{sep}[^>]*>)/i)
350
+ end
351
+ htx_parts.flatten!
352
+
353
+ if level <= 2
354
+ # Setext syntax: underlined by '=' (h1) or '-' (h2)
355
+ # For now require four '-' to avoid confusion with <hr>
356
+ # or demo YAML front-matter
357
+ stx = level == 1 ? "=" : '-' * 4
358
+ htx_parts.each do |section|
359
+ init_pages << section.split(/(?=^.+\n#{stx}+$)/)
360
+ end
361
+
362
+ else
363
+ init_pages = htx_parts
364
+ end
365
+
366
+ init_pages.flatten!
367
+ else
368
+ init_pages = content.split(sep)
369
+ end
370
+
371
+ return if init_pages.length == 1
372
+
373
+ # Unescape special characters inside code blocks, for main content
374
+ # Main content was modified by adding header IDs
375
+ content.gsub!(/~\|(.)\|/, '\1')
376
+
377
+ # Make page length the minimum, if specified
378
+ if @config[:minimum]
379
+ pages = []
380
+ init_pages.each do |page_content|
381
+ i = pages.empty? ? 0 : pages.length - 1
382
+ if !pages[i] || pages[i].length < @config[:minimum]
383
+ pages[i] ||= ""
384
+ pages[i] << page_content
385
+ else
386
+ pages << page_content
387
+ i += 1
388
+ end
389
+ end
390
+
391
+ else
392
+ pages = init_pages
393
+ end
394
+
395
+ page_header = pages[0].split(@config[:header])
396
+ pages[0] = page_header[1] || page_header[0]
397
+ header = page_header[1] ? page_header[0] : ''
398
+
399
+ page_footer = pages[-1].split(@config[:footer])
400
+ pages[-1] = page_footer[0]
401
+ footer = page_footer[1] || ''
402
+
403
+ new_items = []
404
+ page_data = {}
405
+
406
+ dirname = ""
407
+ filename = ""
408
+
409
+ # For SEO; 'canonical' is a personal override ;-)
410
+ site_url = (@site.config['canonical'] || @site.config['url']) + @site.config['baseurl']
411
+ site_url.gsub!(/\/$/, '')
412
+
413
+ # For the permalink
414
+ base = item.url
415
+
416
+ user_props = @config[:user_props]
417
+
418
+ first_page_path = ''
419
+ total_pages = 0
420
+ single_page = ''
421
+ id = ("%10.9f" % Time.now.to_f).to_s
422
+
423
+ num = 1
424
+ max = pages.length
425
+
426
+ # Find the anchors/targets
427
+ a_locations = {}
428
+ i = 1
429
+ pages.each do |page|
430
+ # TODO: Optimize this regex
431
+ page.scan(/<a\s+name=['"](\S+)['"]>[^<]*<\/a>|<[^>]*id=['"](\S+)['"][^>]*>|{:.*id=['"](\S+)['"][^}]*}/i).each do |a|
432
+ anchor = a[0] || a[1] || a[2]
433
+ a_locations[anchor] = i
434
+ end
435
+ i += 1
436
+ end
437
+
438
+ ######################################## Main processing
439
+
440
+ pages.each do |page|
441
+ # Unescape special characters inside code blocks, for pages
442
+ page.gsub!(/~\|(.)\|/, '\1')
443
+
444
+ plink_all = nil
445
+ plink_next = nil
446
+ plink_prev = nil
447
+
448
+ paginator = {}
449
+
450
+ first = num == 1
451
+ last = num == max
452
+
453
+ if m = base.match(/(.*\/[^\.]*)(\.[^\.]+)$/)
454
+ # /.../filename.ext
455
+ plink = _permalink(m[1], num, max)
456
+ plink_all = m[1] + @config[:single_page]
457
+ plink_prev = _permalink(m[1], num-1, max) if !first
458
+ plink_next = _permalink(m[1],num+1, max) if !last
459
+ else
460
+ # /.../folder/
461
+ plink = _permalink(base, num, max)
462
+ plink_all = base + @config[:single_page]
463
+ plink_prev = _permalink(base, num-1, max) if !first
464
+ plink_next = _permalink(base, num+1, max) if !last
465
+ end
466
+
467
+ plink_all.gsub!(/\/\//,'/')
468
+
469
+ # TODO: Put these in classes
470
+
471
+ if @collection == "pages"
472
+ if first
473
+ # Keep the info of the original page to avoid warnings
474
+ # while creating the new virtual pages
475
+ dirname = File.dirname(plink)
476
+ filename = item.name
477
+ page_data = item.data
478
+ end
479
+
480
+ paginator.merge!(page_data)
481
+ new_part = Page.new(item, @site, dirname, filename)
482
+ else
483
+ new_part = Document.new(item, @site, @collection)
484
+ end
485
+
486
+ # Find the section names from the first h1 etc.
487
+ # TODO: Simplify/merge regex
488
+ candidates = {}
489
+ if m = /(.*\r?\n|)#+\s+(.*)\s*#*/.match(page)
490
+ candidates[m[2]] = m[1].length
491
+ end
492
+
493
+ if m = /(.*\r?\n|)([^\r\n]+)\r?\n(=+|\-{4,})\s*\r?\n/.match(page)
494
+ candidates[m[2]] = m[1].length
495
+ end
496
+
497
+ if m = /<h([1-6])[^>]*>\s*([^\r\n<]+)(\s*<\/h\1)/mi.match(page)
498
+ candidates[m[2]] = m[1].length
499
+ end
500
+
501
+ if candidates.empty?
502
+ section = "Untitled"
503
+ else
504
+ section = candidates.sort_by { |k,v| v }.first.flatten[0]
505
+ end
506
+
507
+ paginator['section'] = section
508
+
509
+ paginator['paginated'] = true
510
+ paginator['page_num'] = num
511
+ paginator['page_path'] = @config[:base_url] + _permalink(base, num, max)
512
+
513
+ paginator['first_page'] = 1
514
+ paginator['first_page_path'] = @config[:base_url] + base
515
+
516
+ paginator['last_page'] = pages.length
517
+ paginator['last_page_path'] = @config[:base_url] + _permalink(base, max, max)
518
+
519
+ paginator['total_pages'] = max
520
+
521
+ paginator['single_page'] = @config[:base_url] + plink_all
522
+
523
+ if first
524
+ paginator['is_first'] = true
525
+ first_page_path = @config[:base_url] + base
526
+ total_pages = max
527
+ single_page = plink_all
528
+ else
529
+ paginator['previous_page'] = num - 1
530
+ paginator['previous_page_path'] = @config[:base_url] + plink_prev
531
+ end
532
+
533
+ if last
534
+ paginator['is_last'] = true
535
+ else
536
+ paginator['next_page'] = num + 1
537
+ paginator['next_page_path'] = @config[:base_url] + plink_next
538
+ end
539
+
540
+ paginator['previous_is_first'] = (num == 2)
541
+ paginator['next_is_last'] = (num == max - 1)
542
+
543
+ paginator['has_previous'] = (num >= 2)
544
+ paginator['has_next'] = (num < max)
545
+
546
+ seo = {}
547
+ seo['canonical'] = _seo('canonical', site_url + plink_all) if @config[:seo_canonical];
548
+ seo['prev'] = _seo('prev', site_url + plink_prev) if plink_prev
549
+ seo['next'] = _seo('next', site_url + plink_next) if plink_next
550
+ seo['links'] = seo.map {|k,v| v }.join($/)
551
+
552
+ paginator['seo'] = seo
553
+
554
+ # Set the paginator
555
+ new_part.pager = Pager.new(paginator)
556
+
557
+ # Set up the frontmatter properties
558
+ _set_properties(item, new_part, 'all', user_props)
559
+ _set_properties(item, new_part, 'first', user_props) if first
560
+ _set_properties(item, new_part, 'last', user_props) if last
561
+ _set_properties(item, new_part, 'part', user_props) if !first && !last
562
+
563
+ # Don't allow these to be overriden,
564
+ # i.e. set/reset layout, date, title, permalink
565
+
566
+ new_part.data['layout'] = item.data['layout']
567
+ new_part.data['date'] = item.data['date']
568
+ new_part.data['permalink'] = plink
569
+
570
+ # title is set together with trail below as it may rely on section name
571
+
572
+ new_part.data['pagination_info'] =
573
+ {
574
+ 'curr_page' => num,
575
+ 'total_pages' => max,
576
+ 'type' => first ? 'first' : ( last ? 'last' : 'part'),
577
+ 'id' => id
578
+ }
579
+
580
+ new_part.content = header + page + footer
581
+
582
+ new_items << new_part
583
+
584
+ num += 1
585
+ end
586
+
587
+ t_config = @config[:trail]
588
+ t_config[:title] = @config[:title]
589
+
590
+ # Replace #target with page_path#target
591
+ i = 0
592
+ new_items.each do |item|
593
+ content = item.content
594
+
595
+ _adjust_links(new_items, item.content, a_locations, i+1)
596
+
597
+ # Adjust the TOC relative to this page
598
+ if @toc
599
+ toc = @toc.dup
600
+ _adjust_links(new_items, toc, a_locations, i+1)
601
+ else
602
+ toc = nil
603
+ end
604
+
605
+ item.pager.toc = { 'simple' => toc }
606
+
607
+ item.pager.page_trail = _page_trail(@config[:base_url] + base, new_items, i+1,
608
+ new_items.length, t_config)
609
+
610
+ # Previous/next section name assignments
611
+ item.pager.previous_section = new_items[i-1].pager.section if i > 0
612
+ item.pager.next_section = new_items[i+1].pager.section if i < new_items.length - 1
613
+
614
+ i += 1
615
+ end
616
+
617
+ # This is in another loop to avoid messing with the titles
618
+ # during page trail generation
619
+ i = 1
620
+ new_items.each do |item|
621
+ item.data['title'] =
622
+ _title(@config[:title], new_items, i, new_items.length,
623
+ @config[:retitle_first])
624
+ i += 1
625
+ end
626
+
627
+ # Setup single-page view
628
+
629
+ if !@config[:single_page].empty?
630
+ if @collection == "pages"
631
+ single = Page.new(item, @site, dirname, item.name)
632
+ else
633
+ single = Document.new(item, @site, @collection)
634
+ end
635
+
636
+ _set_properties(item, single, 'all', user_props)
637
+ _set_properties(item, single, 'single', user_props)
638
+
639
+ single.data['pagination_info'] = {
640
+ 'type' => 'single',
641
+ 'id' => id
642
+ }
643
+
644
+ # Restore original properties for these
645
+ single.data['permalink'] = single_page
646
+ single.data['layout'] = item.data['layout']
647
+ single.data['date'] = item.data['date']
648
+ single.data['title'] = item.data['title']
649
+
650
+ # Just some limited data for the single page
651
+ seo = @config[:seo_canonical] ?
652
+ _seo('canonical', site_url + single_page) : ""
653
+
654
+ single_paginator = {
655
+ 'first_page_path' => first_page_path,
656
+ 'total_pages' => total_pages,
657
+ 'toc' => {
658
+ 'simple' => @toc
659
+ },
660
+ 'seo' => {
661
+ 'links' => seo,
662
+ 'canonical' => seo
663
+ }
664
+ }
665
+
666
+ single.pager = Pager.new(single_paginator)
667
+ single.content = item.content
668
+
669
+ new_items << single
670
+ end
671
+
672
+ @items = new_items
673
+ end
674
+
675
+ private
676
+ def _page_trail(base, items, page, max, config)
677
+ page_trail = []
678
+
679
+ before = config["before"] || 0
680
+ after = config["after"] || 0
681
+
682
+ (before <= 0 || before >= max) ? 0 : before
683
+ (after <= 0 || after >= max) ? 0 : after
684
+
685
+ if before.zero? && after.zero?
686
+ start_page = 1
687
+ end_page = max
688
+ else
689
+ start_page = page - before
690
+ start_page = 1 if start_page <= 0
691
+
692
+ end_page = start_page + before + after
693
+ if end_page > max
694
+ end_page = max
695
+ start_page = max - before - after
696
+ start_page = 1 if start_page <= 0
697
+ end
698
+ end
699
+
700
+ i = start_page
701
+ while i <= end_page do
702
+ title = _title(config[:title], items, i, max)
703
+ page_trail <<
704
+ {
705
+ 'num' => i,
706
+ 'path' => _permalink(base, i, max),
707
+ 'title' => title
708
+ }
709
+ i += 1
710
+ end
711
+
712
+ page_trail
713
+ end
714
+
715
+ def _seo(type, url)
716
+ " <link rel=\"#{type}\" href=\"#{url}\" />"
717
+ end
718
+
719
+ def _permalink(base, page, max)
720
+ return base if page == 1
721
+
722
+ (base + @config[:permalink]).
723
+ gsub(/:num/, page.to_s).
724
+ gsub(/:max/, max.to_s).
725
+ gsub(/\/\//, '/')
726
+ end
727
+
728
+ def _title(format, items, page, max, retitle_first = false)
729
+ orig = items[page-1].data['title']
730
+ return orig if !format || (page == 1 && !retitle_first)
731
+
732
+ section = items[page-1].pager.section
733
+
734
+ format.gsub(/:title/, orig || '').
735
+ gsub(/:section/, section).
736
+ gsub(/:num/, page.to_s).
737
+ gsub(/:max/, max.to_s)
738
+ end
739
+
740
+ def _set_properties(original, item, stage, user_props = nil)
741
+ stage_props = {}
742
+ stage_props.merge!(@config[:properties][stage])
743
+
744
+ if user_props && user_props.has_key?(stage)
745
+ stage_props.merge!(user_props[stage])
746
+ end
747
+
748
+ return if stage_props.empty?
749
+
750
+ # Handle special values
751
+ stage_props.delete_if do |k,v|
752
+ if k == "pagination_info"
753
+ false
754
+ elsif v == "/"
755
+ true
756
+ else
757
+ if v.is_a?(String) && m = /\$\.?(.*)$/.match(v)
758
+ stage_props[k] = m[1].empty? ?
759
+ original.data[k] : original.data[m[1]]
760
+ end
761
+ false
762
+ end
763
+ end
764
+
765
+ if item.respond_to?('merge_data')
766
+ item.merge_data!(stage_props)
767
+ else
768
+ item.data.merge!(stage_props)
769
+ end
770
+
771
+ end
772
+
773
+ def _adjust_links(new_items, content, a_locations, num)
774
+ # TODO: Try to merge these
775
+
776
+ # [Something](#target)
777
+ content.scan(/\[[^\]]+\]\(#(.*)\)/i).flatten.each do |a|
778
+ if (page_num = a_locations[a]) && (page_num != num)
779
+ content.gsub!(/(\[[^\]]+\]\()##{a}(\))/i,
780
+ '\1' + @site.config['baseurl'] + new_items[page_num-1].data['permalink']+'#'+a+'\2')
781
+ end
782
+ end
783
+
784
+ # [Something]: #target
785
+ content.scan(/\[[^\]]+\]:\s*#(\S+)/i).flatten.each do |a|
786
+ if (page_num = a_locations[a]) && (page_num != num)
787
+ content.gsub!(/(\[[^\]]+\]:\s*)##{a}/i,
788
+ '\1' + @site.config['baseurl'] + new_items[page_num-1].data['permalink']+'#'+a)
789
+ end
790
+ end
791
+
792
+ end
793
+ end
794
+ end
795
+ end