hematite 0.1.12 → 0.1.15

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3bf0eaea458e2c4fba03dbcc759aaa04eb811832e1850b210d5e5a378fab2526
4
- data.tar.gz: 80dcbb081d5342365d4f119ffcb6f1dfd2be5b6533e23b0c23a18bde93709b54
3
+ metadata.gz: a65a76e006bbd87ea26a66325be19c7acb2308d195f05763874847ed12e57b45
4
+ data.tar.gz: 1236b78bc5fc3da5b7135690276bb054aad7fe0c83f8dcf0cfe94ced4fce401d
5
5
  SHA512:
6
- metadata.gz: 23b88d8d1249320f6b4712e19d9a00f0a2bc0de0ef1e191aab371e56e019f38da43b8f26726e0e6d22a9a271bd647dc175bae4d58997222a04a4939700642908
7
- data.tar.gz: 916e067da5e530e001003b82f83d3a91241ba268823112662ff87102c16e5800c70b2d909d8e150c9a9c08017d9f6f4d7c5c76ac980f2faf4a8e22caa77bb625
6
+ metadata.gz: 4dd620ed3184b4d68aaa698f8fe9d5266e1f75dd9dc8daaa397a8d8ce107d95d32dd18efb85a7646bbe28e25d108b8e5ac6e7597c82879eced10e15d9c5ccfc3
7
+ data.tar.gz: ddc863b0e58fc4386c43a6d421de74f538d581aa4956875e25afc8a27f9d651de1c5116f75e3d5cc6bd7f2812458b64fedb2788c612382c9d9a99c13418674be
@@ -0,0 +1,29 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg class="chain-link-root" width="120" height="120" version="1.1" viewBox="5 0 115 115" xmlns="http://www.w3.org/2000/svg">
3
+ <style>
4
+ .chain-link-group {
5
+ stroke: var(--primary-text-color);
6
+ fill: var(--primary-text-color);
7
+ transform: scale(1, 1);
8
+ cursor: pointer;
9
+
10
+ transition: transform 0.5s ease;
11
+ }
12
+
13
+ *:hover > .chain-link-root > .chain-link-group, *:focus-visible > .chain-link-root > .chain-link-group {
14
+ transform: scale(0.9, 0.9) rotate(3deg);
15
+ }
16
+
17
+ .first-link {
18
+ transition: transform 0.5s ease;
19
+ }
20
+
21
+ *:hover > .chain-link-root .first-link, *:focus-visible > .chain-link-root .first-link {
22
+ transform: rotate(-1deg) translate(3px, 5px);
23
+ }
24
+ </style>
25
+ <g class="chain-link-group" stroke-width="1.3073px">
26
+ <path class="first-link" d="m33.397 50.158c-2.6598 5.036-2.2862 10.261-1.9847 14.517 0 0-15.64 22.738-7.7042 28.627 11.282 8.3732 27.844-11.316 35.784-22.274 4.6398-6.404 11.497-17.806 4.8574-22.551-4.4648-3.1911-15.534 6.396-15.534 6.396 2.4001-4.8081 3.8451-9.2997 8.8749-14.979 0 0 12.876-7.89 17.582-4.4037 10.785 7.9897 4.045 26.844-2.9517 37.755-11.67 18.198-40.276 49.141-57.96 35.093-15.977-12.692 19.037-58.18 19.037-58.18z"/>
27
+ <path class="second-link chain-link" d="m76.584 70.435c3.391-6.7587 5.3144-12.629 6.102-17.811 0 0 15.266-26.145 6.953-34.912-9.4283-9.9438-27.656 11.689-35.415 22.752-4.5343 6.4656-10.798 17.514-4.4871 22.614 3.5372 2.8589 13.627-3.6947 13.627-3.6947-3.7755 9.069-3.9072 9.1256-8.0298 14.333 0 0-12.197 5.8204-16.306 2.4937-10.388-8.4101-4.8557-26.982 2.3324-37.79 11.277-16.957 39.806-44.356 56.165-30.668 7.7991 6.5255 4.0194 21.892 0.36418 31.865-2.4291 6.628-21.307 30.818-21.307 30.818z"/>
28
+ </g>
29
+ </svg>
@@ -1,5 +1,5 @@
1
1
 
2
2
  @font-face {
3
3
  font-family: FoulisGreek;
4
- src: url({{ assets/fonts/FoulisGreek.ttf | absolute_path }});
4
+ src: url({{ "assets/fonts/FoulisGreek.ttf" | relative_url }});
5
5
  }
@@ -59,6 +59,7 @@ pageThemeSelect.innerHTML =
59
59
  `
60
60
  <option value='${Settings.THEME_AUTO}'>${stringLookup('page_theme_auto')}</option>
61
61
  <option value='${Settings.THEME_DARK}'>${stringLookup('page_theme_dark')}</option>
62
+ <option value='${Settings.THEME_DARKEST}'>${stringLookup('page_theme_darkest')}</option>
62
63
  <option value='${Settings.THEME_LIGHT}'>${stringLookup('page_theme_light')}</option>
63
64
  `;
64
65
 
data/_sass/_colors.scss CHANGED
@@ -29,15 +29,11 @@
29
29
  --syntax-comment-fg: #338;
30
30
  --syntax-name-fg: #331;
31
31
  --syntax-normal-fg: black;
32
+ --code-background-color: #f9f5f5;
32
33
  }
33
34
 
34
- @media screen {
35
- :root:not(.darkTheme), :root.lightTheme {
36
- --code-background-color: #f9f5f5;
37
- }
38
- }
39
35
 
40
- @mixin dark-theme-colors {
36
+ @mixin very-dark-theme-colors {
41
37
  --primary-text-color: white;
42
38
  --secondary-text-color: white;
43
39
  --text-color-faint: #ccc;
@@ -47,7 +43,7 @@
47
43
  --selected-item-fg-color: white;
48
44
  --selected-item-bg-color: #530033;
49
45
 
50
- --primary-background-color: black;
46
+ --primary-background-color: #000;
51
47
  --secondary-background-color: #111;
52
48
  --code-background-color: #222;
53
49
  --line-color-light: #222;
@@ -70,6 +66,39 @@
70
66
  --syntax-normal-fg: white;
71
67
  }
72
68
 
69
+ @mixin dark-theme-colors {
70
+ --primary-text-color: white;
71
+ --secondary-text-color: white;
72
+ --text-color-faint: #ccc;
73
+ --link-color: #fa0;
74
+ --visited-link-color: #f0a;
75
+
76
+ --selected-item-fg-color: white;
77
+ --selected-item-bg-color: #530033;
78
+
79
+ --primary-background-color: #333;
80
+ --secondary-background-color: #222;
81
+ --code-background-color: #222;
82
+ --line-color-light: #555;
83
+ --line-color-normal: #aaa;
84
+
85
+ --color-alert-background: #300;
86
+ --color-alert-foreground: white;
87
+
88
+ --shadow-color-light: rgba(220, 220, 200, 0.5);
89
+
90
+
91
+ // Syntax highlighting
92
+ --syntax-keyword-fg: pink;
93
+ --syntax-string-fg: #cfc;
94
+ --syntax-number-fg: #eef;
95
+ --syntax-bracket-fg: #faf;
96
+ --syntax-operator-fg: #aff;
97
+ --syntax-comment-fg: #aef;
98
+ --syntax-name-fg: #edf;
99
+ --syntax-normal-fg: white;
100
+ }
101
+
73
102
  @media screen and (prefers-color-scheme: dark) {
74
103
  :root:not(.lightTheme) {
75
104
  @include dark-theme-colors;
@@ -80,5 +109,9 @@
80
109
  :root.darkTheme {
81
110
  @include dark-theme-colors;
82
111
  }
112
+
113
+ :root.veryDarkTheme {
114
+ @include very-dark-theme-colors;
115
+ }
83
116
  }
84
117
 
data/_sass/_elements.scss CHANGED
@@ -116,6 +116,10 @@ main pre {
116
116
  padding: 4px;
117
117
  }
118
118
 
119
+ .linkToHeaderContainer > a {
120
+ background-color: transparent;
121
+ }
122
+
119
123
  code {
120
124
  box-shadow: inset -1px -1px 4px var(--shadow-color-light);
121
125
  }
data/_sass/_layout.scss CHANGED
@@ -4,7 +4,7 @@
4
4
  @import "_calendar";
5
5
 
6
6
  .main-container {
7
- box-shadow: 1px 1px 3px var(--shadow-color-light);
7
+ box-shadow: 0px -1px 2px var(--shadow-color-light);
8
8
  background-color: var(--primary-background-color);
9
9
 
10
10
  max-width: $site-content-preferred-width;
@@ -139,15 +139,28 @@ nav#post_next_prev {
139
139
  }
140
140
 
141
141
  .linkToHeaderContainer {
142
- float: right;
143
- opacity: 0.1;
144
142
  cursor: pointer;
143
+ position: absolute;
144
+ opacity: 0;
145
+
146
+ a {
147
+ margin-left: -1em;
148
+
149
+ svg {
150
+ width: 1em;
151
+ height: 0.7em;
152
+ }
153
+ }
145
154
 
146
155
  transition: opacity 0.2s ease;
147
156
  }
148
157
 
149
- .linkToHeaderContainer:hover, .linkToHeaderContainer:focus-within {
150
- opacity: 1;
158
+ h1, h2, h3, h4, h5, h6 {
159
+ &:hover, &:focus-visible {
160
+ .linkToHeaderContainer {
161
+ opacity: 1;
162
+ }
163
+ }
151
164
  }
152
165
 
153
166
  .spacer {
data/_sass/_nav.scss CHANGED
@@ -124,7 +124,6 @@ nav.sidebar {
124
124
  transform: scale(0, 1) translate(-30px, 0);
125
125
 
126
126
  color: var(--primary-text-color);
127
- background-color: var(--primary-background-color);
128
127
  box-shadow: 0 0 2px var(--shadow-color-light);
129
128
  padding: $navbar-padding;
130
129
 
@@ -138,6 +137,15 @@ nav.sidebar {
138
137
  transform-origin: left;
139
138
  transition: opacity 0.5s ease, transform 0.5s ease, width 0.5s ease;
140
139
 
140
+ // Work around a rendering bug in Safari — apply the background color to both
141
+ // the results pane and the container.
142
+ //
143
+ // Without this, Safari may display elements of pinned-pages in the same
144
+ // location as the search results.
145
+ &, .search-results {
146
+ background-color: var(--primary-background-color);
147
+ }
148
+
141
149
 
142
150
  // Container with search box and button
143
151
  > .search-container {
@@ -25,6 +25,7 @@ h1, h2, h3 {
25
25
  }
26
26
 
27
27
  body.mdSourceView {
28
+ padding-top: 50px;
28
29
  background: white;
29
30
  color: black;
30
31
  }
@@ -0,0 +1,37 @@
1
+
2
+ import { assertEq } from "./assertions.mjs";
3
+
4
+ let htmlReplacements = [
5
+ [ /[&]/g, '&amp;' ],
6
+ [ /[<]/g, '&lt;' ],
7
+ [ /[>]/g, '&gt;' ],
8
+ ];
9
+
10
+ var EscapeHelper = {
11
+ escapeHTML(text) {
12
+ for (const item of htmlReplacements) {
13
+ text = text.replace(item[0], item[1]);
14
+ }
15
+
16
+ return text;
17
+ },
18
+ // Escape for usage within a regex expression
19
+ escapeRegex(text) {
20
+ return text.replace(/([\*\(\).\[\]\{\}\^\$\-\=\;\:\'\"\\])/g, '\\$1'); //'
21
+ },
22
+ // Escape text for usage within a replacement pattern
23
+ // E.g. "foobar".replaceAll("foo", text);
24
+ escapeReplacePattern(text) {
25
+ return text.replace(/\$/g, '$$$$');
26
+ }
27
+ };
28
+
29
+ assertEq("Test escape <", EscapeHelper.escapeHTML('<'), '&lt;');
30
+ assertEq("Test escape multiple <", EscapeHelper.escapeHTML('<a></a>'), '&lt;a&gt;&lt;/a&gt;');
31
+ assertEq("Test escape >", EscapeHelper.escapeHTML('>'), '&gt;');
32
+ assertEq("Test escape identity", EscapeHelper.escapeHTML('Hello, world.'), 'Hello, world.');
33
+ assertEq("Test regex escape simple", EscapeHelper.escapeRegex(".*"), "\\.\\*");
34
+ assertEq("Test regex more complicated escape", EscapeHelper.escapeRegex("This, is a test... :)"), "This, is a test\\.\\.\\. \\:\\)");
35
+ assertEq("Test replace pattern escape", EscapeHelper.escapeReplacePattern("0$ and 24 cents"), "0$$ and 24 cents");
36
+
37
+ export default EscapeHelper;
@@ -8,6 +8,7 @@ const THEME_TRANSITION_TIME = 500; // ms
8
8
  class Settings {
9
9
  THEME_AUTO = 'auto';
10
10
  THEME_DARK = 'dark';
11
+ THEME_DARKEST = 'darkest';
11
12
  THEME_LIGHT = 'light';
12
13
 
13
14
  FONT_DEFAULT = 'default';
@@ -94,20 +95,13 @@ class Settings {
94
95
  return this.getFontSize() != this.FONT_SIZE_DEFAULT;
95
96
  }
96
97
 
97
- forcingDarkTheme_() {
98
- return this.getTheme() == this.THEME_DARK;
99
- }
100
-
101
- forcingLightTheme_() {
102
- return this.getTheme() == this.THEME_LIGHT;
103
- }
104
-
105
98
  async applySettings() {
106
99
  document.documentElement.classList.add("changingTheme");
107
100
 
108
101
  // Clean up previous changes. We might be re-applying styles.
109
102
  document.documentElement.classList.remove("lightTheme");
110
103
  document.documentElement.classList.remove("darkTheme");
104
+ document.documentElement.classList.remove("veryDarkTheme");
111
105
 
112
106
  if (!this.style_) {
113
107
  this.style_ = document.createElement("style");
@@ -147,11 +141,16 @@ class Settings {
147
141
  document.documentElement.appendChild(this.style_);
148
142
 
149
143
  // Theme
150
- if (this.forcingDarkTheme_()) {
151
- document.documentElement.classList.add("darkTheme");
152
- }
153
- else if (this.forcingLightTheme_()) {
154
- document.documentElement.classList.add("lightTheme");
144
+ switch (this.getTheme()) {
145
+ case this.THEME_DARK:
146
+ document.documentElement.classList.add("darkTheme");
147
+ break;
148
+ case this.THEME_DARKEST:
149
+ document.documentElement.classList.add("veryDarkTheme");
150
+ break;
151
+ case this.THEME_LIGHT:
152
+ document.documentElement.classList.add("lightTheme");
153
+ break;
155
154
  }
156
155
 
157
156
  if (this.getHeaderMinimized()) {
@@ -37,6 +37,12 @@ function createTagLinks(page) {
37
37
  let label = document.querySelector("#post_tags > #tag_header_lbl");
38
38
  label.innerText = stringLookup(`tags`);
39
39
 
40
+ // Don't show the tag container if there aren't any tags
41
+ if (!page.tags || page.tags.length == 0) {
42
+ container.style.display = "none";
43
+ return;
44
+ }
45
+
40
46
  for (const tag of page.tags) {
41
47
  let tagElem = document.createElement("a");
42
48
  tagElem.style.display = "inline-block";
@@ -47,11 +53,6 @@ function createTagLinks(page) {
47
53
 
48
54
  container.appendChild(tagElem);
49
55
  }
50
-
51
- // Don't show the tag container if there aren't any tags
52
- if (page.tags.length == 0) {
53
- container.style.display = "none";
54
- }
55
56
  }
56
57
 
57
58
  function fillDate(page) {
@@ -1,8 +1,10 @@
1
+ ---
2
+ ---
1
3
 
2
4
  import { PageAlert } from "./PageAlert.mjs";
3
5
  import { stringLookup as _L } from './strings.mjs';
4
6
 
5
- const INTERIOR_LINK_TEXT = "🔗";
7
+ const INTERIOR_LINK_CONTENT_HTML = `{% include img/chain-link.svg %}`;
6
8
  const INTERIOR_LINK_CONTAINER_CLSS = "linkToHeaderContainer";
7
9
 
8
10
  /// Creates 🔗 buttons for every header with an ID.
@@ -25,9 +27,10 @@ function createLinkButton_(header) {
25
27
  let headerText = header.innerText;
26
28
 
27
29
  container.classList.add(INTERIOR_LINK_CONTAINER_CLSS);
28
- link.innerText = INTERIOR_LINK_TEXT;
29
30
  link.title = _L("copy_link_to_header", headerText);
30
31
  link.href = `#${header.getAttribute('id')}`;
32
+ link.innerHTML = INTERIOR_LINK_CONTENT_HTML;
33
+
31
34
  link.onclick = () => {
32
35
  navigator.clipboard.writeText(link.href);
33
36
  PageAlert.builder()
data/assets/js/search.mjs CHANGED
@@ -7,6 +7,7 @@ import { expandContainingDropdowns } from "./dropdownExpander.mjs";
7
7
  import AnimationUtil from "./AnimationUtil.mjs";
8
8
  import AsyncUtil from "./AsyncUtil.mjs";
9
9
  import UrlHelper from "./UrlHelper.mjs";
10
+ import EscapeHelper from "./EscapeHelper.mjs";
10
11
 
11
12
  const PAGE_DATA_URL = `{{ "/assets/search_data.json" | relative_url }}`;
12
13
  const MATCHING_TITLE_PRIORITY_INCREMENT = 15;
@@ -50,15 +51,25 @@ class Searcher {
50
51
  .replaceAll(/[&]ldquo;/g, '"')
51
52
  .replaceAll(/[&]rdquo;/g, '"')
52
53
  .replaceAll(/[&]amp;/g, "&")
54
+ // Remove the astrisks around **bolded** text
55
+ .replaceAll(/(\s|^)[*]{2}[^*]+[*]{2}(\s|$)/g, "$1$2$3")
56
+ // Remove the backticks around `text` that is code-formatted
57
+ .replaceAll(/(\s|^)[`]([^`]+)[`](\s|$)/g, "$1$2$3") // `
53
58
  .replaceAll(/\s+/g, ' ');
54
59
 
55
60
  }
56
61
 
62
+ filterQuery_(query) {
63
+ return query
64
+ .toLowerCase()
65
+ .replaceAll(/\s+/g, ' ');
66
+ }
67
+
57
68
  /// Get number of full matches for [query] in [text].
58
69
  /// @precondition [text] is already in a searchable (i.e.
59
70
  /// filtered) form.
60
71
  getNumberOfMatches(query, text) {
61
- query = query.toLowerCase();
72
+ query = this.filterQuery_(query);
62
73
 
63
74
  return text.toLowerCase().split(query).length - 1;
64
75
  }
@@ -68,7 +79,7 @@ class Searcher {
68
79
  /// is found.
69
80
  /// @precondition [text] is already in a searchable form.
70
81
  getIdxOfFirstMatch(query, text, startPos) {
71
- query = query.toLowerCase();
82
+ query = this.filterQuery_(query);
72
83
 
73
84
  return text.toLowerCase().indexOf(query, startPos);
74
85
  }
@@ -167,6 +178,7 @@ class Searcher {
167
178
  results.push({
168
179
  index,
169
180
  context,
181
+ matchIndex: matchLoc,
170
182
  pageData,
171
183
  });
172
184
 
@@ -295,17 +307,35 @@ function handleSearch(searcher) {
295
307
 
296
308
  searchResults.replaceChildren(descriptionElem);
297
309
 
310
+ // Adds HTML to bold/italicize all results in [context].
311
+ // [context] is HTML-escaped before adding to the result.
312
+ const boldResults = (context) => {
313
+ let result = "";
314
+ let queryRegex = new RegExp(`(${EscapeHelper.escapeRegex(query)})`, `ig`);
315
+ let contextHTML = "";
316
+ let lastIdx = 0;
317
+ for (const match of context.matchAll(queryRegex)) {
318
+ result += EscapeHelper.escapeHTML(context.substring(lastIdx, match.index));
319
+ result += "<b><i>" + EscapeHelper.escapeHTML(match[0]) + "</i></b>";
320
+ lastIdx = match.index + match[0].length;
321
+ }
322
+ result += EscapeHelper.escapeHTML(context.substring(lastIdx));
323
+
324
+ return result;
325
+ };
326
+
298
327
  for (const result of results) {
299
328
  let link = document.createElement("a");
300
- let context = document.createElement("div");
301
- context.classList.add('context');
329
+ let contextElem = document.createElement("div");
330
+ contextElem.classList.add('context');
302
331
 
303
332
  link.innerText = result.pageData.title ?? stringLookup(`untitled`);
304
333
  link.href =
305
334
  result.pageData.url + `?query=${escape(query)},index=${result.index}`;
306
- context.innerText = result.context;
307
335
 
308
- link.appendChild(context);
336
+ contextElem.innerHTML = boldResults(result.context);
337
+
338
+ link.appendChild(contextElem);
309
339
  searchResults.appendChild(link);
310
340
  }
311
341
 
@@ -50,6 +50,7 @@ export default {
50
50
  page_font_large: 'Large',
51
51
  page_theme_auto: 'Automatic',
52
52
  page_theme_dark: 'Dark Theme',
53
+ page_theme_darkest: 'Very Dark Theme',
53
54
  page_theme_light: 'Light Theme',
54
55
  settings_minimize_header: 'Hide Header: ',
55
56
 
@@ -48,6 +48,7 @@ export default {
48
48
  page_font_large: 'Grande',
49
49
  page_theme_auto: 'Automático',
50
50
  page_theme_dark: 'Oscuro',
51
+ page_theme_darkest: 'Muy Oscuro',
51
52
  page_theme_light: 'Brillante',
52
53
  settings_minimize_header: 'Título escondido: ',
53
54
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hematite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.12
4
+ version: 0.1.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Henry Heino
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-05-29 00:00:00.000000000 Z
11
+ date: 2022-06-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jekyll
@@ -36,6 +36,7 @@ files:
36
36
  - _config.yml
37
37
  - _includes/extern_library_imports.html
38
38
  - _includes/footer.html
39
+ - _includes/img/chain-link.svg
39
40
  - _includes/img/hamburger_menu.svg
40
41
  - _includes/img/loading_icon.svg
41
42
  - _includes/img/search_icon.svg
@@ -75,6 +76,7 @@ files:
75
76
  - assets/js/AnimationUtil.mjs
76
77
  - assets/js/AsyncUtil.mjs
77
78
  - assets/js/DateUtil.mjs
79
+ - assets/js/EscapeHelper.mjs
78
80
  - assets/js/PageAlert.mjs
79
81
  - assets/js/Settings.mjs
80
82
  - assets/js/UrlHelper.mjs