@windcoder/astro-theme-orchid 0.4.4 → 0.4.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windcoder/astro-theme-orchid",
3
- "version": "0.4.4",
3
+ "version": "0.4.5",
4
4
  "description": "清新简约的 Astro 博客主题,以 Integration 形式提供,支持版本化升级与组件覆盖",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -0,0 +1,75 @@
1
+ ---
2
+ /**
3
+ * 文章资源区:frontmatter resources(name/url/password)在正文后渲染资源卡。
4
+ * 按 url 域名识别品牌图标(gitee/github),其余地址用通用外链图标;
5
+ * password 有值时展示提取码。新地址类型无需改组件,直接复用 name/url 展示。
6
+ */
7
+ import { t } from "../i18n/index.js";
8
+
9
+ interface ResourceItem {
10
+ name: string;
11
+ url: string;
12
+ password?: string;
13
+ }
14
+
15
+ interface Props {
16
+ resources?: ResourceItem[];
17
+ }
18
+
19
+ const { resources } = Astro.props;
20
+ const items = (resources ?? []).filter((r) => r.url && r.url.trim() !== "");
21
+
22
+ type IconKind = "gitee" | "github" | "link";
23
+ function iconFor(url: string): IconKind {
24
+ let host = "";
25
+ try {
26
+ host = new URL(url).hostname.replace(/^www\./, "");
27
+ } catch {
28
+ return "link";
29
+ }
30
+ if (host === "gitee.com") return "gitee";
31
+ if (host === "github.com") return "github";
32
+ return "link";
33
+ }
34
+ ---
35
+
36
+ {
37
+ items.length > 0 && (
38
+ <section class="post-resources card reveal" aria-label={t("postResources")}>
39
+ <h2 class="post-resources-title">
40
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/></svg>
41
+ {t("postResources")}
42
+ </h2>
43
+ <ul class="post-resources-list">
44
+ {items.map((r) => {
45
+ const kind = iconFor(r.url);
46
+ return (
47
+ <li class="post-resources-item">
48
+ <a href={r.url} target="_blank" rel="noopener">
49
+ <span class="post-resources-icon" aria-hidden="true">
50
+ {kind === "gitee" && (
51
+ <svg viewBox="0 0 1024 1024" fill="currentColor"><path d="M512 1024C229.222 1024 0 794.778 0 512S229.222 0 512 0s512 229.222 512 512-229.222 512-512 512zm259.149-568.883h-290.74a25.293 25.293 0 0 0-25.292 25.293l-0.081 63.206c0 13.952 11.315 25.293 25.267 25.293h177.024c13.978 0 25.293 11.315 25.293 25.267v12.646a75.853 75.853 0 0 1-75.853 75.853h-240.23a25.293 25.293 0 0 1-25.267-25.293V417.203a75.853 75.853 0 0 1 75.827-75.853h353.946a25.293 25.293 0 0 0 25.267-25.292l0.077-63.207a25.293 25.293 0 0 0-25.268-25.293H417.152a189.62 189.62 0 0 0-189.62 189.645V771.15c0 13.977 11.316 25.293 25.294 25.293h372.94a170.65 170.65 0 0 0 170.65-170.65V480.384a25.293 25.293 0 0 0-25.267-25.267z"/></svg>
52
+ )}
53
+ {kind === "github" && (
54
+ <svg viewBox="0 0 16 16" fill="currentColor"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8z"/></svg>
55
+ )}
56
+ {kind === "link" && (
57
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
58
+ )}
59
+ </span>
60
+ <span class="post-resources-name">{r.name}</span>
61
+ {r.password && r.password.trim() !== "" && (
62
+ <span class="post-resources-password">
63
+ {t("resourcePassword")}
64
+ <code>{r.password}</code>
65
+ </span>
66
+ )}
67
+ <svg class="post-resources-ext" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/></svg>
68
+ </a>
69
+ </li>
70
+ );
71
+ })}
72
+ </ul>
73
+ </section>
74
+ )
75
+ }
package/src/i18n/en.ts CHANGED
@@ -146,6 +146,9 @@ const en: Record<UiKey, string> = {
146
146
  "No series yet. Add a series field to a post's frontmatter to create one.",
147
147
  seriesProgress: "{cur} / {total}",
148
148
  seriesCatalog: "Series catalog",
149
+ /* Post resources: resource card rendered from frontmatter resources */
150
+ postResources: "Related resources",
151
+ resourcePassword: "Code",
149
152
  /* Music player (§4.36) */
150
153
  musicOpen: "Music player",
151
154
  musicClose: "Collapse player",
package/src/i18n/ja.ts CHANGED
@@ -150,6 +150,9 @@ const ja: Record<UiKey, string> = {
150
150
  "シリーズがまだありません。記事の frontmatter に series を記入すると作成できます。",
151
151
  seriesProgress: "{cur} / {total}",
152
152
  seriesCatalog: "シリーズ目次",
153
+ /* 記事リソース:frontmatter resources から描画されるリソースカード */
154
+ postResources: "関連リソース",
155
+ resourcePassword: "パスワード",
153
156
  /* 音楽プレイヤー */
154
157
  musicOpen: "音楽プレイヤー",
155
158
  musicClose: "プレイヤーを閉じる",
package/src/i18n/ko.ts CHANGED
@@ -149,6 +149,9 @@ const ko: Record<UiKey, string> = {
149
149
  "아직 시리즈가 없습니다. 글 frontmatter에 series를 입력하면 생성됩니다.",
150
150
  seriesProgress: "{cur} / {total}",
151
151
  seriesCatalog: "시리즈 목차",
152
+ /* 게시글 리소스: frontmatter resources 로 렌더링되는 리소스 카드 */
153
+ postResources: "관련 자료",
154
+ resourcePassword: "추출 코드",
152
155
  /* 음악 플레이어 */
153
156
  musicOpen: "음악 플레이어",
154
157
  musicClose: "플레이어 접기",
package/src/i18n/ru.ts CHANGED
@@ -150,6 +150,9 @@ const ru: Record<UiKey, string> = {
150
150
  "Серий пока нет. Заполните поле series в frontmatter записи, чтобы создать.",
151
151
  seriesProgress: "{cur} / {total}",
152
152
  seriesCatalog: "Содержание серии",
153
+ /* Ресурсы статьи: карточка из frontmatter resources */
154
+ postResources: "Материалы",
155
+ resourcePassword: "Код доступа",
153
156
  /* Музыкальный плеер */
154
157
  musicOpen: "Музыкальный плеер",
155
158
  musicClose: "Свернуть плеер",
package/src/i18n/zh-CN.ts CHANGED
@@ -166,6 +166,9 @@ const zhCN = {
166
166
  seriesEmpty: "还没有专题,在文章 frontmatter 中填写 series 字段即可创建。",
167
167
  seriesProgress: "{cur} / {total}",
168
168
  seriesCatalog: "专题目录",
169
+ /* 文章资源区:frontmatter resources 渲染的资源卡 */
170
+ postResources: "相关资源",
171
+ resourcePassword: "提取码",
169
172
  /* 音乐播放器(§4.36):data-labels 经组件 data-labels 属性带入客户端 */
170
173
  musicOpen: "音乐播放器",
171
174
  musicClose: "收起播放器",
package/src/i18n/zh-TW.ts CHANGED
@@ -145,6 +145,9 @@ const zhTW: Record<UiKey, string> = {
145
145
  seriesEmpty: "還沒有專題,在文章 frontmatter 中填寫 series 欄位即可建立。",
146
146
  seriesProgress: "{cur} / {total}",
147
147
  seriesCatalog: "專題目錄",
148
+ /* 文章資源區:frontmatter resources 渲染的資源卡 */
149
+ postResources: "相關資源",
150
+ resourcePassword: "提取碼",
148
151
  /* 音樂播放器 */
149
152
  musicOpen: "音樂播放器",
150
153
  musicClose: "收起播放器",
@@ -10,6 +10,7 @@ import EncryptedContent from "virtual:theme/components/EncryptedContent.astro";
10
10
  import License from "virtual:theme/components/License.astro";
11
11
  import OutdatedCard from "virtual:theme/components/OutdatedCard.astro";
12
12
  import PostNav from "virtual:theme/components/PostNav.astro";
13
+ import PostResources from "virtual:theme/components/PostResources.astro";
13
14
  import RecommendedPost from "virtual:theme/components/RecommendedPost.astro";
14
15
  import Sidebar from "virtual:theme/components/Sidebar.astro";
15
16
  import Toc from "virtual:theme/components/Toc.astro";
@@ -303,6 +304,8 @@ const jsonLd = {
303
304
  {/* 过时提醒(§4.11):许可卡之前展示;加密文章不渲染 */}
304
305
  {!d.password && <OutdatedCard updated={d.updated ?? d.published} published={d.published} />}
305
306
  <License licenseName={d.licenseName} licenseUrl={d.licenseUrl} enabled={d.license} />
307
+ {/* 文章资源区:frontmatter resources 非空时渲染;加密文章随正文语义一并隐藏 */}
308
+ {!d.password && <PostResources resources={d.resources} />}
306
309
  <PostNav prev={prev ?? undefined} next={next ?? undefined} />
307
310
  {/* 支持卡(P2):打赏入口 + 分享海报(share-poster.ts 仍按 #qy-share-btn 与 data-* 绑定) */}
308
311
  {
package/src/schema.ts CHANGED
@@ -1639,6 +1639,20 @@ export function definePostSchema(extensions: z.ZodRawShape = {}) {
1639
1639
  * 组名直接来自本字段文本,无 i18n 参与。
1640
1640
  */
1641
1641
  seriesGroup: z.string().optional(),
1642
+ /**
1643
+ * 文章相关资源:正文后渲染资源卡区。name 展示名、url 跳转地址、
1644
+ * password 提取码(可空)。按 url 域名识别品牌图标(gitee/github),
1645
+ * 其余地址类型直接复用通用外链图标展示,无需再改 schema。
1646
+ */
1647
+ resources: z
1648
+ .array(
1649
+ z.object({
1650
+ name: z.string(),
1651
+ url: z.string(),
1652
+ password: z.string().optional(),
1653
+ }),
1654
+ )
1655
+ .optional(),
1642
1656
  })
1643
1657
  .extend(extensions);
1644
1658
  }
@@ -451,6 +451,8 @@ export async function openSharePoster(btn: HTMLElement) {
451
451
  img.src = poster;
452
452
  img.hidden = false;
453
453
  }
454
+ /* 生成完成:移除 loading(否则转圈常驻在海报上方) */
455
+ modal.querySelector<HTMLElement>(".poster-loading")?.remove();
454
456
  if (saveBtn) {
455
457
  saveBtn.disabled = false;
456
458
  saveBtn.addEventListener("click", () => {
@@ -3339,6 +3339,94 @@ html.dark .outdated-warning {
3339
3339
  border-radius: var(--radius-sm);
3340
3340
  background: var(--bg-soft);
3341
3341
  }
3342
+ /* 文章资源区:frontmatter resources 渲染的资源卡(License 之后、上下篇之前) */
3343
+ .post-resources {
3344
+ margin-top: 28px;
3345
+ padding: 16px 20px;
3346
+ }
3347
+ .post-resources-title {
3348
+ display: flex;
3349
+ align-items: center;
3350
+ gap: 8px;
3351
+ margin: 0 0 10px;
3352
+ font-size: 15.5px;
3353
+ font-weight: 700;
3354
+ color: var(--ink);
3355
+ }
3356
+ .post-resources-title svg {
3357
+ width: 16px;
3358
+ height: 16px;
3359
+ color: var(--accent);
3360
+ }
3361
+ .post-resources-list {
3362
+ list-style: none;
3363
+ margin: 0;
3364
+ padding: 0;
3365
+ display: grid;
3366
+ gap: 4px;
3367
+ }
3368
+ .post-resources-item a {
3369
+ display: flex;
3370
+ align-items: center;
3371
+ gap: 10px;
3372
+ padding: 7px 8px;
3373
+ border-radius: var(--radius-sm);
3374
+ font-size: 14px;
3375
+ color: var(--ink-2);
3376
+ transition: background 0.15s ease, color 0.15s ease;
3377
+ }
3378
+ .post-resources-item a:hover {
3379
+ background: var(--bg-soft);
3380
+ color: var(--accent-deep);
3381
+ }
3382
+ .post-resources-icon {
3383
+ flex: none;
3384
+ display: inline-flex;
3385
+ width: 18px;
3386
+ height: 18px;
3387
+ color: var(--muted);
3388
+ }
3389
+ .post-resources-item a:hover .post-resources-icon {
3390
+ color: var(--accent);
3391
+ }
3392
+ .post-resources-icon svg {
3393
+ width: 100%;
3394
+ height: 100%;
3395
+ }
3396
+ .post-resources-name {
3397
+ min-width: 0;
3398
+ overflow: hidden;
3399
+ text-overflow: ellipsis;
3400
+ white-space: nowrap;
3401
+ }
3402
+ .post-resources-password {
3403
+ flex: none;
3404
+ display: inline-flex;
3405
+ align-items: center;
3406
+ gap: 4px;
3407
+ margin-left: auto;
3408
+ font-size: 12.5px;
3409
+ color: var(--muted);
3410
+ }
3411
+ .post-resources-password code {
3412
+ padding: 1px 6px;
3413
+ border-radius: 4px;
3414
+ border: 1px solid var(--border);
3415
+ background: var(--bg-soft);
3416
+ font-size: 12px;
3417
+ color: var(--ink-2);
3418
+ }
3419
+ .post-resources-ext {
3420
+ flex: none;
3421
+ width: 14px;
3422
+ height: 14px;
3423
+ color: var(--muted);
3424
+ opacity: 0.6;
3425
+ }
3426
+ .post-resources-item a:hover .post-resources-ext {
3427
+ color: var(--accent);
3428
+ opacity: 1;
3429
+ }
3342
3430
  .post-nav {
3343
3431
  margin-top: 24px;
3344
3432
  display: grid;
@@ -5592,6 +5680,10 @@ a[data-encoded-email] {
5592
5680
  height: auto;
5593
5681
  border-radius: var(--radius-sm);
5594
5682
  }
5683
+ /* display:block 会覆盖 [hidden] 的 UA 样式,显式声明防生成前空框露出 */
5684
+ .poster-img[hidden] {
5685
+ display: none;
5686
+ }
5595
5687
  .poster-loading {
5596
5688
  display: flex;
5597
5689
  align-items: center;