astro-pure 1.3.2 → 1.3.3

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,19 @@ 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
86
  background-color: hsl(var(--primary-foreground) / var(--un-text-opacity));
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
+ }
85
96
  </style>
86
97
 
87
98
  <script>
@@ -122,25 +133,22 @@ const [owner, repoName] = repo.split('/')
122
133
  try {
123
134
  const data = await this.fetchGithub(this.dataset.repo)
124
135
  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 =
136
+ // Update stats
137
+ this.updateElement('#gh-stars', this.numberFormat(data.stargazers_count))
138
+ this.updateElement('#gh-forks', this.numberFormat(data.forks))
139
+ this.updateElement('#gh-language', data.language || 'N/A')
140
+ // Update description
141
+ const description =
133
142
  typeof data.description === 'string'
134
143
  ? data.description.replace(/:[a-zA-Z0-9_]+:/g, '')
135
144
  : 'Description not set'
136
-
145
+ this.updateElement('#gh-description', description)
146
+ // Update license
147
+ const licenseText = data.license?.spdx_id || 'N/A'
137
148
  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
-
149
+ licenseEl.textContent = licenseText
150
+ licenseEl.classList.toggle('no-license', licenseText === 'N/A')
151
+ // Update avatar
144
152
  const avatarEl = this.querySelector('#gh-avatar') as HTMLElement
145
153
  if (avatarEl) {
146
154
  avatarEl.style.backgroundImage = `url(${data.owner.avatar_url})`
@@ -150,7 +158,14 @@ const [owner, repoName] = repo.split('/')
150
158
  this.classList.remove('loading')
151
159
  } catch (e) {
152
160
  console.error('Error setting Github data:', e)
153
- ;(this.querySelector('#gh-description') as HTMLElement).textContent = 'Failed to fetch data'
161
+ this.updateElement('#gh-description', 'Failed to fetch data')
162
+ }
163
+ }
164
+
165
+ private updateElement(selector: string, text: string) {
166
+ const element = this.querySelector(selector) as HTMLElement
167
+ if (element) {
168
+ element.textContent = text
154
169
  }
155
170
  }
156
171
  }
@@ -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'
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.3",
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
@@ -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)
@@ -1,148 +0,0 @@
1
- ---
2
- import config from 'virtual:config'
3
-
4
- import '@waline/client/style'
5
-
6
- import { cn } from '../../utils'
7
-
8
- const { class: className } = Astro.props
9
- ---
10
-
11
- {
12
- config.integ.waline.enable && (
13
- <comment-component>
14
- <div id='waline' class={cn('not-prose', className)}>
15
- Comment seems to stuck. Try to refresh?✨
16
- </div>
17
- </comment-component>
18
- )
19
- }
20
-
21
- <script>
22
- import { init as walineInit } from '@waline/client'
23
- import type { WalineEmojiPresets } from '@waline/client'
24
- import config from 'virtual:config'
25
-
26
- const walineConfig = config.integ.waline
27
-
28
- class Comment extends HTMLElement {
29
- constructor() {
30
- super()
31
- }
32
-
33
- connectedCallback() {
34
- // Prevent Vue log errors
35
- ;(globalThis as unknown as { __VUE_OPTIONS_API__: boolean }).__VUE_OPTIONS_API__ = true
36
- ;(globalThis as unknown as { __VUE_PROD_DEVTOOLS__: boolean }).__VUE_PROD_DEVTOOLS__ = false
37
- ;(
38
- globalThis as unknown as { __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: boolean }
39
- ).__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ = false
40
-
41
- const emoji = walineConfig.emoji?.map(
42
- (preset) => `${config.npmCDN}/@waline/emojis@1.2.0/${preset}` as WalineEmojiPresets
43
- )
44
-
45
- walineInit({
46
- el: '#waline',
47
- serverURL: walineConfig.server || '',
48
- emoji,
49
- reaction: ['/icons/heart-item.svg'],
50
- ...walineConfig.additionalConfigs
51
- })
52
- }
53
- }
54
-
55
- if (walineConfig.enable) customElements.define('comment-component', Comment)
56
- </script>
57
-
58
- <style>
59
- /* Basic theme */
60
- #waline {
61
- /* Font size */
62
- --waline-font-size: 16px;
63
- /* Basic color */
64
- --waline-white: hsl(var(--background) / var(--un-bg-opacity, 1));
65
- --waline-light-grey: #999;
66
- --waline-dark-grey: #666;
67
- /* Theme color */
68
- --waline-theme-color: hsl(var(--foreground) / var(--un-text-opacity, 1));
69
- --waline-active-color: hsl(var(--primary) / var(--un-text-opacity, 1));
70
- /* Layout */
71
- --waline-color: hsl(var(--muted-foreground) / var(--un-text-opacity, 1));
72
- --waline-bg-color: hsl(var(--muted) / var(--un-bg-opacity, 1));
73
- --waline-bg-color-light: hsl(var(--input) / var(--un-text-opacity, 1));
74
- --waline-bg-color-hover: #f0f0f0;
75
- --waline-border-color: hsl(var(--border) / var(--un-border-opacity, 1));
76
- --waline-disable-bg-color: #f8f8f8;
77
- --waline-disable-color: #bbb;
78
- --waline-code-bg-color: #282c34;
79
- /* Special */
80
- --waline-bq-color: #f0f0f0;
81
- /* Avatar */
82
- --waline-avatar-size: 3.25rem;
83
- --waline-m-avatar-size: calc(var(--waline-avatar-size) * 9 / 13);
84
- /* Badge */
85
- --waline-badge-color: hsl(var(--border) / var(--un-border-opacity, 1));
86
- --waline-badge-font-size: 0.775em;
87
- /* Info */
88
- --waline-info-bg-color: var(--waline-bg-color-light);
89
- --waline-info-color: var(--waline-color);
90
- --waline-info-font-size: 0.625em;
91
- /* Render selection */
92
- --waline-border: 1px solid var(--waline-border-color);
93
- --waline-avatar-radius: 50%;
94
- --waline-box-shadow: none;
95
- }
96
-
97
- /* Reaction buttons */
98
- #waline :global(.wl-reaction-title, .wl-reaction-text) {
99
- display: none;
100
- }
101
- #waline :global(.wl-reaction) {
102
- overflow: visible;
103
- margin-bottom: 0.5em;
104
- }
105
- #waline :global(.wl-reaction-img) {
106
- width: auto;
107
- display: flex;
108
- height: 35px;
109
- align-items: center;
110
- column-gap: 0.4rem;
111
- }
112
- #waline :global(.wl-reaction-votes) {
113
- position: inherit;
114
- top: inherit;
115
- min-width: inherit;
116
- inset-inline-end: inherit;
117
- display: flex;
118
- font-weight: normal;
119
- border: none;
120
- background: none;
121
- color: inherit;
122
- padding: 0.2rem 0.4rem;
123
- border-radius: 6px;
124
- }
125
- #waline :global(.wl-reaction-loading) {
126
- position: inherit;
127
- top: inherit;
128
- min-width: inherit;
129
- }
130
- #waline :global(.wl-reaction-item.active .wl-reaction-votes) {
131
- background: var(--waline-theme-color);
132
- color: var(--waline-bg-color);
133
- }
134
-
135
- #waline :global(.wl-reaction-votes:after) {
136
- margin-left: 0.25em;
137
- content: 'Like(s)';
138
- display: inline-block;
139
- clear: both;
140
- border: 0;
141
- }
142
- #waline :global(.wl-reaction img) {
143
- filter: invert(25%);
144
- }
145
- :global(.dark) #waline :global(.wl-reaction img) {
146
- filter: invert(75%);
147
- }
148
- </style>
@@ -1,18 +0,0 @@
1
- ---
2
- import { cn } from '../../utils'
3
-
4
- const { class: className, hideComment, ...props } = Astro.props
5
-
6
- const path = Astro.url.pathname
7
- ---
8
-
9
- <div class={cn('text-base text-sm text-muted-foreground', className)} {...props}>
10
- <span class='waline-pageview-count' data-path={path}></span> views
11
- {
12
- !hideComment && (
13
- <a href='#waline'>
14
- | <span class='waline-comment-count' data-path={path} /> comments
15
- </a>
16
- )
17
- }
18
- </div>