runeblog 0.2.3 → 0.2.5

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.
@@ -0,0 +1,757 @@
1
+ require 'ostruct'
2
+ require 'pp'
3
+ require 'date'
4
+
5
+ require 'livetext'
6
+ require 'runeblog'
7
+
8
+ errfile = File.new("/tmp/liveblog.out", "w")
9
+ STDERR.reopen(errfile)
10
+
11
+ def init_liveblog # FIXME - a lot of this logic sucks
12
+ log!(enter: __method__)
13
+ here = Dir.pwd
14
+ dir = here
15
+ loop { dir = Dir.pwd; break if File.exist?("config"); Dir.chdir("..") }
16
+ Dir.chdir(here)
17
+ log!(str: "Calling RuneBlog.new")
18
+ @blog = $_blog = RuneBlog.new(dir)
19
+ @root = @blog.root
20
+ @view = @blog.view
21
+ @view_name = @blog.view.name
22
+ @vdir = @blog.view.dir
23
+ @version = RuneBlog::VERSION
24
+ @theme = @vdir + "/themes/standard/"
25
+ end
26
+
27
+ # FIXME - stale? and livetext are duplicated from helpers-blog
28
+
29
+ def stale?(src, dst)
30
+ log!(enter: __method__, args: [src, dst])
31
+ return true unless File.exist?(dst)
32
+ return true if File.mtime(src) > File.mtime(dst)
33
+ return false
34
+ end
35
+
36
+ def livetext(src, dst=nil)
37
+ log!(enter: __method__, args: [src, dst])
38
+ src += ".lt3" unless src.end_with?(".lt3")
39
+ if dst
40
+ dst += ".html" unless dst.end_with?(".html")
41
+ else
42
+ dst = src.sub(/.lt3$/, "")
43
+ end
44
+ return unless stale?(src, dst)
45
+ system("livetext #{src} >#{dst}")
46
+ end
47
+
48
+ def post
49
+ log!(enter: __method__)
50
+ @meta = OpenStruct.new
51
+ @meta.num = _args[0]
52
+ _out " <!-- Post number #{@meta.num} -->\n "
53
+ end
54
+
55
+ def quote
56
+ log!(enter: __method__)
57
+ _passthru "<blockquote>"
58
+ _passthru _body
59
+ _passthru "</blockquote>"
60
+ _optional_blank_line
61
+ end
62
+
63
+ def categories # does nothing right now
64
+ log!(str: "Does nothing yet", enter: __method__)
65
+ end
66
+
67
+ def style
68
+ log!(enter: __method__)
69
+ fname = _args[0]
70
+ _passthru %[<link rel="stylesheet" href="???/etc/#{fname}')">]
71
+ end
72
+
73
+ # Move elsewhere later!
74
+
75
+ def h1; _passthru "<h1>#{@_data}</h1>"; end
76
+ def h2; _passthru "<h2>#{@_data}</h2>"; end
77
+ def h3; _passthru "<h3>#{@_data}</h3>"; end
78
+ def h4; _passthru "<h4>#{@_data}</h4>"; end
79
+ def h5; _passthru "<h5>#{@_data}</h5>"; end
80
+ def h6; _passthru "<h6>#{@_data}</h6>"; end
81
+
82
+ def hr; _passthru "<hr>"; end
83
+
84
+ def list
85
+ log!(enter: __method__)
86
+ _out "<ul>"
87
+ _body {|line| _out "<li>#{line}</li>" }
88
+ _out "</ul>"
89
+ _optional_blank_line
90
+ end
91
+
92
+ def list!
93
+ log!(enter: __method__)
94
+ _out "<ul>"
95
+ lines = _body.each
96
+ loop do
97
+ line = lines.next
98
+ line = _format(line)
99
+ if line[0] == " "
100
+ _out line
101
+ else
102
+ _out "<li>#{line}</li>"
103
+ end
104
+ end
105
+ _out "</ul>"
106
+ _optional_blank_line
107
+ end
108
+
109
+ def _html_body(file)
110
+ log!(enter: __method__, args: [file])
111
+ file.puts "<html>\n <body>"
112
+ yield
113
+ file.puts " </body>\n</html>"
114
+ end
115
+
116
+ def _write_card(cardfile, mainfile, pairs, card_title, tag, relative: true)
117
+ # HTML for sidebar card
118
+ log!(str: "Creating #{cardfile}.html", pwd: true)
119
+ File.open("#{cardfile}.html", "w") do |f|
120
+ f.puts <<-EOS
121
+ <div class="card mb-3">
122
+ <div class="card-body">
123
+ <h5 class="card-title">
124
+ <a href="javascript: void(0)"
125
+ onclick="javascript:open_main('widgets/#{tag}/#{mainfile}.html')"
126
+ style="text-decoration: none; color: black">#{card_title}</a>
127
+ </h5>
128
+ <!-- <ul class="list-group list-group-flush"> -->
129
+ EOS
130
+ log!(str: "Writing data pairs to #{cardfile}.html", pwd: true)
131
+ top = relative ? "widgets/#{tag}/" : ""
132
+ pairs.each do |file, title|
133
+ f.puts <<-EOS
134
+ <li class="list-group-item"> <a href="javascript: void(0)"
135
+ onclick="javascript:open_main('#{top}#{file}')">#{title}</a> </li>
136
+ EOS
137
+ end
138
+ f.puts <<-EOS
139
+ <!-- </ul> -->
140
+ </div>
141
+ </div>
142
+ EOS
143
+ end
144
+
145
+ def _write_main(mainfile, pairs, card_title)
146
+ # HTML for main area (iframe)
147
+ log!(str: "Creating #{mainfile}.html", pwd: true)
148
+ File.open("#{mainfile}.html", "w") do |f|
149
+ _html_body(f) do
150
+ f.puts "<h1>#{card_title}</h1>"
151
+ pairs.each do |file, title|
152
+ f.puts %[<a style="text-decoration: none; font-size: 24px" href="#{file}">#{title}</a> <br>]
153
+ end
154
+ end
155
+ end
156
+ end
157
+
158
+ def make_main_links
159
+ log!(enter: __method__)
160
+ # FIXME remember strings may not be safe
161
+ line = _data.chomp
162
+ tag, card_title = *line.split(" ", 2)
163
+ cardfile, mainfile = "card-#{tag}", "main-#{tag}"
164
+ input = "list.data"
165
+ log!(str: "Reading #{input}", pwd: true)
166
+ pairs = File.readlines(input).map {|line| line.chomp.split(",", 2) }
167
+ _write_main(mainfile, pairs, card_title)
168
+ widget_relative = (tag != "news") # FIXME kludge!!!
169
+ _write_card(cardfile, mainfile, pairs, card_title, tag, relative: widget_relative)
170
+ end
171
+ log!(str: "...returning from method", pwd: true)
172
+ end
173
+
174
+ ### inset
175
+
176
+ def inset
177
+ log!(enter: __method__)
178
+ lines = _body
179
+ box = ""
180
+ lines.each do |line|
181
+ line = line.dup
182
+ if line[0] == "/" # Only into inset
183
+ line[0] = ' '
184
+ box << line.dup + " "
185
+ line.replace(" ")
186
+ end
187
+ if line[0] == "|" # Into inset and body
188
+ line[0] = ' '
189
+ box << line.dup + " "
190
+ end
191
+ _passthru(line)
192
+ end
193
+ lr = _args.first
194
+ wide = _args[1] || "25"
195
+ _passthru "<div style='float:#{lr}; width: #{wide}%; padding:8px; padding-right:12px; font-family:verdana'>"
196
+ _passthru '<b><i>'
197
+ _passthru box
198
+ _passthru_noline '</i></b></div>'
199
+ _optional_blank_line
200
+ end
201
+
202
+ def _errout(*args)
203
+ log!(str: args.join(" "), enter: __method__)
204
+ ::STDERR.puts *args
205
+ end
206
+
207
+ def _passthru(line)
208
+ log!(enter: __method__, args: [line])
209
+ return if line.nil?
210
+ line = _format(line)
211
+ _out line + "\n"
212
+ _out "<p>" if line.empty? && ! @_nopara
213
+ end
214
+
215
+ def _passthru_noline(line)
216
+ log!(enter: __method__, args: [line])
217
+ return if line.nil?
218
+ line = _format(line)
219
+ _out line
220
+ _out "<p>" if line.empty? && ! @_nopara
221
+ end
222
+
223
+ def title
224
+ log!(enter: __method__)
225
+ raise "'post' was not called" unless @meta
226
+ title = @_data.chomp
227
+ @meta.title = title
228
+ setvar :title, title
229
+ _out %[<h1 class="post-title">#{title}</h1><br>]
230
+ _optional_blank_line
231
+ end
232
+
233
+ def pubdate
234
+ log!(enter: __method__)
235
+ raise "'post' was not called" unless @meta
236
+ _debug "data = #@_data"
237
+ # Check for discrepancy?
238
+ match = /(\d{4}).(\d{2}).(\d{2})/.match @_data
239
+ junk, y, m, d = match.to_a
240
+ y, m, d = y.to_i, m.to_i, d.to_i
241
+ @meta.date = ::Date.new(y, m, d)
242
+ @meta.pubdate = "%04d-%02d-%02d" % [y, m, d]
243
+ _optional_blank_line
244
+ end
245
+
246
+ def image # primitive so far
247
+ log!(enter: __method__)
248
+ _debug "img: huh? <img src=#{_args.first}></img>"
249
+ fname = _args.first
250
+ path = "assets/#{fname}"
251
+ _out "<img src=#{path}></img>"
252
+ _optional_blank_line
253
+ end
254
+
255
+ def tags
256
+ log!(enter: __method__)
257
+ raise "'post' was not called" unless @meta
258
+ _debug "args = #{_args}"
259
+ @meta.tags = _args.dup || []
260
+ _optional_blank_line
261
+ end
262
+
263
+ def views
264
+ log!(enter: __method__)
265
+ raise "'post' was not called" unless @meta
266
+ _debug "data = #{_args}"
267
+ @meta.views = _args.dup
268
+ _optional_blank_line
269
+ end
270
+
271
+ def pin
272
+ log!(enter: __method__)
273
+ raise "'post' was not called" unless @meta
274
+ _debug "data = #{_args}"
275
+ # verify only already-specified views?
276
+ @meta.pinned = _args.dup
277
+ _optional_blank_line
278
+ end
279
+
280
+ def write_post
281
+ log!(enter: __method__)
282
+ raise "'post' was not called" unless @meta
283
+ save = Dir.pwd
284
+ @postdir.gsub!(/\/\//, "/") # FIXME unneeded?
285
+ Dir.mkdir(@postdir) unless Dir.exist?(@postdir) # FIXME remember assets!
286
+ Dir.chdir(@postdir)
287
+ @meta.views = @meta.views.join(" ") if @meta.views.is_a? Array
288
+ @meta.tags = @meta.tags.join(" ") if @meta.tags.is_a? Array
289
+ File.write("teaser.txt", @meta.teaser)
290
+
291
+ fields = [:num, :title, :date, :pubdate, :views, :tags]
292
+
293
+ fname2 = "metadata.txt"
294
+ f2 = File.open(fname2, "w") do |f2|
295
+ fields.each {|fld| f2.puts "#{fld}: #{@meta.send(fld)}" }
296
+ end
297
+ Dir.chdir(save)
298
+ rescue => err
299
+ puts "err = #{err}"
300
+ puts err.backtrace.join("\n")
301
+ end
302
+
303
+ def teaser
304
+ log!(enter: __method__)
305
+ raise "'post' was not called" unless @meta
306
+ @meta.teaser = _body_text
307
+ setvar :teaser, @meta.teaser
308
+ _out @meta.teaser + "\n"
309
+ # FIXME
310
+ end
311
+
312
+ def finalize
313
+ log!(enter: __method__)
314
+ unless @meta
315
+ puts @live.body
316
+ return
317
+ end
318
+ if @blog.nil?
319
+ return @meta
320
+ end
321
+
322
+ @slug = @blog.make_slug(@meta)
323
+ slug_dir = @slug
324
+ @postdir = @blog.view.dir + "/posts/#{slug_dir}"
325
+ STDERR.puts "--- finalize: pwd = #{Dir.pwd} postdir = #@postdir"
326
+ write_post
327
+ @meta
328
+ end
329
+
330
+ $Dot = self # Clunky! for dot commands called from Functions class
331
+
332
+ # Find a better way to do this?
333
+
334
+ class Livetext::Functions
335
+
336
+ def br(n="1")
337
+ # Thought: Maybe make a way for functions to "simply" call the
338
+ # dot command of the same name?? Is this trivial??
339
+ n = n.empty? ? 1 : n.to_i
340
+ "<br>"*n
341
+ end
342
+
343
+ def h1(param); "<h1>#{param}</h1>"; end
344
+ def h2(param); "<h2>#{param}</h2>"; end
345
+ def h3(param); "<h3>#{param}</h3>"; end
346
+ def h4(param); "<h4>#{param}</h4>"; end
347
+ def h5(param); "<h5>#{param}</h5>"; end
348
+ def h6(param); "<h6>#{param}</h6>"; end
349
+
350
+ def hr(param=nil)
351
+ $Dot.hr
352
+ end
353
+
354
+ def image(param)
355
+ "<img src='#{param}'></img>"
356
+ end
357
+
358
+ end
359
+
360
+ ###### experimental...
361
+
362
+ class Livetext::Functions
363
+ def _var(name)
364
+ ::Livetext::Vars[name] || "[:#{name} is undefined]"
365
+ end
366
+
367
+ def link
368
+ file, cdata = self.class.param.split("||", 2)
369
+ %[<link type="application/atom+xml" rel="alternate" href="#{_var(:host)}#{file}" title="#{_var(:title)}">]
370
+ end
371
+ end
372
+
373
+ ###
374
+
375
+ def _var(name) # FIXME scope issue!
376
+ ::Livetext::Vars[name] || "[:#{name} is undefined]"
377
+ end
378
+
379
+ def head # Does NOT output <head> tags
380
+ log!(enter: __method__)
381
+ args = _args
382
+ args.each do |inc|
383
+ self.data = inc
384
+ _include
385
+ end
386
+ # Depends on vars: title, desc, host
387
+ defaults = {}
388
+ defaults = { "charset" => %[<meta charset="utf-8">],
389
+ "http-equiv" => %[<meta http-equiv="X-UA-Compatible" content="IE=edge">],
390
+ "title" => %[<title>\n #{_var(:blog)} | #{_var("blog.desc")}\n </title>],
391
+ "generator" => %[<meta name="generator" content="Runeblog v #@version">],
392
+ "og:title" => %[<meta property="og:title" content="#{_var(:blog)}">],
393
+ "og:locale" => %[<meta property="og:locale" content="#{_var(:locale)}">],
394
+ "description" => %[<meta name="description" content="#{_var("blog.desc")}">],
395
+ "og:description" => %[<meta property="og:description" content="#{_var("blog.desc")}">],
396
+ "linkc" => %[<link rel="canonical" href="#{_var(:host)}">],
397
+ "og:url" => %[<meta property="og:url" content="#{_var(:host)}">],
398
+ "og:site_name" => %[<meta property="og:site_name" content="#{_var(:blog)}">],
399
+ "style" => %[<link rel="stylesheet" href="etc/blog.css">],
400
+ "feed" => %[<link type="application/atom+xml" rel="alternate" href="#{_var(:host)}/feed.xml" title="#{_var(:blog)}">],
401
+ "favicon" => %[<link rel="shortcut icon" type="image/x-icon" href="../etc/favicon.ico">\n <link rel="apple-touch-icon" href="../etc/favicon.ico">]
402
+ }
403
+ result = {}
404
+ lines = _body
405
+ lines.each do |line|
406
+ line.chomp
407
+ word, remain = line.split(" ", 2)
408
+ case word
409
+ when "viewport"
410
+ result["viewport"] = %[<meta name="viewport" content="#{remain}">]
411
+ when "script" # FIXME this is broken
412
+ file = remain
413
+ text = File.read(file)
414
+ result["script"] = Livetext.new.transform(text)
415
+ when "style"
416
+ result["style"] = %[<link rel="stylesheet" href="('/etc/#{remain}')">]
417
+ # Later: allow other overrides
418
+ when ""; break
419
+ else
420
+ STDERR.puts "-- got '#{word}'; old value = #{result[word].inspect}"
421
+ if defaults[word]
422
+ result[word] = %[<meta property="#{word}" content="#{remain}">]
423
+ STDERR.puts "-- new value = #{result[word].inspect}"
424
+ else
425
+ puts "Unknown tag '#{word}'"
426
+ end
427
+ end
428
+ end
429
+ hash = defaults.dup.update(result) # FIXME collisions?
430
+
431
+ # _out "<!-- "; _out hash.inspect; _out "--> "
432
+ hash.each_value {|x| _out " " + x }
433
+ _out "<body>"
434
+ end
435
+
436
+ ########## newer stuff...
437
+
438
+ def meta
439
+ log!(enter: __method__)
440
+ args = _args
441
+ enum = args.each
442
+ str = "<meta"
443
+ arg = enum.next
444
+ loop do
445
+ if arg.end_with?(":")
446
+ str << " " << arg[0..-2] << "="
447
+ a2 = enum.next
448
+ str << %["#{a2}"]
449
+ else
450
+ STDERR.puts "=== meta error?"
451
+ end
452
+ arg = enum.next
453
+ end
454
+ str << ">"
455
+ _out str
456
+ end
457
+
458
+ def recent_posts # side-effect
459
+ log!(enter: __method__)
460
+ _out %[<div class="col-lg-9 col-md-9 col-sm-9 col-xs-12">]
461
+ all_teasers
462
+ _out %[</div>]
463
+ end
464
+
465
+ def sidebar
466
+ log!(enter: __method__)
467
+ _out %[<div class="col-lg-3 col-md-3 col-sm-3 col-xs-12">]
468
+ _args do |token|
469
+ tag = token.chomp.strip.downcase
470
+ # FIXME will someone please refactor this horrible code??
471
+ # Think of the children!!
472
+ case tag[0]
473
+ when "." # Only in sidebar, special code
474
+ tag = tag[1..-1]
475
+ Dir.chdir("widgets/#{tag}") do
476
+ name = tag + ".lt3"
477
+ log!(str: "DOT About to do: livetext #{name} in #{Dir.pwd}", dir: true)
478
+ livetext tag + ".lt3", "card-#{tag}"
479
+ log!(str: "DOT after livetext call", pwd: true, dir: true)
480
+ _include_file "card-#{tag}.html"
481
+ end
482
+ when ":" # Card AND main, but still special
483
+ tag = tag[1..-1]
484
+ Dir.chdir("widgets/#{tag}") do
485
+ name = tag + ".lt3"
486
+ log!(str: "COL About to do: livetext #{name} in #{Dir.pwd}", dir: true)
487
+ livetext tag + ".lt3", "card-#{tag}"
488
+ system("card-#{tag}.html main-#{tag}.html") # FIXME HTML will be wrong??
489
+ log!(str: "COL after livetext call", pwd: true, dir: true)
490
+ _include_file "card-#{tag}.html"
491
+ end
492
+ else
493
+ Dir.chdir("widgets/#{tag}") do
494
+ name = tag + ".lt3"
495
+ log!(str: "NRM About to do: livetext #{name} in #{Dir.pwd}", dir: true)
496
+ livetext tag + ".lt3"
497
+ log!(str: "NRM after livetext call", pwd: true, dir: true)
498
+ _include_file "card-#{tag}.html"
499
+ end
500
+ end
501
+ end
502
+ _out %[</div>]
503
+ end
504
+
505
+ def stylesheet
506
+ log!(enter: __method__)
507
+ lines = _body
508
+ url = lines[0]
509
+ integ = lines[1]
510
+ cross = lines[2] || "anonymous"
511
+ _out %[<link rel="stylesheet" href="#{url}" integrity="#{integ}" crossorigin="#{cross}"></link>]
512
+ end
513
+
514
+ def script
515
+ log!(enter: __method__)
516
+ lines = _body
517
+ url = lines[0]
518
+ integ = lines[1]
519
+ cross = lines[2] || "anonymous"
520
+ _out %[<script src="#{url}" integrity="#{integ}" crossorigin="#{cross}"></script>]
521
+ end
522
+
523
+
524
+ ### How this next bit works:
525
+ ###
526
+ ### all_teasers will call _find_recent_posts
527
+ ###
528
+ ### _find_recent_posts will search higher in the directory structure
529
+ ### for where the posts are (0001, 0002, ...) NOTE: This implies you
530
+ ### must be in some specific place when this code is run.
531
+ ### It returns the 20 most recent posts.
532
+ ###
533
+ ### all_teasers will then pick a small number of posts and call _teaser
534
+
535
+ ### on each one. (The code in _teaser really belongs in a small template
536
+ ### somewhere.)
537
+ ###
538
+
539
+ def _find_recent_posts
540
+ log!(enter: __method__)
541
+ @vdir = _var(:FileDir).match(%r[(^.*/views/.*?)/])[1]
542
+ posts = nil
543
+ dir_posts = @vdir + "/posts"
544
+ entries = Dir.entries(dir_posts)
545
+ posts = entries.grep(/^\d\d\d\d/).map {|x| dir_posts + "/" + x }
546
+ posts.select! {|x| File.directory?(x) }
547
+ # directories that start with four digits
548
+ posts = posts.sort {|a, b| b.to_i <=> a.to_i } # sort descending
549
+ posts[0..19] # return 20 at most
550
+ end
551
+
552
+ def all_teasers
553
+ log!(enter: __method__)
554
+ open = <<-HTML
555
+ <section class="posts">
556
+ HTML
557
+ close = <<-HTML
558
+ </section>
559
+ HTML
560
+
561
+ text = <<-HTML
562
+ <html>
563
+ <head><link rel="stylesheet" href="etc/blog.css"></head>
564
+ <body>
565
+ HTML
566
+ posts = _find_recent_posts
567
+ wanted = [5, posts.size].min # estimate how many we want?
568
+ enum = posts.each
569
+ wanted.times do
570
+ postid = File.basename(enum.next)
571
+ postid = postid.to_i
572
+ text << _teaser(postid) # side effect! calls _out
573
+ end
574
+ text << "</body></html>"
575
+ File.write("recent.html", text)
576
+ _out %[<iframe id="main" style="width: 100vw; height: 100vh; position: relative;" src='recent.html' width=100% frameborder="0" allowfullscreen></iframe>]
577
+ end
578
+
579
+ def _post_lookup(postid) # side-effect
580
+ log!(enter: __method__, args: [postid])
581
+ # .. = templates, ../.. = views/thisview
582
+ slug = title = date = teaser_text = nil
583
+
584
+ dir_posts = @vdir + "/posts"
585
+ posts = Dir.entries(dir_posts).grep(/^\d\d\d\d/).map {|x| dir_posts + "/" + x }
586
+ posts.select! {|x| File.directory?(x) }
587
+
588
+ post = posts.select {|x| File.basename(x).to_i == postid }
589
+ raise "Error: More than one post #{postid}" if post.size > 1
590
+ postdir = post.first
591
+ vp = RuneBlog::ViewPost.new(@blog.view, postdir)
592
+ vp
593
+ end
594
+
595
+ def _interpolate(str, context) # FIXME move this later
596
+ log!(str: "Duplicated method?", enter: __method__, args: [str, context])
597
+ wrapped = "%[" + str.dup + "]" # could fail...
598
+ eval(wrapped, context)
599
+ end
600
+
601
+ def _teaser(slug)
602
+ log!(enter: __method__, args: [slug])
603
+ id = slug.to_i
604
+ text = nil
605
+ post_entry_name = @theme + "blog/post_entry.lt3"
606
+ @_post_entry ||= File.read(post_entry_name)
607
+ vp = _post_lookup(id)
608
+ nslug, aslug, title, date, teaser_text =
609
+ vp.nslug, vp.aslug, vp.title, vp.date, vp.teaser_text
610
+ path = vp.path
611
+ url = "#{path}/#{aslug}.html" # Should be relative to .blogs!! FIXME
612
+ date = Date.parse(date)
613
+ date = date.strftime("%B %e<br>%Y")
614
+ text = _interpolate(@_post_entry, binding)
615
+ text
616
+ end
617
+
618
+ def _card_generic(card_title:, middle:, extra: "")
619
+ log!(enter: __method__, args: [card_title, middle, extra])
620
+ front = <<-HTML
621
+ <div class="card #{extra} mb-3">
622
+ <div class="card-body">
623
+ <h5 class="card-title">#{card_title}</h5>
624
+ HTML
625
+
626
+ tail = <<-HTML
627
+ </div>
628
+ </div>
629
+ HTML
630
+ text = front + middle + tail
631
+ _out text + "\n "
632
+ end
633
+
634
+ def card_iframe
635
+ log!(enter: __method__)
636
+ title, lines = _data, _body
637
+ lines.map!(&:chomp)
638
+ url = lines[0].chomp
639
+ stuff = lines[1..-1].join(" ") # FIXME later
640
+ middle = <<-HTML
641
+ <iframe src="#{url}" #{stuff}
642
+ style="border: 0" #{stuff}
643
+ frameborder="0" scrolling="no">
644
+ </iframe>
645
+ HTML
646
+
647
+ _card_generic(card_title: title, middle: middle, extra: "bg-dark text-white")
648
+ end
649
+
650
+ def _main(url)
651
+ log!(enter: __method__, args: [url])
652
+ %[href="javascript: void(0)" onclick="javascript:open_main('#{url}')"]
653
+ end
654
+
655
+ def card1
656
+ log!(enter: __method__)
657
+ title, lines = _data, _body
658
+ lines.map!(&:chomp)
659
+
660
+ card_text = lines[0]
661
+ url, classname, cdata = lines[1].split(",", 4)
662
+ main = _main(url)
663
+
664
+ middle = <<-HTML
665
+ <p class="card-text">#{card_text}</p>
666
+ <a #{main} class="#{classname}">#{cdata}</a>
667
+ HTML
668
+
669
+ _card_generic(card_title: title, middle: middle, extra: "bg-dark text-white")
670
+ end
671
+
672
+ def card2
673
+ log!(enter: __method__)
674
+ str = _data
675
+ file, card_title = str.chomp.split(" ", 2)
676
+ card_title = %[<a #{_main(file)} style="text-decoration: none; color: black">#{card_title}</a>]
677
+
678
+ # FIXME is this wrong??
679
+
680
+ open = <<-HTML
681
+ <div class="card mb-3">
682
+ <div class="card-body">
683
+ <h5 class="card-title">#{card_title}</h5>
684
+ <ul class="list-group list-group-flush">
685
+ HTML
686
+ _out open
687
+ _body do |line|
688
+ url, cdata = line.chomp.split(",", 3)
689
+ main = _main(url)
690
+ _out %[<li class="list-group-item"><a #{main}}">#{cdata}</a> </li>]
691
+ end
692
+ close = %[ </ul>\n </div>\n </div>]
693
+ _out close
694
+ end
695
+
696
+ def tag_cloud
697
+ log!(enter: __method__)
698
+ title = _data
699
+ title = "Tag Cloud" if title.empty?
700
+ open = <<-HTML
701
+ <div class="card mb-3">
702
+ <div class="card-body">
703
+ <h5 class="card-title">#{title}</h5>
704
+ HTML
705
+ _out open
706
+ _body do |line|
707
+ line.chomp!
708
+ url, classname, cdata = line.split(",", 4)
709
+ main = _main(url)
710
+ _out %[<a #{main} class="#{classname}">#{cdata}</a>]
711
+ end
712
+ close = %[ </div>\n </div>]
713
+ _out close
714
+ end
715
+
716
+ def navbar
717
+ log!(enter: __method__)
718
+ title = _var(:blog)
719
+
720
+ open = <<-HTML
721
+ <nav class="navbar navbar-expand-lg navbar-light bg-light">
722
+ <a class="navbar-brand" href="index.html">#{title}</a>
723
+ <button class="navbar-toggler"
724
+ type="button"
725
+ data-toggle="collapse"
726
+ data-target="#navbarSupportedContent"
727
+ aria-controls="navbarSupportedContent"
728
+ aria-expanded="false"
729
+ aria-label="Toggle navigation">
730
+ <span class="navbar-toggler-icon"></span>
731
+ </button>
732
+ <div class="collapse navbar-collapse pull-right"
733
+ id="navbarSupportedContent">
734
+ <ul class="navbar-nav mr-auto">
735
+ HTML
736
+ close = <<-HTML
737
+ </ul>
738
+ </div>
739
+ </nav>
740
+ HTML
741
+
742
+ first = true
743
+ _out open
744
+ _body do |line|
745
+ href, cdata = line.chomp.strip.split(" ", 2)
746
+ main = _main(href)
747
+ if first
748
+ first = false
749
+ _out %[<li class="nav-item active"> <a class="nav-link" href="#{href}">#{cdata}<span class="sr-only">(current)</span></a> </li>]
750
+ else
751
+ main = _main("navbar/#{href}")
752
+ _out %[<li class="nav-item"> <a class="nav-link" #{main}>#{cdata}</a> </li>]
753
+ end
754
+ end
755
+ _out close
756
+ end
757
+