astro-pure 1.3.2 → 1.3.4

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.
@@ -13,49 +13,57 @@ const [owner, repoName] = repo.split('/')
13
13
  <a
14
14
  href={`https://github.com/${repo}`}
15
15
  target='_blank'
16
- class='group block flex flex-col gap-y-1.5 rounded-xl border px-5 py-4 transition-colors hover:bg-muted hover:text-muted-foreground'
16
+ class='group block flex flex-col gap-y-2 rounded-xl border px-4 py-3 transition-colors hover:bg-muted hover:text-muted-foreground sm:px-5 sm:py-4'
17
17
  >
18
- <div class='flex items-center justify-between'>
19
- <div class='flex items-center gap-x-2 text-foreground group-hover:text-primary'>
18
+ <div class='flex items-center justify-between gap-x-2'>
19
+ <div
20
+ class='flex min-w-0 flex-1 items-center gap-x-2 text-foreground group-hover:text-primary'
21
+ >
20
22
  <div
21
23
  id='gh-avatar'
22
- class='gh-text me-2 size-7'
23
- style='border-radius:999px;background-size:cover'
24
+ class='load-block me-2 size-7 flex-shrink-0 bg-cover sm:size-8'
25
+ style='border-radius:999px'
24
26
  >
25
27
  </div>
26
- <span class='text-lg transition-colors'>{owner}</span>
27
- <span class='text-muted-foreground'>/</span>
28
- <span class='text-lg font-bold transition-colors'>{repoName}</span>
28
+ <div class='min-w-0 flex-1'>
29
+ <div class='flex items-center gap-x-1 max-sm:flex-wrap'>
30
+ <span class='truncate text-base transition-colors sm:text-lg'>{owner}</span>
31
+ <span class='text-muted-foreground max-sm:hidden'>/</span>
32
+ <span class='truncate text-base font-bold transition-colors sm:text-lg max-sm:w-full'
33
+ >{repoName}</span
34
+ >
35
+ </div>
36
+ </div>
29
37
  </div>
30
- <div class='rounded-full bg-primary-foreground p-1'>
38
+ <div class='flex-shrink-0 rounded-full bg-primary-foreground p-1'>
31
39
  <Icon name='github' />
32
40
  </div>
33
41
  </div>
34
- <p id='gh-description' class='gh-text'>Waiting for api.github.com...</p>
35
- <div class='text-sm flex items-center justify-between mt-0.5'>
36
- <div class='gh-text flex flex-wrap items-center gap-x-5'>
37
- <div class='flex items-center gap-x-2'>
42
+ <p id='gh-description' class='load-block text-sm sm:text-base'>Waiting for api.github.com...</p>
43
+ <div class='flex items-center justify-between gap-x-2'>
44
+ <div class='load-block flex flex-wrap items-center gap-x-3 gap-y-1 sm:gap-x-5'>
45
+ <div class='flex items-center gap-x-1.5 sm:gap-x-2'>
38
46
  {/* mingcute:star-line */}
39
47
  <!-- prettier-ignore -->
40
- <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"><g fill="none" fill-rule="evenodd"><path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"/><path fill="currentColor" d="M10.92 2.868a1.25 1.25 0 0 1 2.16 0l2.795 4.798l5.428 1.176a1.25 1.25 0 0 1 .667 2.054l-3.7 4.141l.56 5.525a1.25 1.25 0 0 1-1.748 1.27L12 19.592l-5.082 2.24a1.25 1.25 0 0 1-1.748-1.27l.56-5.525l-3.7-4.14a1.25 1.25 0 0 1 .667-2.055l5.428-1.176zM12 4.987L9.687 8.959a1.25 1.25 0 0 1-.816.592l-4.492.973l3.062 3.427c.234.262.347.61.312.959l-.463 4.573l4.206-1.854a1.25 1.25 0 0 1 1.008 0l4.206 1.854l-.463-4.573a1.25 1.25 0 0 1 .311-.959l3.063-3.427l-4.492-.973a1.25 1.25 0 0 1-.816-.592z"/></g></svg>
41
- <span id='gh-stars' class='leading-tight'>???</span>
48
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" class="sm:w-[22px] sm:h-[22px]"><g fill="none" fill-rule="evenodd"><path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"/><path fill="currentColor" d="M10.92 2.868a1.25 1.25 0 0 1 2.16 0l2.795 4.798l5.428 1.176a1.25 1.25 0 0 1 .667 2.054l-3.7 4.141l.56 5.525a1.25 1.25 0 0 1-1.748 1.27L12 19.592l-5.082 2.24a1.25 1.25 0 0 1-1.748-1.27l.56-5.525l-3.7-4.14a1.25 1.25 0 0 1 .667-2.055l5.428-1.176zM12 4.987L9.687 8.959a1.25 1.25 0 0 1-.816.592l-4.492.973l3.062 3.427c.234.262.347.61.312.959l-.463 4.573l4.206-1.854a1.25 1.25 0 0 1 1.008 0l4.206 1.854l-.463-4.573a1.25 1.25 0 0 1 .311-.959l3.063-3.427l-4.492-.973a1.25 1.25 0 0 1-.816-.592z"/></g></svg>
49
+ <span id='gh-stars' class='text-sm leading-tight sm:text-base'>???</span>
42
50
  </div>
43
51
 
44
- <div class='flex items-center gap-x-2'>
52
+ <div class='flex items-center gap-x-1.5 sm:gap-x-2'>
45
53
  {/* mingcute:git-branch-line */}
46
54
  <!-- prettier-ignore -->
47
- <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"><g fill="none"><path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"/><path fill="currentColor" d="M18 3a3 3 0 0 1 1 5.83V9a4 4 0 0 1-4 4H9a2 2 0 0 0-2 2v.17a3.001 3.001 0 1 1-2 0V8.83a3.001 3.001 0 1 1 2 0v2.705A4 4 0 0 1 9 11h6a2 2 0 0 0 2-2v-.17A3.001 3.001 0 0 1 18 3M6 17a1 1 0 1 0 0 2a1 1 0 0 0 0-2M6 5a1 1 0 1 0 0 2a1 1 0 0 0 0-2m12 0a1 1 0 1 0 0 2a1 1 0 0 0 0-2"/></g></svg>
48
- <span id='gh-forks' class='leading-tight'>???</span>
55
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" class="sm:w-[22px] sm:h-[22px]"><g fill="none"><path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"/><path fill="currentColor" d="M18 3a3 3 0 0 1 1 5.83V9a4 4 0 0 1-4 4H9a2 2 0 0 0-2 2v.17a3.001 3.001 0 1 1-2 0V8.83a3.001 3.001 0 1 1 2 0v2.705A4 4 0 0 1 9 11h6a2 2 0 0 0 2-2v-.17A3.001 3.001 0 0 1 18 3M6 17a1 1 0 1 0 0 2a1 1 0 0 0 0-2M6 5a1 1 0 1 0 0 2a1 1 0 0 0 0-2m12 0a1 1 0 1 0 0 2a1 1 0 0 0 0-2"/></g></svg>
56
+ <span id='gh-forks' class='text-sm leading-tight sm:text-base'>???</span>
49
57
  </div>
50
58
 
51
- <div class='flex items-center gap-x-2'>
59
+ <div class='flex items-center gap-x-1.5 sm:gap-x-2'>
52
60
  {/* mingcute:balance-line */}
53
61
  <!-- prettier-ignore -->
54
- <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24"><g fill="none" fill-rule="evenodd"><path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"/><path fill="currentColor" d="M12 3a1 1 0 0 1 1 1v1h.764a2 2 0 0 1 .894.211L16.236 6H20a1 1 0 1 1 0 2h-.382l2.276 4.553c.07.139.106.292.106.447a4 4 0 0 1-8 0c0-.155.036-.308.106-.447L16.382 8h-.146a2 2 0 0 1-.894-.211L13.764 7H13v12h3a1 1 0 1 1 0 2H8a1 1 0 1 1 0-2h3V7h-.764l-1.578.789A2 2 0 0 1 7.764 8h-.146l2.276 4.553A1 1 0 0 1 10 13a4 4 0 0 1-8 0a1 1 0 0 1 .106-.447L4.382 8H4a1 1 0 0 1 0-2h3.764l1.578-.789A2 2 0 0 1 10.236 5H11V4a1 1 0 0 1 1-1M6 9.236l-1.989 3.977a2 2 0 0 0 3.978 0zm12 0l-1.989 3.977a2 2 0 0 0 3.955.157l.023-.156z"/></g></svg>
55
- <span id='gh-license' class='leading-tight'>???</span>
62
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" class="sm:w-[22px] sm:h-[22px]"><g fill="none" fill-rule="evenodd"><path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z"/><path fill="currentColor" d="M12 3a1 1 0 0 1 1 1v1h.764a2 2 0 0 1 .894.211L16.236 6H20a1 1 0 1 1 0 2h-.382l2.276 4.553c.07.139.106.292.106.447a4 4 0 0 1-8 0c0-.155.036-.308.106-.447L16.382 8h-.146a2 2 0 0 1-.894-.211L13.764 7H13v12h3a1 1 0 1 1 0 2H8a1 1 0 1 1 0-2h3V7h-.764l-1.578.789A2 2 0 0 1 7.764 8h-.146l2.276 4.553A1 1 0 0 1 10 13a4 4 0 0 1-8 0a1 1 0 0 1 .106-.447L4.382 8H4a1 1 0 0 1 0-2h3.764l1.578-.789A2 2 0 0 1 10.236 5H11V4a1 1 0 0 1 1-1M6 9.236l-1.989 3.977a2 2 0 0 0 3.978 0zm12 0l-1.989 3.977a2 2 0 0 0 3.955.157l.023-.156z"/></g></svg>
63
+ <span id='gh-license' class='text-sm leading-tight sm:text-base'>???</span>
56
64
  </div>
57
65
  </div>
58
- <span id='gh-language' class='gh-text leading-tight'>?????</span>
66
+ <span id='gh-language' class='load-block text-sm leading-tight sm:text-base'>?????</span>
59
67
  </div>
60
68
  </a>
61
69
  </github-card>
@@ -72,16 +80,22 @@ const [owner, repoName] = repo.split('/')
72
80
  opacity: 1;
73
81
  }
74
82
  }
75
- .loading .gh-text {
83
+ .loading .load-block {
76
84
  color: transparent;
77
85
  border-radius: calc(var(--radius) - 3px);
78
- background-color: hsl(var(--primary-foreground) / var(--un-text-opacity));
86
+ background-color: hsl(var(--primary-foreground) / var(--un-bg-opacity, 1));
79
87
  animation: pulsate 2s infinite linear;
80
88
  user-select: none;
81
89
  }
82
- .loading .gh-text:nth-child(2) {
90
+ .loading .load-block:nth-child(2) {
83
91
  animation-delay: 1s;
84
92
  }
93
+ .no-license {
94
+ opacity: 0.5;
95
+ }
96
+ :not(.loading) #gh-avatar {
97
+ background-color: hsl(var(--primary-foreground) / var(--un-bg-opacity));
98
+ }
85
99
  </style>
86
100
 
87
101
  <script>
@@ -122,35 +136,38 @@ const [owner, repoName] = repo.split('/')
122
136
  try {
123
137
  const data = await this.fetchGithub(this.dataset.repo)
124
138
  if (!data) return
125
- ;(this.querySelector('#gh-stars') as HTMLElement).textContent = this.numberFormat(
126
- data.stargazers_count
127
- )
128
- ;(this.querySelector('#gh-forks') as HTMLElement).textContent = this.numberFormat(
129
- data.forks
130
- )
131
- ;(this.querySelector('#gh-language') as HTMLElement).textContent = data.language || 'N/A'
132
- ;(this.querySelector('#gh-description') as HTMLElement).textContent =
139
+ // Update stats
140
+ this.updateElement('#gh-stars', this.numberFormat(data.stargazers_count))
141
+ this.updateElement('#gh-forks', this.numberFormat(data.forks))
142
+ this.updateElement('#gh-language', data.language || 'N/A')
143
+ // Update description
144
+ const description =
133
145
  typeof data.description === 'string'
134
146
  ? data.description.replace(/:[a-zA-Z0-9_]+:/g, '')
135
147
  : 'Description not set'
136
-
148
+ this.updateElement('#gh-description', description)
149
+ // Update license
150
+ const licenseText = data.license?.spdx_id || 'N/A'
137
151
  const licenseEl = this.querySelector('#gh-license') as HTMLElement
138
- if (data.license?.spdx_id) {
139
- licenseEl.textContent = data.license.spdx_id
140
- } else {
141
- licenseEl.classList.add('no-license')
142
- }
143
-
152
+ licenseEl.textContent = licenseText
153
+ licenseEl.classList.toggle('no-license', licenseText === 'N/A')
154
+ // Update avatar
144
155
  const avatarEl = this.querySelector('#gh-avatar') as HTMLElement
145
156
  if (avatarEl) {
146
157
  avatarEl.style.backgroundImage = `url(${data.owner.avatar_url})`
147
- avatarEl.style.backgroundColor = 'transparent'
148
158
  }
149
159
 
150
160
  this.classList.remove('loading')
151
161
  } catch (e) {
152
162
  console.error('Error setting Github data:', e)
153
- ;(this.querySelector('#gh-description') as HTMLElement).textContent = 'Failed to fetch data'
163
+ this.updateElement('#gh-description', 'Failed to fetch data')
164
+ }
165
+ }
166
+
167
+ private updateElement(selector: string, text: string) {
168
+ const element = this.querySelector(selector) as HTMLElement
169
+ if (element) {
170
+ element.textContent = text
154
171
  }
155
172
  }
156
173
  }
@@ -6,6 +6,3 @@ export { default as LinkPreview } from './LinkPreview.astro'
6
6
  // Data transformer
7
7
  export { default as QRCode } from './QRCode.astro'
8
8
  export { default as MediumZoom } from './MediumZoom.astro'
9
-
10
- // Individual server integration
11
- export { default as Comment } from './Comment.astro'
@@ -15,7 +15,7 @@ social.rss = {
15
15
  const footerLink1 = footerConf.links?.filter(({ pos }) => pos === 1) || []
16
16
  ---
17
17
 
18
- <footer class='mx-auto mb-5 mt-16'>
18
+ <footer class='mx-auto mb-5 mt-16 w-full'>
19
19
  <div class='border-t pt-5'>
20
20
  <div class='flex items-center gap-y-3 max-sm:flex-col sm:justify-between sm:gap-y-0'>
21
21
  <div
@@ -5,7 +5,7 @@ import { Icon } from '../user'
5
5
  ---
6
6
 
7
7
  <header-component
8
- class='group sticky top-4 z-30 md:z-50 mb-12 flex items-center justify-between rounded-xl border border-transparent max-sm:py-1 sm:rounded-2xl'
8
+ class='group sticky top-4 z-50 max-md:z-30 mb-12 flex items-center justify-between rounded-xl border border-transparent max-sm:py-1 sm:rounded-2xl'
9
9
  >
10
10
  <a
11
11
  class='z-30 text-xl font-semibold group-[.not-top]:ms-2 sm:group-[.not-top]:ms-3'
@@ -104,8 +104,7 @@ const shares = {
104
104
  }
105
105
  </div>
106
106
  <QRCode
107
- aria-expanded='false'
108
- class='absolute z-10 -mt-2 box-content max-h-0 max-w-44 overflow-hidden rounded-xl border bg-muted p-4 opacity-0 transition-all duration-300 ease-in-out aria-expanded:max-h-44 aria-expanded:translate-y-4 aria-expanded:opacity-100'
107
+ class='absolute z-10 -mt-2 box-content max-h-0 max-w-44 overflow-hidden rounded-xl border bg-muted p-4 opacity-0 transition-all duration-300 ease-in-out'
109
108
  />
110
109
  </div>
111
110
  </div>
@@ -121,6 +120,14 @@ const shares = {
121
120
  </a>
122
121
  </div>
123
122
 
123
+ <style>
124
+ #qrcode-container.expanded {
125
+ max-height: 11rem;
126
+ transform: translateY(4px);
127
+ opacity: 100;
128
+ }
129
+ </style>
130
+
124
131
  <script>
125
132
  import { showToast } from '../../utils'
126
133
 
@@ -135,12 +142,5 @@ const shares = {
135
142
  // QRCode
136
143
  const getQRCode = document.getElementById('get-qrcode')
137
144
  const qrcodeContainer = document.getElementById('qrcode-container')
138
- if (!qrcodeContainer) throw new Error('qrcode container not found')
139
- getQRCode?.addEventListener('click', () => {
140
- if (qrcodeContainer.ariaExpanded === 'true') {
141
- qrcodeContainer.ariaExpanded = 'false'
142
- } else {
143
- qrcodeContainer.ariaExpanded = 'true'
144
- }
145
- })
145
+ getQRCode?.addEventListener('click', () => qrcodeContainer?.classList.toggle('expanded'))
146
146
  </script>
@@ -2,7 +2,6 @@
2
2
  import { Image } from 'astro:assets'
3
3
  import type { InferEntrySchema } from 'astro:content'
4
4
 
5
- import { PageInfo } from '.'
6
5
  import { FormattedDate, Icon } from '../user'
7
6
 
8
7
  interface Props {
@@ -11,17 +10,7 @@ interface Props {
11
10
  }
12
11
 
13
12
  const {
14
- data: {
15
- title,
16
- description,
17
- draft,
18
- heroImage,
19
- publishDate,
20
- updatedDate,
21
- comment: enableComment,
22
- tags,
23
- language
24
- },
13
+ data: { title, description, draft, heroImage, publishDate, updatedDate, tags, language },
25
14
  remarkPluginFrontmatter
26
15
  } = Astro.props
27
16
 
@@ -119,12 +108,12 @@ const dateTimeOptions: Intl.DateTimeFormatOptions = {
119
108
 
120
109
  <div class='mt-3 italic'>
121
110
  <blockquote class='text-sm text-muted-foreground'><q>{description}</q></blockquote>
122
- {!draft && enableComment && <PageInfo class='mt-1' />}
111
+ <slot name='description' />
123
112
  </div>
124
113
  </div>
125
114
 
126
115
  {/* Dividing line */}
127
- <div class='mt-4 w-1/2 border-t max-lg:mx-auto sm:mt-6 sm:w-1/3'></div>
116
+ <div class='mt-4 w-1/2 border-t max-md:mx-auto sm:mt-6 sm:w-1/3'></div>
128
117
 
129
118
  <script>
130
119
  const viewportHeight = window.innerHeight
@@ -2,7 +2,6 @@ export { default as ArticleBottom } from './ArticleBottom.astro'
2
2
  export { default as BackToTop } from './BackToTop.astro'
3
3
  export { default as Copyright } from './Copyright.astro'
4
4
  export { default as Hero } from './Hero.astro'
5
- export { default as PageInfo } from './PageInfo.astro'
6
5
  export { default as Paginator } from './Paginator.astro'
7
6
  export { default as PostPreview } from './PostPreview.astro'
8
7
  export { default as TOC } from './TOC.astro'
@@ -1,11 +1,14 @@
1
1
  ---
2
+ import type { HTMLTag, Polymorphic } from 'astro/types'
3
+
2
4
  import { cn } from '../../utils'
3
5
 
4
- interface Props {
6
+ type Props<Tag extends HTMLTag> = Polymorphic<{ as: Tag }> & {
5
7
  as?: string
6
8
  title?: string
7
9
  href?: string
8
10
  variant?: 'button' | 'pill' | 'back' | 'ahead'
11
+ target?: string
9
12
  class?: string
10
13
  }
11
14
 
@@ -1,6 +1,15 @@
1
1
  ---
2
+ import type { HTMLTag, Polymorphic } from 'astro/types'
3
+
2
4
  import { cn } from '../../utils'
3
5
 
6
+ type Props<Tag extends HTMLTag> = Polymorphic<{ as: Tag }> & {
7
+ heading?: string
8
+ subheading?: string
9
+ date?: string
10
+ class?: string
11
+ }
12
+
4
13
  const { as: Tag = 'div', class: className, href, heading, subheading, date } = Astro.props
5
14
  ---
6
15
 
@@ -1,14 +1,19 @@
1
1
  ---
2
+ import type { HTMLTag, Polymorphic } from 'astro/types'
3
+
2
4
  import type { CardList } from '../../types'
3
5
  import { cn } from '../../utils'
4
6
 
5
- type Props = { main?: boolean; children: CardList }
7
+ type Props<Tag extends HTMLTag> = Polymorphic<{ as: Tag }> & {
8
+ main?: boolean
9
+ children: CardList
10
+ }
6
11
  const { main = false, children } = Astro.props
7
12
  ---
8
13
 
9
14
  <ul class={cn('flex flex-col gap-y-1', !main && 'subitem list-disc ms-5')}>
10
15
  {
11
- children.map((child) => {
16
+ children.map((child: CardList[number]) => {
12
17
  const Tag = child.link ? 'a' : 'p'
13
18
  return (
14
19
  <li>
@@ -1,6 +1,13 @@
1
1
  ---
2
+ import type { HTMLTag, Polymorphic } from 'astro/types'
3
+
2
4
  import { cn } from '../../utils'
3
5
 
6
+ type Props<Tag extends HTMLTag> = Polymorphic<{ as: Tag }> & {
7
+ title: string
8
+ href?: string
9
+ class?: string
10
+ }
4
11
  const { class: className, as: Tag = 'div', title, href, ...props } = Astro.props
5
12
  ---
6
13
 
@@ -1,6 +1,12 @@
1
1
  ---
2
+ import type { HTMLTag, Polymorphic } from 'astro/types'
3
+
2
4
  import { cn } from '../../utils'
3
5
 
6
+ type Props<Tag extends HTMLTag> = Polymorphic<{ as: Tag }> & {
7
+ class?: string
8
+ }
9
+
4
10
  const { as: Tag = 'span', class: className } = Astro.props
5
11
  ---
6
12
 
package/index.ts CHANGED
@@ -6,9 +6,10 @@ import type { AstroIntegration, RehypePlugins, RemarkPlugins } from 'astro'
6
6
  // Integrations
7
7
  import mdx from '@astrojs/mdx'
8
8
  import sitemap from '@astrojs/sitemap'
9
- import rehypeExternalLinks from 'rehype-external-links'
10
9
  import UnoCSS from 'unocss/astro'
11
10
 
11
+ import rehypeExternalLinks from './plugins/rehype-external-links'
12
+ import rehypeTable from './plugins/rehype-table'
12
13
  import { remarkAddZoomable, remarkReadingTime } from './plugins/remark-plugins'
13
14
  import { vitePluginUserConfig } from './plugins/virtual-user-config'
14
15
  import { UserConfigSchema, type UserInputConfig } from './types/user-config'
@@ -51,11 +52,14 @@ export default function AstroPureIntegration(opts: UserInputConfig): AstroIntegr
51
52
  rehypePlugins.push([
52
53
  rehypeExternalLinks,
53
54
  {
54
- content: { type: 'text', value: userConfig.content.externalLinksContent },
55
- target: '_blank',
56
- rel: ['nofollow', 'noopener', 'noreferrer']
55
+ content: {
56
+ type: 'text',
57
+ value: userConfig.content.externalLinks.content
58
+ },
59
+ contentProperties: userConfig.content.externalLinks.properties
57
60
  }
58
61
  ])
62
+ rehypePlugins.push(rehypeTable)
59
63
  // Add Starlight directives restoration integration at the end of the list so that remark
60
64
  // plugins injected by Starlight plugins through Astro integrations can handle text and
61
65
  // leaf directives before they are transformed back to their original form.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-pure",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "description": "A simple, clean but powerful blog theme build by astro.",
5
5
  "type": "module",
6
6
  "author": "CWorld",
@@ -35,11 +35,11 @@
35
35
  "./libs": "./libs/index.ts"
36
36
  },
37
37
  "dependencies": {
38
- "@astrojs/mdx": "^4.3.0",
38
+ "@astrojs/mdx": "^4.3.1",
39
39
  "@astrojs/sitemap": "^3.4.1",
40
40
  "@pagefind/default-ui": "^1.3.0",
41
41
  "@unocss/reset": "^66.3.3",
42
- "astro": "^5.11.1",
42
+ "astro": "^5.12.0",
43
43
  "hast-util-select": "^6.0.4",
44
44
  "node-html-parser": "^7.0.1",
45
45
  "pagefind": "^1.3.0",
@@ -1,9 +1,10 @@
1
1
  // https://github.com/rehypejs/rehype-external-links
2
2
 
3
3
  import type { Element, ElementContent, Root } from 'hast'
4
- import isAbsoluteUrl from 'is-absolute-url'
5
4
  import { visit } from 'unist-util-visit'
6
5
 
6
+ import isAbsoluteUrl from '../utils/is-absolute-url'
7
+
7
8
  export interface ExternalLinkOptions {
8
9
  content?: ElementContent | ElementContent[]
9
10
  contentProperties?: Record<string, unknown>
@@ -0,0 +1,37 @@
1
+ import type { Element, Root } from 'hast'
2
+ import type { Plugin } from 'unified'
3
+ import { visit } from 'unist-util-visit'
4
+
5
+ /**
6
+ * Rehype plugin that wraps direct table children of #content in a scrollable div
7
+ */
8
+ const rehypeWrapContentTables: Plugin<[], Root> = () => {
9
+ return (tree) => {
10
+ // console.log('tree', tree)
11
+ // Find all tables and check their parent
12
+ visit(tree, 'element', (node: Element, index: number | undefined, parent?: Element | Root) => {
13
+ if (
14
+ // Check if this is a table element
15
+ node.tagName === 'table' &&
16
+ // Verify it has a parent
17
+ parent &&
18
+ typeof index === 'number'
19
+ ) {
20
+ // Create wrapper div with appropriate classes
21
+ const wrapper: Element = {
22
+ type: 'element',
23
+ tagName: 'div',
24
+ properties: {
25
+ className: ['overflow-x-auto', 'w-full', 'flex', 'justify-center']
26
+ },
27
+ children: [node] // Place the table inside the wrapper
28
+ }
29
+
30
+ // Replace the table with the wrapper (that contains the table)
31
+ parent.children.splice(index, 1, wrapper)
32
+ }
33
+ })
34
+ }
35
+ }
36
+
37
+ export default rehypeWrapContentTables
package/scripts/new.mjs CHANGED
@@ -7,6 +7,7 @@
7
7
  * -l, --lang <en|zh> Set the language (default: en)
8
8
  * -d, --draft Create a draft post (default: false)
9
9
  * -m, --mdx Use MDX format (default: false)
10
+ * -f, --folder Create the post in a folder (default: false)
10
11
  * -h, --help Show this help message
11
12
  *
12
13
  * Example:
@@ -32,6 +33,7 @@ function getDate() {
32
33
  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
33
34
  }
34
35
 
36
+ /** get blog title slug */
35
37
  function getPostSlug(postTitle) {
36
38
  let slug = slugify(postTitle).toLocaleLowerCase()
37
39
  if (slug === '') {
@@ -46,6 +48,7 @@ Options:
46
48
  -l, --lang Set the language (default: null)
47
49
  -d, --draft Create a draft post (default: false)
48
50
  -m, --mdx Use MDX format (default: false)
51
+ -f, --folder Create the post in a folder (default: false)
49
52
  -h, --help Show this help message
50
53
 
51
54
  Example:
@@ -57,17 +60,19 @@ const TARGET_DIR = 'src/content/blog/'
57
60
  export default function main(args) {
58
61
  const parsedArgs = minimist(args, {
59
62
  string: ['lang'],
60
- boolean: ['draft', 'mdx', 'help'],
63
+ boolean: ['draft', 'mdx', 'help', 'folder'],
61
64
  default: {
62
65
  lang: null,
63
66
  draft: false,
64
- mdx: false
67
+ mdx: false,
68
+ folder: false
65
69
  },
66
70
  alias: {
67
71
  l: 'lang',
68
72
  d: 'draft',
69
73
  m: 'mdx',
70
- h: 'help'
74
+ h: 'help',
75
+ f: 'folder'
71
76
  }
72
77
  })
73
78
 
@@ -84,7 +89,20 @@ export default function main(args) {
84
89
 
85
90
  const fileExtension = parsedArgs.mdx ? '.mdx' : '.md'
86
91
  const fileName = getPostSlug(postTitle) + fileExtension
87
- const fullPath = path.join(TARGET_DIR, fileName)
92
+
93
+ let fullPath
94
+ if (parsedArgs.folder) {
95
+ const folderName = getPostSlug(postTitle);
96
+ const folderPath = path.join(TARGET_DIR, folderName);
97
+ if (!fs.existsSync(folderPath)) {
98
+ fs.mkdirSync(folderPath, { recursive: true });
99
+ }
100
+ const fileName = 'index' + fileExtension;
101
+ fullPath = path.join(folderPath, fileName);
102
+ } else {
103
+ fullPath = path.join(TARGET_DIR, fileName);
104
+ }
105
+
88
106
 
89
107
  console.log('Full path:', fullPath)
90
108
 
@@ -100,7 +118,9 @@ publishDate: ${getDate()}
100
118
  `
101
119
  content += parsedArgs.draft ? 'draft: true\n' : ''
102
120
  content += parsedArgs.lang ? `lang: ${parsedArgs.lang}\n` : ''
103
- content += `tags: ['tag1', 'tag2']
121
+ content += `tags:
122
+ - Example
123
+ - Technology
104
124
  ---
105
125
 
106
126
  Write your content here.
@@ -162,14 +162,23 @@ export const ThemeConfigSchema = () =>
162
162
  }),
163
163
 
164
164
  content: z.object({
165
- externalLinksContent: z.string().optional().default(' ↗'),
165
+ externalLinks: z.object({
166
+ /** Content to show for external links */
167
+ content: z
168
+ .string()
169
+ .optional()
170
+ .default(' ↗')
171
+ .describe('Content to show for external links'),
172
+ /** Properties for the external links element */
173
+ properties: z
174
+ .record(z.string())
175
+ .optional()
176
+ .describe('Properties for the external links element')
177
+ }),
166
178
 
167
179
  /** Blog page size for pagination */
168
180
  blogPageSize: z.number().optional().default(8),
169
181
 
170
- /** Show external link arrow */
171
- externalLinkArrow: z.boolean().optional().default(true),
172
-
173
182
  /** Share buttons to show */
174
183
  share: ShareSchema()
175
184
  })
package/utils/index.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  export { default as clsx } from './clsx'
3
3
  export { default as mdastToString } from './mdast-util-to-string'
4
4
  export { default as getReadingTime } from './reading-time'
5
+ export { default as isAbsoluteUrl } from './is-absolute-url'
5
6
 
6
7
  // Class merge
7
8
  export { cn } from './class-merge'
@@ -0,0 +1,35 @@
1
+ // https://github.com/sindresorhus/is-absolute-url
2
+
3
+ // Scheme: https://tools.ietf.org/html/rfc3986#section-3.1
4
+ // Absolute URL: https://tools.ietf.org/html/rfc3986#section-4.3
5
+ const ABSOLUTE_URL_REGEX = /^[a-zA-Z][a-zA-Z\d+\-.]*?:/
6
+
7
+ // Windows paths like `c:\`
8
+ const WINDOWS_PATH_REGEX = /^[a-zA-Z]:\\/
9
+
10
+ /**
11
+ * Check if a URL is absolute.
12
+ * @param url - The URL to check.
13
+ * @example
14
+ * ```
15
+ * import isAbsoluteUrl from 'is-absolute-url';
16
+ *
17
+ * isAbsoluteUrl('http://sindresorhus.com/foo/bar');
18
+ * //=> true
19
+ *
20
+ * isAbsoluteUrl('//sindresorhus.com');
21
+ * //=> false
22
+ *
23
+ * isAbsoluteUrl('foo/bar');
24
+ * //=> false
25
+ * ```
26
+ */
27
+ export default function isAbsoluteUrl(url: string): boolean {
28
+ if (typeof url !== 'string') {
29
+ throw new TypeError(`Expected a \`string\`, got \`${typeof url}\``)
30
+ }
31
+ if (WINDOWS_PATH_REGEX.test(url)) {
32
+ return false
33
+ }
34
+ return ABSOLUTE_URL_REGEX.test(url)
35
+ }
@@ -3,6 +3,19 @@ type Options = {
3
3
  includeHtml?: boolean
4
4
  }
5
5
 
6
+ /**
7
+ * Get the text content of a node or list of nodes.
8
+ *
9
+ * Prefers the node’s plain-text fields, otherwise serializes its children,
10
+ * and if the given value is an array, serialize the nodes in it.
11
+ *
12
+ * @param {unknown} [value]
13
+ * Thing to serialize, typically `Node`.
14
+ * @param {Options | null | undefined} [options]
15
+ * Configuration (optional).
16
+ * @returns {string}
17
+ * Serialized `value`.
18
+ */
6
19
  export default function toString(value: unknown, options?: Options): string {
7
20
  const { includeImageAlt = true, includeHtml = true } = options || {}
8
21
  return serialize(value, includeImageAlt, includeHtml)