@ouraihub/hugo 0.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.
@@ -0,0 +1,269 @@
1
+ {{- $menuItems := .menuItems | default (slice) -}}
2
+ {{- $logo := .logo | default "" -}}
3
+ {{- $logoAlt := .logoAlt | default "Logo" -}}
4
+
5
+ <nav
6
+ data-ui-component="navigation"
7
+ data-ui-mobile-breakpoint="768"
8
+ class="navigation-container"
9
+ role="navigation"
10
+ aria-label="主导航"
11
+ >
12
+ <div class="navigation-inner">
13
+ {{- if $logo -}}
14
+ <div class="navigation-logo">
15
+ <a href="/" aria-label="返回首页">
16
+ <img src="{{ $logo }}" alt="{{ $logoAlt }}" />
17
+ </a>
18
+ </div>
19
+ {{- end -}}
20
+
21
+ <button
22
+ class="navigation-mobile-toggle"
23
+ aria-label="切换菜单"
24
+ aria-expanded="false"
25
+ aria-controls="navigation-menu"
26
+ >
27
+ <span class="hamburger-icon">
28
+ <span></span>
29
+ <span></span>
30
+ <span></span>
31
+ </span>
32
+ </button>
33
+
34
+ <div
35
+ id="navigation-menu"
36
+ class="navigation-menu"
37
+ role="menu"
38
+ >
39
+ <ul class="navigation-list">
40
+ {{- range $menuItems -}}
41
+ <li class="navigation-item{{ if .children }} has-dropdown{{ end }}" role="none">
42
+ {{- if .children -}}
43
+ <button
44
+ class="navigation-link navigation-dropdown-toggle"
45
+ aria-haspopup="true"
46
+ aria-expanded="false"
47
+ role="menuitem"
48
+ >
49
+ {{ .title }}
50
+ <span class="dropdown-arrow" aria-hidden="true">▼</span>
51
+ </button>
52
+ <ul class="navigation-dropdown" role="menu">
53
+ {{- range .children -}}
54
+ <li class="navigation-dropdown-item" role="none">
55
+ <a
56
+ href="{{ .url }}"
57
+ class="navigation-dropdown-link"
58
+ role="menuitem"
59
+ >
60
+ {{ .title }}
61
+ </a>
62
+ </li>
63
+ {{- end -}}
64
+ </ul>
65
+ {{- else -}}
66
+ <a
67
+ href="{{ .url }}"
68
+ class="navigation-link"
69
+ role="menuitem"
70
+ >
71
+ {{ .title }}
72
+ </a>
73
+ {{- end -}}
74
+ </li>
75
+ {{- end -}}
76
+ </ul>
77
+ </div>
78
+ </div>
79
+ </nav>
80
+
81
+ <style>
82
+ .navigation-container {
83
+ border-bottom: 1px solid var(--ui-border, #e0e0e0);
84
+ background: var(--ui-background, #ffffff);
85
+ position: sticky;
86
+ top: 0;
87
+ z-index: 100;
88
+ }
89
+
90
+ .navigation-inner {
91
+ max-width: var(--ui-max-width, 1200px);
92
+ margin: 0 auto;
93
+ padding: 1rem;
94
+ display: flex;
95
+ align-items: center;
96
+ justify-content: space-between;
97
+ }
98
+
99
+ .navigation-logo img {
100
+ height: 2rem;
101
+ width: auto;
102
+ }
103
+
104
+ .navigation-mobile-toggle {
105
+ display: none;
106
+ border: none;
107
+ background: transparent;
108
+ padding: 0.5rem;
109
+ cursor: pointer;
110
+ }
111
+
112
+ .hamburger-icon {
113
+ display: flex;
114
+ flex-direction: column;
115
+ gap: 0.25rem;
116
+ }
117
+
118
+ .hamburger-icon span {
119
+ display: block;
120
+ width: 1.5rem;
121
+ height: 2px;
122
+ background: var(--ui-text, #333333);
123
+ transition: transform 0.2s ease;
124
+ }
125
+
126
+ .navigation-menu {
127
+ display: flex;
128
+ }
129
+
130
+ .navigation-list {
131
+ display: flex;
132
+ gap: 1.5rem;
133
+ list-style: none;
134
+ margin: 0;
135
+ padding: 0;
136
+ }
137
+
138
+ .navigation-item {
139
+ position: relative;
140
+ }
141
+
142
+ .navigation-link {
143
+ color: var(--ui-text, #333333);
144
+ text-decoration: none;
145
+ padding: 0.5rem 0.75rem;
146
+ display: flex;
147
+ align-items: center;
148
+ gap: 0.25rem;
149
+ border: none;
150
+ background: transparent;
151
+ cursor: pointer;
152
+ font-size: 1rem;
153
+ transition: color 0.2s ease;
154
+ }
155
+
156
+ .navigation-link:hover {
157
+ color: var(--ui-primary, #0066cc);
158
+ }
159
+
160
+ .navigation-dropdown {
161
+ position: absolute;
162
+ top: 100%;
163
+ left: 0;
164
+ min-width: 12rem;
165
+ background: var(--ui-background, #ffffff);
166
+ border: 1px solid var(--ui-border, #e0e0e0);
167
+ border-radius: 0.25rem;
168
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
169
+ list-style: none;
170
+ margin: 0.25rem 0 0 0;
171
+ padding: 0.5rem 0;
172
+ opacity: 0;
173
+ visibility: hidden;
174
+ transform: translateY(-0.5rem);
175
+ transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s;
176
+ }
177
+
178
+ .navigation-item.has-dropdown:hover .navigation-dropdown,
179
+ .navigation-dropdown-toggle[aria-expanded="true"] + .navigation-dropdown {
180
+ opacity: 1;
181
+ visibility: visible;
182
+ transform: translateY(0);
183
+ }
184
+
185
+ .navigation-dropdown-item {
186
+ margin: 0;
187
+ }
188
+
189
+ .navigation-dropdown-link {
190
+ color: var(--ui-text, #333333);
191
+ text-decoration: none;
192
+ padding: 0.5rem 1rem;
193
+ display: block;
194
+ transition: background-color 0.2s ease;
195
+ }
196
+
197
+ .navigation-dropdown-link:hover {
198
+ background: var(--ui-background-hover, #f5f5f5);
199
+ }
200
+
201
+ .dropdown-arrow {
202
+ font-size: 0.75rem;
203
+ transition: transform 0.2s ease;
204
+ }
205
+
206
+ .navigation-dropdown-toggle[aria-expanded="true"] .dropdown-arrow {
207
+ transform: rotate(180deg);
208
+ }
209
+
210
+ @media (max-width: 768px) {
211
+ .navigation-mobile-toggle {
212
+ display: block;
213
+ }
214
+
215
+ .navigation-menu {
216
+ position: absolute;
217
+ top: 100%;
218
+ left: 0;
219
+ right: 0;
220
+ background: var(--ui-background, #ffffff);
221
+ border-top: 1px solid var(--ui-border, #e0e0e0);
222
+ max-height: 0;
223
+ overflow: hidden;
224
+ transition: max-height 0.3s ease;
225
+ }
226
+
227
+ .navigation-menu[aria-hidden="false"] {
228
+ max-height: 100vh;
229
+ }
230
+
231
+ .navigation-list {
232
+ flex-direction: column;
233
+ gap: 0;
234
+ padding: 1rem;
235
+ }
236
+
237
+ .navigation-item {
238
+ border-bottom: 1px solid var(--ui-border, #e0e0e0);
239
+ }
240
+
241
+ .navigation-item:last-child {
242
+ border-bottom: none;
243
+ }
244
+
245
+ .navigation-link {
246
+ padding: 0.75rem 0;
247
+ }
248
+
249
+ .navigation-dropdown {
250
+ position: static;
251
+ border: none;
252
+ box-shadow: none;
253
+ padding-left: 1rem;
254
+ margin-top: 0;
255
+ }
256
+
257
+ .navigation-mobile-toggle[aria-expanded="true"] .hamburger-icon span:nth-child(1) {
258
+ transform: rotate(45deg) translate(0.4rem, 0.4rem);
259
+ }
260
+
261
+ .navigation-mobile-toggle[aria-expanded="true"] .hamburger-icon span:nth-child(2) {
262
+ opacity: 0;
263
+ }
264
+
265
+ .navigation-mobile-toggle[aria-expanded="true"] .hamburger-icon span:nth-child(3) {
266
+ transform: rotate(-45deg) translate(0.4rem, -0.4rem);
267
+ }
268
+ }
269
+ </style>
@@ -0,0 +1,317 @@
1
+ {{- $searchEndpoint := .searchEndpoint | default "/search.json" -}}
2
+ {{- $placeholder := .placeholder | default "搜索..." -}}
3
+ {{- $noResultsText := .noResultsText | default "未找到结果" -}}
4
+
5
+ <div
6
+ data-ui-component="search-modal"
7
+ data-ui-search-endpoint="{{ $searchEndpoint }}"
8
+ class="search-modal"
9
+ role="dialog"
10
+ aria-modal="true"
11
+ aria-labelledby="search-modal-title"
12
+ aria-hidden="true"
13
+ >
14
+ <div class="search-modal-overlay" aria-hidden="true"></div>
15
+
16
+ <div class="search-modal-content">
17
+ <div class="search-modal-header">
18
+ <h2 id="search-modal-title" class="search-modal-title">搜索</h2>
19
+ <button
20
+ class="search-modal-close"
21
+ aria-label="关闭搜索"
22
+ type="button"
23
+ >
24
+ <span aria-hidden="true">×</span>
25
+ </button>
26
+ </div>
27
+
28
+ <div class="search-modal-body">
29
+ <div class="search-input-wrapper">
30
+ <input
31
+ type="search"
32
+ class="search-input"
33
+ placeholder="{{ $placeholder }}"
34
+ aria-label="搜索内容"
35
+ aria-describedby="search-shortcut-hint"
36
+ autocomplete="off"
37
+ spellcheck="false"
38
+ />
39
+ <kbd id="search-shortcut-hint" class="search-shortcut">
40
+ <span class="search-shortcut-key">Ctrl</span>
41
+ <span class="search-shortcut-separator">+</span>
42
+ <span class="search-shortcut-key">K</span>
43
+ </kbd>
44
+ </div>
45
+
46
+ <div
47
+ class="search-results"
48
+ role="listbox"
49
+ aria-label="搜索结果"
50
+ >
51
+ <div class="search-loading" aria-live="polite" aria-hidden="true">
52
+ <span class="search-loading-spinner" aria-hidden="true"></span>
53
+ <span>搜索中...</span>
54
+ </div>
55
+
56
+ <div class="search-no-results" aria-live="polite" aria-hidden="true">
57
+ {{ $noResultsText }}
58
+ </div>
59
+
60
+ <ul class="search-results-list" aria-hidden="true">
61
+ <!-- 搜索结果将通过 JavaScript 动态插入 -->
62
+ </ul>
63
+ </div>
64
+ </div>
65
+
66
+ <div class="search-modal-footer">
67
+ <div class="search-shortcuts">
68
+ <span class="search-shortcut-item">
69
+ <kbd>↑↓</kbd> 导航
70
+ </span>
71
+ <span class="search-shortcut-item">
72
+ <kbd>Enter</kbd> 选择
73
+ </span>
74
+ <span class="search-shortcut-item">
75
+ <kbd>Esc</kbd> 关闭
76
+ </span>
77
+ </div>
78
+ </div>
79
+ </div>
80
+ </div>
81
+
82
+ <style>
83
+ .search-modal {
84
+ position: fixed;
85
+ top: 0;
86
+ left: 0;
87
+ right: 0;
88
+ bottom: 0;
89
+ z-index: 1000;
90
+ display: none;
91
+ align-items: flex-start;
92
+ justify-content: center;
93
+ padding: 4rem 1rem;
94
+ }
95
+
96
+ .search-modal[aria-hidden="false"] {
97
+ display: flex;
98
+ }
99
+
100
+ .search-modal-overlay {
101
+ position: absolute;
102
+ top: 0;
103
+ left: 0;
104
+ right: 0;
105
+ bottom: 0;
106
+ background: rgba(0, 0, 0, 0.5);
107
+ backdrop-filter: blur(4px);
108
+ }
109
+
110
+ .search-modal-content {
111
+ position: relative;
112
+ width: 100%;
113
+ max-width: 40rem;
114
+ background: var(--ui-background, #ffffff);
115
+ border-radius: 0.5rem;
116
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
117
+ display: flex;
118
+ flex-direction: column;
119
+ max-height: calc(100vh - 8rem);
120
+ }
121
+
122
+ .search-modal-header {
123
+ display: flex;
124
+ align-items: center;
125
+ justify-content: space-between;
126
+ padding: 1rem 1.5rem;
127
+ border-bottom: 1px solid var(--ui-border, #e0e0e0);
128
+ }
129
+
130
+ .search-modal-title {
131
+ margin: 0;
132
+ font-size: 1.125rem;
133
+ font-weight: 600;
134
+ color: var(--ui-text, #333333);
135
+ }
136
+
137
+ .search-modal-close {
138
+ border: none;
139
+ background: transparent;
140
+ font-size: 1.5rem;
141
+ line-height: 1;
142
+ cursor: pointer;
143
+ padding: 0.25rem;
144
+ color: var(--ui-text-secondary, #666666);
145
+ transition: color 0.2s ease;
146
+ }
147
+
148
+ .search-modal-close:hover {
149
+ color: var(--ui-text, #333333);
150
+ }
151
+
152
+ .search-modal-body {
153
+ flex: 1;
154
+ overflow: hidden;
155
+ display: flex;
156
+ flex-direction: column;
157
+ }
158
+
159
+ .search-input-wrapper {
160
+ position: relative;
161
+ padding: 1rem 1.5rem;
162
+ border-bottom: 1px solid var(--ui-border, #e0e0e0);
163
+ }
164
+
165
+ .search-input {
166
+ width: 100%;
167
+ padding: 0.75rem 1rem;
168
+ padding-right: 6rem;
169
+ border: 1px solid var(--ui-border, #e0e0e0);
170
+ border-radius: 0.375rem;
171
+ font-size: 1rem;
172
+ background: var(--ui-background, #ffffff);
173
+ color: var(--ui-text, #333333);
174
+ transition: border-color 0.2s ease;
175
+ }
176
+
177
+ .search-input:focus {
178
+ outline: none;
179
+ border-color: var(--ui-primary, #0066cc);
180
+ }
181
+
182
+ .search-shortcut {
183
+ position: absolute;
184
+ right: 2.5rem;
185
+ top: 50%;
186
+ transform: translateY(-50%);
187
+ display: flex;
188
+ gap: 0.25rem;
189
+ align-items: center;
190
+ font-size: 0.75rem;
191
+ color: var(--ui-text-secondary, #666666);
192
+ }
193
+
194
+ .search-shortcut-key {
195
+ padding: 0.125rem 0.375rem;
196
+ border: 1px solid var(--ui-border, #e0e0e0);
197
+ border-radius: 0.25rem;
198
+ background: var(--ui-background-secondary, #f9f9f9);
199
+ font-family: monospace;
200
+ }
201
+
202
+ .search-shortcut-separator {
203
+ padding: 0 0.125rem;
204
+ }
205
+
206
+ .search-results {
207
+ flex: 1;
208
+ overflow-y: auto;
209
+ padding: 1rem 0;
210
+ }
211
+
212
+ .search-loading,
213
+ .search-no-results {
214
+ padding: 2rem 1.5rem;
215
+ text-align: center;
216
+ color: var(--ui-text-secondary, #666666);
217
+ }
218
+
219
+ .search-loading {
220
+ display: flex;
221
+ flex-direction: column;
222
+ align-items: center;
223
+ gap: 0.75rem;
224
+ }
225
+
226
+ .search-loading-spinner {
227
+ width: 2rem;
228
+ height: 2rem;
229
+ border: 3px solid var(--ui-border, #e0e0e0);
230
+ border-top-color: var(--ui-primary, #0066cc);
231
+ border-radius: 50%;
232
+ animation: spin 0.8s linear infinite;
233
+ }
234
+
235
+ @keyframes spin {
236
+ to { transform: rotate(360deg); }
237
+ }
238
+
239
+ .search-results-list {
240
+ list-style: none;
241
+ margin: 0;
242
+ padding: 0;
243
+ }
244
+
245
+ .search-result-item {
246
+ margin: 0;
247
+ }
248
+
249
+ .search-result-link {
250
+ display: block;
251
+ padding: 0.75rem 1.5rem;
252
+ color: var(--ui-text, #333333);
253
+ text-decoration: none;
254
+ transition: background-color 0.2s ease;
255
+ }
256
+
257
+ .search-result-link:hover,
258
+ .search-result-link[aria-selected="true"] {
259
+ background: var(--ui-background-hover, #f5f5f5);
260
+ }
261
+
262
+ .search-result-title {
263
+ font-weight: 600;
264
+ margin-bottom: 0.25rem;
265
+ }
266
+
267
+ .search-result-excerpt {
268
+ font-size: 0.875rem;
269
+ color: var(--ui-text-secondary, #666666);
270
+ line-height: 1.5;
271
+ }
272
+
273
+ .search-result-highlight {
274
+ background: var(--ui-highlight, #fef08a);
275
+ font-weight: 600;
276
+ }
277
+
278
+ .search-modal-footer {
279
+ padding: 0.75rem 1.5rem;
280
+ border-top: 1px solid var(--ui-border, #e0e0e0);
281
+ background: var(--ui-background-secondary, #f9f9f9);
282
+ }
283
+
284
+ .search-shortcuts {
285
+ display: flex;
286
+ gap: 1rem;
287
+ justify-content: center;
288
+ font-size: 0.75rem;
289
+ color: var(--ui-text-secondary, #666666);
290
+ }
291
+
292
+ .search-shortcut-item kbd {
293
+ padding: 0.125rem 0.375rem;
294
+ border: 1px solid var(--ui-border, #e0e0e0);
295
+ border-radius: 0.25rem;
296
+ background: var(--ui-background, #ffffff);
297
+ font-family: monospace;
298
+ margin-right: 0.25rem;
299
+ }
300
+
301
+ @media (max-width: 768px) {
302
+ .search-modal {
303
+ padding: 0;
304
+ align-items: stretch;
305
+ }
306
+
307
+ .search-modal-content {
308
+ max-width: none;
309
+ border-radius: 0;
310
+ max-height: 100vh;
311
+ }
312
+
313
+ .search-shortcut {
314
+ display: none;
315
+ }
316
+ }
317
+ </style>
@@ -0,0 +1,91 @@
1
+ {{- $title := .Title | default .Site.Title -}}
2
+ {{- $description := .Description | default .Summary | default .Site.Params.description -}}
3
+ {{- $author := .Params.author | default .Site.Params.author -}}
4
+ {{- $image := .Params.image | default .Site.Params.defaultImage -}}
5
+ {{- $imageAlt := .Params.imageAlt | default $title -}}
6
+ {{- $siteName := .Site.Title -}}
7
+ {{- $locale := .Site.Language.Lang | default "zh-CN" -}}
8
+ {{- $twitterCard := .Params.twitterCard | default "summary_large_image" -}}
9
+ {{- $twitterSite := .Site.Params.twitterSite | default "" -}}
10
+ {{- $twitterCreator := .Params.twitterCreator | default .Site.Params.twitterCreator | default "" -}}
11
+ {{- $keywords := .Params.keywords | default .Site.Params.keywords -}}
12
+ {{- $robots := .Params.robots | default "index, follow" -}}
13
+
14
+ {{- /* 基本 Meta 标签 */ -}}
15
+ <meta charset="utf-8">
16
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
17
+ <meta name="description" content="{{ $description | plainify | htmlUnescape }}">
18
+ {{- with $author }}
19
+ <meta name="author" content="{{ . | plainify | htmlUnescape }}">
20
+ {{- end }}
21
+ {{- with $keywords }}
22
+ <meta name="keywords" content="{{ delimit . ", " | plainify | htmlUnescape }}">
23
+ {{- end }}
24
+ <meta name="robots" content="{{ $robots }}">
25
+
26
+ {{- /* Canonical URL */ -}}
27
+ {{- if .Params.canonical }}
28
+ <link rel="canonical" href="{{ .Params.canonical }}">
29
+ {{- else if not .IsHome }}
30
+ <link rel="canonical" href="{{ .Permalink }}">
31
+ {{- else }}
32
+ <link rel="canonical" href="{{ .Site.BaseURL }}">
33
+ {{- end }}
34
+
35
+ {{- /* Open Graph 标签 */ -}}
36
+ <meta property="og:type" content="{{ if .IsPage }}article{{ else }}website{{ end }}">
37
+ <meta property="og:title" content="{{ $title | plainify | htmlUnescape }}">
38
+ <meta property="og:description" content="{{ $description | plainify | htmlUnescape }}">
39
+ <meta property="og:url" content="{{ .Permalink }}">
40
+ <meta property="og:site_name" content="{{ $siteName | plainify | htmlUnescape }}">
41
+ <meta property="og:locale" content="{{ $locale }}">
42
+ {{- with $image }}
43
+ {{- if hasPrefix . "http" }}
44
+ <meta property="og:image" content="{{ . }}">
45
+ {{- else }}
46
+ <meta property="og:image" content="{{ . | absURL }}">
47
+ {{- end }}
48
+ <meta property="og:image:alt" content="{{ $imageAlt | plainify | htmlUnescape }}">
49
+ {{- end }}
50
+ {{- if .IsPage }}
51
+ {{- with .PublishDate }}
52
+ <meta property="article:published_time" content="{{ .Format "2006-01-02T15:04:05Z07:00" }}">
53
+ {{- end }}
54
+ {{- with .Lastmod }}
55
+ <meta property="article:modified_time" content="{{ .Format "2006-01-02T15:04:05Z07:00" }}">
56
+ {{- end }}
57
+ {{- with .Params.tags }}
58
+ {{- range . }}
59
+ <meta property="article:tag" content="{{ . | plainify | htmlUnescape }}">
60
+ {{- end }}
61
+ {{- end }}
62
+ {{- end }}
63
+
64
+ {{- /* Twitter Card 标签 */ -}}
65
+ <meta name="twitter:card" content="{{ $twitterCard }}">
66
+ <meta name="twitter:title" content="{{ $title | plainify | htmlUnescape }}">
67
+ <meta name="twitter:description" content="{{ $description | plainify | htmlUnescape }}">
68
+ {{- with $twitterSite }}
69
+ <meta name="twitter:site" content="{{ . }}">
70
+ {{- end }}
71
+ {{- with $twitterCreator }}
72
+ <meta name="twitter:creator" content="{{ . }}">
73
+ {{- end }}
74
+ {{- with $image }}
75
+ {{- if hasPrefix . "http" }}
76
+ <meta name="twitter:image" content="{{ . }}">
77
+ {{- else }}
78
+ <meta name="twitter:image" content="{{ . | absURL }}">
79
+ {{- end }}
80
+ <meta name="twitter:image:alt" content="{{ $imageAlt | plainify | htmlUnescape }}">
81
+ {{- end }}
82
+
83
+ {{- /* 额外的 Meta 标签 */ -}}
84
+ {{- if .IsPage }}
85
+ {{- with .PublishDate }}
86
+ <meta name="publish-date" content="{{ .Format "2006-01-02T15:04:05Z07:00" }}">
87
+ {{- end }}
88
+ {{- with .Lastmod }}
89
+ <meta name="last-modified" content="{{ .Format "2006-01-02T15:04:05Z07:00" }}">
90
+ {{- end }}
91
+ {{- end }}