hematite 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +58 -0
  4. data/_config.yml +33 -0
  5. data/_data/strings/en.yml +32 -0
  6. data/_data/strings/es.yml +33 -0
  7. data/_includes/img/hamburger_menu.svg +78 -0
  8. data/_includes/img/search_icon.svg +99 -0
  9. data/_includes/katex_includes.html +26 -0
  10. data/_includes/nav/page_navigation.html +10 -0
  11. data/_includes/nav/pages_list.html +17 -0
  12. data/_includes/nav/pinned_page.html +12 -0
  13. data/_includes/nav/sidebar.html +25 -0
  14. data/_layouts/calendar.html +36 -0
  15. data/_layouts/default.html +31 -0
  16. data/_layouts/page.html +5 -0
  17. data/_layouts/post.html +42 -0
  18. data/_sass/_animations.scss +16 -0
  19. data/_sass/_calendar.scss +63 -0
  20. data/_sass/_colors.scss +73 -0
  21. data/_sass/_elements.scss +125 -0
  22. data/_sass/_layout.scss +224 -0
  23. data/_sass/_nav.scss +180 -0
  24. data/_sass/_rogue.scss +50 -0
  25. data/_sass/_sizes.scss +18 -0
  26. data/_sass/hematite.scss +10 -0
  27. data/assets/html/all_tags.html +26 -0
  28. data/assets/img/favicon.svg +12 -0
  29. data/assets/js/AnimationUtil.mjs +72 -0
  30. data/assets/js/AsyncUtil.mjs +18 -0
  31. data/assets/js/DateUtil.mjs +123 -0
  32. data/assets/js/PageAlert.mjs +143 -0
  33. data/assets/js/UrlHelper.mjs +118 -0
  34. data/assets/js/assertions.mjs +9 -0
  35. data/assets/js/dropdownExpander.mjs +78 -0
  36. data/assets/js/layout/calendar.mjs +478 -0
  37. data/assets/js/layout/post.mjs +65 -0
  38. data/assets/js/linkButtonGenerator.mjs +45 -0
  39. data/assets/js/main.mjs +19 -0
  40. data/assets/js/search.mjs +358 -0
  41. data/assets/js/sidebar.mjs +97 -0
  42. data/assets/js/string_data.mjs +19 -0
  43. data/assets/js/strings.mjs +167 -0
  44. data/assets/plugin/katex/README.md +119 -0
  45. data/assets/plugin/katex/contrib/auto-render.min.js +1 -0
  46. data/assets/plugin/katex/contrib/copy-tex.min.css +1 -0
  47. data/assets/plugin/katex/contrib/copy-tex.min.js +1 -0
  48. data/assets/plugin/katex/contrib/mathtex-script-type.min.js +1 -0
  49. data/assets/plugin/katex/contrib/mhchem.min.js +1 -0
  50. data/assets/plugin/katex/contrib/render-a11y-string.min.js +1 -0
  51. data/assets/plugin/katex/fonts/KaTeX_AMS-Regular.ttf +0 -0
  52. data/assets/plugin/katex/fonts/KaTeX_AMS-Regular.woff +0 -0
  53. data/assets/plugin/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  54. data/assets/plugin/katex/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  55. data/assets/plugin/katex/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  56. data/assets/plugin/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  57. data/assets/plugin/katex/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  58. data/assets/plugin/katex/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  59. data/assets/plugin/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  60. data/assets/plugin/katex/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  61. data/assets/plugin/katex/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  62. data/assets/plugin/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  63. data/assets/plugin/katex/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  64. data/assets/plugin/katex/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  65. data/assets/plugin/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  66. data/assets/plugin/katex/fonts/KaTeX_Main-Bold.ttf +0 -0
  67. data/assets/plugin/katex/fonts/KaTeX_Main-Bold.woff +0 -0
  68. data/assets/plugin/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
  69. data/assets/plugin/katex/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  70. data/assets/plugin/katex/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  71. data/assets/plugin/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  72. data/assets/plugin/katex/fonts/KaTeX_Main-Italic.ttf +0 -0
  73. data/assets/plugin/katex/fonts/KaTeX_Main-Italic.woff +0 -0
  74. data/assets/plugin/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
  75. data/assets/plugin/katex/fonts/KaTeX_Main-Regular.ttf +0 -0
  76. data/assets/plugin/katex/fonts/KaTeX_Main-Regular.woff +0 -0
  77. data/assets/plugin/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
  78. data/assets/plugin/katex/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  79. data/assets/plugin/katex/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  80. data/assets/plugin/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  81. data/assets/plugin/katex/fonts/KaTeX_Math-Italic.ttf +0 -0
  82. data/assets/plugin/katex/fonts/KaTeX_Math-Italic.woff +0 -0
  83. data/assets/plugin/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
  84. data/assets/plugin/katex/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  85. data/assets/plugin/katex/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  86. data/assets/plugin/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  87. data/assets/plugin/katex/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  88. data/assets/plugin/katex/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  89. data/assets/plugin/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  90. data/assets/plugin/katex/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  91. data/assets/plugin/katex/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  92. data/assets/plugin/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  93. data/assets/plugin/katex/fonts/KaTeX_Script-Regular.ttf +0 -0
  94. data/assets/plugin/katex/fonts/KaTeX_Script-Regular.woff +0 -0
  95. data/assets/plugin/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
  96. data/assets/plugin/katex/fonts/KaTeX_Size1-Regular.ttf +0 -0
  97. data/assets/plugin/katex/fonts/KaTeX_Size1-Regular.woff +0 -0
  98. data/assets/plugin/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  99. data/assets/plugin/katex/fonts/KaTeX_Size2-Regular.ttf +0 -0
  100. data/assets/plugin/katex/fonts/KaTeX_Size2-Regular.woff +0 -0
  101. data/assets/plugin/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  102. data/assets/plugin/katex/fonts/KaTeX_Size3-Regular.ttf +0 -0
  103. data/assets/plugin/katex/fonts/KaTeX_Size3-Regular.woff +0 -0
  104. data/assets/plugin/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  105. data/assets/plugin/katex/fonts/KaTeX_Size4-Regular.ttf +0 -0
  106. data/assets/plugin/katex/fonts/KaTeX_Size4-Regular.woff +0 -0
  107. data/assets/plugin/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  108. data/assets/plugin/katex/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  109. data/assets/plugin/katex/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  110. data/assets/plugin/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  111. data/assets/plugin/katex/katex.min.css +1 -0
  112. data/assets/plugin/katex/katex.min.js +1 -0
  113. data/assets/search_data.json +36 -0
  114. data/assets/style.scss +15 -0
  115. metadata +170 -0
@@ -0,0 +1,478 @@
1
+ ---
2
+ ---
3
+
4
+ import { stringLookup } from "../strings.mjs";
5
+ import { announceForAccessibility } from "../PageAlert.mjs";
6
+ import DateUtil from "../DateUtil.mjs";
7
+
8
+ const DATE_SPEC_ELEM_TAG = "h1";
9
+ const LIST_SPEC_ELEM_TAG = "ul";
10
+ const CALENDAR_DATE_FMT_OPTIONS =
11
+ {{ site.hematite.short_date_format | default: nil | jsonify }}
12
+ ?? { day: "2-digit", year: "2-digit", month: "2-digit" };
13
+
14
+ // Used for generating unique IDs
15
+ let nextViewModeSelectorId = 0;
16
+
17
+ /// Pull calendar data from [elem]. If [formatElemLabels], apply special calendar markup
18
+ /// to the contents of [elem], changing [elem].
19
+ function getCalendarData(elem, formatElemLabels) {
20
+ let result = [];
21
+
22
+ // Last date set by a header we've encountered
23
+ let lastDate = null;
24
+ let lastHeaderId = "";
25
+
26
+ for (const child of elem.children) {
27
+ let tagName = child.tagName.toLowerCase();
28
+
29
+ if (tagName == DATE_SPEC_ELEM_TAG) {
30
+ // Remove '-rd' and '-th' suffixes.
31
+ try {
32
+ lastDate = DateUtil.parse(child.innerText);
33
+ lastHeaderId = child.getAttribute("id");
34
+ }
35
+ catch (e) {
36
+ child.innerText = stringLookup(`invalid_date`, dateText);
37
+ lastDate = null;
38
+ }
39
+ }
40
+
41
+ if (tagName == LIST_SPEC_ELEM_TAG && lastDate) {
42
+ let listItems = [];
43
+
44
+ for (const item of child.children) {
45
+ if (item.tagName.toLowerCase() == "li") {
46
+ let agendaItem = new AgendaItem(item.innerHTML);
47
+ listItems.push(agendaItem);
48
+
49
+ // Reformat tags in the item, if requested.
50
+ if (formatElemLabels) {
51
+ let itemTags = document.createElement('span');
52
+ let itemContent = document.createElement('span');
53
+
54
+ for (const tag of agendaItem.tags) {
55
+ let tagLink = document.createElement('a');
56
+ tagLink.classList.add('tag');
57
+
58
+ tagLink.href = `{{ 'assets/html/all_tags.html' | relative_url }}#tag__${escape(tag)}`;
59
+ tagLink.innerText = tag;
60
+ tagLink.classList.add(AgendaItem.getTagClass(tag));
61
+
62
+ itemTags.appendChild(tagLink);
63
+ }
64
+
65
+ itemContent.innerHTML = agendaItem.html;
66
+ item.replaceChildren(itemTags, itemContent);
67
+ }
68
+ }
69
+ }
70
+
71
+ result.push({
72
+ agenda: listItems,
73
+ date: lastDate,
74
+ link: `#${lastHeaderId}`
75
+ });
76
+ }
77
+ }
78
+
79
+ result.sort(AgendaItem.compare);
80
+
81
+ return result;
82
+ }
83
+
84
+ /// Adds post data to the given calendar data item.
85
+ function addPostData(data) {
86
+ let postDates = [
87
+ {% for item in site.posts %}
88
+ {{ item.date | date_to_xmlschema | jsonify }},
89
+ {% endfor %}
90
+ ].map((dateItem) =>
91
+ DateUtil.parse(dateItem)
92
+ );
93
+ let postTags = {{ site.posts | map: "tags" | jsonify }};
94
+ let postTitles = {{ site.posts | map: "title" | jsonify }};
95
+ let postLinks = [
96
+ {% for item in site.posts %}
97
+ {{ item.url | relative_url | jsonify }},
98
+ {% endfor %}
99
+ ];
100
+
101
+ if (postDates.length != postTitles.length || postLinks.length != postTitles.length) {
102
+ console.warn("Some post data array has a different length, refusing to show posts.");
103
+ return;
104
+ }
105
+
106
+ for (let i = 0; i < postDates.length; i++) {
107
+ data.push({
108
+ date: postDates[i],
109
+ agenda: [ AgendaItem.forPost(postTitles[i], postTags[i], postLinks[i]) ],
110
+ link: undefined,
111
+ });
112
+ }
113
+
114
+ data.sort(AgendaItem.compare);
115
+
116
+ let newData = [];
117
+ let currentItem;
118
+
119
+ for (let i = 0; i < data.length; i++) {
120
+ if (!currentItem) {
121
+ currentItem = { date: data[i].date, agenda: [], };
122
+ newData.push(currentItem);
123
+ }
124
+
125
+ if (DateUtil.datesAreOnSameDay(currentItem.date, data[i].date)) {
126
+ for (const item of data[i].agenda) {
127
+ currentItem.agenda.push(item);
128
+ }
129
+
130
+ currentItem.link ??= data[i].link;
131
+ } else {
132
+ currentItem = data[i];
133
+ newData.push(currentItem);
134
+ }
135
+ }
136
+
137
+ return newData;
138
+ }
139
+
140
+ class AgendaItem {
141
+ /// Creates an AgendaItem from text [data] which is made up of
142
+ /// HTML and leading format tags.
143
+ constructor(data) {
144
+ let htmlStart = 0;
145
+
146
+ this.tags = [];
147
+ for (const match of data.matchAll(/[\[](\w+)[\]]/g)) {
148
+ this.tags.push(match[1]);
149
+ htmlStart = match.index + match[0].length;
150
+ }
151
+ this.html = data.substring(htmlStart);
152
+ }
153
+ }
154
+
155
+ AgendaItem.forPost = (title, tags, url) => {
156
+ let result = new AgendaItem('');
157
+ title = title
158
+ .replaceAll(/[>]/g, '&gt;')
159
+ .replaceAll(/[<]/g, '&lt;');
160
+ result.html = `<a href="${url}">${title}</a>`;
161
+
162
+ result.tags = [...tags];
163
+
164
+ return result;
165
+ };
166
+
167
+ AgendaItem.getTagClass = (tag) => {
168
+ return `calendarTag__${tag}`;
169
+ };
170
+
171
+ AgendaItem.compare = (a, b) => {
172
+ if (a.date < b.date) {
173
+ return -1;
174
+ }
175
+
176
+ if (a.date > b.date) {
177
+ return 1;
178
+ }
179
+
180
+ return 0;
181
+ };
182
+
183
+ class Calendar {
184
+ VIEW_MODE_MONTH = 1;
185
+ VIEW_MODE_WEEK = 2;
186
+ VIEW_MODE_DAY = 3;
187
+
188
+ constructor(data, containerElem, headerElem) {
189
+ this.mode_ = this.VIEW_MODE_WEEK;
190
+ this.container_ = document.createElement("div");
191
+ this.header_ = headerElem ?? null;
192
+ this.data_ = data;
193
+ this.anchorDate_ = this.closestItemDateTo_(new Date())?.date ?? new Date();
194
+
195
+ this.updateLayout_();
196
+
197
+ containerElem.appendChild(this.container_);
198
+ }
199
+
200
+ /// Returns the item with closest date to [searchDate].
201
+ closestItemDateTo_(searchDate) {
202
+ let i = Math.floor(this.data_.length / 2);
203
+ let lastI;
204
+ let searchStart = 0;
205
+ let searchStop = this.data_.length;
206
+
207
+ let isBetterMatch = (otherIdx) => {
208
+ if (0 > otherIdx || otherIdx >= this.data_.length) {
209
+ return false;
210
+ }
211
+
212
+ let dtOther =
213
+ this.data_[otherIdx].date.getTime() - searchDate.getTime();
214
+ let dtCurrent =
215
+ this.data_[i].date.getTime() - searchDate.getTime();
216
+
217
+ if (Math.abs(dtOther) < Math.abs(dtCurrent)) {
218
+ return true;
219
+ }
220
+ return false;
221
+ };
222
+
223
+
224
+ // Binary search
225
+ do {
226
+ lastI = i;
227
+
228
+ // Bounds check
229
+ if (0 > i || i >= this.data_.length) {
230
+ break;
231
+ }
232
+
233
+ let current = this.data_[i];
234
+ if (DateUtil.datesAreOnSameDay(current.date, searchDate)) {
235
+ return current;
236
+ }
237
+
238
+ if (current.date > searchDate) {
239
+ searchStop = i;
240
+ }
241
+ else if (current.date < searchDate) {
242
+ searchStart = i + 1;
243
+ }
244
+
245
+ i = Math.floor((searchStart + searchStop) / 2);
246
+ }
247
+ while (lastI != i);
248
+
249
+ if (0 <= i && i < this.data_.length) {
250
+ if (isBetterMatch(i + 1)) {
251
+ return this.data_[i + 1];
252
+ }
253
+ else if (isBetterMatch(i - 1)) {
254
+ return this.data_[i - 1];
255
+ }
256
+ return this.data_[i];
257
+ }
258
+
259
+ return null;
260
+ }
261
+
262
+ /// Get an item in [data_] from [date], if [searchDate]
263
+ /// is the same day as the requested item. If there
264
+ /// are multiple matches, one of them is returned.
265
+ lookupItem_(searchDate) {
266
+ let closest = this.closestItemDateTo_(searchDate);
267
+ if (closest == null || !DateUtil.datesAreOnSameDay(closest.date, searchDate)) {
268
+ return null;
269
+ }
270
+
271
+ return closest;
272
+ }
273
+
274
+ createCardForDay_(date) {
275
+ let card = document.createElement("div");
276
+ let header = document.createElement("a");
277
+ let details = document.createElement("ul");
278
+
279
+ let content = this.lookupItem_(date);
280
+ card.classList.add("calendar-card");
281
+
282
+ header.innerText = date.toLocaleDateString(CALENDAR_DATE_FMT_OPTIONS);
283
+
284
+ if (content) {
285
+ if (content.link) {
286
+ header.href = content.link;
287
+ }
288
+
289
+ for (let itemData of content.agenda) {
290
+ let container = document.createElement("li");
291
+
292
+ container.innerHTML = itemData.html;
293
+ for (const tag of itemData.tags) {
294
+ container.classList.add(AgendaItem.getTagClass(tag));
295
+ }
296
+
297
+ details.appendChild(container);
298
+ }
299
+ }
300
+
301
+ if (DateUtil.dateIsToday(date)) {
302
+ card.classList.add("today");
303
+ }
304
+
305
+ card.appendChild(header);
306
+ card.appendChild(details);
307
+ return card;
308
+ }
309
+
310
+ updateLayout_() {
311
+ this.content_?.remove();
312
+ this.content_ = document.createElement("div");
313
+ this.content_.classList.add("calendar-content");
314
+
315
+ let startDate = this.anchorDate_;
316
+ let endDate = DateUtil.nextDay(this.anchorDate_);
317
+
318
+ if (this.mode_ == this.VIEW_MODE_WEEK) {
319
+ startDate = DateUtil.beginningOfWeek(this.anchorDate_);
320
+ endDate = DateUtil.nextWeek(startDate);
321
+ this.content_.classList.add("week-display");
322
+ }
323
+ else if (this.mode_ == this.VIEW_MODE_MONTH) {
324
+ startDate = DateUtil.beginningOfWeek(DateUtil.beginningOfMonth(this.anchorDate_));
325
+ endDate = DateUtil.beginningOfMonth(DateUtil.nextMonth(this.anchorDate_));
326
+ this.content_.classList.add("month-display");
327
+ console.log(startDate, endDate);
328
+ }
329
+
330
+ for (const date of DateUtil.daysInRange(startDate, endDate)) {
331
+ this.content_.appendChild(this.createCardForDay_(date));
332
+ }
333
+
334
+ if (this.header_) {
335
+ let startStr = startDate.toLocaleDateString(CALENDAR_DATE_FMT_OPTIONS);
336
+ let endStr = endDate.toLocaleDateString(CALENDAR_DATE_FMT_OPTIONS);
337
+ this.header_.innerText = stringLookup(`calendar_header_date_range`, startStr, endStr);
338
+ }
339
+ this.container_.appendChild(this.content_);
340
+ }
341
+
342
+ /// Get the next anchor (i.e. advance the anchor by a week, month,
343
+ /// day, etc.
344
+ getNextAnchor_() {
345
+ let result = this.anchorDate_;
346
+
347
+ if (this.mode_ == this.VIEW_MODE_WEEK) {
348
+ result = DateUtil.nextWeek(this.anchorDate_);
349
+ }
350
+ else if (this.mode_ == this.VIEW_MODE_DAY) {
351
+ result = DateUtil.nextDay(this.anchorDate_);
352
+ }
353
+ else {
354
+ result = DateUtil.nextMonth(this.anchorDate_);
355
+ }
356
+
357
+ return result;
358
+ }
359
+
360
+ getPrevAnchor_() {
361
+ let result = this.anchorDate_;
362
+
363
+ if (this.mode_ == this.VIEW_MODE_WEEK) {
364
+ result = DateUtil.prevWeek(this.anchorDate_);
365
+ }
366
+ else if (this.mode_ == this.VIEW_MODE_DAY) {
367
+ result = DateUtil.prevDay(this.anchorDate_);
368
+ }
369
+ else {
370
+ result = DateUtil.prevMonth(this.anchorDate_);
371
+ }
372
+
373
+ return result;
374
+ }
375
+
376
+ /// Transition to the next time unit.
377
+ next() {
378
+ this.anchorDate_ = this.getNextAnchor_();
379
+ this.updateLayout_();
380
+ }
381
+
382
+ prev() {
383
+ this.anchorDate_ = this.getPrevAnchor_();
384
+ this.updateLayout_();
385
+ }
386
+
387
+ setMode(mode) {
388
+ this.mode_ = mode;
389
+ this.updateLayout_();
390
+ }
391
+
392
+ getMode() {
393
+ return this.mode_;
394
+ }
395
+
396
+ getLocalizedMode() {
397
+ if (this.mode_ == this.VIEW_MODE_WEEK) {
398
+ return stringLookup(`calendar_mode_week`);
399
+ }
400
+ else if (this.mode_ == this.VIEW_MODE_DAY) {
401
+ return stringLookup(`calendar_mode_day`);
402
+ }
403
+ return stringLookup(`calendar_mode_month`);
404
+ }
405
+ }
406
+
407
+
408
+ /// Creates a visual calendar, pulling input from [inputElem]
409
+ /// and writing output to [outputElem]. If [includePosts], all post-formatted
410
+ /// articles are also included.
411
+ function calendarSetup(sourceElem, outputElem, calendarTitleElem, includePosts) {
412
+ let data = getCalendarData(sourceElem, true);
413
+
414
+ if (includePosts) {
415
+ data = addPostData(data);
416
+ }
417
+
418
+ let controlsContainer = document.createElement("div");
419
+ controlsContainer.classList.add('controls');
420
+ outputElem.appendChild(controlsContainer);
421
+
422
+ let calendar = new Calendar(data, outputElem, calendarTitleElem);
423
+
424
+ let viewModeContainer = document.createElement("div");
425
+ let viewModeLabel = document.createElement("label");
426
+ let viewModeSelector = document.createElement("select");
427
+ viewModeSelector.innerHTML = `
428
+ <option value='${calendar.VIEW_MODE_DAY}'>${stringLookup('calendar_mode_day')}</option>
429
+ <option value='${calendar.VIEW_MODE_WEEK}'>${stringLookup('calendar_mode_week')}</option>
430
+ <option value='${calendar.VIEW_MODE_MONTH}'>${stringLookup('calendar_mode_month')}</option>
431
+ `;
432
+ viewModeSelector.setAttribute("id", `viewModeSelector${nextViewModeSelectorId}`);
433
+ viewModeLabel.setAttribute("for", `viewModeSelector${nextViewModeSelectorId++}`);
434
+
435
+ let nextBtn = document.createElement("button");
436
+ let prevBtn = document.createElement("button");
437
+ viewModeLabel.innerText = stringLookup(`calendar_choose_view_mode`);
438
+
439
+
440
+ // Update elements/controls based on the current calendar mode.
441
+ let updateModeLabels = () => {
442
+ let mode = calendar.getLocalizedMode();
443
+ nextBtn.innerText = stringLookup(`calendar_next_btn`, mode);
444
+ prevBtn.innerText = stringLookup(`calendar_prev_btn`, mode);
445
+
446
+ viewModeSelector.value = calendar.getMode();
447
+ };
448
+
449
+ updateModeLabels();
450
+
451
+ nextBtn.onclick = () => {
452
+ calendar.next();
453
+
454
+ let mode = calendar.getLocalizedMode();
455
+ announceForAccessibility(stringLookup(`calendar_went_next`, mode));
456
+ };
457
+
458
+ prevBtn.onclick = () => {
459
+ calendar.prev();
460
+
461
+ let mode = calendar.getLocalizedMode();
462
+ announceForAccessibility(stringLookup(`calendar_went_prev`, mode));
463
+ };
464
+
465
+ viewModeSelector.onchange = () => {
466
+ calendar.setMode(parseInt(viewModeSelector.value));
467
+ updateModeLabels();
468
+
469
+ let mode = calendar.getLocalizedMode();
470
+ announceForAccessibility(stringLookup(`calendar_changed_mode`, mode));
471
+ };
472
+
473
+
474
+ viewModeContainer.replaceChildren(viewModeLabel, viewModeSelector);
475
+ controlsContainer.replaceChildren(prevBtn, viewModeContainer, nextBtn);
476
+ }
477
+
478
+ export default calendarSetup;
@@ -0,0 +1,65 @@
1
+ ---
2
+ ---
3
+ import { stringLookup } from "../strings.mjs";
4
+ import DateUtil from "../DateUtil.mjs";
5
+
6
+ const NEXT_POST_ID = "next_post_link_btn";
7
+ const PREV_POST_ID = "prev_post_link_btn";
8
+
9
+ function createNextPrevLinks(page) {
10
+ let nextLink = document.createElement("a");
11
+ let prevLink = document.createElement("a");
12
+ let container = document.querySelector("#post_next_prev");
13
+
14
+ if (page.previous) {
15
+ prevLink.innerText = stringLookup(`prev_post`, page.previous.title);
16
+ prevLink.href = page.previous.url + `#${PREV_POST_ID}`;;
17
+
18
+ prevLink.setAttribute("id", PREV_POST_ID);
19
+ prevLink.classList.add(`prev-post-link`);
20
+
21
+ container.appendChild(prevLink);
22
+ }
23
+
24
+ if (page.next) {
25
+ nextLink.innerText = stringLookup(`next_post`, page.next.title);
26
+ nextLink.href = page.next.url + `#${NEXT_POST_ID}`;
27
+
28
+ nextLink.setAttribute("id", NEXT_POST_ID);
29
+ nextLink.classList.add(`next-post-link`);
30
+
31
+ container.appendChild(nextLink);
32
+ }
33
+ }
34
+
35
+ function createTagLinks(page) {
36
+ let container = document.querySelector("#post_tags");
37
+ let label = document.querySelector("#post_tags > #tag_header_lbl");
38
+ label.innerText = stringLookup(`tags`);
39
+
40
+ for (const tag of page.tags) {
41
+ let tagElem = document.createElement("a");
42
+ tagElem.style.display = "inline-block";
43
+ tagElem.classList.add(`post-tag`);
44
+
45
+ tagElem.href = `{{ 'assets/html/all_tags.html' | relative_url }}#tag__${escape(tag)}`;
46
+ tagElem.innerText = tag;
47
+
48
+ container.appendChild(tagElem);
49
+ }
50
+ }
51
+
52
+ function fillDate(page) {
53
+ let postDateContainer = document.querySelector(`#post_date`);
54
+ postDateContainer.innerText = DateUtil.toString(new Date(page.date));
55
+ }
56
+
57
+ function initPost(pageData) {
58
+ createNextPrevLinks(pageData);
59
+ createTagLinks(pageData);
60
+
61
+ fillDate(pageData);
62
+ }
63
+
64
+ export { initPost };
65
+ export default initPost;
@@ -0,0 +1,45 @@
1
+
2
+ import { PageAlert } from "./PageAlert.mjs";
3
+ import { stringLookup as _L } from './strings.mjs';
4
+
5
+ const INTERIOR_LINK_TEXT = "🔗";
6
+ const INTERIOR_LINK_CONTAINER_CLSS = "linkToHeaderContainer";
7
+
8
+ /// Creates 🔗 buttons for every header with an ID.
9
+ function generateHeaderLinks() {
10
+ let headers = document.querySelectorAll("h1,h2,h3,h4,h5,h6");
11
+
12
+ for (const header of headers) {
13
+ if (header.getAttribute("id")) {
14
+ let btn = createLinkButton_(header);
15
+
16
+ // Add the link button before the header
17
+ header.insertBefore(btn, header.childNodes[0]);
18
+ }
19
+ }
20
+ }
21
+
22
+ function createLinkButton_(header) {
23
+ let container = document.createElement("span");
24
+ let link = document.createElement("a");
25
+ let headerText = header.innerText;
26
+
27
+ container.classList.add(INTERIOR_LINK_CONTAINER_CLSS);
28
+ link.innerText = INTERIOR_LINK_TEXT;
29
+ link.title = _L("copy_link_to_header", headerText);
30
+ link.href = `#${header.getAttribute('id')}`;
31
+ link.onclick = () => {
32
+ navigator.clipboard.writeText(link.href);
33
+ PageAlert.builder()
34
+ .withText(_L("link_to_header_copied_alert", headerText))
35
+ .build()
36
+ .show();
37
+ };
38
+
39
+
40
+ container.appendChild(link);
41
+ return container;
42
+ }
43
+
44
+ export { generateHeaderLinks };
45
+ export default generateHeaderLinks;
@@ -0,0 +1,19 @@
1
+
2
+ import { generateHeaderLinks } from "./linkButtonGenerator.mjs";
3
+ import handleSidebar from "./sidebar.mjs";
4
+ import handleSearch from "./search.mjs";
5
+ import autoExpandDropdowns from "./dropdownExpander.mjs";
6
+
7
+ // After loading elements, images, css, etc.
8
+ addEventListener("load", () => {
9
+ generateHeaderLinks(document.querySelector("main"));
10
+ handleSearch();
11
+
12
+ // Expand dropdowns on print, etc.
13
+ autoExpandDropdowns();
14
+ });
15
+
16
+ // After loading elements, but before loading elements like images.
17
+ addEventListener("DOMContentLoaded", () => {
18
+ handleSidebar();
19
+ });