astro-pure 1.2.2 → 1.2.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.
package/README.md CHANGED
@@ -1,12 +1,11 @@
1
- # Astro Theme Pure
2
-
3
- [![GitHub commit activity](https://img.shields.io/github/commit-activity/t/cworld1/astro-theme-pure?label=commits&style=flat-square)](https://github.com/cworld1/astro-theme-pure/commits)
4
- [![GitHub stars](https://img.shields.io/github/stars/cworld1/astro-theme-pure?style=flat-square)](https://github.com/cworld1/astro-theme-pure/stargazers)
5
- [![vercel status](https://img.shields.io/website?down_message=offline&label=vercel&logo=vercel&style=flat-square&up_message=online&url=https%3A%2F%2Fastro-pure.js.org)](#)
6
- [![GitHub license](https://img.shields.io/github/license/cworld1/astro-theme-pure?style=flat-square)](https://github.com/cworld1/astro-theme-pure/blob/main/LICENSE)
1
+ # Astro Theme Pure (Integration Package)
7
2
 
8
3
  A simple, fast and powerful blog theme built by Astro.
9
4
 
5
+ [![GitHub deployments](https://img.shields.io/github/deployments/cworld1/astro-theme-pure/production?style=flat&logo=vercel&label=vercel)](https://astro-pure.js.org/)
6
+ [![NPM Version](https://img.shields.io/npm/v/astro-pure?style=flat)](https://www.npmjs.com/package/astro-pure)
7
+ [![GitHub License](https://img.shields.io/github/license/cworld1/astro-theme-pure?style=flat)](https://github.com/cworld1/astro-theme-pure/blob/main/LICENSE)
8
+
10
9
  ![img](https://github.com/user-attachments/assets/6c42b061-df7e-4696-a29b-bff07fe17d88)
11
10
 
12
11
  ## Usage
package/bun.lockb CHANGED
Binary file
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  import config from 'virtual:config'
3
3
 
4
+ import '@waline/client/style'
5
+
4
6
  import { cn } from '../../utils'
5
7
 
6
8
  const { class: className } = Astro.props
@@ -21,8 +23,6 @@ const { class: className } = Astro.props
21
23
  import type { WalineEmojiPresets } from '@waline/client'
22
24
  import config from 'virtual:config'
23
25
 
24
- import '@waline/client/style'
25
-
26
26
  const walineConfig = config.integ.waline
27
27
 
28
28
  class Comment extends HTMLElement {
@@ -1,4 +1,6 @@
1
1
  ---
2
+ import { Icon } from '../user'
3
+
2
4
  const { repo: repoRaw } = Astro.props
3
5
 
4
6
  // Remove 'https://github.com/' headings from the repo string
@@ -21,26 +23,30 @@ const [owner, repoName] = repo.split('/')
21
23
  <span class='text-lg font-bold transition-colors'>{repoName}</span>
22
24
  </div>
23
25
  <div class='rounded-full bg-primary-foreground p-1'>
24
- <svg class='size-6'>
25
- <use href='/icons/social.svg#mingcute-github-line'></use>
26
- </svg>
26
+ <Icon name='github' />
27
27
  </div>
28
28
  </div>
29
29
  <p id='gh-description' class='gh-text'>Waiting for api.github.com...</p>
30
30
  <div class='flex items-center justify-between'>
31
31
  <div class='gh-text flex flex-wrap items-center gap-x-5'>
32
32
  <div class='flex items-center gap-x-2'>
33
- <svg class='size-5'><use href='/icons/github-card.svg#mingcute-star-line'></use></svg>
33
+ {/* mingcute:star-line */}
34
+ <!-- prettier-ignore -->
35
+ <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" 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>
34
36
  <span id='gh-stars' class='leading-tight'>???</span>
35
37
  </div>
38
+
36
39
  <div class='flex items-center gap-x-2'>
37
- <svg class='size-5'
38
- ><use href='/icons/github-card.svg#mingcute-git-branch-line'></use></svg
39
- >
40
+ {/* mingcute:git-branch-line */}
41
+ <!-- prettier-ignore -->
42
+ <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" 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>
40
43
  <span id='gh-forks' class='leading-tight'>???</span>
41
44
  </div>
45
+
42
46
  <div class='flex items-center gap-x-2'>
43
- <svg class='size-5'><use href='/icons/github-card.svg#mingcute-balance-line'></use></svg>
47
+ {/* mingcute:balance-line */}
48
+ <!-- prettier-ignore -->
49
+ <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" 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>
44
50
  <span id='gh-license' class='leading-tight'>???</span>
45
51
  </div>
46
52
  </div>
@@ -63,7 +63,7 @@ social.rss = {
63
63
  href={url}
64
64
  aria-label={label}
65
65
  >
66
- <Icon class='size-6' name={platform as keyof typeof footerConf.social} />
66
+ <Icon name={platform as keyof typeof footerConf.social} />
67
67
  </a>
68
68
  ))
69
69
  }
@@ -20,6 +20,7 @@
20
20
  </script>
21
21
 
22
22
  <script>
23
+ import { Icons } from '../../libs/icons'
23
24
  import { listenThemeChange } from '../../utils'
24
25
 
25
26
  // Listen for theme change event
@@ -30,7 +31,7 @@
30
31
  const toast = document.createElement('div')
31
32
  toast.className =
32
33
  'animate fixed bottom-8 z-20 px-4 py-2 bg-muted text-foreground rounded-lg border border-border shadow-lg flex items-center gap-2'
33
- toast.innerHTML = `<svg class='size-6'><use href='/icons/ui.svg#mingcute-information-line' /></svg> <span>${message}</span>`
34
+ toast.innerHTML = `<svg width='22' height='22' viewBox='0 0 24 24' fill='currentColor'>${Icons.info}</svg> <span>${message}</span>`
34
35
  document.body.appendChild(toast)
35
36
  setTimeout(() => {
36
37
  toast.remove()
@@ -38,7 +38,6 @@ const next = collections[index + 1]
38
38
  stroke-linejoin='round'
39
39
  class='shrink-0 rotate-180 stroke-muted-foreground transition-colors group-hover:stroke-primary'
40
40
  >
41
- {' '}
42
41
  <line
43
42
  x1='5'
44
43
  y1='12'
@@ -1,4 +1,6 @@
1
1
  ---
2
+ import { Icon } from '../user'
3
+
2
4
  const { header: headerName, content: contentName, needPercent = true } = Astro.props
3
5
  ---
4
6
 
@@ -14,11 +16,10 @@ const { header: headerName, content: contentName, needPercent = true } = Astro.p
14
16
  <span class='text'>10</span>
15
17
  <span class='text-xs'>%</span>
16
18
  </div>
17
- <svg
18
- class='size-6 opacity-0 transition-opacity group-hover:opacity-100 group-[.ended]:opacity-100'
19
- >
20
- <use href='/icons/ui.svg#mingcute-up-line'></use>
21
- </svg>
19
+ <Icon
20
+ name='up'
21
+ class='opacity-0 transition-opacity group-hover:opacity-100 group-[.ended]:opacity-100'
22
+ />
22
23
  </button>
23
24
 
24
25
  <script is:inline define:vars={{ headerName, contentName, needPercent }}>
@@ -90,13 +90,13 @@ const shares = {
90
90
  </button>
91
91
  {
92
92
  config.content.share.map((share) => (
93
- <a href={shares[share].link} target='_blank'>
94
- <button
95
- id={`share-${share}`}
96
- class='group rounded-full bg-muted p-1 text-muted-foreground transition-colors hover:text-primary sm:p-1.5'
97
- >
98
- <Icon class='size-5' name={share} />
99
- </button>
93
+ <a
94
+ href={shares[share].link}
95
+ target='_blank'
96
+ id={`share-${share}`}
97
+ class='group rounded-full bg-muted p-1 text-muted-foreground transition-colors hover:text-primary sm:p-1.5'
98
+ >
99
+ <Icon class='size-5' name={share} />
100
100
  </a>
101
101
  ))
102
102
  }
@@ -0,0 +1,70 @@
1
+ ---
2
+ import '@pagefind/default-ui/css/ui.css'
3
+ ---
4
+
5
+ <site-search>
6
+ <aside class='form my-4'>
7
+ {
8
+ import.meta.env.DEV ? (
9
+ <div class='w-full rounded-xl border-2 bg-transparent px-4 py-2 pe-3 outline-none focus:border-foreground'>
10
+ You are in Dev mode, the search is disabled.
11
+ </div>
12
+ ) : (
13
+ <div id='site-search' />
14
+ )
15
+ }
16
+ </aside>
17
+ </site-search>
18
+
19
+ <script>
20
+ class SiteSearch extends HTMLElement {
21
+ constructor() {
22
+ super()
23
+
24
+ // const shouldStrip = this.dataset.stripTrailingSlash !== undefined
25
+ // const stripTrailingSlash = (path: string) => path.replace(/(.)\/(#.*)?$/, '$1$2')
26
+ // const formatURL = shouldStrip ? stripTrailingSlash : (path: string) => path
27
+ const formatURL = (path: string) => path.replace(/(.)\/(#.*)?$/, '$1$2')
28
+
29
+ window.addEventListener('DOMContentLoaded', () => {
30
+ // if (import.meta.env.DEV) return
31
+ const onIdle = window.requestIdleCallback || ((cb) => setTimeout(cb, 1))
32
+ onIdle(async () => {
33
+ // @ts-expect-error — Missing types for @pagefind/default-ui package.
34
+ const { PagefindUI } = await import('@pagefind/default-ui')
35
+ new PagefindUI({
36
+ element: '#site-search',
37
+ baseUrl: import.meta.env.BASE_URL,
38
+ bundlePath: import.meta.env.BASE_URL.replace(/\/$/, '') + '/pagefind/',
39
+ showImages: false,
40
+ showSubResults: true,
41
+ processResult: (result: { url: string; sub_results: Array<{ url: string }> }) => {
42
+ result.url = formatURL(result.url)
43
+ result.sub_results = result.sub_results.map((sub_result) => {
44
+ sub_result.url = formatURL(sub_result.url)
45
+ return sub_result
46
+ })
47
+ }
48
+ })
49
+ })
50
+ })
51
+ }
52
+ }
53
+ customElements.define('site-search', SiteSearch)
54
+ </script>
55
+
56
+ <style>
57
+ #site-search {
58
+ --pagefind-ui-scale: 0.8;
59
+ --pagefind-ui-primary: hsl(var(--primary) / var(--tw-bg-opacity, 1));
60
+ --pagefind-ui-text: hsl(var(--foreground) / var(--tw-text-opacity, 1));
61
+ --pagefind-ui-background: hsl(var(--muted) / var(--tw-bg-opacity, 1));
62
+ --pagefind-ui-border: hsl(var(--border) / var(--tw-border-opacity, 1));
63
+ --pagefind-ui-tag: hsl(var(--muted-foreground) / var(--tw-text-opacity, 1));
64
+ --pagefind-ui-border-width: 2px;
65
+ --pagefind-ui-border-radius: calc(var(--radius) + 2px);
66
+ --pagefind-ui-image-border-radius: calc(var(--radius) + 2px);
67
+ --pagefind-ui-image-box-ratio: 3 / 2;
68
+ --pagefind-ui-font: sans-serif;
69
+ }
70
+ </style>
@@ -17,7 +17,6 @@ const { as: Tag = 'div', post, detailed = false, class: className } = Astro.prop
17
17
  const { id, data } = post
18
18
 
19
19
  const { remarkPluginFrontmatter } = await render(post)
20
-
21
20
  const postDate = data.updatedDate ?? data.publishDate
22
21
  ---
23
22
 
@@ -7,3 +7,4 @@ export { default as Paginator } from './Paginator.astro'
7
7
  export { default as PostPreview } from './PostPreview.astro'
8
8
  export { default as TOC } from './TOC.astro'
9
9
  export { default as TOCHeading } from './TOCHeading.astro'
10
+ export { default as PFSearch } from './PFSearch.astro'
@@ -3,13 +3,14 @@
3
3
  import { AstroError } from 'astro/errors'
4
4
 
5
5
  import { cn } from '../../utils'
6
+ import Icon from './Icon.astro'
6
7
 
7
8
  const asideVariants = ['note', 'tip', 'caution', 'danger'] as const
8
9
  const icons = {
9
- note: 'ui.svg#mingcute-information-line',
10
- tip: 'aside.svg#mingcute-bulb-line',
11
- caution: 'aside.svg#mingcute-alert-line',
12
- danger: 'aside.svg#mingcute-alert-octagon-line'
10
+ note: 'info',
11
+ tip: 'bulb',
12
+ caution: 'alert',
13
+ danger: 'octangon'
13
14
  } as const
14
15
 
15
16
  interface Props {
@@ -37,9 +38,7 @@ if (!title) {
37
38
  class={cn('aside-container border-l-8 border-primary px-4 py-3 bg-primary', `aside-${type}`)}
38
39
  >
39
40
  <p class='not-prose flex items-center gap-x-2 font-medium text-primary' aria-hidden='true'>
40
- <svg class='size-6 text-primary'>
41
- <use href={`/icons/${icons[type]}`}></use>
42
- </svg>
41
+ <Icon name={icons[type]} />
43
42
  {title}
44
43
  </p>
45
44
  <div class='aside-content mt-2'>
@@ -16,8 +16,8 @@ const a11yAttrs = label ? ({ 'aria-label': label } as const) : ({ 'aria-hidden':
16
16
  <svg
17
17
  {...a11yAttrs}
18
18
  class={className}
19
- width='16'
20
- height='16'
19
+ width='22'
20
+ height='22'
21
21
  viewBox='0 0 24 24'
22
22
  fill='currentColor'
23
23
  set:html={Icons[name]}
@@ -28,7 +28,7 @@ const a11yAttrs = label ? ({ 'aria-label': label } as const) : ({ 'aria-hidden':
28
28
  svg {
29
29
  color: var(--sl-icon-color);
30
30
  font-size: var(--sl-icon-size, 1em);
31
- width: 1em;
32
- height: 1em;
31
+ width: 1.5em;
32
+ height: 1.5em;
33
33
  }
34
34
  </style>
@@ -0,0 +1,48 @@
1
+ ---
2
+ interface Props {
3
+ width?: string
4
+ }
5
+
6
+ const { width } = Astro.props
7
+ ---
8
+
9
+ <div class='mdx-repl overflow-hidden rounded-xl border'>
10
+ <div class='mdx-repl-container flex flex-col items-center justify-center p-4'>
11
+ <slot />
12
+ </div>
13
+ <div class='bg-muted'>
14
+ <slot name='desc' />
15
+ </div>
16
+ </div>
17
+
18
+ <style define:vars={{ width }}>
19
+ .mdx-repl {
20
+ background: linear-gradient(
21
+ 135deg,
22
+ hsl(var(--primary) / 0.05) 0%,
23
+ hsl(var(--muted) / 0.2) 100%
24
+ );
25
+ }
26
+
27
+ /* Container */
28
+ .mdx-repl-container > :global(*) {
29
+ width: var(--width);
30
+ }
31
+ .mdx-repl-container > :global(*):first-child {
32
+ margin-top: 0;
33
+ }
34
+ .mdx-repl-container > :global(*):last-child {
35
+ margin-bottom: 0;
36
+ }
37
+
38
+ /* Desc especial adaption */
39
+ /* Code */
40
+ .mdx-repl :global(.astro-code) {
41
+ margin: 0;
42
+ border-radius: 0;
43
+ }
44
+ /* Tabs */
45
+ .mdx-repl :global(div[role='tabpanel']) {
46
+ margin-top: 0;
47
+ }
48
+ </style>
@@ -0,0 +1,41 @@
1
+ ---
2
+ // https://github.com/jasikpark/astro-svg-loader/blob/main/src/components/Svg/Svg.astro
3
+ import { overrideSvgAttributes, type SVGAttributes } from '../../plugins/override-svg-attributes'
4
+
5
+ // accepts SVG attributes which will override the ones in the original SVG
6
+ export interface Props extends SVGAttributes {
7
+ /** pass an `import("*.svg?raw")` to `Svg` for the svg file to use */
8
+ src: Promise<typeof import('*.svg?raw')>
9
+ }
10
+
11
+ const { src, ...attributeOverrides } = Astro.props as Props
12
+
13
+ const SVG_NOT_FOUND = 'Could not find an SVG at the provided `src`'
14
+ const ALT_NOT_ALLOWED =
15
+ '`alt` is not a valid prop for svg, perhaps you mean `aria-label` or `aria-hidden`?'
16
+
17
+ const svgImport = await src
18
+
19
+ if (!svgImport) {
20
+ throw new Error(SVG_NOT_FOUND)
21
+ }
22
+
23
+ const svgSource = svgImport.default
24
+
25
+ if (!svgSource) {
26
+ throw new Error(SVG_NOT_FOUND)
27
+ }
28
+
29
+ if ('alt' in attributeOverrides) {
30
+ throw new Error(ALT_NOT_ALLOWED)
31
+ }
32
+
33
+ if (!svgSource.trimStart().toLowerCase().startsWith('<svg')) {
34
+ throw new Error(`${SVG_NOT_FOUND}, provided: ${svgSource.slice(0, 23)}
35
+ maybe you need to add '?raw' to the end of the import?`)
36
+ }
37
+
38
+ const contents = overrideSvgAttributes(svgSource, attributeOverrides)
39
+ ---
40
+
41
+ <Fragment set:html={contents} />
@@ -1,9 +1,10 @@
1
- // Caontainer
1
+ // Container
2
2
  export { default as Card } from './Card.astro'
3
3
  export { default as Collapse } from './Collapse.astro'
4
4
  export { default as Aside } from './Aside.astro'
5
5
  export { default as Tabs } from './Tabs.astro'
6
6
  export { default as TabItem } from './TabItem.astro'
7
+ export { default as MdxRepl } from './MdxRepl.astro'
7
8
 
8
9
  // List
9
10
  export { default as CardList } from './CardList.astro'
@@ -15,6 +16,7 @@ export { default as Button } from './Button.astro'
15
16
  export { default as Spoiler } from './Spoiler.astro'
16
17
  export { default as FormattedDate } from './FormattedDate.astro'
17
18
  export { default as Label } from './Label.astro'
19
+ export { default as Svg } from './Svg.astro'
18
20
 
19
21
  // Sources
20
22
  export { default as Icon } from './Icon.astro'
package/index.ts CHANGED
@@ -9,7 +9,7 @@ import sitemap from '@astrojs/sitemap'
9
9
  import tailwind from '@astrojs/tailwind'
10
10
  import rehypeExternalLinks from 'rehype-external-links'
11
11
 
12
- import { remarkAddZoomable } from './plugins/remark-plugins'
12
+ import { remarkAddZoomable, remarkReadingTime } from './plugins/remark-plugins'
13
13
  import { vitePluginUserConfig } from './plugins/virtual-user-config'
14
14
  import { UserConfigSchema, type UserInputConfig } from './types/user-config'
15
15
  import { parseWithFriendlyErrors } from './utils/error-map'
@@ -44,6 +44,7 @@ export default function AstroPureIntegration(opts: UserInputConfig): AstroIntegr
44
44
  // Add supported remark plugins based on user config.
45
45
  if (userConfig.integ.mediumZoom.enable)
46
46
  remarkPlugins.push([remarkAddZoomable, userConfig.integ.mediumZoom.options])
47
+ remarkPlugins.push(remarkReadingTime)
47
48
 
48
49
  // Add supported rehype plugins based on user config.
49
50
  rehypePlugins.push([
package/libs/icons.ts CHANGED
@@ -40,6 +40,18 @@ export const BuiltInIcons = {
40
40
  // tabler:brand-steam
41
41
  steam:
42
42
  '<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M16.5 5a4.5 4.5 0 1 1-.653 8.953L11.5 16.962V17a3 3 0 0 1-2.824 3H8.5a3 3 0 0 1-2.94-2.402L3 16.5V13l3.51 1.755a2.99 2.99 0 0 1 2.834-.635l2.727-3.818A4.5 4.5 0 0 1 16.5 5"/><circle cx="16.5" cy="9.5" r="1" fill="currentColor"/></g>',
43
+ // ri:bilibili-line
44
+ bilibili:
45
+ '<path fill="currentColor" d="M7.172 2.757L10.414 6h3.171l3.243-3.242a1 1 0 1 1 1.415 1.415L16.414 6H18.5A3.5 3.5 0 0 1 22 9.5v8a3.5 3.5 0 0 1-3.5 3.5h-13A3.5 3.5 0 0 1 2 17.5v-8A3.5 3.5 0 0 1 5.5 6h2.085L5.757 4.171a1 1 0 0 1 1.415-1.415M18.5 8h-13a1.5 1.5 0 0 0-1.493 1.356L4 9.5v8a1.5 1.5 0 0 0 1.356 1.493L5.5 19h13a1.5 1.5 0 0 0 1.493-1.355L20 17.5v-8A1.5 1.5 0 0 0 18.5 8M8 11a1 1 0 0 1 1 1v2a1 1 0 1 1-2 0v-2a1 1 0 0 1 1-1m8 0a1 1 0 0 1 1 1v2a1 1 0 1 1-2 0v-2a1 1 0 0 1 1-1"/>',
46
+ // ri:zhihu-line
47
+ zhihu:
48
+ '<path fill="currentColor" d="m12.345 17.963l-1.688 1.074l-2.132-3.35c-.44 1.402-1.171 2.665-2.138 3.825c-.402.483-.82.918-1.301 1.376c-.155.146-.775.716-.878.82l-1.414-1.415c.139-.139.787-.735.914-.856c.43-.408.796-.79 1.143-1.205C6.117 16.712 6.88 15.02 6.988 13H3v-2h4V7h-.868c-.689 1.266-1.558 2.222-2.618 2.858L2.486 8.143c1.396-.838 2.426-2.603 3.039-5.36l1.952.434q-.21.95-.489 1.783h4.513v2H9v4h2.5v2H9.186zm3.838-.07L17.3 17h1.702V7h-4v10h.736zM13.001 5h8v14h-3l-2.5 2l-1-2H13z"/>',
49
+ // iconfont:coolapk
50
+ coolapk:
51
+ '<path transform="scale(0.031, 0.031) translate(-120, -100)" d="M315.2 343.093c53.547-12.8 111.52-1.92 158.72 25.76 34.987 21.12 65.44 48.64 97.493 73.707-17.706 12-35.253 24.16-53.173 35.84-32.747-24.693-62.933-54.88-102.56-68.48-36.373-12.533-78.827-14.4-113.387 4.64-35.413 18.773-59.733 57.333-59.68 97.653-1.066 40.48 22.667 79.734 57.76 99.414 37.814 21.76 86.08 18.88 124.534 0.32 47.626-23.307 80.32-67.147 109.653-109.814 31.52-48.96 59.573-100.053 90.293-149.493 6.72-12.533 22.507-19.253 36-14.133 12.534 4 17.92 16.96 23.894 27.466 47.466 85.6 95.52 170.827 143.573 256.054 7.52 13.653 18.507 27.84 13.867 44.48-3.52 20.533-30.827 27.573-46.4 16-66.24-49.067-132.8-97.814-198.934-147.147 16.374-13.707 34.934-24.533 52.374-36.8 22.933 17.013 45.866 34.08 68.906 50.933-22.026-40.853-45.6-80.8-67.893-121.493-42.613 66.08-77.387 138.773-135.04 193.653-41.387 41.12-98.453 69.227-157.813 65.974-92.054 4.213-175.414-78.774-174.614-170.347-3.84-79.68 55.04-156 132.427-174.187z" fill="currentColor"></path>',
52
+ // ri:netease-cloud-music-line
53
+ netease:
54
+ '<path fill="currentColor" d="M10.422 11.375c-.294 1.028.012 2.065.784 2.653c1.061.81 2.565.3 2.874-.995c.08-.337.103-.722.027-1.056c-.23-1.001-.521-1.988-.792-2.996c-1.33.154-2.543 1.172-2.893 2.394m5.548-.287c.273 1.012.285 2.017-.127 3c-1.128 2.69-4.722 3.14-6.573.826c-1.302-1.627-1.28-3.961.06-5.734c.78-1.032 1.804-1.707 3.048-2.054l.379-.104c-.084-.415-.188-.816-.243-1.224c-.176-1.317.512-2.503 1.744-3.04c1.226-.535 2.708-.216 3.53.76c.406.479.395 1.08-.025 1.464c-.412.377-.997.346-1.435-.09c-.247-.246-.51-.44-.877-.436c-.525.006-.987.418-.945.937c.037.468.172.93.3 1.386c.022.078.216.135.338.153c1.333.197 2.504.731 3.472 1.676c2.558 2.493 2.861 6.531.672 9.44c-1.529 2.032-3.61 3.169-6.127 3.409c-4.621.44-8.664-2.53-9.7-7.058C2.516 10.255 4.84 5.831 8.796 4.25c.586-.234 1.143-.031 1.371.498c.232.537-.019 1.086-.61 1.35c-2.368 1.06-3.817 2.855-4.215 5.424c-.533 3.433 1.656 6.776 5 7.72c2.723.77 5.658-.166 7.308-2.33c1.586-2.08 1.4-5.099-.427-6.873A4 4 0 0 0 15.4 9.026c.198.716.389 1.388.57 2.062"/>',
43
55
 
44
56
  // === Copyright & share & sponsor ===
45
57
  // mingcute:link-2-line
@@ -84,6 +96,13 @@ export const BuiltInIcons = {
84
96
  // mingcute:hashtag-line
85
97
  hashtag:
86
98
  '<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.124 3.008a1 1 0 0 1 .868 1.116L10.508 8h3.984l.516-4.124a1 1 0 1 1 1.984.248L16.508 8H20a1 1 0 1 1 0 2h-3.742l-.5 4H19.5a1 1 0 1 1 0 2h-3.992l-.516 4.124a1 1 0 1 1-1.984-.248L13.492 16H9.508l-.516 4.124a1 1 0 1 1-1.984-.248L7.492 16H4.5a1 1 0 1 1 0-2h3.242l.5-4H5a1 1 0 0 1 0-2h3.492l.516-4.124a1 1 0 0 1 1.116-.868M13.742 14l.5-4h-3.984l-.5 4z"/></g>',
99
+ // mingcute:information-line
100
+ info: '<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="M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10S2 17.523 2 12S6.477 2 12 2m0 2a8 8 0 1 0 0 16a8 8 0 0 0 0-16m-.01 6c.558 0 1.01.452 1.01 1.01v5.124A1 1 0 0 1 12.5 18h-.49A1.01 1.01 0 0 1 11 16.99V12a1 1 0 1 1 0-2zM12 7a1 1 0 1 1 0 2a1 1 0 0 1 0-2"/></g>',
101
+ // mingcute:up-line
102
+ up: '<g fill="none" fill-rule="evenodd"><path d="M24 0v24H0V0zM12.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-.019-.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="M11.293 8.293a1 1 0 0 1 1.414 0l5.657 5.657a1 1 0 0 1-1.414 1.414L12 10.414l-4.95 4.95a1 1 0 0 1-1.414-1.414z"/></g>',
103
+ // mingcute:tag-2-line
104
+ 'tag-2':
105
+ '<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="M10.238 4.827a3 3 0 0 1 2.122.878l6.485 6.486a3 3 0 0 1 0 4.242l-4.243 4.243a3 3 0 0 1-4.242 0l-6.486-6.485a3 3 0 0 1-.878-2.122V7.327a2.5 2.5 0 0 1 2.5-2.5zm0 2H5.496a.5.5 0 0 0-.5.5v4.742a1 1 0 0 0 .292.707l6.486 6.486a1 1 0 0 0 1.414 0l4.243-4.243a1 1 0 0 0 0-1.414L10.945 7.12a1 1 0 0 0-.707-.293M7.531 9.362a1.5 1.5 0 1 1 2.121 2.122a1.5 1.5 0 0 1-2.121-2.122M11.652 2a3 3 0 0 1 2.122.878l7.192 7.193a1 1 0 0 1-1.414 1.414L12.36 4.29a1 1 0 0 0-.708-.29H7a1 1 0 0 1 0-2z"/></g>',
87
106
 
88
107
  // === Project ===
89
108
  'github-circle':
@@ -98,7 +117,17 @@ export const BuiltInIcons = {
98
117
  // === Home ===
99
118
  // mingcute:location-line
100
119
  location:
101
- '<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 2a9 9 0 0 1 9 9c0 3.074-1.676 5.59-3.442 7.395a20.4 20.4 0 0 1-2.876 2.416l-.426.29l-.2.133l-.377.24l-.336.205l-.416.242a1.87 1.87 0 0 1-1.854 0l-.416-.242l-.52-.32l-.192-.125l-.41-.273a20.6 20.6 0 0 1-3.093-2.566C4.676 16.589 3 14.074 3 11a9 9 0 0 1 9-9m0 2a7 7 0 0 0-7 7c0 2.322 1.272 4.36 2.871 5.996a18 18 0 0 0 2.222 1.91l.458.326q.222.155.427.288l.39.25l.343.209l.289.169l.455-.269l.367-.23q.293-.186.627-.417l.458-.326a18 18 0 0 0 2.222-1.91C17.728 15.361 19 13.322 19 11a7 7 0 0 0-7-7m0 3a4 4 0 1 1 0 8a4 4 0 0 1 0-8m0 2a2 2 0 1 0 0 4a2 2 0 0 0 0-4"/></g>'
120
+ '<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 2a9 9 0 0 1 9 9c0 3.074-1.676 5.59-3.442 7.395a20.4 20.4 0 0 1-2.876 2.416l-.426.29l-.2.133l-.377.24l-.336.205l-.416.242a1.87 1.87 0 0 1-1.854 0l-.416-.242l-.52-.32l-.192-.125l-.41-.273a20.6 20.6 0 0 1-3.093-2.566C4.676 16.589 3 14.074 3 11a9 9 0 0 1 9-9m0 2a7 7 0 0 0-7 7c0 2.322 1.272 4.36 2.871 5.996a18 18 0 0 0 2.222 1.91l.458.326q.222.155.427.288l.39.25l.343.209l.289.169l.455-.269l.367-.23q.293-.186.627-.417l.458-.326a18 18 0 0 0 2.222-1.91C17.728 15.361 19 13.322 19 11a7 7 0 0 0-7-7m0 3a4 4 0 1 1 0 8a4 4 0 0 1 0-8m0 2a2 2 0 1 0 0 4a2 2 0 0 0 0-4"/></g>',
121
+
122
+ // === Aside ===
123
+ // mingcute:bulb-line
124
+ bulb: '<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="M13 20a1 1 0 1 1 0 2h-2a1 1 0 1 1 0-2zM12 2c4.41 0 8 3.543 8 7.933c0 3.006-1.522 5.196-2.78 6.494l-.284.283l-.27.252l-.252.22l-.33.27l-.328.244c-.196.138-.34.329-.466.535l-.145.251l-.141.252c-.24.412-.518.766-1.111.766h-3.786c-.593 0-.871-.354-1.11-.766l-.213-.378c-.145-.253-.305-.494-.54-.66l-.232-.171l-.199-.155l-.227-.188l-.252-.22l-.27-.252l-.285-.283C5.522 15.129 4 12.939 4 9.933C4 5.543 7.59 2 12 2m0 2C8.677 4 6 6.665 6 9.933c0 2.624 1.533 4.494 2.593 5.471l.245.218l.22.182l.27.208l.072.052c.315.222.549.531.762.854l.373.582h2.93l.373-.582c.213-.323.447-.632.762-.854l.243-.182l.206-.165l.233-.2C16.342 14.576 18 12.662 18 9.933C18 6.665 15.323 4 12 4m.293 2.293a1 1 0 0 1 1.497 1.32l-.083.094L12.414 9l1.286 1.286c.364.364.392.937.084 1.333l-.084.095l-1.993 1.993a1 1 0 0 1-1.497-1.32l.083-.094L11.586 11L10.3 9.714a1.01 1.01 0 0 1-.084-1.333l.084-.095z"/></g>',
125
+ // mingcute:alert-line
126
+ alert:
127
+ '<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="m13.299 3.148l8.634 14.954a1.5 1.5 0 0 1-1.299 2.25H3.366a1.5 1.5 0 0 1-1.299-2.25l8.634-14.954c.577-1 2.02-1 2.598 0M12 4.898L4.232 18.352h15.536zM12 15a1 1 0 1 1 0 2a1 1 0 0 1 0-2m0-7a1 1 0 0 1 1 1v4a1 1 0 1 1-2 0V9a1 1 0 0 1 1-1"/></g>',
128
+ // mingcute:alert-octagon-line
129
+ octangon:
130
+ '<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="M15.314 2a2 2 0 0 1 1.414.586l4.686 4.686A2 2 0 0 1 22 8.686v6.628a2 2 0 0 1-.586 1.414l-4.686 4.686a2 2 0 0 1-1.414.586H8.686a2 2 0 0 1-1.414-.586l-4.686-4.686A2 2 0 0 1 2 15.314V8.686a2 2 0 0 1 .586-1.414l4.686-4.686A2 2 0 0 1 8.686 2zm0 2H8.686L4 8.686v6.628L8.686 20h6.628L20 15.314V8.686zM12 15a1 1 0 1 1 0 2a1 1 0 0 1 0-2m0-9a1 1 0 0 1 1 1v6a1 1 0 1 1-2 0V7a1 1 0 0 1 1-1"/></g>'
102
131
  }
103
132
 
104
133
  export const Icons = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-pure",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "description": "A simple, clean but powerful blog theme build by astro.",
5
5
  "type": "module",
6
6
  "author": "CWorld",
@@ -35,10 +35,13 @@
35
35
  "./libs": "./libs/index.ts"
36
36
  },
37
37
  "dependencies": {
38
- "@astrojs/mdx": "^4.0.1",
38
+ "@astrojs/mdx": "^4.0.3",
39
39
  "@astrojs/sitemap": "^3.2.1",
40
40
  "@astrojs/tailwind": "^5.1.3",
41
+ "@pagefind/default-ui": "^1.3.0",
41
42
  "astro": "^5.0.3",
43
+ "hast-util-select": "^6.0.3",
44
+ "node-html-parser": "^7.0.1",
42
45
  "pagefind": "^1.2.0",
43
46
  "rehype-external-links": "^3.0.0",
44
47
  "tailwind-merge": "^2.5.5"
@@ -0,0 +1,41 @@
1
+ /// <reference types="astro/astro-jsx" />
2
+
3
+ import { parse, render, type DocumentNode, type Node } from 'ultrahtml'
4
+
5
+ export type SVGAttributes = Omit<
6
+ astroHTML.JSX.SVGAttributes,
7
+ 'client:list' | 'set:text' | 'set:html' | 'is:raw'
8
+ >
9
+
10
+ const EMPTY_STRING_ERR = '`svgSource` must have content'
11
+ const MUST_START_WITH_SVG = '`svgSource` must begin with `<svg`'
12
+
13
+ export async function overrideSvgAttributes(
14
+ svgSource: string,
15
+ attributeOverrides: SVGAttributes = {}
16
+ ): Promise<string> {
17
+ if (!svgSource) {
18
+ throw new Error(EMPTY_STRING_ERR)
19
+ }
20
+ if (!svgSource.trimStart().toLowerCase().startsWith('<svg')) {
21
+ throw new Error(MUST_START_WITH_SVG)
22
+ }
23
+
24
+ const doc = parse(svgSource) as DocumentNode
25
+
26
+ const firstSVGNode = doc.children.find(({ type, name }: Node) => type === 1 && /svg/i.test(name))
27
+
28
+ const mergedAttributes = Object.fromEntries(
29
+ Object.entries({
30
+ ...firstSVGNode.attributes,
31
+ ...attributeOverrides
32
+ }).filter(([, value]) => !!value)
33
+ )
34
+
35
+ const updatedSVG = {
36
+ ...firstSVGNode,
37
+ attributes: mergedAttributes
38
+ }
39
+
40
+ return render(updatedSVG)
41
+ }
@@ -2,6 +2,10 @@ import type { Node, Root } from 'mdast'
2
2
  import type { Plugin } from 'unified'
3
3
  import { visit } from 'unist-util-visit'
4
4
 
5
+ // Cannot use '../utils' for plugin absolute path
6
+ import mdastToString from '../utils/mdast-util-to-string'
7
+ import getReadingTime from '../utils/reading-time'
8
+
5
9
  export const remarkAddZoomable: Plugin<[{ className?: string }], Root> = function ({
6
10
  className = 'zoomable'
7
11
  }) {
@@ -11,3 +15,16 @@ export const remarkAddZoomable: Plugin<[{ className?: string }], Root> = functio
11
15
  })
12
16
  }
13
17
  }
18
+
19
+ export const remarkReadingTime: Plugin<[], Root> = function () {
20
+ return function (tree, { data }) {
21
+ const textOnPage = mdastToString(tree)
22
+ const readingTime = getReadingTime(textOnPage)
23
+ // readingTime.text will give us minutes read as a friendly string,
24
+ // i.e. "3 min read"
25
+ if (data.astro && data.astro.frontmatter) {
26
+ data.astro.frontmatter.minutesRead = readingTime.text
27
+ data.astro.frontmatter.words = readingTime.words
28
+ }
29
+ }
30
+ }
package/schemas/social.ts CHANGED
@@ -1,20 +1,6 @@
1
1
  import { z } from 'astro/zod'
2
2
 
3
- export const socialLinks = [
4
- 'github',
5
- 'gitlab',
6
- 'discord',
7
- 'youtube',
8
- 'instagram',
9
- 'x',
10
- 'telegram',
11
- 'rss',
12
- 'email',
13
- 'reddit',
14
- 'bluesky',
15
- 'tiktok',
16
- 'steam'
17
- ] as const
3
+ import { socialLinks } from '../types/constants'
18
4
 
19
5
  export const SocialLinksSchema = () =>
20
6
  z
@@ -42,7 +28,12 @@ export const SocialLinksSchema = () =>
42
28
  reddit: 'Reddit',
43
29
  bluesky: 'BlueSky',
44
30
  tiktok: 'TikTok',
45
- steam: 'Steam'
31
+ weibo: 'Weibo',
32
+ steam: 'Steam',
33
+ bilibili: 'Bilibili',
34
+ zhihu: 'Zhihu',
35
+ coolapk: 'Coolapk',
36
+ netease: 'NetEase'
46
37
  }[key]
47
38
  labelledLinks[key] = { label, url }
48
39
  }
package/scripts/index.js CHANGED
@@ -21,9 +21,9 @@ switch (args._[0]) {
21
21
  console.log()
22
22
  console.log('\x1b[46m%s\x1b[0m', ' Astro Theme Pure ')
23
23
  console.log('\nInformation:')
24
- console.log(`- Package version: ${packageJson.version}`)
25
- console.log(`- Node.js version: ${process.version}`)
26
- console.log(`- Platform: ${process.platform}`)
24
+ console.log(`- Package:\t${packageJson.version}`)
25
+ console.log(`- Node.js:\t${process.version}`)
26
+ console.log(`- Platform:\t${process.platform}`)
27
27
  break
28
28
  case 'help':
29
29
  console.log('Usage:')
@@ -0,0 +1,20 @@
1
+ export const socialLinks = [
2
+ 'github',
3
+ 'gitlab',
4
+ 'discord',
5
+ 'youtube',
6
+ 'instagram',
7
+ 'x',
8
+ 'telegram',
9
+ 'rss',
10
+ 'email',
11
+ 'reddit',
12
+ 'bluesky',
13
+ 'tiktok',
14
+ 'weibo',
15
+ 'steam',
16
+ 'bilibili',
17
+ 'zhihu',
18
+ 'coolapk',
19
+ 'netease'
20
+ ] as const
package/types/index.ts CHANGED
@@ -28,3 +28,5 @@ export type TimelineEvent = {
28
28
  }
29
29
 
30
30
  export type iconsType = keyof typeof Icons
31
+
32
+ export { socialLinks } from './constants'
@@ -7,7 +7,7 @@ export const IntegrationConfigSchema = () =>
7
7
  links: FriendLinksSchema(),
8
8
 
9
9
  /**
10
- * Define whether Starlight’s default site search provider Pagefind is enabled.
10
+ * Define whether default site search provider Pagefind is enabled.
11
11
  * Set to `false` to disable indexing your site with Pagefind.
12
12
  * This will also hide the default search UI if in use.
13
13
  */
@@ -117,7 +117,7 @@ export const ThemeConfigSchema = () =>
117
117
  menu: HeaderMenuSchema()
118
118
  }),
119
119
 
120
- /** Configure the header of your site. */
120
+ /** Configure the footer of your site. */
121
121
  footer: z.object({
122
122
  /** Registration information for ICP (optional) */
123
123
  registration: z.object({
package/utils/index.ts CHANGED
@@ -1,4 +1,9 @@
1
- // Tailwind
1
+ // Modules
2
+ export { default as clsx } from './clsx'
3
+ export { default as mdastToString } from './mdast-util-to-string'
4
+ export { default as getReadingTime } from './reading-time'
5
+
6
+ // Tailwind cn
2
7
  export { cn } from './tailwind'
3
8
 
4
9
  // Date
@@ -0,0 +1,36 @@
1
+ type Options = {
2
+ includeImageAlt?: boolean
3
+ includeHtml?: boolean
4
+ }
5
+
6
+ export default function toString(value: unknown, options?: Options): string {
7
+ const { includeImageAlt = true, includeHtml = true } = options || {}
8
+ return serialize(value, includeImageAlt, includeHtml)
9
+ }
10
+
11
+ function serialize(value: unknown, includeImageAlt: boolean, includeHtml: boolean): string {
12
+ if (isNode(value)) {
13
+ if ('value' in value) {
14
+ return value.type === 'html' && !includeHtml ? '' : (value.value as string)
15
+ }
16
+ if (includeImageAlt && 'alt' in value && value.alt) {
17
+ return value.alt as string
18
+ }
19
+ if ('children' in value) {
20
+ return serializeAll(value.children as unknown[], includeImageAlt, includeHtml)
21
+ }
22
+ }
23
+
24
+ if (Array.isArray(value)) {
25
+ return serializeAll(value, includeImageAlt, includeHtml)
26
+ }
27
+ return ''
28
+ }
29
+
30
+ function serializeAll(values: unknown[], includeImageAlt: boolean, includeHtml: boolean): string {
31
+ return values.map((value) => serialize(value, includeImageAlt, includeHtml)).join('')
32
+ }
33
+
34
+ function isNode(value: unknown): value is Record<string, unknown> {
35
+ return Boolean(value && typeof value === 'object')
36
+ }
@@ -0,0 +1,79 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * Checks if a character is a CJK character
5
+ * @param {string} char - The character to check
6
+ * @returns {boolean} - Returns true if the character is CJK, otherwise false
7
+ */
8
+ function isCJK(char: string): boolean {
9
+ const charCode = char.charCodeAt(0)
10
+ return (
11
+ (charCode >= 0x4e00 && charCode <= 0x9fff) || // CJK Unified Ideographs
12
+ (charCode >= 0x3400 && charCode <= 0x4dbf) || // CJK Unified Ideographs Extension A
13
+ (charCode >= 0x20000 && charCode <= 0x2a6df) || // CJK Unified Ideographs Extension B
14
+ (charCode >= 0x2a700 && charCode <= 0x2b73f) || // CJK Unified Ideographs Extension C
15
+ (charCode >= 0x2b740 && charCode <= 0x2b81f) || // CJK Unified Ideographs Extension D
16
+ (charCode >= 0x2b820 && charCode <= 0x2ceaf) || // CJK Unified Ideographs Extension E
17
+ (charCode >= 0xf900 && charCode <= 0xfaff) || // CJK Compatibility Ideographs
18
+ (charCode >= 0x2f800 && charCode <= 0x2fa1f) // CJK Compatibility Ideographs Supplement
19
+ )
20
+ }
21
+
22
+ interface ReadingTimeResult {
23
+ text: string
24
+ minutes: number
25
+ time: number
26
+ words: number
27
+ }
28
+
29
+ /**
30
+ * Calculates the reading time of a text
31
+ * @param {string} text - The text to calculate
32
+ * @param {number} wordsPerMinute - The number of words read per minute, default is 200
33
+ * @returns {ReadingTimeResult} - An object containing the reading time
34
+ */
35
+ export function getReadingTime(text: string, wordsPerMinute: number = 200): ReadingTimeResult {
36
+ let words = 0
37
+ const length = text.length
38
+
39
+ // Normalize text by adding a space at the end
40
+ const normalizedText = text + ' '
41
+
42
+ for (let i = 0; i < length; i++) {
43
+ const char = normalizedText[i]
44
+
45
+ // If the character is CJK, count it as a word
46
+ if (isCJK(char)) {
47
+ words++
48
+ // Skip subsequent punctuation and whitespace characters
49
+ while (i < length && (normalizedText[i + 1] === ' ' || isCJK(normalizedText[i + 1]))) {
50
+ i++
51
+ }
52
+ } else if (char === ' ' || char === '\n' || char === '\r') {
53
+ // Count the end of a word when encountering whitespace characters
54
+ if (
55
+ i > 0 &&
56
+ normalizedText[i - 1] !== ' ' &&
57
+ normalizedText[i - 1] !== '\n' &&
58
+ normalizedText[i - 1] !== '\r'
59
+ ) {
60
+ words++
61
+ }
62
+ }
63
+ }
64
+
65
+ // Calculate reading time
66
+ const minutes = words / wordsPerMinute
67
+ const time = Math.round(minutes * 60 * 1000) // Convert to milliseconds
68
+ const displayed = Math.ceil(minutes)
69
+
70
+ return {
71
+ text: displayed + ' min read',
72
+ minutes: minutes,
73
+ time: time,
74
+ words: words
75
+ }
76
+ }
77
+
78
+ // Export module
79
+ export default getReadingTime