astro-pure 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/bun.lockb +0 -0
  2. package/bunfig.toml +2 -0
  3. package/components/advanced/Comment.astro +148 -0
  4. package/components/advanced/GithubCard.astro +148 -0
  5. package/components/advanced/LinkPreview.astro +82 -0
  6. package/components/advanced/MediumZoom.astro +50 -0
  7. package/components/advanced/QRCode.astro +35 -0
  8. package/components/advanced/Quote.astro +44 -0
  9. package/components/advanced/index.ts +11 -0
  10. package/components/user/Aside.astro +74 -0
  11. package/components/user/Button.astro +79 -0
  12. package/components/user/Card.astro +23 -0
  13. package/components/user/CardList.astro +28 -0
  14. package/components/user/CardListChildren.astro +24 -0
  15. package/components/user/Collapse.astro +84 -0
  16. package/components/user/FormattedDate.astro +21 -0
  17. package/components/user/Label.astro +18 -0
  18. package/components/user/Spoiler.astro +11 -0
  19. package/components/user/Steps.astro +84 -0
  20. package/components/user/TabItem.astro +18 -0
  21. package/components/user/Tabs.astro +266 -0
  22. package/components/user/Timeline.astro +38 -0
  23. package/components/user/index.ts +17 -0
  24. package/index.ts +74 -0
  25. package/package.json +38 -0
  26. package/plugins/link-preview.ts +110 -0
  27. package/plugins/rehype-steps.ts +98 -0
  28. package/plugins/rehype-tabs.ts +112 -0
  29. package/plugins/virtual-user-config.ts +83 -0
  30. package/schemas/favicon.ts +42 -0
  31. package/schemas/head.ts +18 -0
  32. package/schemas/logo.ts +28 -0
  33. package/schemas/social.ts +51 -0
  34. package/types/common.d.ts +48 -0
  35. package/types/index.d.ts +6 -0
  36. package/types/integrations-config.ts +43 -0
  37. package/types/theme-config.ts +125 -0
  38. package/types/user-config.ts +24 -0
  39. package/utils/clsx.ts +24 -0
  40. package/utils/collections.ts +48 -0
  41. package/utils/date.ts +17 -0
  42. package/utils/docsContents.ts +36 -0
  43. package/utils/index.ts +23 -0
  44. package/utils/module.d.ts +25 -0
  45. package/utils/server.ts +11 -0
  46. package/utils/tailwind.ts +7 -0
  47. package/utils/theme.ts +40 -0
  48. package/utils/toast.ts +3 -0
  49. package/utils/toc.ts +41 -0
  50. package/virtual.d.ts +2 -0
@@ -0,0 +1,48 @@
1
+ declare module 'virtual:types' {
2
+ export type MenuLinks = { link: string; label: string }[]
3
+
4
+ export interface PaginationLink {
5
+ url: string
6
+ text?: string
7
+ srLabel?: string
8
+ }
9
+
10
+ export interface SiteMeta {
11
+ title: string
12
+ description?: string
13
+ ogImage?: string | undefined
14
+ articleDate?: string | undefined
15
+ }
16
+
17
+ export interface SocialLink {
18
+ name:
19
+ | 'coolapk'
20
+ | 'telegram'
21
+ | 'github'
22
+ | 'bilibili'
23
+ | 'twitter'
24
+ | 'zhihu'
25
+ | 'steam'
26
+ | 'netease_music'
27
+ | 'mail'
28
+ url: string
29
+ }
30
+
31
+ export type ShareItem = 'weibo' | 'x' | 'bluesky'
32
+
33
+ export type CardListData = {
34
+ title: string
35
+ list: CardList
36
+ }
37
+
38
+ export type CardList = {
39
+ title: string
40
+ link?: string
41
+ children?: CardList
42
+ }[]
43
+
44
+ export type TimelineEvent = {
45
+ date: string
46
+ content: string
47
+ }
48
+ }
@@ -0,0 +1,6 @@
1
+ /// <reference path="./common.d.ts" />
2
+
3
+ declare module 'virtual:config' {
4
+ const Config: import('./user-config').UserConfig
5
+ export default Config
6
+ }
@@ -0,0 +1,43 @@
1
+ import { z } from 'astro/zod'
2
+
3
+ export const IntegrationConfigSchema = () =>
4
+ z.object({
5
+ /**
6
+ * Define whether Starlight’s default site search provider Pagefind is enabled.
7
+ * Set to `false` to disable indexing your site with Pagefind.
8
+ * This will also hide the default search UI if in use.
9
+ */
10
+ pagefind: z.boolean().optional(),
11
+ quote: z.object({
12
+ /** The server to fetch the quote from. */
13
+ server: z.string(),
14
+ // target: (data: unknown) => string
15
+ target: z.function().args(z.unknown()).returns(z.string())
16
+ }),
17
+ typography: z.object({
18
+ /** The class to apply to the typography. */
19
+ class: z
20
+ .string()
21
+ .default('prose prose-pure dark:prose-invert dark:prose-pure prose-headings:font-medium')
22
+ }),
23
+ mediumZoom: z.object({
24
+ /** Enable the medium zoom library. */
25
+ enable: z.boolean().default(true),
26
+ /** The selector to apply the zoom effect to. */
27
+ selector: z.string().default('.prose .zoomable'),
28
+ /** Options to pass to the medium zoom library. */
29
+ options: z.record(z.string(), z.any()).default({ className: 'zoomable' })
30
+ }),
31
+ waline: z.object({
32
+ /** Enable the Waline comment system. */
33
+ enable: z.boolean().default(false),
34
+ /** The server to use for the Waline comment system. */
35
+ server: z.string().optional(),
36
+ /** The emoji to use for the Waline comment system. */
37
+ emoji: z.array(z.string()).optional(),
38
+ /** Additional configurations for the Waline comment system. */
39
+ additionalConfigs: z.record(z.string(), z.any()).default({})
40
+ })
41
+ })
42
+
43
+ export type IntegrationConfig = z.infer<ReturnType<typeof IntegrationConfigSchema>>
@@ -0,0 +1,125 @@
1
+ import { z } from 'astro/zod'
2
+
3
+ import { FaviconSchema } from '../schemas/favicon'
4
+ import { HeadConfigSchema } from '../schemas/head'
5
+ import { LogoConfigSchema } from '../schemas/logo'
6
+ import { SocialLinksSchema } from '../schemas/social'
7
+
8
+ export const ThemeConfigSchema = z.object({
9
+ /** Title for your website. Will be used in metadata and as browser tab title. */
10
+ title: z
11
+ .string()
12
+ .describe('Title for your website. Will be used in metadata and as browser tab title.'),
13
+
14
+ /** Description metadata for your website. Can be used in page metadata. */
15
+ description: z
16
+ .string()
17
+ .optional()
18
+ .describe('Description metadata for your website. Can be used in page metadata.'),
19
+
20
+ /** Set a logo image to show in the homepage. */
21
+ logo: LogoConfigSchema(),
22
+
23
+ /**
24
+ * Optional details about the social media accounts for this site.
25
+ *
26
+ * @example
27
+ * social: {
28
+ * codeberg: 'https://codeberg.org/knut/examples',
29
+ * discord: 'https://astro.build/chat',
30
+ * github: 'https://github.com/withastro/starlight',
31
+ * gitlab: 'https://gitlab.com/delucis',
32
+ * linkedin: 'https://www.linkedin.com/company/astroinc',
33
+ * mastodon: 'https://m.webtoo.ls/@astro',
34
+ * threads: 'https://www.threads.net/@nmoodev',
35
+ * twitch: 'https://www.twitch.tv/bholmesdev',
36
+ * twitter: 'https://twitter.com/astrodotbuild',
37
+ * youtube: 'https://youtube.com/@astrodotbuild',
38
+ * }
39
+ */
40
+ social: SocialLinksSchema(),
41
+
42
+ /** The tagline for your website. */
43
+ tagline: z.string().optional().describe('The tagline for your website.'),
44
+
45
+ /** Configure the defaults for the table of contents on each page. */
46
+ // tableOfContents: TableOfContentsSchema(),
47
+
48
+ /**
49
+ * Specify the default language for this site.
50
+ *
51
+ * The default locale will be used to provide fallback content where translations are missing.
52
+ */
53
+ locale: z.string().optional(),
54
+
55
+ /**
56
+ * Add extra tags to your site’s `<head>`.
57
+ *
58
+ * Can also be set for a single page in a page’s frontmatter.
59
+ *
60
+ * @example
61
+ * // Add Fathom analytics to your site
62
+ * starlight({
63
+ * head: [
64
+ * {
65
+ * tag: 'script',
66
+ * attrs: {
67
+ * src: 'https://cdn.usefathom.com/script.js',
68
+ * 'data-site': 'MY-FATHOM-ID',
69
+ * defer: true,
70
+ * },
71
+ * },
72
+ * ],
73
+ * })
74
+ */
75
+ head: HeadConfigSchema(),
76
+
77
+ /**
78
+ * Provide CSS files to customize the look and feel of your Starlight site.
79
+ *
80
+ * Supports local CSS files relative to the root of your project,
81
+ * e.g. `'/src/custom.css'`, and CSS you installed as an npm
82
+ * module, e.g. `'@fontsource/roboto'`.
83
+ *
84
+ * @example
85
+ * starlight({
86
+ * customCss: ['/src/custom-styles.css', '@fontsource/roboto'],
87
+ * })
88
+ */
89
+ customCss: z.string().array().optional().default([]),
90
+
91
+ /** The default favicon for your site which should be a path to an image in the `public/` directory. */
92
+ favicon: FaviconSchema(),
93
+
94
+ /** Will be used as title delimiter in the generated `<title>` tag. */
95
+ titleDelimiter: z
96
+ .string()
97
+ .default('|')
98
+ .describe('Will be used as title delimiter in the generated `<title>` tag.'),
99
+
100
+ /**
101
+ * Define whether Starlight pages should be prerendered or not.
102
+ * Defaults to always prerender Starlight pages, even when the project is
103
+ * set to "server" output mode.
104
+ */
105
+ prerender: z.boolean().default(true),
106
+
107
+ /** Enable displaying a “Built with Starlight” link in your site’s footer. */
108
+ credits: z
109
+ .boolean()
110
+ .default(false)
111
+ .describe('Enable displaying a “Built with Starlight” link in your site’s footer.'),
112
+
113
+ /** The npm CDN to use for loading npm packages.
114
+ * @example
115
+ * npmCDN: 'https://cdn.jsdelivr.net/npm'
116
+ * npmCDN: 'https://cdn.smartcis.cn/npm'
117
+ * npmCDN: 'https://unkpg.com'
118
+ * npmCDN: 'https://cdn.cbd.int'
119
+ * npmCDN: 'https://esm.sh'
120
+ */
121
+ npmCDN: z
122
+ .string()
123
+ .default('https://esm.sh')
124
+ .describe('The npm CDN to use for loading npm packages.')
125
+ })
@@ -0,0 +1,24 @@
1
+ import { z } from 'zod'
2
+
3
+ import { IntegrationConfigSchema } from './integrations-config'
4
+ import { ThemeConfigSchema } from './theme-config'
5
+
6
+ export const UserConfigSchema = ThemeConfigSchema.strict()
7
+ .merge(
8
+ z.object({
9
+ integ: IntegrationConfigSchema()
10
+ })
11
+ )
12
+ .transform((config) => ({
13
+ ...config,
14
+ // Pagefind only defaults to true if prerender is also true.
15
+ integ: {
16
+ ...config.integ,
17
+ pagefind: config.integ.pagefind ?? config.prerender
18
+ }
19
+ }))
20
+ .refine((config) => !(config.integ.pagefind && !config.prerender), {
21
+ message: 'Pagefind search is not supported with prerendering disabled.'
22
+ })
23
+
24
+ export type UserConfig = z.infer<typeof UserConfigSchema>
package/utils/clsx.ts ADDED
@@ -0,0 +1,24 @@
1
+ export type ClassValue = ClassArray | ClassDictionary | string | number | null | boolean | undefined
2
+ export type ClassDictionary = Record<string, unknown>
3
+ export type ClassArray = ClassValue[]
4
+
5
+ export function clsx(...inputs: ClassValue[]): string {
6
+ let str = ''
7
+ for (let i = 0; i < inputs.length; i++) {
8
+ const input = inputs[i]
9
+ if (typeof input === 'string' || typeof input === 'number') {
10
+ str += (str && ' ') + input
11
+ } else if (Array.isArray(input)) {
12
+ str += (str && ' ') + clsx(...input)
13
+ } else if (typeof input === 'object') {
14
+ for (const key in input) {
15
+ if (input[key]) {
16
+ str += (str && ' ') + key
17
+ }
18
+ }
19
+ }
20
+ }
21
+ return str
22
+ }
23
+
24
+ export default clsx
@@ -0,0 +1,48 @@
1
+ import { type CollectionEntry, type CollectionKey } from 'astro:content'
2
+
3
+ type Collections = CollectionEntry<CollectionKey>[]
4
+
5
+ export function groupCollectionsByYear<T extends CollectionKey>(
6
+ collections: Collections
7
+ ): [number, CollectionEntry<T>[]][] {
8
+ const collectionsByYear = collections.reduce((acc, collection) => {
9
+ const year = new Date(collection.data.updatedDate ?? collection.data.publishDate).getFullYear()
10
+ if (!acc.has(year)) {
11
+ acc.set(year, [])
12
+ }
13
+ acc.get(year)!.push(collection)
14
+ return acc
15
+ }, new Map<number, Collections>())
16
+
17
+ return Array.from(
18
+ collectionsByYear.entries() as IterableIterator<[number, CollectionEntry<T>[]]>
19
+ ).sort((a, b) => b[0] - a[0])
20
+ }
21
+
22
+ export function sortMDByDate(collections: Collections) {
23
+ return collections.sort((a, b) => {
24
+ const aDate = new Date(a.data.updatedDate ?? a.data.publishDate).valueOf()
25
+ const bDate = new Date(b.data.updatedDate ?? b.data.publishDate).valueOf()
26
+ return bDate - aDate
27
+ })
28
+ }
29
+
30
+ /** Note: This function doesn't filter draft posts, pass it the result of getAllPosts above to do so. */
31
+ export function getAllTags(collections: Collections) {
32
+ return collections.flatMap((collection) => [...collection.data.tags])
33
+ }
34
+
35
+ /** Note: This function doesn't filter draft posts, pass it the result of getAllPosts above to do so. */
36
+ export function getUniqueTags(collections: Collections) {
37
+ return [...new Set(getAllTags(collections))]
38
+ }
39
+
40
+ /** Note: This function doesn't filter draft posts, pass it the result of getAllPosts above to do so. */
41
+ export function getUniqueTagsWithCount(collections: Collections): [string, number][] {
42
+ return [
43
+ ...getAllTags(collections).reduce(
44
+ (acc, t) => acc.set(t, (acc.get(t) || 0) + 1),
45
+ new Map<string, number>()
46
+ )
47
+ ].sort((a, b) => b[1] - a[1])
48
+ }
package/utils/date.ts ADDED
@@ -0,0 +1,17 @@
1
+ import { siteConfig } from '@/site-config'
2
+
3
+ const dateFormat = new Intl.DateTimeFormat(siteConfig.date.locale, siteConfig.date.options)
4
+
5
+ export function getFormattedDate(
6
+ date: string | number | Date,
7
+ options?: Intl.DateTimeFormatOptions
8
+ ) {
9
+ if (typeof options !== 'undefined') {
10
+ return new Date(date).toLocaleDateString(siteConfig.date.locale, {
11
+ ...(siteConfig.date.options as Intl.DateTimeFormatOptions),
12
+ ...options
13
+ })
14
+ }
15
+
16
+ return dateFormat.format(new Date(date))
17
+ }
@@ -0,0 +1,36 @@
1
+ import type { CardListData } from '@/types'
2
+
3
+ // Docs content declaration
4
+ export const docs: CardListData = {
5
+ title: 'Docs content',
6
+ list: [
7
+ {
8
+ title: 'Setup',
9
+ children: [
10
+ { title: 'Getting Started', link: '/docs/setup/getting-started' },
11
+ { title: 'Configuration', link: '/docs/setup/configuration' },
12
+ { title: 'Authoring Content', link: '/docs/setup/content' },
13
+ { title: 'Deployment', link: '/docs/setup/deployment' }
14
+ ]
15
+ },
16
+ {
17
+ title: 'Integrations',
18
+ children: [
19
+ { title: 'Comment System', link: '/docs/integrations/comment' },
20
+ { title: 'Friend Links', link: '/docs/integrations/links' },
21
+ { title: 'Shiki Code', link: '/docs/integrations/code' },
22
+ { title: 'User Components', link: '/docs/integrations/components' },
23
+ { title: 'Advanced Components', link: '/docs/integrations/advanced' },
24
+ { title: 'Other Integrations', link: '/docs/integrations/others' }
25
+ ]
26
+ },
27
+ {
28
+ title: 'Advanced',
29
+ children: [
30
+ { title: 'Update Theme', link: '/docs/advanced/update' },
31
+ { title: 'Optimize Your Site', link: '/docs/advanced/optimize' },
32
+ { title: 'Acknowledgements', link: '/docs/advanced/thanks' }
33
+ ]
34
+ }
35
+ ]
36
+ }
package/utils/index.ts ADDED
@@ -0,0 +1,23 @@
1
+ // Tailwind
2
+ export { cn } from './tailwind'
3
+
4
+ // Collections
5
+ export {
6
+ groupCollectionsByYear,
7
+ sortMDByDate,
8
+ getUniqueTags,
9
+ getUniqueTagsWithCount
10
+ } from './collections'
11
+
12
+ // Date
13
+ export { getFormattedDate } from './date'
14
+
15
+ // Toc
16
+ export { generateToc } from './toc'
17
+ export type { TocItem } from './toc'
18
+
19
+ // Theme
20
+ export { getTheme, listenThemeChange, setTheme } from './theme'
21
+
22
+ // Toast
23
+ export { showToast } from './toast'
@@ -0,0 +1,25 @@
1
+ declare module '../../utils' {
2
+ // Tailwind
3
+ export { cn } from './tailwind'
4
+
5
+ // Collections
6
+ export {
7
+ groupCollectionsByYear,
8
+ sortMDByDate,
9
+ getUniqueTags,
10
+ getUniqueTagsWithCount
11
+ } from './collections'
12
+
13
+ // Date
14
+ export { getFormattedDate } from './date'
15
+
16
+ // Toc
17
+ export { generateToc } from './toc'
18
+ export type { TocItem } from './toc'
19
+
20
+ // Theme
21
+ export { getTheme, listenThemeChange, setTheme } from './theme'
22
+
23
+ // Toast
24
+ export { showToast } from './toast'
25
+ }
@@ -0,0 +1,11 @@
1
+ import { getCollection, type CollectionEntry, type CollectionKey } from 'astro:content'
2
+
3
+ export const prod = import.meta.env.PROD
4
+
5
+ /** Note: this function filters out draft posts based on the environment */
6
+ export async function getBlogCollection(contentType: CollectionKey = 'blog') {
7
+ return await getCollection(contentType, ({ data }: CollectionEntry<typeof contentType>) => {
8
+ // Not in production & draft is not false
9
+ return !prod || data.draft !== false
10
+ })
11
+ }
@@ -0,0 +1,7 @@
1
+ import { twMerge } from 'tailwind-merge'
2
+
3
+ import { clsx, type ClassValue } from './clsx'
4
+
5
+ export function cn(...inputs: ClassValue[]) {
6
+ return twMerge(clsx(inputs))
7
+ }
package/utils/theme.ts ADDED
@@ -0,0 +1,40 @@
1
+ export function getTheme() {
2
+ return localStorage.getItem('theme')
3
+ }
4
+
5
+ export function listenThemeChange(theme?: string) {
6
+ if (!theme || theme === 'system') return // if theme is specified, no need to listen window theme change
7
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
8
+ setTheme(e.matches ? 'dark' : 'light')
9
+ })
10
+ }
11
+
12
+ export function setTheme(theme?: string, save = false) {
13
+ const themes = ['system', 'dark', 'light']
14
+ if (theme) {
15
+ if (!themes.includes(theme)) return
16
+ if (save) localStorage.setItem('theme', theme)
17
+ } else {
18
+ theme = getTheme() ?? undefined
19
+ if (save) {
20
+ // Set theme equals undefined, switch cycle in ['system', 'dark', 'light']
21
+ const currentIndex = themes.indexOf(theme ?? 'system')
22
+ theme = themes[(currentIndex + 1) % themes.length]
23
+ localStorage.setItem('theme', theme) // save theme
24
+ }
25
+ }
26
+ let targetTheme = theme
27
+ if (theme === 'system') {
28
+ targetTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
29
+ // Listen theme change
30
+ listenThemeChange(theme)
31
+ }
32
+
33
+ // Set theme
34
+ document.documentElement.classList.toggle('dark', targetTheme === 'dark')
35
+ document
36
+ .querySelector('meta[name="theme-color"]')
37
+ ?.setAttribute('content', targetTheme === 'dark' ? '#0B0B10' : '#FCFCFD')
38
+
39
+ return theme
40
+ }
package/utils/toast.ts ADDED
@@ -0,0 +1,3 @@
1
+ export function showToast(detail: { message: string }) {
2
+ document.dispatchEvent(new CustomEvent('toast', { detail }))
3
+ }
package/utils/toc.ts ADDED
@@ -0,0 +1,41 @@
1
+ import type { MarkdownHeading } from 'astro'
2
+
3
+ export interface TocItem extends MarkdownHeading {
4
+ subheadings: TocItem[]
5
+ }
6
+
7
+ function diveChildren(item: TocItem, depth: number): TocItem[] {
8
+ if (depth === 1 || !item.subheadings.length) {
9
+ return item.subheadings
10
+ } else {
11
+ // e.g., 2
12
+ return diveChildren(item.subheadings[item.subheadings.length - 1] as TocItem, depth - 1)
13
+ }
14
+ }
15
+
16
+ export function generateToc(headings: readonly MarkdownHeading[]) {
17
+ // this ignores/filters out h1 element(s)
18
+ const bodyHeadings = [...headings.filter(({ depth }) => depth > 1)]
19
+ const toc: TocItem[] = []
20
+
21
+ bodyHeadings.forEach((h) => {
22
+ const heading: TocItem = { ...h, subheadings: [] }
23
+
24
+ // add h2 elements into the top level
25
+ if (heading.depth === 2) {
26
+ toc.push(heading)
27
+ } else {
28
+ const lastItemInToc = toc[toc.length - 1]!
29
+ if (heading.depth < lastItemInToc.depth) {
30
+ throw new Error(`Orphan heading found: ${heading.text}.`)
31
+ }
32
+
33
+ // higher depth
34
+ // push into children, or children's children
35
+ const gap = heading.depth - lastItemInToc.depth
36
+ const target = diveChildren(lastItemInToc, gap)
37
+ target.push(heading)
38
+ }
39
+ })
40
+ return toc
41
+ }
package/virtual.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ /// <reference path="./types/index.d.ts" />
2
+ /// <reference path="./utils/module.d.ts" />