ezal-theme-example 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 (102) hide show
  1. package/LICENSE +21 -0
  2. package/assets/scripts/404.ts +353 -0
  3. package/assets/scripts/_article.ts +290 -0
  4. package/assets/scripts/_base.ts +65 -0
  5. package/assets/scripts/_pagefind.d.ts +424 -0
  6. package/assets/scripts/_search.ts +88 -0
  7. package/assets/scripts/_utils.ts +74 -0
  8. package/assets/scripts/archive.ts +143 -0
  9. package/assets/scripts/article.ts +18 -0
  10. package/assets/scripts/category.ts +4 -0
  11. package/assets/scripts/home.ts +73 -0
  12. package/assets/scripts/links.ts +14 -0
  13. package/assets/scripts/main.ts +11 -0
  14. package/assets/scripts/page.ts +11 -0
  15. package/assets/scripts/tag.ts +4 -0
  16. package/assets/scripts/tsconfig.json +10 -0
  17. package/assets/styles/404.styl +31 -0
  18. package/assets/styles/_article/fold.styl +15 -0
  19. package/assets/styles/_article/footnote.styl +12 -0
  20. package/assets/styles/_article/heading.styl +29 -0
  21. package/assets/styles/_article/image.styl +30 -0
  22. package/assets/styles/_article/kbd.styl +10 -0
  23. package/assets/styles/_article/links.styl +31 -0
  24. package/assets/styles/_article/list.styl +19 -0
  25. package/assets/styles/_article/note.styl +18 -0
  26. package/assets/styles/_article/other.styl +44 -0
  27. package/assets/styles/_article/table.styl +29 -0
  28. package/assets/styles/_article/tabs.styl +25 -0
  29. package/assets/styles/_code.styl +83 -0
  30. package/assets/styles/_index/contact.styl +20 -0
  31. package/assets/styles/_index/footer.styl +5 -0
  32. package/assets/styles/_index/header.styl +40 -0
  33. package/assets/styles/_index/nav.styl +59 -0
  34. package/assets/styles/_index/search.styl +64 -0
  35. package/assets/styles/_index.styl +91 -0
  36. package/assets/styles/_var.styl +96 -0
  37. package/assets/styles/archive.styl +35 -0
  38. package/assets/styles/article.styl +138 -0
  39. package/assets/styles/category.styl +4 -0
  40. package/assets/styles/home.styl +124 -0
  41. package/assets/styles/links.styl +121 -0
  42. package/assets/styles/page.styl +12 -0
  43. package/assets/styles/tag.styl +4 -0
  44. package/dist/config.d.ts +128 -0
  45. package/dist/feed.d.ts +4 -0
  46. package/dist/image/asset.d.ts +22 -0
  47. package/dist/image/db.d.ts +18 -0
  48. package/dist/image/index.d.ts +10 -0
  49. package/dist/image/metadata.d.ts +2 -0
  50. package/dist/image/utils.d.ts +1 -0
  51. package/dist/index-now.d.ts +1 -0
  52. package/dist/index.d.ts +3 -0
  53. package/dist/index.js +2066 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/layout.d.ts +2 -0
  56. package/dist/markdown/codeblock/data.d.ts +1 -0
  57. package/dist/markdown/codeblock/index.d.ts +6 -0
  58. package/dist/markdown/codeblock/style.d.ts +2 -0
  59. package/dist/markdown/fold.d.ts +6 -0
  60. package/dist/markdown/footnote.d.ts +15 -0
  61. package/dist/markdown/image.d.ts +12 -0
  62. package/dist/markdown/index.d.ts +2 -0
  63. package/dist/markdown/kbd.d.ts +6 -0
  64. package/dist/markdown/link.d.ts +2 -0
  65. package/dist/markdown/links.d.ts +12 -0
  66. package/dist/markdown/note.d.ts +8 -0
  67. package/dist/markdown/table.d.ts +3 -0
  68. package/dist/markdown/tabs.d.ts +7 -0
  69. package/dist/markdown/tex.d.ts +9 -0
  70. package/dist/page/404.d.ts +1 -0
  71. package/dist/page/archive.d.ts +1 -0
  72. package/dist/page/category.d.ts +1 -0
  73. package/dist/page/home.d.ts +2 -0
  74. package/dist/page/tag.d.ts +1 -0
  75. package/dist/pagefind.d.ts +20 -0
  76. package/dist/sitemap.d.ts +2 -0
  77. package/dist/transform/script.d.ts +2 -0
  78. package/dist/transform/stylus.d.ts +2 -0
  79. package/dist/utils.d.ts +2 -0
  80. package/layouts/404.tsx +8 -0
  81. package/layouts/archive.tsx +81 -0
  82. package/layouts/article.tsx +145 -0
  83. package/layouts/base.tsx +20 -0
  84. package/layouts/category.tsx +18 -0
  85. package/layouts/components/ArchiveArticleList.tsx +14 -0
  86. package/layouts/components/Article.tsx +46 -0
  87. package/layouts/components/Contact.tsx +14 -0
  88. package/layouts/components/Footer.tsx +44 -0
  89. package/layouts/components/Head.tsx +119 -0
  90. package/layouts/components/Image.tsx +42 -0
  91. package/layouts/components/Nav.tsx +33 -0
  92. package/layouts/components/Search.tsx +20 -0
  93. package/layouts/components/Waline.tsx +22 -0
  94. package/layouts/context.d.ts +54 -0
  95. package/layouts/home.tsx +74 -0
  96. package/layouts/links.tsx +53 -0
  97. package/layouts/page.tsx +19 -0
  98. package/layouts/tag.tsx +18 -0
  99. package/layouts/tsconfig.json +11 -0
  100. package/package.json +47 -0
  101. package/readme.md +17 -0
  102. package/readme_zh.md +17 -0
@@ -0,0 +1,145 @@
1
+ import { Article, URL } from 'ezal';
2
+ import type { Context } from 'ezal-markdown';
3
+ import base from './base';
4
+ import Image from './components/Image';
5
+
6
+ const { theme } = context;
7
+ const page = context.page as Article;
8
+ const markdown = page.renderedData as Context;
9
+
10
+ const categories = [
11
+ ...page.categories.values().map((cate) => (
12
+ <a
13
+ class="link"
14
+ href={URL.for(URL.encode(`/category/${cate.path.join('/')}/`))}
15
+ >
16
+ {cate.path.join('/')}
17
+ </a>
18
+ )),
19
+ ];
20
+
21
+ const tags = [
22
+ ...page.tags.keys().map((tag) => (
23
+ <a class="link tag" href={URL.for(URL.encode(`/tag/${tag}/`))}>
24
+ {tag}
25
+ </a>
26
+ )),
27
+ ];
28
+
29
+ const date = page.date.toPlainDate().toString();
30
+ const dateTime = page.date.toString({ timeZoneName: 'never' });
31
+ const update = page.updated.toPlainDate().toString();
32
+ const updatedTime = page.updated.toString({ timeZoneName: 'never' });
33
+
34
+ const toc = <aside />;
35
+ let current = toc;
36
+ let currentLevel = 0;
37
+ for (const item of markdown.toc.values()) {
38
+ const el = (
39
+ <li>
40
+ <a href={`#${item.anchor}`}>{item.name}</a>
41
+ </li>
42
+ );
43
+ if (currentLevel < item.level) {
44
+ const next = <ol />;
45
+ next.append(el);
46
+ current.append(next);
47
+ if (current === toc) next.attr.class = 'toc';
48
+ } else if (currentLevel === item.level) {
49
+ current.after(el);
50
+ } else {
51
+ while (currentLevel > item.level) {
52
+ current = current.parent.parent;
53
+ currentLevel--;
54
+ }
55
+ current.after(el);
56
+ }
57
+ current = el;
58
+ currentLevel = item.level;
59
+ }
60
+
61
+ const articles = Article.getAll().sort(context.compareByDate);
62
+ const index = articles.indexOf(page);
63
+ const prev: Article | undefined = articles[index - 1];
64
+ const next: Article | undefined = articles[index + 1];
65
+
66
+ /* const relatedMap = new Map<Article, number>();
67
+ for (const tag of page.tags.values()) {
68
+ for (const article of tag.getArticles()) {
69
+ if (article === page) continue;
70
+ const count = relatedMap.get(article);
71
+ relatedMap.set(article, (count ?? 0) + 1);
72
+ }
73
+ }
74
+ const related = relatedMap
75
+ .entries()
76
+ .toArray()
77
+ .sort(([_, a], [__, b]) => b - a)
78
+ .slice(0, 6)
79
+ .map(([a]) => a);
80
+
81
+ const relatedElements: JSX.Element[] = [];
82
+ if (related.length > 0) {
83
+ relatedElements.push(<h2 class="related-title wrap">相关推荐</h2>);
84
+ relatedElements.push(
85
+ <div class="related wrap">
86
+ {related.map((article) => (
87
+ <a href={article.url}>{article.title}</a>
88
+ ))}
89
+ </div>,
90
+ );
91
+ } */
92
+
93
+ export default base(
94
+ <header>
95
+ {page.data.cover ? <Image url={page.data.cover} alt={page.title} /> : null}
96
+ <div class="wrap">
97
+ <div class="cates">
98
+ {categories}
99
+ {tags}
100
+ </div>
101
+ <h1>{page.title}</h1>
102
+ <div class="article-info">
103
+ <span class="icon-word-count" title="字数">
104
+ {markdown.counter.value}
105
+ </span>
106
+ <span class="icon-timer" title="预计阅读时长">
107
+ ~{markdown.counter.minute2read().toFixed(0)} 分钟
108
+ </span>
109
+ <time class="icon-date" title="发布日期" datetime={dateTime}>
110
+ {date}
111
+ </time>
112
+ <time class="icon-updated" title="更新日期" datetime={updatedTime}>
113
+ {update}
114
+ </time>
115
+ {theme.waline?.pageview && page.data?.comment !== false ? (
116
+ <span class="icon-hot waline-pageview-count" title="阅读量">
117
+ ...
118
+ </span>
119
+ ) : null}
120
+ </div>
121
+ </div>
122
+ </header>,
123
+ <main>
124
+ {toc}
125
+ <article>
126
+ <RawHTML html={page.content} />
127
+ </article>
128
+ </main>,
129
+ // ...relatedElements,
130
+ <div class="pagination wrap rounded">
131
+ {prev ? (
132
+ <a href={URL.for(prev.url)} class="prev">
133
+ <i class="icon-left"></i>
134
+ {prev.title}
135
+ </a>
136
+ ) : null}
137
+ <div class="flex"></div>
138
+ {next ? (
139
+ <a href={URL.for(next.url)} class="next">
140
+ {next.title}
141
+ <i class="icon-right"></i>
142
+ </a>
143
+ ) : null}
144
+ </div>,
145
+ );
@@ -0,0 +1,20 @@
1
+ import Footer from './components/Footer';
2
+ import Head from './components/Head';
3
+ import Nav from './components/Nav';
4
+ import Search from './components/Search';
5
+ import Waline from './components/Waline';
6
+
7
+ export default (...elements: JSX.Element[]) => (
8
+ <Doc lang={context.site.language}>
9
+ <head>
10
+ <Head />
11
+ </head>
12
+ <body>
13
+ <Nav />
14
+ {elements}
15
+ <Search />
16
+ <Waline />
17
+ <Footer />
18
+ </body>
19
+ </Doc>
20
+ );
@@ -0,0 +1,18 @@
1
+ import base from './base';
2
+ import ArchiveArticleList from './components/ArchiveArticleList';
3
+ import type { CategoryPage } from './context';
4
+
5
+ const page = context.page as CategoryPage;
6
+
7
+ export default base(
8
+ <header>
9
+ <div class="wrap">
10
+ <h1>{page.title}</h1>
11
+ </div>
12
+ </header>,
13
+ <main>
14
+ <ArchiveArticleList
15
+ articles={page.data.category.getArticles().sort(context.compareByDate)}
16
+ />
17
+ </main>,
18
+ );
@@ -0,0 +1,14 @@
1
+ import { type Article, URL } from 'ezal';
2
+
3
+ export default ({ articles }: { articles: Article[] }) => (
4
+ <ul class="archive">
5
+ {articles.map((article) => (
6
+ <li>
7
+ <time datetime={article.date.toString({ timeZoneName: 'never' })}>
8
+ {article.date.toPlainDate().toString()}
9
+ </time>
10
+ <a href={URL.for(article.url)}>{article.title}</a>
11
+ </li>
12
+ ))}
13
+ </ul>
14
+ );
@@ -0,0 +1,46 @@
1
+ import { type Article, URL } from 'ezal';
2
+ import Image from './Image';
3
+
4
+ export default ({ article }: { article: Article }): JSX.Element => (
5
+ <a class="article rounded" href={URL.for(article.url)}>
6
+ {article.data?.cover
7
+ ? [
8
+ <Image
9
+ class="bloom"
10
+ url={URL.for(URL.resolve(article.url, article.data.cover))}
11
+ alt={article.title}
12
+ />,
13
+ <Image
14
+ url={URL.for(URL.resolve(article.url, article.data.cover))}
15
+ alt={article.title}
16
+ />,
17
+ ]
18
+ : null}
19
+ <div class="article-info">
20
+ <object>
21
+ {...article.categories
22
+ .values()
23
+ .map((category) => (
24
+ <a class="link" href={URL.for('category', ...category.path)}>
25
+ {category.path.join('/')}
26
+ </a>
27
+ ))
28
+ .toArray()}
29
+ </object>
30
+ <h2>{article.title}</h2>
31
+ <object>
32
+ {article.tags
33
+ .values()
34
+ .map((tag) => (
35
+ <a class="tag link" href={URL.for(`/tag/${tag.name}/`)}>
36
+ {tag.name}
37
+ </a>
38
+ ))
39
+ .toArray()}
40
+ <time datetime={article.date.toString({ timeZoneName: 'never' })}>
41
+ {article.date.toPlainDate().toString()}
42
+ </time>
43
+ </object>
44
+ </div>
45
+ </a>
46
+ );
@@ -0,0 +1,14 @@
1
+ import { URL } from 'ezal';
2
+
3
+ export default (options?: { style?: JSX.IntrinsicAttributes['style'] }) => (
4
+ <div class="contact" style={options?.style}>
5
+ {context.theme.contact?.map(({ url, name, icon, color }) => (
6
+ <a
7
+ class={`icon-${icon}`}
8
+ href={URL.for(url)}
9
+ title={name}
10
+ style={{ $color: color }}
11
+ ></a>
12
+ ))}
13
+ </div>
14
+ );
@@ -0,0 +1,44 @@
1
+ import { Time } from 'ezal';
2
+ import Contact from './Contact';
3
+
4
+ const since = context.theme.since;
5
+ const now = Time.now();
6
+ const author = context.site.author;
7
+
8
+ export default () => (
9
+ <footer>
10
+ <div class="wrap">
11
+ <Contact />
12
+ <br />
13
+ {'小站运行了'}
14
+ <time id="since" datetime={since?.toString({ timeZoneName: 'never' })}>
15
+ 好多好多
16
+ </time>
17
+ {'天'}
18
+ <br />
19
+ {'上次更新于 '}
20
+ <time
21
+ id="last"
22
+ datetime={now.toString({ timeZoneName: 'never' })}
23
+ >{`${now.year} 年 ${now.year} 月 ${now.month} 日`}</time>
24
+ <br />
25
+ {'基于 '}
26
+ <a href="https://github.com/JonnyJong/ezal">Ezal</a>
27
+ {' 博客框架 | '}
28
+ <a href="https://github.com/JonnyJong/ezal/tree/main/packages/ezal-theme-example">
29
+ ezal-theme-example
30
+ </a>
31
+ {' 主题'}
32
+ <br />
33
+ {`©${since ? `${since.year}~` : ''}`}
34
+ <time id="now"></time>
35
+ {` By ${author}`}
36
+ <br />
37
+ {'若无特别说明,所有文章均采用 '}
38
+ <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/">
39
+ CC-BY-NC-SA 4.0
40
+ </a>
41
+ {' 许可协议'}
42
+ </div>
43
+ </footer>
44
+ );
@@ -0,0 +1,119 @@
1
+ import { URL } from 'ezal';
2
+ import * as mime from 'mime-types';
3
+
4
+ const { site, page, theme } = context;
5
+
6
+ function resolveFavicons(): [type: string, href: string][] {
7
+ if (!theme.favicon) return [];
8
+ let icons = theme.favicon;
9
+ if (!Array.isArray(icons)) icons = [icons];
10
+ return icons.map(String).map<[string, string]>((href) => {
11
+ const type = mime.lookup(href);
12
+ return [type ? type : '', href];
13
+ });
14
+ }
15
+
16
+ const favicons = resolveFavicons();
17
+ const title = page.title ? `${page.title} - ${site.title}` : site.title;
18
+ const description = page.description ?? site.description;
19
+ const keywords = (page.keywords ?? site.keywords ?? []).join(',');
20
+ const canonicalUrl = URL.full(page.url);
21
+ const cover = page.data.cover ? URL.full(page.url, page.data.cover) : undefined;
22
+
23
+ const katex =
24
+ theme.cdn?.katex ?? 'https://unpkg.com/katex@0.16.21/dist/katex.min.css';
25
+
26
+ const enableComment =
27
+ theme.waline &&
28
+ (page.data?.comment || ('tags' in page && page.data?.comment !== false));
29
+ const waline =
30
+ theme.cdn?.walineCSS ?? 'https://unpkg.com/@waline/client@v3/dist/waline.css';
31
+
32
+ export default (slot?: any) => (
33
+ <head>
34
+ {/* 基本 */}
35
+ <meta charset="UTF-8" />
36
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
37
+ <link rel="canonical" href={canonicalUrl} />
38
+ {favicons.map(([type, href]) => (
39
+ <link rel="icon" type={type} href={URL.full(href)} />
40
+ ))}
41
+ <title>{title}</title>
42
+ <link rel="sitemap" href={URL.full('sitemap.xml')} />
43
+ <link
44
+ rel="alternate"
45
+ type="application/atom+xml"
46
+ title={site.title}
47
+ href={URL.full('atom.xml')}
48
+ />
49
+ <link
50
+ rel="alternate"
51
+ type="application/rss+xml"
52
+ title={site.title}
53
+ href={URL.full('rss.xml')}
54
+ />
55
+ <link
56
+ rel="alternate"
57
+ type="application/feed+json"
58
+ title={site.title}
59
+ href={URL.full('feed.json')}
60
+ />
61
+ <meta name="theme-color" content="dark light" />
62
+ <meta name="author" content={site.author} />
63
+ {description ? <meta name="description" content={description} /> : null}
64
+ {keywords ? <meta name="keywords" content={keywords} /> : null}
65
+ {/* Open Graph */}
66
+ <meta property="og:site_name" content={site.title} />
67
+ <meta property="og:title" content={title} />
68
+ {cover ? <meta property="og:image" content={cover} /> : null}
69
+ <meta property="og:url" content={canonicalUrl} />
70
+ <meta property="og:locale" content={site.language} />
71
+ {favicons[0] ? (
72
+ <meta property="og:image" content={URL.full(favicons[0][1])} />
73
+ ) : null}
74
+ <meta
75
+ property="og:type"
76
+ content={page.layout === 'article' ? 'article' : 'website'}
77
+ />
78
+ {/* Twitter */}
79
+ <meta name="twitter:card" content="card" />
80
+ <meta name="twitter:author" content={site.author} />
81
+ <meta name="twitter:title" content={title} />
82
+ <meta name="twitter:description" content={description} />
83
+ {cover ? <meta name="twitter:image" content={cover} /> : null}
84
+ {'tags' in page
85
+ ? [
86
+ <meta name="twitter:tag" content={page.tags.keys().toArray().join(',')} />,
87
+ <meta name="twitter:published_time" content={page.date.toString()} />,
88
+ <meta name="twitter:modified_time" content={page.updated.toString()} />,
89
+ ]
90
+ : null}
91
+ {/* 资源 */}
92
+ {'renderedData' in page && page.renderedData.shared.tex ? (
93
+ <link rel="stylesheet" href={URL.for(katex)} />
94
+ ) : null}
95
+ {enableComment ? <link rel="stylesheet" href={URL.for(waline)} /> : null}
96
+ <link
97
+ rel="preload"
98
+ href="https://unpkg.com/@fontsource/maple-mono@5.2.4/files/maple-mono-latin-400-normal.woff2"
99
+ as="font"
100
+ type="font/woff2"
101
+ crossorigin
102
+ />
103
+ <link
104
+ rel="preload"
105
+ href="https://unpkg.com/@fontsource/maple-mono@5.2.4/files/maple-mono-latin-400-normal.woff"
106
+ as="font"
107
+ type="font/woff"
108
+ crossorigin
109
+ />
110
+ <link rel="stylesheet" href={URL.for(`styles/${page.layout}.css`)} />
111
+ <script src={URL.for(`scripts/${page.layout}.js`)} async defer></script>
112
+ {'renderedData' in page && page.renderedData.shared.codeblock ? (
113
+ <link rel="stylesheet" href={URL.for('styles/code.css')} />
114
+ ) : null}
115
+ {/* 额外 */}
116
+ {slot}
117
+ {theme.inject ? <RawHTML html={theme.inject} /> : null}
118
+ </head>
119
+ );
@@ -0,0 +1,42 @@
1
+ import { URL } from 'ezal';
2
+
3
+ const { page, getImageInfo, mime } = context;
4
+
5
+ export default ({
6
+ url,
7
+ alt,
8
+ ...extra
9
+ }: { url: string; alt: string } & JSX.IntrinsicAttributes) => {
10
+ const info = getImageInfo(URL.resolve(page.url, url));
11
+ if (!info.rule)
12
+ return (
13
+ <img
14
+ src={URL.for(url)}
15
+ alt={alt}
16
+ width={info.metadata?.width}
17
+ height={info.metadata?.height}
18
+ style={{ $imgColor: info.metadata?.color }}
19
+ loading="lazy"
20
+ {...extra}
21
+ />
22
+ );
23
+ return (
24
+ <picture>
25
+ {info.rule.slice(0, -1).map((ext) => (
26
+ <source
27
+ srcset={URL.for(URL.encode(URL.extname(url, `.opt${ext}`)))}
28
+ type={mime.lookup(ext) as any}
29
+ />
30
+ ))}
31
+ <img
32
+ src={URL.for(URL.encode(URL.extname(url, info.rule.at(-1))))}
33
+ alt={alt}
34
+ width={info.metadata?.width}
35
+ height={info.metadata?.height}
36
+ style={{ $imgColor: info.metadata?.color }}
37
+ loading="lazy"
38
+ {...extra}
39
+ />
40
+ </picture>
41
+ );
42
+ };
@@ -0,0 +1,33 @@
1
+ import { URL } from 'ezal';
2
+
3
+ const { site, theme } = context;
4
+
5
+ export default () => (
6
+ <nav>
7
+ <a class="site link" href={URL.for('/')}>
8
+ {site.title}
9
+ </a>
10
+ <div class="flex"></div>
11
+ <ul class="nav">
12
+ {theme.nav?.map(({ name, link }) => (
13
+ <li>
14
+ <a class="link" href={URL.for(link)}>
15
+ {name}
16
+ </a>
17
+ </li>
18
+ ))}
19
+ </ul>
20
+ <button
21
+ class="icon-search link"
22
+ id="search"
23
+ title="搜索"
24
+ type="button"
25
+ ></button>
26
+ <button
27
+ class="icon-nav link"
28
+ id="nav"
29
+ title="导航菜单"
30
+ type="button"
31
+ ></button>
32
+ </nav>
33
+ );
@@ -0,0 +1,20 @@
1
+ export default () => (
2
+ <dialog class="rounded search">
3
+ <div class="search-bar">
4
+ <input
5
+ type="search"
6
+ id="search-input"
7
+ class="search-input"
8
+ placeholder="键入以搜索..."
9
+ />
10
+ <button
11
+ class="icon-close link"
12
+ title="关闭"
13
+ type="button"
14
+ id="search-close"
15
+ ></button>
16
+ </div>
17
+ <progress></progress>
18
+ <div class="search-result"></div>
19
+ </dialog>
20
+ );
@@ -0,0 +1,22 @@
1
+ import { Article, Page } from 'ezal';
2
+
3
+ export default () => {
4
+ const { page, theme } = context;
5
+
6
+ if (!theme.waline) return;
7
+ if (!(page instanceof Page)) return;
8
+ if (page instanceof Article) {
9
+ if (page.data?.comment === false) return;
10
+ } else if (!page.data?.comment) return;
11
+
12
+ const options = { ...theme.waline, el: '#waline', path: page.url };
13
+
14
+ const module =
15
+ theme.cdn?.walineJS ?? 'https://unpkg.com/@waline/client@v3/dist/waline.js';
16
+ const script = `import{init}from'${module}';init(${JSON.stringify(options)})`;
17
+
18
+ return <Container>
19
+ <div id="waline" class="wrap"></div>
20
+ <script type="module" defer><RawHTML html={script}/></script>
21
+ </Container>;
22
+ };
@@ -0,0 +1,54 @@
1
+ import type {
2
+ Article,
3
+ Category,
4
+ Page,
5
+ SiteConfig,
6
+ Tag,
7
+ VirtualPage,
8
+ } from 'ezal';
9
+ import type { Context as MarkdownContext } from 'ezal-markdown';
10
+ import type mime from 'mime-types';
11
+ import type { ThemeConfig } from '../src/config';
12
+ import type { ImageInfo } from '../src/image';
13
+
14
+ export interface HomePageData {
15
+ index: number;
16
+ getPages(): HomePage[];
17
+ getArticles(index: number): Article[];
18
+ }
19
+ export type HomePage = VirtualPage & { data: HomePageData };
20
+
21
+ export interface ArchivePageData {
22
+ years: Map<number, number>;
23
+ getArticles(year: number): Article[];
24
+ }
25
+ export type ArchivePage = VirtualPage & { data: ArchivePageData };
26
+
27
+ export interface CategoryPageData {
28
+ category: Category;
29
+ }
30
+ export type CategoryPage = VirtualPage & { data: CategoryPageData };
31
+
32
+ export interface TagPageData {
33
+ tag: Tag;
34
+ }
35
+ export type TagPage = VirtualPage & { data: TagPageData };
36
+
37
+ export interface Context {
38
+ site: SiteConfig;
39
+ theme: ThemeConfig;
40
+ page?:
41
+ | (Page & { renderedData: MarkdownContext })
42
+ | (Article & { renderedData: MarkdownContext })
43
+ | HomePage
44
+ | ArchivePage
45
+ | CategoryPage
46
+ | TagPage;
47
+ getImageInfo(url: string): ImageInfo | null;
48
+ mime: typeof mime;
49
+ compareByDate(a: Article, b: Article): number;
50
+ }
51
+
52
+ declare global {
53
+ const context: Context;
54
+ }
@@ -0,0 +1,74 @@
1
+ import { URL } from 'ezal';
2
+ import base from './base';
3
+ import Article from './components/Article';
4
+ import Contact from './components/Contact';
5
+ import type { HomePage } from './context';
6
+
7
+ const { home } = context.theme;
8
+ const page = context.page as HomePage;
9
+ const pages = page.data.getPages();
10
+ const current = pages.indexOf(page);
11
+
12
+ const slogan = home?.slogan ? (
13
+ <RawHTML html={home.slogan} />
14
+ ) : (
15
+ <Container>
16
+ {'Hi there!👋'}
17
+ <br />
18
+ {`I'm ${context.site.author}.`}
19
+ </Container>
20
+ );
21
+
22
+ const logo = home?.logo ? (
23
+ <svg
24
+ class="home-logo"
25
+ viewBox={home.logo.viewBox}
26
+ role="img"
27
+ aria-label="logo"
28
+ >
29
+ <g id="logo">
30
+ <RawHTML html={home.logo.g} />
31
+ </g>
32
+ </svg>
33
+ ) : null;
34
+
35
+ let header: JSX.Element;
36
+ if (current) {
37
+ header = <header style={{ height: 70 }} />;
38
+ } else {
39
+ header = (
40
+ <header>
41
+ <div class="wrap home">
42
+ <div class="home-title">
43
+ {slogan}
44
+ <Contact style={{ paddingTop: 8 }} />
45
+ </div>
46
+ {logo}
47
+ </div>
48
+ <div class="home-indicator icon-down"></div>
49
+ </header>
50
+ );
51
+ }
52
+
53
+ const pagination = pages.map((p, i) => {
54
+ if (p === page) return <div class="page rounded">{i + 1}</div>;
55
+ return (
56
+ <a class="page rounded" href={`${URL.for(p.url)}#posts`}>
57
+ {i + 1}
58
+ </a>
59
+ );
60
+ });
61
+
62
+ export default base(
63
+ header,
64
+ <main>
65
+ <div class="wrap">
66
+ <div class="article-list" id="posts">
67
+ {page.data.getArticles(page.data.index).map((article) => (
68
+ <Article article={article} />
69
+ ))}
70
+ </div>
71
+ {pagination.length > 1 ? <div class="pages">{pagination}</div> : null}
72
+ </div>
73
+ </main>,
74
+ );