hematite 0.0.1

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 (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
+ });