432bbc2b384572a85be34cf85d52009b8ef02269
2 # Copyright (C) 2016 Ian Kelling
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation, either version 2 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 module B
# blog module
23 JS_INFO
= "<p>All JavaScript has <a href=\"https://www.gnu.org/software/librejs/index.html\">LibreJS</a> support.</p>"
28 DURL
= 'https://' + FQDN
29 DESCRIPTION
= "Ian Kelling's personal site and blog on software"
30 DATE_LEN
= 'YYYY-MM-DD'.length
32 WAIT_DATE
= NOW
- 60*60*24*1
35 SQLite3
::Database.new('../proposed-comments/comments.sqlite')
38 # from the redcarpet readme, then a bunch of googling to figure
39 # out what to do on exception.
40 class HTMLwithPygments
< Redcarpet
::Render::HTML
41 def block_code(code
, language
)
43 Pygments
.highlight(code
, lexer
: language
)
45 # when language detection fails
46 Pygments
.highlight(code
, lexer
: 'text')
51 def fwrite(output_path
, string
)
52 output_path
= File
.join('./', output_path
)
53 FileUtils
.mkdir_p(File
.dirname(output_path
))
54 File
.write(output_path
, string
)
57 def fskel(rel_path
, title
, content
, o
={})
59 <link rel="canonical" href="#{DURL}/#{rel_path}">
61 if rel_path
=~
%r
{^
/blog/|^blog
.html
}
63 <link rel="alternate" type="application/atom+xml" title="#{DN}" href="#{DURL}/feed.xml">
67 fwrite(rel_path
, skel(title
, content
, o
))
69 def skel(title
, content
, o
={})
70 # got meta viewport from jekyll's default later. It's for better
76 <meta charset="utf-8">
77 <title>#{title}</title>
78 <link rel="stylesheet" href="/main.css">
79 <link rel="stylesheet" href="/common.css">
80 <link rel="shortcut icon" href="/assets/favicon.png" />
81 <meta name="description" content="#{o[:description] || DESCRIPTION}">
83 <meta name="viewport" content="width=device-width, initial-scale=1">
87 <h3><a href="/">iankelling.org</a>#{o[:header]}</h3>
89 <div class="main-content-stripe">
94 <div id="comment-stripe">
99 <p>This site has a <a href="/git/?p=iankelling.org;a=summary">git repo</a>. Unless stated otherwise, <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"><img id="cc-by-sa" alt="Creative Commons License" src="/assets/cc-by-sa-4.0-80x15.png" /></a></p>
100 <p><address><a href="mailto:ian@iankelling.org">ian@iankelling.org</a> let me know what you think</address></p>
108 def stdpage(page_name
, content
)
109 fskel("#{page_name}.html",
110 "#{DN}/#{page_name}",
112 header
: " / <a href=\"/#{page_name}.html\">#{page_name}</a>")
116 # Using redcarpet over kramdown because syntax highlighting is
117 # simpler. kramdown uses some crap highlighter by default,
118 # supports using rouge, but then the classes are all screwy
119 # for what pygments css expects, rouge has a pygments compatibility mode,
120 # but that is a pita to get working, then it doesn't even work right.
121 # kramdown is jekyll's default markdown parser, but it doesn't use
122 # it for code blocks, it strips them out using custom templating
123 # extension class, then uses rouge, then wraps it in some
124 # custom html for pygments compatibility. It's a complicated mess.
125 Redcarpet
::Markdown.new(HTMLwithPygments
, fenced_code_blocks
: true).render(md
)
128 def comment_html(comment
, date
)
129 inner
= Redcarpet
::Markdown.new(Redcarpet
::Render::HTML, fenced_code_blocks
: true).render(<<EOF)
131 <span class="comment-date">#{Time.at(date).strftime("%b %-d '%y")}</span>
133 # I tried putting the time, %I:%M %p UTC, but it looks kinda
134 # clunky, going against my simple theme.
136 <div class="comment">
142 def post(file
, build_time
=false)
143 content
= File
.read(file
)
144 content
=~
%r
{\
A(---\s
*\n.*?\n?)^
((---)\s
*$
\n?)}m
# yaml front matter
145 # stuff after last match. jekyll uses $POSTMATCH,
146 # but it's nil for me, I don't know what magic they are using.,
147 # but only $' is listed here http://ruby-doc.org/core-2.3.1/doc/globals_rdoc.html,
150 front = SafeYAML.load(Regexp.last_match(1))
151 title = front['title
']
152 $page_title = "#{title} | #{DN}"
153 header_rel = ' / <a href="/blog
.html
">blog</a> /'
155 footer_extra = <<-EOF
156 <p><a class="icon-rss" href="/feed.xml">Subscribe</a></p>
158 footer_extra
+= JS_INFO
if content
=~
/<script/
162 b
= File
.basename(file
,'.md')
163 # date is in the format: YYYY-MM-DD-
164 date
= Time
.parse(b
[0..DATE_LEN
])
165 rel_path
= "/blog/#{b[(DATE_LEN + 1)..-1]}.html"
166 comments
= $db.execute
<<-SQL, [WAIT_DATE]
167 select comment, date from c
168 where page = '#{rel_path}' and (
169 state = 'picked' or state = 'known' or state = 'timed'
170 or (state = 'waiting' and date < ?))
172 # get earliest comment. earlier ones stored in git will also be
173 # published. This get's us easily sharable comments, and allows us
174 # to expire unpublished comments and ip addresses which are PII and
175 # should never be kept around indefinitely.
176 sql_start_date
= $db.execute('select min(date) from c')[0][0] || NOW
177 comment_file_dir
= "../comments/#{rel_path}"
178 old_comments
= Dir
["#{comment_file_dir}/*"].reduce([]) do |memo
, f
|
179 dt
= File
.basename(f
).to_f
180 if dt
< sql_start_date
181 memo
<< [File
.read(f
), dt
]
183 FileUtils
.rm(f
) if build_time
188 FileUtils
.mkdir_p comment_file_dir
189 comments
.each
do |c
, c_date
|
190 # fyi: there is an extremely small chance of 2 comments having
191 # the same floating point time and thus overwriting each other.
192 # Small enough that it won't happen at my site's scale.
193 File
.write(File
.join(comment_file_dir
, c_date
.to_s
), c
)
195 # https://piwik.org/docs/privacy/ says keep logs for 3-6 months
196 $db.execute("delete from c where date < #{NOW - DAY*180}")
198 comments
= old_comments
+ comments
199 pending_comments
= $db.execute(<<-SQL, [WAIT_DATE])[0][0]
200 select count(*) from c
201 where page = '#{rel_path}' and
202 (state = 'waiting' and date > ? or state = 'suspect')
205 feed_html
= md_to_html(content
)
207 <header class="post-header">
208 <h1 class="post-title">#{title}</h1>
209 <p class="post-date">#{date.strftime("%b %-d, %Y")}</p>
214 comments
.each
{ |c
, date
| com_list
+= comment_html(c
, date
) }
215 if pending_comments
> 0
216 if pending_comments
>= 2
217 text
= "are #{pending_comments} new comments"
219 text
= 'is 1 new comment'
222 comment_html("Note: there #{text} pending approval.", NOW
)
225 <form class="comment" action="/cgi/comment" method="post">
226 <input class="misc" type="text" name="url">
227 <input name="goto" type="hidden" value="#{rel_path}">
228 <textarea rows="10" name="comment" placeholder="markdown" maxlength="1000"></textarea>
229 <input type="submit" value="Leave a comment">
235 links
= front
['comment_links']
237 link_html
= links
.map
{ |name
,url
| "<a href=\"#{url}\">#{name}</a>" }
239 com_section
+= (<<EOF)
240 <p>More comments at #{link_html}</p>
244 blog_toc_entry
= "<li><a href=\"#{rel_path}\">#{title}</a></li>"
247 <div id="comment-section">
253 if front
['description']
254 description
= front
['description']
256 # the first 300 saves ~ 1 ms
257 # regex for striping html from liquid template src
258 description
= feed_html
[0..300].gsub(/<script.*?<\/script
>/m
, '').
259 gsub(/<!--.*?-->/m
, '').gsub(/<style.*?<\/style
>/m
, '').
261 if description
.length
> 160
262 description
= description
[0..156] + '...'
269 fskel(rel_path
, title
, page_html
,
271 footer
: footer_extra
,
272 comments
: com_section
,
273 description
: description
)
274 url
="#{DURL}#{rel_path}"
277 # following from https://creativecommons.org/choose,
278 # with the addition of "unless otherwise noted", for js licenses.
279 feed_copyright
= <<-EOF
280 <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" /></a><br /><span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">#{title}</span> by <a xmlns:cc="http://creativecommons.org/ns#" href="#{url}" property="cc:attributionName" rel="cc:attributionURL">Ian Kelling</a> unless otherwise noted is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">Creative Commons Attribution-ShareAlike 4.0 International License</a>.
285 <title>#{title}</title>
286 <link rel="alternate" href="#{url}"/>
288 <updated>#{date.to_datetime.rfc3339}</updated>
289 <content type="html" xml:lang="en-us" xml:base="#{DURL}/blog">
299 return [feed_entry
, blog_toc_entry
]