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.
Files changed (183) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/_includes/alert.md +0 -0
  4. data/_includes/footer.md +0 -0
  5. data/_includes/head.html +0 -0
  6. data/_includes/header.md +1 -0
  7. data/_includes/nav.md +0 -0
  8. data/_layouts/page.html +308 -0
  9. data/_layouts/redirect.html +12 -0
  10. data/_sass/bootstrap/LICENSE +22 -0
  11. data/_sass/bootstrap/scss/_accordion.scss +146 -0
  12. data/_sass/bootstrap/scss/_alert.scss +71 -0
  13. data/_sass/bootstrap/scss/_badge.scss +40 -0
  14. data/_sass/bootstrap/scss/_breadcrumb.scss +42 -0
  15. data/_sass/bootstrap/scss/_button-group.scss +142 -0
  16. data/_sass/bootstrap/scss/_buttons.scss +158 -0
  17. data/_sass/bootstrap/scss/_card.scss +234 -0
  18. data/_sass/bootstrap/scss/_carousel.scss +229 -0
  19. data/_sass/bootstrap/scss/_close.scss +40 -0
  20. data/_sass/bootstrap/scss/_code.scss +48 -0
  21. data/_sass/bootstrap/scss/_containers.scss +41 -0
  22. data/_sass/bootstrap/scss/_custom-forms.scss +526 -0
  23. data/_sass/bootstrap/scss/_dropdown.scss +248 -0
  24. data/_sass/bootstrap/scss/_forms.scss +9 -0
  25. data/_sass/bootstrap/scss/_functions.scss +302 -0
  26. data/_sass/bootstrap/scss/_grid.scss +33 -0
  27. data/_sass/bootstrap/scss/_helpers.scss +10 -0
  28. data/_sass/bootstrap/scss/_images.scss +42 -0
  29. data/_sass/bootstrap/scss/_input-group.scss +208 -0
  30. data/_sass/bootstrap/scss/_jumbotron.scss +17 -0
  31. data/_sass/bootstrap/scss/_list-group.scss +191 -0
  32. data/_sass/bootstrap/scss/_maps.scss +54 -0
  33. data/_sass/bootstrap/scss/_media.scss +8 -0
  34. data/_sass/bootstrap/scss/_mixins.scss +43 -0
  35. data/_sass/bootstrap/scss/_modal.scss +237 -0
  36. data/_sass/bootstrap/scss/_nav.scss +172 -0
  37. data/_sass/bootstrap/scss/_navbar.scss +274 -0
  38. data/_sass/bootstrap/scss/_offcanvas.scss +143 -0
  39. data/_sass/bootstrap/scss/_pagination.scss +111 -0
  40. data/_sass/bootstrap/scss/_placeholders.scss +51 -0
  41. data/_sass/bootstrap/scss/_popover.scss +198 -0
  42. data/_sass/bootstrap/scss/_print.scss +141 -0
  43. data/_sass/bootstrap/scss/_progress.scss +59 -0
  44. data/_sass/bootstrap/scss/_reboot.scss +610 -0
  45. data/_sass/bootstrap/scss/_root.scss +76 -0
  46. data/_sass/bootstrap/scss/_spinners.scss +85 -0
  47. data/_sass/bootstrap/scss/_tables.scss +164 -0
  48. data/_sass/bootstrap/scss/_toasts.scss +70 -0
  49. data/_sass/bootstrap/scss/_tooltip.scss +122 -0
  50. data/_sass/bootstrap/scss/_transitions.scss +27 -0
  51. data/_sass/bootstrap/scss/_type.scss +104 -0
  52. data/_sass/bootstrap/scss/_utilities.scss +647 -0
  53. data/_sass/bootstrap/scss/_variables.scss +1631 -0
  54. data/_sass/bootstrap/scss/bootstrap-grid.scss +68 -0
  55. data/_sass/bootstrap/scss/bootstrap-reboot.scss +14 -0
  56. data/_sass/bootstrap/scss/bootstrap-utilities.scss +19 -0
  57. data/_sass/bootstrap/scss/bootstrap.scss +54 -0
  58. data/_sass/bootstrap/scss/forms/_floating-labels.scss +70 -0
  59. data/_sass/bootstrap/scss/forms/_form-check.scss +175 -0
  60. data/_sass/bootstrap/scss/forms/_form-control.scss +189 -0
  61. data/_sass/bootstrap/scss/forms/_form-range.scss +91 -0
  62. data/_sass/bootstrap/scss/forms/_form-select.scss +71 -0
  63. data/_sass/bootstrap/scss/forms/_form-text.scss +11 -0
  64. data/_sass/bootstrap/scss/forms/_input-group.scss +121 -0
  65. data/_sass/bootstrap/scss/forms/_labels.scss +36 -0
  66. data/_sass/bootstrap/scss/forms/_validation.scss +12 -0
  67. data/_sass/bootstrap/scss/helpers/_clearfix.scss +3 -0
  68. data/_sass/bootstrap/scss/helpers/_color-bg.scss +10 -0
  69. data/_sass/bootstrap/scss/helpers/_colored-links.scss +12 -0
  70. data/_sass/bootstrap/scss/helpers/_position.scss +36 -0
  71. data/_sass/bootstrap/scss/helpers/_ratio.scss +26 -0
  72. data/_sass/bootstrap/scss/helpers/_stacks.scss +15 -0
  73. data/_sass/bootstrap/scss/helpers/_stretched-link.scss +15 -0
  74. data/_sass/bootstrap/scss/helpers/_text-truncation.scss +7 -0
  75. data/_sass/bootstrap/scss/helpers/_visually-hidden.scss +8 -0
  76. data/_sass/bootstrap/scss/helpers/_vr.scss +8 -0
  77. data/_sass/bootstrap/scss/mixins/_alert.scss +15 -0
  78. data/_sass/bootstrap/scss/mixins/_backdrop.scss +14 -0
  79. data/_sass/bootstrap/scss/mixins/_background-variant.scss +23 -0
  80. data/_sass/bootstrap/scss/mixins/_badge.scss +17 -0
  81. data/_sass/bootstrap/scss/mixins/_border-radius.scss +78 -0
  82. data/_sass/bootstrap/scss/mixins/_box-shadow.scss +18 -0
  83. data/_sass/bootstrap/scss/mixins/_breakpoints.scss +127 -0
  84. data/_sass/bootstrap/scss/mixins/_buttons.scss +71 -0
  85. data/_sass/bootstrap/scss/mixins/_caret.scss +64 -0
  86. data/_sass/bootstrap/scss/mixins/_clearfix.scss +9 -0
  87. data/_sass/bootstrap/scss/mixins/_color-scheme.scss +7 -0
  88. data/_sass/bootstrap/scss/mixins/_container.scss +11 -0
  89. data/_sass/bootstrap/scss/mixins/_deprecate.scss +10 -0
  90. data/_sass/bootstrap/scss/mixins/_float.scss +14 -0
  91. data/_sass/bootstrap/scss/mixins/_forms.scss +152 -0
  92. data/_sass/bootstrap/scss/mixins/_gradients.scss +47 -0
  93. data/_sass/bootstrap/scss/mixins/_grid-framework.scss +80 -0
  94. data/_sass/bootstrap/scss/mixins/_grid.scss +151 -0
  95. data/_sass/bootstrap/scss/mixins/_hover.scss +37 -0
  96. data/_sass/bootstrap/scss/mixins/_image.scss +16 -0
  97. data/_sass/bootstrap/scss/mixins/_list-group.scss +24 -0
  98. data/_sass/bootstrap/scss/mixins/_lists.scss +7 -0
  99. data/_sass/bootstrap/scss/mixins/_nav-divider.scss +11 -0
  100. data/_sass/bootstrap/scss/mixins/_pagination.scss +10 -0
  101. data/_sass/bootstrap/scss/mixins/_reset-text.scss +17 -0
  102. data/_sass/bootstrap/scss/mixins/_resize.scss +6 -0
  103. data/_sass/bootstrap/scss/mixins/_screen-reader.scss +34 -0
  104. data/_sass/bootstrap/scss/mixins/_size.scss +7 -0
  105. data/_sass/bootstrap/scss/mixins/_table-row.scss +39 -0
  106. data/_sass/bootstrap/scss/mixins/_table-variants.scss +24 -0
  107. data/_sass/bootstrap/scss/mixins/_text-emphasis.scss +17 -0
  108. data/_sass/bootstrap/scss/mixins/_text-hide.scss +11 -0
  109. data/_sass/bootstrap/scss/mixins/_text-truncate.scss +8 -0
  110. data/_sass/bootstrap/scss/mixins/_transition.scss +26 -0
  111. data/_sass/bootstrap/scss/mixins/_utilities.scss +92 -0
  112. data/_sass/bootstrap/scss/mixins/_visibility.scss +8 -0
  113. data/_sass/bootstrap/scss/mixins/_visually-hidden.scss +29 -0
  114. data/_sass/bootstrap/scss/utilities/_align.scss +8 -0
  115. data/_sass/bootstrap/scss/utilities/_api.scss +47 -0
  116. data/_sass/bootstrap/scss/utilities/_background.scss +19 -0
  117. data/_sass/bootstrap/scss/utilities/_borders.scss +75 -0
  118. data/_sass/bootstrap/scss/utilities/_clearfix.scss +3 -0
  119. data/_sass/bootstrap/scss/utilities/_display.scss +26 -0
  120. data/_sass/bootstrap/scss/utilities/_embed.scss +39 -0
  121. data/_sass/bootstrap/scss/utilities/_flex.scss +51 -0
  122. data/_sass/bootstrap/scss/utilities/_float.scss +11 -0
  123. data/_sass/bootstrap/scss/utilities/_interactions.scss +5 -0
  124. data/_sass/bootstrap/scss/utilities/_overflow.scss +5 -0
  125. data/_sass/bootstrap/scss/utilities/_position.scss +32 -0
  126. data/_sass/bootstrap/scss/utilities/_screenreaders.scss +11 -0
  127. data/_sass/bootstrap/scss/utilities/_shadows.scss +6 -0
  128. data/_sass/bootstrap/scss/utilities/_sizing.scss +20 -0
  129. data/_sass/bootstrap/scss/utilities/_spacing.scss +73 -0
  130. data/_sass/bootstrap/scss/utilities/_stretched-link.scss +19 -0
  131. data/_sass/bootstrap/scss/utilities/_text.scss +72 -0
  132. data/_sass/bootstrap/scss/utilities/_visibility.scss +13 -0
  133. data/_sass/bootstrap/scss/vendor/_rfs.scss +354 -0
  134. data/_sass/jekyll-theme-cs50.scss +298 -0
  135. data/_sass/page.scss +228 -0
  136. data/_sass/rouge.scss +35 -0
  137. data/assets/@fortawesome/fontawesome-free/css/all.min.css +5 -0
  138. data/assets/@fortawesome/fontawesome-free/webfonts/fa-brands-400.eot +0 -0
  139. data/assets/@fortawesome/fontawesome-free/webfonts/fa-brands-400.svg +3717 -0
  140. data/assets/@fortawesome/fontawesome-free/webfonts/fa-brands-400.ttf +0 -0
  141. data/assets/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff +0 -0
  142. data/assets/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff2 +0 -0
  143. data/assets/@fortawesome/fontawesome-free/webfonts/fa-regular-400.eot +0 -0
  144. data/assets/@fortawesome/fontawesome-free/webfonts/fa-regular-400.svg +801 -0
  145. data/assets/@fortawesome/fontawesome-free/webfonts/fa-regular-400.ttf +0 -0
  146. data/assets/@fortawesome/fontawesome-free/webfonts/fa-regular-400.woff +0 -0
  147. data/assets/@fortawesome/fontawesome-free/webfonts/fa-regular-400.woff2 +0 -0
  148. data/assets/@fortawesome/fontawesome-free/webfonts/fa-solid-900.eot +0 -0
  149. data/assets/@fortawesome/fontawesome-free/webfonts/fa-solid-900.svg +5034 -0
  150. data/assets/@fortawesome/fontawesome-free/webfonts/fa-solid-900.ttf +0 -0
  151. data/assets/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff +0 -0
  152. data/assets/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff2 +0 -0
  153. data/assets/algolia-min.css +1 -0
  154. data/assets/algoliasearch/dist/algoliasearch-lite.umd.js +2 -0
  155. data/assets/algoliasearchLite.min.js +3 -0
  156. data/assets/bootstrap/LICENSE +22 -0
  157. data/assets/bootstrap/dist/js/bootstrap.bundle.min.js +7 -0
  158. data/assets/bootstrap/dist/js/bootstrap.bundle.min.js.map +1 -0
  159. data/assets/bootstrap/dist/js/bootstrap.min.js +7 -0
  160. data/assets/bootstrap-table/LICENSE +21 -0
  161. data/assets/bootstrap-table/dist/bootstrap-table.min.css +10 -0
  162. data/assets/bootstrap-table/dist/bootstrap-table.min.js +10 -0
  163. data/assets/bootstrap-table/dist/extensions/mobile/bootstrap-table-mobile.min.js +10 -0
  164. data/assets/instantsearch.js/LICENSE +21 -0
  165. data/assets/instantsearch.js/dist/instantsearch.production.min.js +3 -0
  166. data/assets/instantsearch.js/dist/instantsearch.production.min.js.map +1 -0
  167. data/assets/instantsearch.production.min.js +3 -0
  168. data/assets/jekyll-theme-cs50.js +620 -0
  169. data/assets/jquery/LICENSE.txt +20 -0
  170. data/assets/jquery/dist/jquery.min.js +2 -0
  171. data/assets/luxon.min.js +1 -0
  172. data/assets/moment-timezone-with-data.min.js +1 -0
  173. data/assets/moment.min.js +1 -0
  174. data/assets/page.js +4 -0
  175. data/assets/page.scss +4 -0
  176. data/assets/popper.js/dist/umd/popper.min.js +5 -0
  177. data/assets/reset-min.css +1 -0
  178. data/assets/scratchblocks.min.js +11 -0
  179. data/assets/scratchblocks.min.js.map +1 -0
  180. data/lib/jekyll-theme-cs50/constants.rb +59 -0
  181. data/lib/jekyll-theme-cs50.rb +606 -0
  182. data/lib/liquid-tag-parser.rb +1 -0
  183. 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`