jekyll-theme-apcsp 1.1.0
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 +7 -0
- data/LICENSE.txt +21 -0
- data/_includes/alert.md +0 -0
- data/_includes/footer.md +0 -0
- data/_includes/head.html +0 -0
- data/_includes/header.md +1 -0
- data/_includes/nav.md +0 -0
- data/_layouts/page.html +308 -0
- data/_layouts/redirect.html +12 -0
- data/_sass/bootstrap/LICENSE +22 -0
- data/_sass/bootstrap/scss/_accordion.scss +146 -0
- data/_sass/bootstrap/scss/_alert.scss +71 -0
- data/_sass/bootstrap/scss/_badge.scss +40 -0
- data/_sass/bootstrap/scss/_breadcrumb.scss +42 -0
- data/_sass/bootstrap/scss/_button-group.scss +142 -0
- data/_sass/bootstrap/scss/_buttons.scss +158 -0
- data/_sass/bootstrap/scss/_card.scss +234 -0
- data/_sass/bootstrap/scss/_carousel.scss +229 -0
- data/_sass/bootstrap/scss/_close.scss +40 -0
- data/_sass/bootstrap/scss/_code.scss +48 -0
- data/_sass/bootstrap/scss/_containers.scss +41 -0
- data/_sass/bootstrap/scss/_custom-forms.scss +526 -0
- data/_sass/bootstrap/scss/_dropdown.scss +248 -0
- data/_sass/bootstrap/scss/_forms.scss +9 -0
- data/_sass/bootstrap/scss/_functions.scss +302 -0
- data/_sass/bootstrap/scss/_grid.scss +33 -0
- data/_sass/bootstrap/scss/_helpers.scss +10 -0
- data/_sass/bootstrap/scss/_images.scss +42 -0
- data/_sass/bootstrap/scss/_input-group.scss +208 -0
- data/_sass/bootstrap/scss/_jumbotron.scss +17 -0
- data/_sass/bootstrap/scss/_list-group.scss +191 -0
- data/_sass/bootstrap/scss/_maps.scss +54 -0
- data/_sass/bootstrap/scss/_media.scss +8 -0
- data/_sass/bootstrap/scss/_mixins.scss +43 -0
- data/_sass/bootstrap/scss/_modal.scss +237 -0
- data/_sass/bootstrap/scss/_nav.scss +172 -0
- data/_sass/bootstrap/scss/_navbar.scss +274 -0
- data/_sass/bootstrap/scss/_offcanvas.scss +143 -0
- data/_sass/bootstrap/scss/_pagination.scss +111 -0
- data/_sass/bootstrap/scss/_placeholders.scss +51 -0
- data/_sass/bootstrap/scss/_popover.scss +198 -0
- data/_sass/bootstrap/scss/_print.scss +141 -0
- data/_sass/bootstrap/scss/_progress.scss +59 -0
- data/_sass/bootstrap/scss/_reboot.scss +610 -0
- data/_sass/bootstrap/scss/_root.scss +76 -0
- data/_sass/bootstrap/scss/_spinners.scss +85 -0
- data/_sass/bootstrap/scss/_tables.scss +164 -0
- data/_sass/bootstrap/scss/_toasts.scss +70 -0
- data/_sass/bootstrap/scss/_tooltip.scss +122 -0
- data/_sass/bootstrap/scss/_transitions.scss +27 -0
- data/_sass/bootstrap/scss/_type.scss +104 -0
- data/_sass/bootstrap/scss/_utilities.scss +647 -0
- data/_sass/bootstrap/scss/_variables.scss +1631 -0
- data/_sass/bootstrap/scss/bootstrap-grid.scss +68 -0
- data/_sass/bootstrap/scss/bootstrap-reboot.scss +14 -0
- data/_sass/bootstrap/scss/bootstrap-utilities.scss +19 -0
- data/_sass/bootstrap/scss/bootstrap.scss +54 -0
- data/_sass/bootstrap/scss/forms/_floating-labels.scss +70 -0
- data/_sass/bootstrap/scss/forms/_form-check.scss +175 -0
- data/_sass/bootstrap/scss/forms/_form-control.scss +189 -0
- data/_sass/bootstrap/scss/forms/_form-range.scss +91 -0
- data/_sass/bootstrap/scss/forms/_form-select.scss +71 -0
- data/_sass/bootstrap/scss/forms/_form-text.scss +11 -0
- data/_sass/bootstrap/scss/forms/_input-group.scss +121 -0
- data/_sass/bootstrap/scss/forms/_labels.scss +36 -0
- data/_sass/bootstrap/scss/forms/_validation.scss +12 -0
- data/_sass/bootstrap/scss/helpers/_clearfix.scss +3 -0
- data/_sass/bootstrap/scss/helpers/_color-bg.scss +10 -0
- data/_sass/bootstrap/scss/helpers/_colored-links.scss +12 -0
- data/_sass/bootstrap/scss/helpers/_position.scss +36 -0
- data/_sass/bootstrap/scss/helpers/_ratio.scss +26 -0
- data/_sass/bootstrap/scss/helpers/_stacks.scss +15 -0
- data/_sass/bootstrap/scss/helpers/_stretched-link.scss +15 -0
- data/_sass/bootstrap/scss/helpers/_text-truncation.scss +7 -0
- data/_sass/bootstrap/scss/helpers/_visually-hidden.scss +8 -0
- data/_sass/bootstrap/scss/helpers/_vr.scss +8 -0
- data/_sass/bootstrap/scss/mixins/_alert.scss +15 -0
- data/_sass/bootstrap/scss/mixins/_backdrop.scss +14 -0
- data/_sass/bootstrap/scss/mixins/_background-variant.scss +23 -0
- data/_sass/bootstrap/scss/mixins/_badge.scss +17 -0
- data/_sass/bootstrap/scss/mixins/_border-radius.scss +78 -0
- data/_sass/bootstrap/scss/mixins/_box-shadow.scss +18 -0
- data/_sass/bootstrap/scss/mixins/_breakpoints.scss +127 -0
- data/_sass/bootstrap/scss/mixins/_buttons.scss +71 -0
- data/_sass/bootstrap/scss/mixins/_caret.scss +64 -0
- data/_sass/bootstrap/scss/mixins/_clearfix.scss +9 -0
- data/_sass/bootstrap/scss/mixins/_color-scheme.scss +7 -0
- data/_sass/bootstrap/scss/mixins/_container.scss +11 -0
- data/_sass/bootstrap/scss/mixins/_deprecate.scss +10 -0
- data/_sass/bootstrap/scss/mixins/_float.scss +14 -0
- data/_sass/bootstrap/scss/mixins/_forms.scss +152 -0
- data/_sass/bootstrap/scss/mixins/_gradients.scss +47 -0
- data/_sass/bootstrap/scss/mixins/_grid-framework.scss +80 -0
- data/_sass/bootstrap/scss/mixins/_grid.scss +151 -0
- data/_sass/bootstrap/scss/mixins/_hover.scss +37 -0
- data/_sass/bootstrap/scss/mixins/_image.scss +16 -0
- data/_sass/bootstrap/scss/mixins/_list-group.scss +24 -0
- data/_sass/bootstrap/scss/mixins/_lists.scss +7 -0
- data/_sass/bootstrap/scss/mixins/_nav-divider.scss +11 -0
- data/_sass/bootstrap/scss/mixins/_pagination.scss +10 -0
- data/_sass/bootstrap/scss/mixins/_reset-text.scss +17 -0
- data/_sass/bootstrap/scss/mixins/_resize.scss +6 -0
- data/_sass/bootstrap/scss/mixins/_screen-reader.scss +34 -0
- data/_sass/bootstrap/scss/mixins/_size.scss +7 -0
- data/_sass/bootstrap/scss/mixins/_table-row.scss +39 -0
- data/_sass/bootstrap/scss/mixins/_table-variants.scss +24 -0
- data/_sass/bootstrap/scss/mixins/_text-emphasis.scss +17 -0
- data/_sass/bootstrap/scss/mixins/_text-hide.scss +11 -0
- data/_sass/bootstrap/scss/mixins/_text-truncate.scss +8 -0
- data/_sass/bootstrap/scss/mixins/_transition.scss +26 -0
- data/_sass/bootstrap/scss/mixins/_utilities.scss +92 -0
- data/_sass/bootstrap/scss/mixins/_visibility.scss +8 -0
- data/_sass/bootstrap/scss/mixins/_visually-hidden.scss +29 -0
- data/_sass/bootstrap/scss/utilities/_align.scss +8 -0
- data/_sass/bootstrap/scss/utilities/_api.scss +47 -0
- data/_sass/bootstrap/scss/utilities/_background.scss +19 -0
- data/_sass/bootstrap/scss/utilities/_borders.scss +75 -0
- data/_sass/bootstrap/scss/utilities/_clearfix.scss +3 -0
- data/_sass/bootstrap/scss/utilities/_display.scss +26 -0
- data/_sass/bootstrap/scss/utilities/_embed.scss +39 -0
- data/_sass/bootstrap/scss/utilities/_flex.scss +51 -0
- data/_sass/bootstrap/scss/utilities/_float.scss +11 -0
- data/_sass/bootstrap/scss/utilities/_interactions.scss +5 -0
- data/_sass/bootstrap/scss/utilities/_overflow.scss +5 -0
- data/_sass/bootstrap/scss/utilities/_position.scss +32 -0
- data/_sass/bootstrap/scss/utilities/_screenreaders.scss +11 -0
- data/_sass/bootstrap/scss/utilities/_shadows.scss +6 -0
- data/_sass/bootstrap/scss/utilities/_sizing.scss +20 -0
- data/_sass/bootstrap/scss/utilities/_spacing.scss +73 -0
- data/_sass/bootstrap/scss/utilities/_stretched-link.scss +19 -0
- data/_sass/bootstrap/scss/utilities/_text.scss +72 -0
- data/_sass/bootstrap/scss/utilities/_visibility.scss +13 -0
- data/_sass/bootstrap/scss/vendor/_rfs.scss +354 -0
- data/_sass/jekyll-theme-cs50.scss +298 -0
- data/_sass/page.scss +228 -0
- data/_sass/rouge.scss +35 -0
- data/assets/@fortawesome/fontawesome-free/css/all.min.css +5 -0
- data/assets/@fortawesome/fontawesome-free/webfonts/fa-brands-400.eot +0 -0
- data/assets/@fortawesome/fontawesome-free/webfonts/fa-brands-400.svg +3717 -0
- data/assets/@fortawesome/fontawesome-free/webfonts/fa-brands-400.ttf +0 -0
- data/assets/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff +0 -0
- data/assets/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff2 +0 -0
- data/assets/@fortawesome/fontawesome-free/webfonts/fa-regular-400.eot +0 -0
- data/assets/@fortawesome/fontawesome-free/webfonts/fa-regular-400.svg +801 -0
- data/assets/@fortawesome/fontawesome-free/webfonts/fa-regular-400.ttf +0 -0
- data/assets/@fortawesome/fontawesome-free/webfonts/fa-regular-400.woff +0 -0
- data/assets/@fortawesome/fontawesome-free/webfonts/fa-regular-400.woff2 +0 -0
- data/assets/@fortawesome/fontawesome-free/webfonts/fa-solid-900.eot +0 -0
- data/assets/@fortawesome/fontawesome-free/webfonts/fa-solid-900.svg +5034 -0
- data/assets/@fortawesome/fontawesome-free/webfonts/fa-solid-900.ttf +0 -0
- data/assets/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff +0 -0
- data/assets/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff2 +0 -0
- data/assets/algolia-min.css +1 -0
- data/assets/algoliasearch/dist/algoliasearch-lite.umd.js +2 -0
- data/assets/algoliasearchLite.min.js +3 -0
- data/assets/bootstrap/LICENSE +22 -0
- data/assets/bootstrap/dist/js/bootstrap.bundle.min.js +7 -0
- data/assets/bootstrap/dist/js/bootstrap.bundle.min.js.map +1 -0
- data/assets/bootstrap/dist/js/bootstrap.min.js +7 -0
- data/assets/bootstrap-table/LICENSE +21 -0
- data/assets/bootstrap-table/dist/bootstrap-table.min.css +10 -0
- data/assets/bootstrap-table/dist/bootstrap-table.min.js +10 -0
- data/assets/bootstrap-table/dist/extensions/mobile/bootstrap-table-mobile.min.js +10 -0
- data/assets/instantsearch.js/LICENSE +21 -0
- data/assets/instantsearch.js/dist/instantsearch.production.min.js +3 -0
- data/assets/instantsearch.js/dist/instantsearch.production.min.js.map +1 -0
- data/assets/instantsearch.production.min.js +3 -0
- data/assets/jekyll-theme-cs50.js +620 -0
- data/assets/jquery/LICENSE.txt +20 -0
- data/assets/jquery/dist/jquery.min.js +2 -0
- data/assets/luxon.min.js +1 -0
- data/assets/moment-timezone-with-data.min.js +1 -0
- data/assets/moment.min.js +1 -0
- data/assets/page.js +4 -0
- data/assets/page.scss +4 -0
- data/assets/popper.js/dist/umd/popper.min.js +5 -0
- data/assets/reset-min.css +1 -0
- data/assets/scratchblocks.min.js +11 -0
- data/assets/scratchblocks.min.js.map +1 -0
- data/lib/jekyll-theme-cs50/constants.rb +59 -0
- data/lib/jekyll-theme-cs50.rb +606 -0
- data/lib/liquid-tag-parser.rb +1 -0
- metadata +350 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module CS50
|
|
2
|
+
|
|
3
|
+
PLUGINS = {
|
|
4
|
+
"jekyll-algolia" => "1.7.1",
|
|
5
|
+
"jekyll-default-layout" => "0.1.5",
|
|
6
|
+
"jekyll-optional-front-matter" => "0.3.2",
|
|
7
|
+
"jekyll-redirect-from" => "0.16.0",
|
|
8
|
+
"jekyll-titles-from-headings" => "0.5.3"
|
|
9
|
+
}.freeze
|
|
10
|
+
|
|
11
|
+
DEFAULTS = {
|
|
12
|
+
"cs50" => {
|
|
13
|
+
"local" => {
|
|
14
|
+
"day" => "numeric",
|
|
15
|
+
"hour" => "numeric",
|
|
16
|
+
"minute" => "numeric",
|
|
17
|
+
"month" => "long",
|
|
18
|
+
"timeZoneName" => "short",
|
|
19
|
+
"weekday" => "long",
|
|
20
|
+
"year" => "numeric"
|
|
21
|
+
},
|
|
22
|
+
"locale" => "en",
|
|
23
|
+
"tz" => "America/New_York"
|
|
24
|
+
},
|
|
25
|
+
"exclude" => [
|
|
26
|
+
"Gemfile",
|
|
27
|
+
"Gemfile.lock",
|
|
28
|
+
"vendor"
|
|
29
|
+
],
|
|
30
|
+
"include" => [
|
|
31
|
+
"license.md" # For OCW
|
|
32
|
+
],
|
|
33
|
+
"optional_front_matter" => {
|
|
34
|
+
"remove_originals" => true
|
|
35
|
+
},
|
|
36
|
+
"plugins" => CS50::PLUGINS.keys
|
|
37
|
+
}.freeze
|
|
38
|
+
|
|
39
|
+
OVERRIDES = {
|
|
40
|
+
"kramdown" => {
|
|
41
|
+
"gfm_quirks" => "paragraph_end",
|
|
42
|
+
"hard_wrap" => false,
|
|
43
|
+
"input" => "GFM",
|
|
44
|
+
"math_engine" => "mathjax",
|
|
45
|
+
"syntax_highlighter" => "rouge",
|
|
46
|
+
"template" => ""
|
|
47
|
+
},
|
|
48
|
+
"markdown" => "kramdown",
|
|
49
|
+
"permalink" => "pretty",
|
|
50
|
+
"redirect_from" => {
|
|
51
|
+
"json" => false
|
|
52
|
+
},
|
|
53
|
+
"sass" => {
|
|
54
|
+
"style" => "compressed"
|
|
55
|
+
},
|
|
56
|
+
"theme" => "jekyll-theme-cs50"
|
|
57
|
+
}.freeze
|
|
58
|
+
|
|
59
|
+
end
|
|
@@ -0,0 +1,606 @@
|
|
|
1
|
+
require "cgi"
|
|
2
|
+
require "deep_merge"
|
|
3
|
+
require "digest/md5"
|
|
4
|
+
require "jekyll"
|
|
5
|
+
require "jekyll-redirect-from"
|
|
6
|
+
require "kramdown/parser/gfm"
|
|
7
|
+
require "kramdown/parser/kramdown/link"
|
|
8
|
+
require "pathname"
|
|
9
|
+
require "sanitize"
|
|
10
|
+
require "time"
|
|
11
|
+
require "uri"
|
|
12
|
+
|
|
13
|
+
require "jekyll-theme-cs50/constants"
|
|
14
|
+
|
|
15
|
+
# https://stackoverflow.com/a/68039635/5156190
|
|
16
|
+
class Hash
|
|
17
|
+
def deep_compact
|
|
18
|
+
compact.transform_values{|vl| vl.is_a?(Hash) ? vl.deep_compact : vl }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Inspired by http://www.glitchwrks.com/2017/07/25/jekyll-plugins, https://github.com/Shopify/liquid/wiki/Liquid-for-Programmers#create-your-own-tag-blocks
|
|
23
|
+
|
|
24
|
+
module CS50
|
|
25
|
+
|
|
26
|
+
# Convert Markdown to HTML, preserving indentation (so that it can still be stripped elsewhere in pipeline)
|
|
27
|
+
def self.convert(s)
|
|
28
|
+
markdown, indentation = CS50::unindent(s)
|
|
29
|
+
html = $site.find_converter_instance(::Jekyll::Converters::Markdown).convert(markdown).strip
|
|
30
|
+
CS50::indent(html, indentation)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Format Time for time.cs50.io
|
|
34
|
+
# https://stackoverflow.com/a/19329068/5156190
|
|
35
|
+
def self.format(t)
|
|
36
|
+
t.strftime("%Y%m%dT%H%M%S%z").sub(/\+0000/, "Z")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Indent multiline string
|
|
40
|
+
def self.indent(s, n)
|
|
41
|
+
s.gsub(/^/, " " * n)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Sanitize string, allowing only these tags, which are a (reasonable) subset of
|
|
45
|
+
# https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Phrasing_content
|
|
46
|
+
def self.sanitize(s)
|
|
47
|
+
Sanitize.fragment(s, :elements => ["b", "code", "em", "i", "img", "kbd", "span", "strong", "sub", "sup"]).strip
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Parse time
|
|
51
|
+
def self.strptime(s, now = nil)
|
|
52
|
+
|
|
53
|
+
# Try YYYY-MM-DD HH:MM:SS
|
|
54
|
+
begin
|
|
55
|
+
Time.strptime(s, "%Y-%m-%d %H:%M:%S")
|
|
56
|
+
rescue
|
|
57
|
+
|
|
58
|
+
# Try YYYY-MM-DD HH:MM
|
|
59
|
+
begin
|
|
60
|
+
Time.strptime(s, "%Y-%m-%d %H:%M")
|
|
61
|
+
rescue
|
|
62
|
+
|
|
63
|
+
# Try HH:MM:SS, relative to now
|
|
64
|
+
begin
|
|
65
|
+
t = Time.strptime(s, "%H:%M:%S", now)
|
|
66
|
+
rescue
|
|
67
|
+
|
|
68
|
+
# Try HH:MM, relative to now
|
|
69
|
+
begin
|
|
70
|
+
t = Time.strptime(s, "%H:%M", now)
|
|
71
|
+
rescue
|
|
72
|
+
raise "Invalid datetime: #{s}"
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Because Time.strptime parses relative to now's date,
|
|
77
|
+
# not now's date plus time, add one day if t is in past
|
|
78
|
+
if t < now
|
|
79
|
+
t += 24 * 60 * 60
|
|
80
|
+
end
|
|
81
|
+
t
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Unindent multiline string
|
|
87
|
+
# https://github.com/mynyml/unindent/blob/master/lib/unindent.rb
|
|
88
|
+
def self.unindent(s)
|
|
89
|
+
n = s.split("\n").select {|line| !line.strip.empty? }.map {|line| line.index(/[^\s]/) }.compact.min || 0
|
|
90
|
+
s = s.gsub(/^[[:blank:]]{#{n}}/, "")
|
|
91
|
+
return s, n
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# https://github.com/pathawks/liquid-md5/blob/master/lib/liquid-md5.rb
|
|
95
|
+
module MDhash
|
|
96
|
+
def md5(input)
|
|
97
|
+
Digest::MD5.hexdigest input.strip
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
Liquid::Template.register_filter(MDhash)
|
|
101
|
+
|
|
102
|
+
module Mixins
|
|
103
|
+
|
|
104
|
+
def initialize(tag_name, markup, options)
|
|
105
|
+
@tag_name = tag_name
|
|
106
|
+
@markup = markup
|
|
107
|
+
super
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def render(context)
|
|
111
|
+
|
|
112
|
+
# Interpolate any variables, a la render_variable in
|
|
113
|
+
# https://github.com/jekyll/jekyll/blob/master/lib/jekyll/tags/include.rb
|
|
114
|
+
output = @markup.gsub(/\{\{.*?\}\}/) do |s|
|
|
115
|
+
Liquid::Template.parse(s).render(context)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Quote unquoted URLs
|
|
119
|
+
output = output.gsub(/(\S+\s*=\s*".*"|\S+\s*=\s*'.*'|".*"|'.*'|\S+)/) do |s|
|
|
120
|
+
if s =~ /^#{URI::regexp}$/
|
|
121
|
+
"\"#{s}\""
|
|
122
|
+
else
|
|
123
|
+
s
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Parse any arguments
|
|
128
|
+
@args, @kwargs = [], {}
|
|
129
|
+
output.scan(/"[^"]*"|'[^']'|\S+\s*=\s*"[^"]*"|\S+\s*=\s*'[^']*'|\S+\s*=\s*\S+|\S+/).each do |s|
|
|
130
|
+
if s.start_with?("'")
|
|
131
|
+
@args.push(s.gsub(/^'|'$/, ""))
|
|
132
|
+
elsif s.start_with?('"')
|
|
133
|
+
@args.push(s.gsub(/^"|"$/, ""))
|
|
134
|
+
else
|
|
135
|
+
key , value = s.split("=", 2)
|
|
136
|
+
if value.nil?
|
|
137
|
+
@args.push(key)
|
|
138
|
+
else
|
|
139
|
+
@kwargs[key] = value
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Return any content
|
|
145
|
+
super
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
class Tag < Liquid::Tag
|
|
150
|
+
include Mixins
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
class Block < Liquid::Block
|
|
154
|
+
include Mixins
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
class TestTag < Tag
|
|
158
|
+
def render(context)
|
|
159
|
+
super
|
|
160
|
+
puts @args.inspect
|
|
161
|
+
end
|
|
162
|
+
Liquid::Template.register_tag("test", self)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
class AfterBeforeBlock < Block
|
|
166
|
+
|
|
167
|
+
def render(context)
|
|
168
|
+
html = CS50::convert(super)
|
|
169
|
+
|
|
170
|
+
# Parse timestamp
|
|
171
|
+
iso8601 = CS50::strptime(@args[0]).iso8601
|
|
172
|
+
|
|
173
|
+
# Render HTML
|
|
174
|
+
"<div data-#{@tag_name}='#{iso8601}'>#{html}</div>"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
Liquid::Template.register_tag("after", self)
|
|
178
|
+
Liquid::Template.register_tag("before", self)
|
|
179
|
+
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
class AlertBlock < Block
|
|
183
|
+
|
|
184
|
+
def render(context)
|
|
185
|
+
html = CS50::convert(super)
|
|
186
|
+
alert = (["primary", "secondary", "success", "danger", "warning", "info", "light", "dark"].include? @args[0]) ? @args[0] : ""
|
|
187
|
+
"<div class='alert' data-alert='#{alert}' role='alert'>" \
|
|
188
|
+
"#{html}" \
|
|
189
|
+
"</div>"
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
Liquid::Template.register_tag("alert", self)
|
|
193
|
+
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
class CalendarTag < Tag
|
|
197
|
+
|
|
198
|
+
# https://gist.github.com/niquepa/4c59b7d52a15dde2367a
|
|
199
|
+
def initialize(tag_name, markup, options)
|
|
200
|
+
super
|
|
201
|
+
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def render(context)
|
|
205
|
+
super
|
|
206
|
+
|
|
207
|
+
# Deprecated @ctz
|
|
208
|
+
if @kwargs.key?("ctz")
|
|
209
|
+
Jekyll.logger.warn "CS50 warning: no need for @ctz anymore"
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Calendar's height
|
|
213
|
+
height = @kwargs["height"] || "480"
|
|
214
|
+
|
|
215
|
+
# Default components
|
|
216
|
+
components = {
|
|
217
|
+
height: height,
|
|
218
|
+
hl: "en_US",
|
|
219
|
+
mode: @kwargs["mode"] || "AGENDA",
|
|
220
|
+
showCalendars: "0",
|
|
221
|
+
showDate: "0",
|
|
222
|
+
showNav: "0",
|
|
223
|
+
showPrint: "0",
|
|
224
|
+
showTabs: "0",
|
|
225
|
+
showTitle: "0",
|
|
226
|
+
showTz: "1",
|
|
227
|
+
src: @args[0]
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
# Build URL
|
|
231
|
+
src = URI::HTTPS.build(:host => "calendar.google.com", :path => "/calendar/embed", :query => URI.encode_www_form(components))
|
|
232
|
+
|
|
233
|
+
# Render HTML
|
|
234
|
+
"<iframe data-calendar='#{src}' #{@kwargs['ctz'] ? 'data-ctz' : ''} style='height: #{height}px;'></iframe>"
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
Liquid::Template.register_tag("calendar", self)
|
|
238
|
+
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
class LocalTag < Tag
|
|
242
|
+
|
|
243
|
+
@@regex = "\d{4}-\d{2}-\d{2} \d{1,2}:\d{2}(:\d{2})?"
|
|
244
|
+
|
|
245
|
+
def render(context)
|
|
246
|
+
super
|
|
247
|
+
|
|
248
|
+
# Parse required argument
|
|
249
|
+
if @args.length < 1
|
|
250
|
+
raise "Too few arguments"
|
|
251
|
+
elsif @args.length > 2
|
|
252
|
+
raise "Too many arguments: #{@markup}"
|
|
253
|
+
end
|
|
254
|
+
t1 = CS50::strptime(@args[0])
|
|
255
|
+
local = t1.iso8601
|
|
256
|
+
path = CS50::format(t1)
|
|
257
|
+
|
|
258
|
+
# Parse optional argument
|
|
259
|
+
if @args.length == 2
|
|
260
|
+
t2 = CS50::strptime(@args[1], t1)
|
|
261
|
+
if t2 < t1
|
|
262
|
+
raise "Invalid interval: #{@markup}"
|
|
263
|
+
end
|
|
264
|
+
local += "/" + t2.iso8601
|
|
265
|
+
path += "/" + CS50::format(t2)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Return element
|
|
269
|
+
# Even though text node will be replaced by JavaScript, ensures Kramdown can
|
|
270
|
+
# assign an ID if this element is used as a heading
|
|
271
|
+
"<span data-local='#{local}'>#{local}</span>"
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
Liquid::Template.register_tag("local", self)
|
|
275
|
+
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
class NextTag < Tag
|
|
279
|
+
|
|
280
|
+
def render(context)
|
|
281
|
+
super
|
|
282
|
+
button = CS50::sanitize(CS50::convert((@args[0]) ? CGI.escapeHTML(@args[0]) : "Next"))
|
|
283
|
+
"<button class='btn btn-dark btn-sm' data-next type='button'>#{button}</button>"
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
Liquid::Template.register_tag("next", self)
|
|
287
|
+
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
class SpoilerBlock < Block
|
|
291
|
+
|
|
292
|
+
# https://stackoverflow.com/q/19169849/5156190
|
|
293
|
+
# https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button (re phrasing, but not interactive, content)
|
|
294
|
+
def render(context)
|
|
295
|
+
html = CS50::convert(super)
|
|
296
|
+
summary = CS50::sanitize(CS50::convert((@args[0]) ? CGI.escapeHTML(@args[0]) : "Spoiler"))
|
|
297
|
+
"<details>" \
|
|
298
|
+
"<summary>#{summary}</summary>" \
|
|
299
|
+
"#{html}" \
|
|
300
|
+
"</details>"
|
|
301
|
+
end
|
|
302
|
+
Liquid::Template.register_tag("spoiler", self)
|
|
303
|
+
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# Inspired by https://gist.github.com/niquepa/4c59b7d52a15dde2367a
|
|
307
|
+
class VideoTag < Tag
|
|
308
|
+
|
|
309
|
+
def render(context)
|
|
310
|
+
super
|
|
311
|
+
|
|
312
|
+
# Parse YouTube URL
|
|
313
|
+
if @args[0]
|
|
314
|
+
|
|
315
|
+
# If YouTube player
|
|
316
|
+
if @args[0] =~ /^https?:\/\/(?:www\.)?(?:youtube\.com\/(?:[^\/\n\s]+\/\S+\/|(?:v|e(?:mbed)?)\/|\S*?[?&]v=)|youtu\.be\/)([a-zA-Z0-9_-]{11})/
|
|
317
|
+
|
|
318
|
+
# Video's ID
|
|
319
|
+
v = $1
|
|
320
|
+
|
|
321
|
+
# Default components
|
|
322
|
+
components = {
|
|
323
|
+
"modestbranding" => "0",
|
|
324
|
+
"rel" => "0",
|
|
325
|
+
"showinfo" => "0"
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
# Supported components
|
|
329
|
+
params = CGI::parse(URI::parse(@args[0]).query || "")
|
|
330
|
+
["autoplay", "controls", "end", "index", "list", "modestbranding", "mute", "playlist", "rel", "showinfo", "start", "t"].each do |param|
|
|
331
|
+
|
|
332
|
+
# If param was provided
|
|
333
|
+
if params.key?(param)
|
|
334
|
+
|
|
335
|
+
# Add to components, but map t= to start=
|
|
336
|
+
if param == "t" and !params.key?("start")
|
|
337
|
+
components["start"] = params["t"].first
|
|
338
|
+
else
|
|
339
|
+
components[param] = params[param].first
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# Ensure playlist menu appears
|
|
345
|
+
if !params["list"].empty? or !params["playlist"].empty?
|
|
346
|
+
components["showinfo"] = "1"
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# Build URL
|
|
350
|
+
# https://support.google.com/youtube/answer/171780?hl=en
|
|
351
|
+
src = URI::HTTPS.build(:host => "www.youtube.com", :path => "/embed/#{v}", :query => URI.encode_www_form(components))
|
|
352
|
+
|
|
353
|
+
# Return HTML
|
|
354
|
+
return "<div class='ratio ratio-16x9' data-video><iframe allow='accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture' allowfullscreen class='border' data-video src='#{src}'></iframe></div>"
|
|
355
|
+
|
|
356
|
+
# If CS50 Video Player
|
|
357
|
+
elsif @args[0] =~ /^https?:\/\/video\.cs50\.io\/([^?]+)/
|
|
358
|
+
return "<iframe allow='accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture' allowfullscreen class='border' data-video src='#{@args[0]}'></iframe>"
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
# Static
|
|
363
|
+
return "<div class='ratio ratio-16x9'><img alt='static' class='border' data-video src='https://i.imgur.com/xnZ5A2u.gif'></div>"
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
Liquid::Template.register_tag("video", self)
|
|
367
|
+
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
# Disable relative_url filter, since we prepend site.baseurl to all absolute paths,
|
|
371
|
+
# but we're not monkey-patching Jekyll::Filters::URLFilters::relative_url, since it's used by
|
|
372
|
+
# https://github.com/benbalter/jekyll-relative-links/blob/master/lib/jekyll-relative-links/generator.rb
|
|
373
|
+
module Filters
|
|
374
|
+
def relative_url(input)
|
|
375
|
+
Jekyll.logger.warn "CS50 warning: no need to use relative_url with this theme"
|
|
376
|
+
input
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
Liquid::Template.register_filter(CS50::Filters)
|
|
380
|
+
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
Jekyll::Hooks.register :site, :after_reset do |site|
|
|
384
|
+
|
|
385
|
+
# Override site.url so that jekyll-redirect-from doesn't prepend it
|
|
386
|
+
site.config["url"] = nil
|
|
387
|
+
|
|
388
|
+
# Strip trailing slashes from site.baseurl
|
|
389
|
+
unless site.config["baseurl"].nil?
|
|
390
|
+
site.config["baseurl"] = site.config["baseurl"].sub(/\/+$/, "")
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
# Disable jekyll-relative-links because it prepends site.baseurl to relative links
|
|
394
|
+
if site.config.key?("plugins") and site.config["plugins"].kind_of?(Array) and site.config["plugins"].include? "jekyll-relative-links"
|
|
395
|
+
site.config["plugins"] = site.config["plugins"] - ["jekyll-relative-links"]
|
|
396
|
+
Jekyll.logger.warn "CS50 warning: no need to use jekyll-relative-links with this theme"
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
# Merge in theme's configuration
|
|
400
|
+
site.config = CS50::DEFAULTS.dup.deep_merge!(site.config, {:merge_nil_values => true}).deep_merge!(CS50::OVERRIDES, {:merge_nil_values => true})
|
|
401
|
+
site.config["cs50"] = site.config["cs50"].deep_compact
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
Jekyll::Hooks.register :site, :pre_render do |site, payload|
|
|
405
|
+
|
|
406
|
+
# Expose site to Kramdown's monkey patches
|
|
407
|
+
$site = site
|
|
408
|
+
|
|
409
|
+
# Site's time zone
|
|
410
|
+
# https://stackoverflow.com/a/58867058/5156190
|
|
411
|
+
ENV["TZ"] = site.config["cs50"]["tz"]
|
|
412
|
+
|
|
413
|
+
# Promote site.cs50.assign.* to global variables
|
|
414
|
+
begin
|
|
415
|
+
site.config["cs50"]["assign"].each do |key, value|
|
|
416
|
+
payload[key] = value
|
|
417
|
+
end
|
|
418
|
+
rescue
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
Jekyll::Hooks.register [:site], :post_render do |site|
|
|
423
|
+
|
|
424
|
+
# Paths of pages
|
|
425
|
+
$paths = site.pages.map { |page| page.url }
|
|
426
|
+
|
|
427
|
+
def relative_path(from, to)
|
|
428
|
+
|
|
429
|
+
# Resolve to relative path
|
|
430
|
+
if !to.start_with?("/")
|
|
431
|
+
to = from + to
|
|
432
|
+
end
|
|
433
|
+
relative = Pathname.new(to).relative_path_from(Pathname.new(from)).to_s
|
|
434
|
+
|
|
435
|
+
# If path doesn't end with a trailing slash (before any fragment or query)
|
|
436
|
+
if match = relative.match(/\A([^#\?]*[^#\/\?])([#\?].*)?\z/)
|
|
437
|
+
|
|
438
|
+
# Construct absolute path
|
|
439
|
+
absolute = match.captures[0] + "/"
|
|
440
|
+
if !absolute.start_with?("/")
|
|
441
|
+
absolute = from + absolute
|
|
442
|
+
end
|
|
443
|
+
absolute = Pathname.new(absolute).cleanpath.to_s + "/"
|
|
444
|
+
|
|
445
|
+
# If it should have a trailing slash
|
|
446
|
+
if $paths.include?(absolute)
|
|
447
|
+
|
|
448
|
+
# Append trailing slash (plus any query or fragment)
|
|
449
|
+
relative = match.captures[0] + "/"
|
|
450
|
+
if !match.captures[1].nil?
|
|
451
|
+
relative += match.captures[1]
|
|
452
|
+
end
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
# Return path
|
|
457
|
+
relative
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
# For each page
|
|
461
|
+
site.pages.each do |page|
|
|
462
|
+
|
|
463
|
+
# If HTML
|
|
464
|
+
if page.output_ext == ".html"
|
|
465
|
+
|
|
466
|
+
# Parse page, including its layout
|
|
467
|
+
doc = Nokogiri::HTML5.parse(page.output)
|
|
468
|
+
|
|
469
|
+
# For each node in DOM
|
|
470
|
+
doc.traverse do |node|
|
|
471
|
+
|
|
472
|
+
# If meta
|
|
473
|
+
if node.name == "meta"
|
|
474
|
+
|
|
475
|
+
# Parse url
|
|
476
|
+
if node["content"] =~ /^(\d+;\s*url=["']?)(.+)(["']?)$/i
|
|
477
|
+
|
|
478
|
+
# If a local path
|
|
479
|
+
if !/^#{URI::regexp}$/.match?($2) and !$2.start_with?("//")
|
|
480
|
+
|
|
481
|
+
# Resolve as relative path
|
|
482
|
+
node["content"] = $1 + relative_path(page.dir, $2) + $3
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
# If one of these elements
|
|
488
|
+
{"a" => "href", "img" => "src", "link" => "href", "script" => "src"}.each do |name, attribute|
|
|
489
|
+
if node.name == name
|
|
490
|
+
|
|
491
|
+
# With a non-nil attribute
|
|
492
|
+
if !node[attribute].nil?
|
|
493
|
+
|
|
494
|
+
# If a local path
|
|
495
|
+
if node[attribute] !~ /^#{URI::regexp}$/ and !node[attribute].start_with?("//")
|
|
496
|
+
|
|
497
|
+
# Resolve as relative path
|
|
498
|
+
node[attribute] = relative_path(page.dir, node[attribute])
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
end
|
|
502
|
+
end
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
page.output = doc.to_html
|
|
506
|
+
|
|
507
|
+
# If CSS
|
|
508
|
+
elsif page.output_ext == ".css"
|
|
509
|
+
|
|
510
|
+
# Resolve absolute paths in url() to relative paths
|
|
511
|
+
# https://developer.mozilla.org/en-US/docs/Web/CSS/url()
|
|
512
|
+
page.output = page.output.gsub(/url\(\s*([^\)]*)\s*\)/) do |s|
|
|
513
|
+
group = "#{$1}"
|
|
514
|
+
if match = group.match(/\A'(\/.*)'\z/) # url('/...')
|
|
515
|
+
"url('" + relative_path(page.dir, match.captures[0]).to_s + "')"
|
|
516
|
+
elsif match = group.match(/\A"(\/.*)"\z/) # url("/...")
|
|
517
|
+
'url("' + relative_path(page.dir, match.captures[0]).to_s + '")'
|
|
518
|
+
elsif match = group.match(/\A(\/(.*[^'"])?)\z/) # url(/...)
|
|
519
|
+
"url(" + relative_path(page.dir, match.captures[0]).to_s + ")"
|
|
520
|
+
else
|
|
521
|
+
s
|
|
522
|
+
end
|
|
523
|
+
end
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
# TODO: In offline mode, base64-encode images, embed CSS (in style tags) and JS (in script tags), a la
|
|
527
|
+
# https://github.com/jekyll/jekyll-mentions/blob/master/lib/jekyll-mentions.rb and
|
|
528
|
+
# https://github.com/jekyll/jemoji/blob/master/lib/jemoji.rb
|
|
529
|
+
|
|
530
|
+
end
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
# https://github.com/jekyll/jekyll-redirect-from/blob/master/lib/jekyll-redirect-from/redirect_page.rb
|
|
534
|
+
module JekyllRedirectFrom
|
|
535
|
+
class RedirectPage < Jekyll::Page
|
|
536
|
+
def set_paths(from, to)
|
|
537
|
+
@context ||= context
|
|
538
|
+
from = ensure_leading_slash(from)
|
|
539
|
+
data.merge!(
|
|
540
|
+
"permalink" => from,
|
|
541
|
+
"redirect" => {
|
|
542
|
+
"from" => from,
|
|
543
|
+
"to" => to # Omit call to absolute_url, else redirect_to misinterprets relative URLs as absolute from /
|
|
544
|
+
}
|
|
545
|
+
)
|
|
546
|
+
end
|
|
547
|
+
end
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
module Kramdown
|
|
551
|
+
module Parser
|
|
552
|
+
class GFM < Kramdown::Parser::Kramdown
|
|
553
|
+
|
|
554
|
+
def parse_autolink
|
|
555
|
+
super
|
|
556
|
+
|
|
557
|
+
# Get autolink
|
|
558
|
+
current_link = @tree.children.select{ |element| [:a].include?(element.type) }.last
|
|
559
|
+
unless current_link.nil?
|
|
560
|
+
|
|
561
|
+
# Hide scheme and trailing slash
|
|
562
|
+
current_link.children[0].value = current_link.children[0].value.gsub(/^https?:\/\/(www.)?|\/$/, "")
|
|
563
|
+
end
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
def parse_link
|
|
567
|
+
super
|
|
568
|
+
|
|
569
|
+
# Get link
|
|
570
|
+
current_link = @tree.children.select{ |element| [:a].include?(element.type) }.last
|
|
571
|
+
unless current_link.nil?
|
|
572
|
+
|
|
573
|
+
# If inline link ends with .md
|
|
574
|
+
if match = current_link.attr["href"].match(/\A([^\s]*)\.md(\s*.*)\z/)
|
|
575
|
+
|
|
576
|
+
# Rewrite as /, just as jekyll-relative-links does
|
|
577
|
+
current_link.attr["href"] = match.captures[0] + "/" + match.captures[1]
|
|
578
|
+
end
|
|
579
|
+
end
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
# Remember list markers
|
|
583
|
+
def parse_list
|
|
584
|
+
super
|
|
585
|
+
|
|
586
|
+
# Get list
|
|
587
|
+
current_list = @tree.children.select{ |element| [:ul].include?(element.type) }.last
|
|
588
|
+
unless current_list.nil?
|
|
589
|
+
|
|
590
|
+
# For each li
|
|
591
|
+
current_list.children.each do |li|
|
|
592
|
+
|
|
593
|
+
# Line number of li
|
|
594
|
+
location = li.options[:location]
|
|
595
|
+
|
|
596
|
+
# Determine marker, might be nested inside of a blockquote
|
|
597
|
+
# https://kramdown.gettalong.org/syntax.html#blockquotes
|
|
598
|
+
li.attr["data-marker"] = @source.lines[location-1].sub(/^[\s>]*/, "")[0]
|
|
599
|
+
end
|
|
600
|
+
end
|
|
601
|
+
true
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
end
|
|
605
|
+
end
|
|
606
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Avoids `cannot load such file -- liquid-tag-parser`
|