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.
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
+