runeblog 0.2.3 → 0.2.5

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