hexo-component-inferno 4.2.2 → 5.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.
- package/README.md +5 -18
- package/asset/css/github-calendar.css +41 -0
- package/asset/js/algolia.js +18 -69
- package/asset/js/github-calendar.js +185 -0
- package/asset/js/google_cse.js +19 -68
- package/asset/js/insight.js +45 -208
- package/lib/core/commented_yaml.js +35 -0
- package/lib/core/default_value.js +150 -0
- package/lib/core/schema.js +8 -196
- package/lib/core/schema_util.js +14 -0
- package/lib/hexo/helper/cdn.js +10 -97
- package/lib/hexo/tag/tabs.js +4 -84
- package/lib/schema/comment/artalk.json +7 -1
- package/lib/schema/comment/cusdis.json +1 -1
- package/lib/schema/comment/facebook.json +1 -1
- package/lib/schema/comment/gitalk.json +23 -6
- package/lib/schema/comment/gitment.json +1 -1
- package/lib/schema/comment/livere.json +9 -3
- package/lib/schema/comment/twikoo.json +3 -3
- package/lib/schema/comment/waline.json +28 -3
- package/lib/schema/misc/open_graph.json +2 -1
- package/lib/schema/plugin/cookie_consent.json +2 -2
- package/lib/schema/plugin/mathjax.json +1 -1
- package/lib/schema/plugin/outdated_browser.json +1 -1
- package/lib/schema/share/bdshare.json +1 -1
- package/lib/schema/share/sharejs.json +1 -1
- package/lib/schema/share/web_share.json +13 -0
- package/lib/schema/widget/github_calendar.json +30 -0
- package/lib/schema/widget/profile.json +67 -0
- package/lib/util/archives.js +86 -0
- package/lib/util/categories.js +102 -0
- package/lib/util/cdn-rules.js +109 -0
- package/lib/util/cdn.js +116 -0
- package/lib/util/consent.js +122 -0
- package/lib/util/deprecate.js +1 -1
- package/lib/util/gallery.js +227 -0
- package/lib/util/insight-search.js +98 -0
- package/lib/util/searchbox.js +88 -0
- package/lib/util/tab-options.js +109 -0
- package/lib/view/comment/artalk.js +24 -6
- package/lib/view/comment/cusdis.js +4 -0
- package/lib/view/comment/disqus.js +1 -1
- package/lib/view/comment/disqusjs.js +7 -6
- package/lib/view/comment/facebook.js +3 -16
- package/lib/view/comment/gitalk.js +18 -15
- package/lib/view/comment/gitment.js +2 -2
- package/lib/view/comment/livere.js +25 -4
- package/lib/view/comment/twikoo.js +5 -3
- package/lib/view/comment/valine.js +4 -3
- package/lib/view/comment/waline.js +8 -6
- package/lib/view/misc/open_graph.js +3 -5
- package/lib/view/plugin/cookie_consent.js +8 -7
- package/lib/view/plugin/gallery.js +42 -19
- package/lib/view/plugin/katex.js +4 -4
- package/lib/view/plugin/mathjax.js +10 -7
- package/lib/view/plugin/outdated_browser.js +59 -45
- package/lib/view/plugin/progressbar.js +6 -1
- package/lib/view/plugin/waline_pageview.js +2 -2
- package/lib/view/search/algolia.js +8 -3
- package/lib/view/search/google_cse.js +13 -10
- package/lib/view/search/insight.js +9 -2
- package/lib/view/share/addthis.js +4 -9
- package/lib/view/share/addtoany.js +23 -5
- package/lib/view/share/bdshare.js +9 -30
- package/lib/view/share/sharejs.js +7 -34
- package/lib/view/share/sharethis.js +21 -6
- package/lib/view/share/web_share.js +153 -0
- package/lib/view/widget/archives.js +19 -71
- package/lib/view/widget/categories.js +20 -67
- package/lib/view/widget/github_calendar.js +94 -0
- package/lib/view/widget/profile.js +194 -0
- package/lib/view/widget/subscribe_email.js +4 -26
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -13,8 +13,7 @@ for building [Hexo](https://hexo.io/) themes in JSX. It is the component library
|
|
|
13
13
|
plugins, and SEO metadata, each with a JSON schema for its configuration.
|
|
14
14
|
- **Hexo extensions**: generators, helpers, tag plugins, and a filter that merges the theme config into
|
|
15
15
|
the template locals.
|
|
16
|
-
- **Configuration tools**: a JSON schema loader that validates the theme config and generates
|
|
17
|
-
values, and a framework for migrating configs between theme versions.
|
|
16
|
+
- **Configuration tools**: a JSON schema loader that validates the theme config and generates commented defaults.
|
|
18
17
|
|
|
19
18
|
## Requirements
|
|
20
19
|
|
|
@@ -104,9 +103,7 @@ const result = schema.validate(themeConfig); // true, or a list of errors
|
|
|
104
103
|
const defaults = schema.getDefaultValue().toYaml(); // a commented default config
|
|
105
104
|
```
|
|
106
105
|
|
|
107
|
-
For example, `{ "$ref": "/comment/giscus.json" }` in your schema refers to the Giscus schema of this
|
|
108
|
-
library. `lib/core/migrate` upgrades existing configs between theme versions with a chain of `Migration`
|
|
109
|
-
classes.
|
|
106
|
+
For example, `{ "$ref": "/comment/giscus.json" }` in your schema refers to the Giscus schema of this library.
|
|
110
107
|
|
|
111
108
|
### Utilities
|
|
112
109
|
|
|
@@ -124,25 +121,15 @@ classes.
|
|
|
124
121
|
|
|
125
122
|
| Category | Components |
|
|
126
123
|
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
127
|
-
| `comment` | Artalk, Beaudar, Changyan, Cusdis, Disqus, DisqusJS, Facebook, Giscus, Gitalk, Gitment, Isso, LiveRe, Twikoo, Utterances, Valine, Waline
|
|
124
|
+
| `comment` | Artalk, Beaudar, Changyan, Cusdis (deprecated), Disqus, DisqusJS, Facebook, Giscus, Gitalk 2 (preview), Gitment (deprecated), Isso, LiveRe, Twikoo, Utterances, Valine (deprecated), Waline |
|
|
128
125
|
| `search` | Algolia, Baidu, Google CSE, Insight |
|
|
129
|
-
| `share` | AddThis, AddToAny, Baidu Share, Share
|
|
126
|
+
| `share` | AddThis (deprecated), AddToAny, Baidu Share (deprecated), Web Share, ShareThis |
|
|
130
127
|
| `donate` | Afdian, Alipay, Buy Me a Coffee, Patreon, PayPal, WeChat |
|
|
131
|
-
| `widget` | AdSense, archives, categories, follow.it, links, recent posts, email
|
|
128
|
+
| `widget` | AdSense, archives, categories, follow.it, GitHub contribution calendar, links, profile, recent posts, FeedBurner email (deprecated), tags, table of contents |
|
|
132
129
|
| `plugin` | Baidu Analytics, Bing Webmaster, Busuanzi, CNZZ, cookie consent, gallery, Google Analytics, Hotjar, KaTeX, MathJax, Matomo, outdated browser warning, progress bar, StatCounter, Twitter conversion tracking, Umami, Waline page views |
|
|
133
130
|
| `misc` | Article licensing, meta tags, Open Graph, paginator, structured data, web app |
|
|
134
131
|
| `common` | Article media (a thumbnail with a title and date) |
|
|
135
132
|
|
|
136
|
-
The components below are deprecated because their services have shut down or are shutting down. They print
|
|
137
|
-
a warning when they are rendered, and will be removed in the next major version.
|
|
138
|
-
|
|
139
|
-
| Component | Reason | Alternatives |
|
|
140
|
-
| ------------------------ | ------------------------------------------------------------------------------ | -------------------------- |
|
|
141
|
-
| `share/addthis` | AddThis shut down on May 31, 2023. | AddToAny, ShareThis |
|
|
142
|
-
| `comment/gitment` | Gitment is no longer maintained, and its GitHub sign-in proxy no longer works. | Giscus, Utterances, Gitalk |
|
|
143
|
-
| `comment/valine` | LeanCloud, where Valine stores the comments, shuts down on January 12, 2027. | Waline, Twikoo |
|
|
144
|
-
| `widget/subscribe_email` | Google FeedBurner stopped email subscriptions in July 2021. | follow.it |
|
|
145
|
-
|
|
146
133
|
The [API documentation](https://ppoffice.github.io/hexo-component-inferno/) lists the props of every
|
|
147
134
|
component and the options of every module.
|
|
148
135
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
.github-calendar-chart {
|
|
2
|
+
--github-calendar-color: var(--github-calendar-default-color, #40c463);
|
|
3
|
+
--github-calendar-empty: color-mix(in srgb, currentColor 12%, transparent);
|
|
4
|
+
min-height: 116px;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.github-calendar-chart .github-calendar-link {
|
|
8
|
+
display: block;
|
|
9
|
+
color: inherit;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.github-calendar-chart svg {
|
|
13
|
+
display: block;
|
|
14
|
+
max-width: 100%;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.github-calendar-chart text {
|
|
18
|
+
fill: currentColor;
|
|
19
|
+
font-family: inherit;
|
|
20
|
+
font-size: 10px;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.github-calendar-chart rect {
|
|
24
|
+
fill: var(--github-calendar-empty);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.github-calendar-chart .github-calendar-level-1 {
|
|
28
|
+
fill: color-mix(in srgb, var(--github-calendar-color) 25%, var(--github-calendar-empty));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.github-calendar-chart .github-calendar-level-2 {
|
|
32
|
+
fill: color-mix(in srgb, var(--github-calendar-color) 50%, var(--github-calendar-empty));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.github-calendar-chart .github-calendar-level-3 {
|
|
36
|
+
fill: color-mix(in srgb, var(--github-calendar-color) 75%, var(--github-calendar-empty));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.github-calendar-chart .github-calendar-level-4 {
|
|
40
|
+
fill: var(--github-calendar-color);
|
|
41
|
+
}
|
package/asset/js/algolia.js
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
/* global instantsearch
|
|
1
|
+
/* global instantsearch */
|
|
2
2
|
// eslint-disable-next-line no-unused-vars
|
|
3
3
|
function loadAlgolia(config, translation) {
|
|
4
|
+
const $main = $('.searchbox');
|
|
5
|
+
let search = null;
|
|
6
|
+
const controller = window.loadSearchbox($main, {
|
|
7
|
+
onDestroy: () => {
|
|
8
|
+
// Widget initialization can fail before start completes. Dispose only initialized indices.
|
|
9
|
+
if (search && search.mainIndex.getHelper()) search.dispose();
|
|
10
|
+
},
|
|
11
|
+
});
|
|
4
12
|
function escapeHTML(text) {
|
|
5
13
|
return String(text).replace(/[&<>"']/g, (char) => {
|
|
6
14
|
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[char];
|
|
@@ -23,20 +31,23 @@ function loadAlgolia(config, translation) {
|
|
|
23
31
|
.replace(/\[\/algolia-highlight\]/g, '</em>');
|
|
24
32
|
}
|
|
25
33
|
|
|
26
|
-
|
|
34
|
+
search = instantsearch({
|
|
27
35
|
indexName: config.indexName,
|
|
28
|
-
searchClient: algoliasearch(config.applicationId, config.apiKey),
|
|
36
|
+
searchClient: window['algoliasearch/lite'].liteClient(config.applicationId, config.apiKey),
|
|
37
|
+
// Preserve search-only behavior; do not enable automatic Insights tracking after upgrading.
|
|
38
|
+
insights: false,
|
|
29
39
|
});
|
|
30
40
|
|
|
31
41
|
search.addWidgets([
|
|
32
42
|
instantsearch.widgets.configure({
|
|
33
43
|
attributesToSnippet: [config.excerpt],
|
|
34
44
|
}),
|
|
35
|
-
]);
|
|
36
|
-
|
|
37
|
-
search.addWidget(
|
|
38
45
|
instantsearch.widgets.searchBox({
|
|
39
46
|
container: '#algolia-input',
|
|
47
|
+
// Disposing removes the widget DOM, but callers may still hold its old input node.
|
|
48
|
+
queryHook: (query, refine) => {
|
|
49
|
+
if (controller.isCurrent()) refine(query);
|
|
50
|
+
},
|
|
40
51
|
placeholder: translation.hint,
|
|
41
52
|
showReset: false,
|
|
42
53
|
showSubmit: false,
|
|
@@ -47,15 +58,9 @@ function loadAlgolia(config, translation) {
|
|
|
47
58
|
input: 'searchbox-input',
|
|
48
59
|
},
|
|
49
60
|
}),
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
search.addWidget(
|
|
53
61
|
instantsearch.widgets.poweredBy({
|
|
54
62
|
container: '#algolia-poweredby',
|
|
55
63
|
}),
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
search.addWidget(
|
|
59
64
|
instantsearch.widgets.hits({
|
|
60
65
|
container: '.searchbox-body',
|
|
61
66
|
escapeHTML: false,
|
|
@@ -84,9 +89,6 @@ function loadAlgolia(config, translation) {
|
|
|
84
89
|
},
|
|
85
90
|
},
|
|
86
91
|
}),
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
search.addWidget(
|
|
90
92
|
instantsearch.widgets.pagination({
|
|
91
93
|
container: '.searchbox-footer',
|
|
92
94
|
cssClasses: {
|
|
@@ -97,64 +99,11 @@ function loadAlgolia(config, translation) {
|
|
|
97
99
|
disabledItem: 'disabled',
|
|
98
100
|
},
|
|
99
101
|
}),
|
|
100
|
-
);
|
|
102
|
+
]);
|
|
101
103
|
|
|
102
104
|
search.start();
|
|
103
105
|
|
|
104
106
|
if (location.hash.trim() === '#algolia-search') {
|
|
105
107
|
$('.searchbox').addClass('show');
|
|
106
108
|
}
|
|
107
|
-
|
|
108
|
-
const $main = $('.searchbox');
|
|
109
|
-
let opener = null;
|
|
110
|
-
|
|
111
|
-
function open() {
|
|
112
|
-
if (!$main.hasClass('show')) {
|
|
113
|
-
opener = document.activeElement;
|
|
114
|
-
$main.addClass('show');
|
|
115
|
-
}
|
|
116
|
-
$main.find('.searchbox-input').focus();
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Return the focus to the element that opened the search box, unless it moved elsewhere
|
|
120
|
-
function close(restoreFocus = true) {
|
|
121
|
-
$main.removeClass('show');
|
|
122
|
-
if (restoreFocus && opener && document.contains(opener)) {
|
|
123
|
-
opener.focus();
|
|
124
|
-
}
|
|
125
|
-
opener = null;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Open the search box with "/" (unless typing in a field) or Ctrl/Cmd+K
|
|
129
|
-
function isSearchShortcut(e) {
|
|
130
|
-
if (e.altKey) {
|
|
131
|
-
return false;
|
|
132
|
-
}
|
|
133
|
-
if (e.ctrlKey || e.metaKey) {
|
|
134
|
-
return !e.shiftKey && typeof e.key === 'string' && e.key.toLowerCase() === 'k';
|
|
135
|
-
}
|
|
136
|
-
// e.key is "/" whatever keys type it on the keyboard layout, so do not check e.shiftKey
|
|
137
|
-
const target = e.target;
|
|
138
|
-
const typing = target.isContentEditable || /^(input|textarea|select)$/i.test(target.tagName);
|
|
139
|
-
return e.key === '/' && !typing;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
$(document)
|
|
143
|
-
.on('click', '.navbar-main .search', open)
|
|
144
|
-
.on('click', '.searchbox .searchbox-mask', () => close())
|
|
145
|
-
.on('click', '.searchbox-close', () => close())
|
|
146
|
-
.on('focusin', (e) => {
|
|
147
|
-
// Close the search box when the focus moves to the rest of the page, e.g., by Tab
|
|
148
|
-
if ($main.hasClass('show') && !$main[0].contains(e.target)) {
|
|
149
|
-
close(false);
|
|
150
|
-
}
|
|
151
|
-
})
|
|
152
|
-
.on('keydown', (e) => {
|
|
153
|
-
if (isSearchShortcut(e)) {
|
|
154
|
-
e.preventDefault();
|
|
155
|
-
open();
|
|
156
|
-
} else if (e.keyCode === 27 && $main.hasClass('show')) {
|
|
157
|
-
close();
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
109
|
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
const DAY = 86400000;
|
|
3
|
+
const CELL = 11;
|
|
4
|
+
const GAP = 3;
|
|
5
|
+
const NS = 'http://www.w3.org/2000/svg';
|
|
6
|
+
const requests = new Map();
|
|
7
|
+
let destroy = () => {};
|
|
8
|
+
|
|
9
|
+
function load(username) {
|
|
10
|
+
const cached = requests.get(username);
|
|
11
|
+
if (cached && cached.expires > Date.now()) {
|
|
12
|
+
return cached.promise;
|
|
13
|
+
}
|
|
14
|
+
const controller = new AbortController();
|
|
15
|
+
const timer = setTimeout(() => controller.abort(), 15000);
|
|
16
|
+
const promise = fetch(
|
|
17
|
+
'https://github-contributions-api.jogruber.de/v4/' + encodeURIComponent(username) + '?y=last',
|
|
18
|
+
{
|
|
19
|
+
signal: controller.signal,
|
|
20
|
+
credentials: 'omit',
|
|
21
|
+
referrerPolicy: 'no-referrer',
|
|
22
|
+
},
|
|
23
|
+
)
|
|
24
|
+
.then((response) => {
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
throw new Error('Calendar request failed');
|
|
27
|
+
}
|
|
28
|
+
return response.json();
|
|
29
|
+
})
|
|
30
|
+
.then((data) => {
|
|
31
|
+
if (!Array.isArray(data.contributions) || !data.contributions.length) {
|
|
32
|
+
throw new Error('Missing contribution data');
|
|
33
|
+
}
|
|
34
|
+
return data.contributions
|
|
35
|
+
.map((day) => {
|
|
36
|
+
const time = Date.parse(day.date + 'T00:00:00Z');
|
|
37
|
+
if (
|
|
38
|
+
!/^\d{4}-\d{2}-\d{2}$/.test(day.date) ||
|
|
39
|
+
!Number.isFinite(time) ||
|
|
40
|
+
!Number.isInteger(day.count) ||
|
|
41
|
+
day.count < 0 ||
|
|
42
|
+
!Number.isInteger(day.level) ||
|
|
43
|
+
day.level < 0 ||
|
|
44
|
+
day.level > 4
|
|
45
|
+
) {
|
|
46
|
+
throw new Error('Invalid contribution data');
|
|
47
|
+
}
|
|
48
|
+
return { ...day, time };
|
|
49
|
+
})
|
|
50
|
+
.sort((a, b) => a.time - b.time);
|
|
51
|
+
})
|
|
52
|
+
.catch((error) => {
|
|
53
|
+
requests.delete(username);
|
|
54
|
+
throw error;
|
|
55
|
+
})
|
|
56
|
+
.finally(() => clearTimeout(timer));
|
|
57
|
+
requests.set(username, { promise, expires: Date.now() + 3600000 });
|
|
58
|
+
return promise;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function element(tag, attributes = {}, text) {
|
|
62
|
+
const node = document.createElementNS(NS, tag);
|
|
63
|
+
Object.keys(attributes).forEach((key) => node.setAttribute(key, attributes[key]));
|
|
64
|
+
if (text !== undefined) {
|
|
65
|
+
node.textContent = text;
|
|
66
|
+
}
|
|
67
|
+
return node;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function draw(chart, days) {
|
|
71
|
+
const width = chart.clientWidth;
|
|
72
|
+
if (!width) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const last = days[days.length - 1];
|
|
76
|
+
const lastSunday = last.time - new Date(last.time).getUTCDay() * DAY;
|
|
77
|
+
const firstSunday = days[0].time - new Date(days[0].time).getUTCDay() * DAY;
|
|
78
|
+
const totalWeeks = Math.round((lastSunday - firstSunday) / (7 * DAY)) + 1;
|
|
79
|
+
const weeks = Math.min(totalWeeks, Math.max(1, Math.floor((width + GAP) / (CELL + GAP))));
|
|
80
|
+
const start = lastSunday - (weeks - 1) * 7 * DAY;
|
|
81
|
+
const visible = days.filter((day) => day.time >= start);
|
|
82
|
+
const locale = (document.documentElement.lang || 'en').replace(/^vn$/, 'vi');
|
|
83
|
+
let monthFormat, dateFormat;
|
|
84
|
+
try {
|
|
85
|
+
monthFormat = new Intl.DateTimeFormat(locale, { month: 'short', timeZone: 'UTC' });
|
|
86
|
+
dateFormat = new Intl.DateTimeFormat(locale, { dateStyle: 'medium', timeZone: 'UTC' });
|
|
87
|
+
} catch (e) {
|
|
88
|
+
monthFormat = new Intl.DateTimeFormat('en', { month: 'short', timeZone: 'UTC' });
|
|
89
|
+
dateFormat = new Intl.DateTimeFormat('en', { dateStyle: 'medium', timeZone: 'UTC' });
|
|
90
|
+
}
|
|
91
|
+
const svg = element('svg', { width: weeks * (CELL + GAP) - GAP, height: 116, role: 'img' });
|
|
92
|
+
const range = dateFormat.format(visible[0].time) + ' – ' + dateFormat.format(last.time);
|
|
93
|
+
svg.setAttribute(
|
|
94
|
+
'aria-label',
|
|
95
|
+
chart.closest('.widget').querySelector('.menu-label').textContent + ': ' + range,
|
|
96
|
+
);
|
|
97
|
+
const monthLabels = [];
|
|
98
|
+
let month = -1;
|
|
99
|
+
visible.forEach((day) => {
|
|
100
|
+
const week = Math.floor((day.time - start) / (7 * DAY));
|
|
101
|
+
const x = week * (CELL + GAP);
|
|
102
|
+
const date = new Date(day.time);
|
|
103
|
+
if (date.getUTCMonth() !== month) {
|
|
104
|
+
month = date.getUTCMonth();
|
|
105
|
+
const label = element('text', { x, y: 10 }, monthFormat.format(date));
|
|
106
|
+
svg.appendChild(label);
|
|
107
|
+
monthLabels.push({
|
|
108
|
+
label,
|
|
109
|
+
x,
|
|
110
|
+
partial: monthLabels.length === 0 && date.getUTCDate() !== 1,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
const rect = element('rect', {
|
|
114
|
+
x,
|
|
115
|
+
y: 18 + date.getUTCDay() * (CELL + GAP),
|
|
116
|
+
width: CELL,
|
|
117
|
+
height: CELL,
|
|
118
|
+
rx: 2,
|
|
119
|
+
'data-date': day.date,
|
|
120
|
+
'data-level': day.level,
|
|
121
|
+
class: 'github-calendar-level-' + day.level,
|
|
122
|
+
});
|
|
123
|
+
rect.appendChild(element('title', {}, dateFormat.format(date) + ': ' + day.count));
|
|
124
|
+
svg.appendChild(rect);
|
|
125
|
+
});
|
|
126
|
+
chart.querySelector('.github-calendar-grid').replaceChildren(svg);
|
|
127
|
+
// Measure in the document so labels use the actual theme font and locale. A partial
|
|
128
|
+
// opening month must not suppress the next full month when both start in nearby weeks.
|
|
129
|
+
let lastLabelEnd = -8;
|
|
130
|
+
monthLabels.forEach(({ label, x, partial }, index) => {
|
|
131
|
+
const end = x + label.getComputedTextLength();
|
|
132
|
+
const next = monthLabels[index + 1];
|
|
133
|
+
if (
|
|
134
|
+
(partial && next && next.x < end + 8) ||
|
|
135
|
+
x < lastLabelEnd + 8 ||
|
|
136
|
+
end > Number(svg.getAttribute('width'))
|
|
137
|
+
) {
|
|
138
|
+
label.remove();
|
|
139
|
+
} else {
|
|
140
|
+
lastLabelEnd = end;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
window.HexoGithubCalendar = {
|
|
146
|
+
init() {
|
|
147
|
+
destroy();
|
|
148
|
+
let active = true;
|
|
149
|
+
const cleanups = [];
|
|
150
|
+
destroy = () => {
|
|
151
|
+
active = false;
|
|
152
|
+
cleanups.forEach((cleanup) => cleanup());
|
|
153
|
+
};
|
|
154
|
+
document.querySelectorAll('.github-calendar-chart[data-username]').forEach((chart) => {
|
|
155
|
+
const status = chart.querySelector('.github-calendar-status');
|
|
156
|
+
load(chart.dataset.username)
|
|
157
|
+
.then((days) => {
|
|
158
|
+
if (!active || !chart.isConnected) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
status.hidden = true;
|
|
162
|
+
draw(chart, days);
|
|
163
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
164
|
+
const observer = new ResizeObserver(() => draw(chart, days));
|
|
165
|
+
observer.observe(chart);
|
|
166
|
+
cleanups.push(() => observer.disconnect());
|
|
167
|
+
} else {
|
|
168
|
+
const resize = () => draw(chart, days);
|
|
169
|
+
window.addEventListener('resize', resize);
|
|
170
|
+
cleanups.push(() => window.removeEventListener('resize', resize));
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
.catch(() => {
|
|
174
|
+
if (active && chart.isConnected) {
|
|
175
|
+
status.hidden = false;
|
|
176
|
+
status.textContent = chart.dataset.error;
|
|
177
|
+
chart.querySelector('.github-calendar-grid').textContent = chart
|
|
178
|
+
.closest('.widget')
|
|
179
|
+
.querySelector('.menu-label').textContent;
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
})();
|
package/asset/js/google_cse.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/* global google */
|
|
2
2
|
(function (document, $) {
|
|
3
|
+
const controller = window.loadSearchbox($('.searchbox'), {
|
|
4
|
+
onDestroy: () => $(document).off('.googleSearch'),
|
|
5
|
+
});
|
|
3
6
|
function debounce(func, wait, immediate) {
|
|
4
7
|
let timeout;
|
|
5
8
|
return function () {
|
|
@@ -16,72 +19,20 @@
|
|
|
16
19
|
};
|
|
17
20
|
}
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
opener = null;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Open the search box with "/" (unless typing in a field) or Ctrl/Cmd+K
|
|
40
|
-
function isSearchShortcut(e) {
|
|
41
|
-
if (e.altKey) {
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
if (e.ctrlKey || e.metaKey) {
|
|
45
|
-
return !e.shiftKey && typeof e.key === 'string' && e.key.toLowerCase() === 'k';
|
|
46
|
-
}
|
|
47
|
-
// e.key is "/" whatever keys type it on the keyboard layout, so do not check e.shiftKey
|
|
48
|
-
const target = e.target;
|
|
49
|
-
const typing = target.isContentEditable || /^(input|textarea|select)$/i.test(target.tagName);
|
|
50
|
-
return e.key === '/' && !typing;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
$(document)
|
|
54
|
-
.on('click', '.navbar-main .search', open)
|
|
55
|
-
.on('click', '.searchbox .searchbox-mask', () => close())
|
|
56
|
-
.on('click', '.searchbox-close', () => close())
|
|
57
|
-
.on('focusin', (e) => {
|
|
58
|
-
// Close the search box when the focus moves to the rest of the page, e.g., by Tab
|
|
59
|
-
const main = $('.searchbox')[0];
|
|
60
|
-
if (main && $(main).hasClass('show') && !main.contains(e.target)) {
|
|
61
|
-
close(false);
|
|
62
|
-
}
|
|
63
|
-
})
|
|
64
|
-
.on('keydown', (e) => {
|
|
65
|
-
if (isSearchShortcut(e)) {
|
|
66
|
-
e.preventDefault();
|
|
67
|
-
open();
|
|
68
|
-
} else if (e.keyCode === 27 && $('.searchbox').hasClass('show')) {
|
|
69
|
-
close();
|
|
70
|
-
}
|
|
71
|
-
})
|
|
72
|
-
.on(
|
|
73
|
-
'keydown',
|
|
74
|
-
'.searchbox-input',
|
|
75
|
-
debounce(function () {
|
|
76
|
-
const value = $(this).val();
|
|
77
|
-
try {
|
|
78
|
-
const element = google.search.cse.element.getElement('searchresults-only0');
|
|
79
|
-
if (value.trim() === '') {
|
|
80
|
-
element.clearAllResults();
|
|
81
|
-
} else {
|
|
82
|
-
element.execute(value);
|
|
83
|
-
}
|
|
84
|
-
} catch (e) {}
|
|
85
|
-
}, 300),
|
|
86
|
-
);
|
|
22
|
+
$(document).on(
|
|
23
|
+
'input.googleSearch',
|
|
24
|
+
'.searchbox-input',
|
|
25
|
+
debounce(function () {
|
|
26
|
+
if (!controller.isCurrent()) return;
|
|
27
|
+
const value = $(this).val();
|
|
28
|
+
try {
|
|
29
|
+
const element = google.search.cse.element.getElement('searchresults-only0');
|
|
30
|
+
if (value.trim() === '') {
|
|
31
|
+
element.clearAllResults();
|
|
32
|
+
} else {
|
|
33
|
+
element.execute(value);
|
|
34
|
+
}
|
|
35
|
+
} catch (e) {}
|
|
36
|
+
}, 300),
|
|
37
|
+
);
|
|
87
38
|
})(document, jQuery);
|