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.
- package/bun.lockb +0 -0
- package/bunfig.toml +2 -0
- package/components/advanced/Comment.astro +148 -0
- package/components/advanced/GithubCard.astro +148 -0
- package/components/advanced/LinkPreview.astro +82 -0
- package/components/advanced/MediumZoom.astro +50 -0
- package/components/advanced/QRCode.astro +35 -0
- package/components/advanced/Quote.astro +44 -0
- package/components/advanced/index.ts +11 -0
- package/components/user/Aside.astro +74 -0
- package/components/user/Button.astro +79 -0
- package/components/user/Card.astro +23 -0
- package/components/user/CardList.astro +28 -0
- package/components/user/CardListChildren.astro +24 -0
- package/components/user/Collapse.astro +84 -0
- package/components/user/FormattedDate.astro +21 -0
- package/components/user/Label.astro +18 -0
- package/components/user/Spoiler.astro +11 -0
- package/components/user/Steps.astro +84 -0
- package/components/user/TabItem.astro +18 -0
- package/components/user/Tabs.astro +266 -0
- package/components/user/Timeline.astro +38 -0
- package/components/user/index.ts +17 -0
- package/index.ts +74 -0
- package/package.json +38 -0
- package/plugins/link-preview.ts +110 -0
- package/plugins/rehype-steps.ts +98 -0
- package/plugins/rehype-tabs.ts +112 -0
- package/plugins/virtual-user-config.ts +83 -0
- package/schemas/favicon.ts +42 -0
- package/schemas/head.ts +18 -0
- package/schemas/logo.ts +28 -0
- package/schemas/social.ts +51 -0
- package/types/common.d.ts +48 -0
- package/types/index.d.ts +6 -0
- package/types/integrations-config.ts +43 -0
- package/types/theme-config.ts +125 -0
- package/types/user-config.ts +24 -0
- package/utils/clsx.ts +24 -0
- package/utils/collections.ts +48 -0
- package/utils/date.ts +17 -0
- package/utils/docsContents.ts +36 -0
- package/utils/index.ts +23 -0
- package/utils/module.d.ts +25 -0
- package/utils/server.ts +11 -0
- package/utils/tailwind.ts +7 -0
- package/utils/theme.ts +40 -0
- package/utils/toast.ts +3 -0
- package/utils/toc.ts +41 -0
- package/virtual.d.ts +2 -0
package/bun.lockb
ADDED
|
Binary file
|
package/bunfig.toml
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
---
|
|
2
|
+
import config from 'virtual:config'
|
|
3
|
+
|
|
4
|
+
import { cn } from '../../utils'
|
|
5
|
+
|
|
6
|
+
const { class: className } = Astro.props
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
{
|
|
10
|
+
config.integ.waline.enable && (
|
|
11
|
+
<comment-component>
|
|
12
|
+
<div id='waline' class={cn('not-prose', className)}>
|
|
13
|
+
Comment seems to stuck. Try to refresh?✨
|
|
14
|
+
</div>
|
|
15
|
+
</comment-component>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
<script>
|
|
20
|
+
import { init as walineInit } from '@waline/client'
|
|
21
|
+
import type { WalineEmojiPresets } from '@waline/client'
|
|
22
|
+
import config from 'virtual:config'
|
|
23
|
+
|
|
24
|
+
import '@waline/client/style'
|
|
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(--tw-bg-opacity, 1));
|
|
65
|
+
--waline-light-grey: #999;
|
|
66
|
+
--waline-dark-grey: #666;
|
|
67
|
+
/* Theme color */
|
|
68
|
+
--waline-theme-color: hsl(var(--foreground) / var(--tw-text-opacity, 1));
|
|
69
|
+
--waline-active-color: hsl(var(--primary) / var(--tw-text-opacity, 1));
|
|
70
|
+
/* Layout */
|
|
71
|
+
--waline-color: hsl(var(--muted-foreground) / var(--tw-text-opacity, 1));
|
|
72
|
+
--waline-bg-color: hsl(var(--muted) / var(--tw-text-opacity, 1));
|
|
73
|
+
--waline-bg-color-light: hsl(var(--input) / var(--tw-text-opacity, 1));
|
|
74
|
+
--waline-bg-color-hover: #f0f0f0;
|
|
75
|
+
--waline-border-color: hsl(var(--border) / var(--tw-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(--tw-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>
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
---
|
|
2
|
+
const { repo: repoRaw } = Astro.props
|
|
3
|
+
|
|
4
|
+
// Remove 'https://github.com/' headings from the repo string
|
|
5
|
+
const repo = repoRaw.replace(/^https:\/\/github\.com\//, '')
|
|
6
|
+
|
|
7
|
+
const [owner, repoName] = repo.split('/')
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
<github-card class='not-prose loading' data-repo={repo}>
|
|
11
|
+
<a
|
|
12
|
+
href={`https://github.com/${repo}`}
|
|
13
|
+
target='_blank'
|
|
14
|
+
class='group block flex flex-col gap-y-2 rounded-xl border border-border px-5 py-4 transition-colors hover:bg-muted hover:text-muted-foreground'
|
|
15
|
+
>
|
|
16
|
+
<div class='flex items-center justify-between'>
|
|
17
|
+
<div class='flex items-center gap-x-2 text-foreground group-hover:text-primary'>
|
|
18
|
+
<div id='gh-avatar' class='gh-text me-2 size-8 bg-cover' style='border-radius:999px'></div>
|
|
19
|
+
<span class='text-lg transition-colors'>{owner}</span>
|
|
20
|
+
<span class='text-muted-foreground'>/</span>
|
|
21
|
+
<span class='text-lg font-bold transition-colors'>{repoName}</span>
|
|
22
|
+
</div>
|
|
23
|
+
<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>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
<p id='gh-description' class='gh-text'>Waiting for api.github.com...</p>
|
|
30
|
+
<div class='flex items-center justify-between'>
|
|
31
|
+
<div class='gh-text flex flex-wrap items-center gap-x-5'>
|
|
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>
|
|
34
|
+
<span id='gh-stars' class='leading-tight'>???</span>
|
|
35
|
+
</div>
|
|
36
|
+
<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
|
+
<span id='gh-forks' class='leading-tight'>???</span>
|
|
41
|
+
</div>
|
|
42
|
+
<div class='flex items-center gap-x-2'>
|
|
43
|
+
<svg class='size-5'><use href='/icons/github-card.svg#mingcute-balance-line'></use></svg>
|
|
44
|
+
<span id='gh-license' class='leading-tight'>???</span>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
<span id='gh-language' class='gh-text leading-tight'>?????</span>
|
|
48
|
+
</div>
|
|
49
|
+
</a>
|
|
50
|
+
</github-card>
|
|
51
|
+
|
|
52
|
+
<style>
|
|
53
|
+
@keyframes pulsate {
|
|
54
|
+
0% {
|
|
55
|
+
opacity: 1;
|
|
56
|
+
}
|
|
57
|
+
50% {
|
|
58
|
+
opacity: 0.4;
|
|
59
|
+
}
|
|
60
|
+
to {
|
|
61
|
+
opacity: 1;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
.loading .gh-text {
|
|
65
|
+
color: transparent;
|
|
66
|
+
border-radius: calc(var(--radius) - 3px);
|
|
67
|
+
background-color: hsl(var(--primary-foreground) / var(--tw-text-opacity));
|
|
68
|
+
animation: pulsate 2s infinite linear;
|
|
69
|
+
user-select: none;
|
|
70
|
+
}
|
|
71
|
+
.loading .gh-text:nth-child(2) {
|
|
72
|
+
animation-delay: 1s;
|
|
73
|
+
}
|
|
74
|
+
</style>
|
|
75
|
+
|
|
76
|
+
<script>
|
|
77
|
+
interface GithubProps {
|
|
78
|
+
stargazers_count: number
|
|
79
|
+
forks: number
|
|
80
|
+
language: string
|
|
81
|
+
owner: { avatar_url: string }
|
|
82
|
+
license?: { spdx_id: string }
|
|
83
|
+
description: string
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
class GithubCard extends HTMLElement {
|
|
87
|
+
async fetchGithub(repo: string): Promise<GithubProps | null> {
|
|
88
|
+
try {
|
|
89
|
+
const res = await fetch(`https://api.github.com/repos/${repo}`, {
|
|
90
|
+
referrerPolicy: 'no-referrer'
|
|
91
|
+
})
|
|
92
|
+
if (!res.ok) {
|
|
93
|
+
throw new Error(`HTTP error! status: ${res.status}`)
|
|
94
|
+
}
|
|
95
|
+
return (await res.json()) as GithubProps
|
|
96
|
+
} catch (e) {
|
|
97
|
+
console.error('Failed to fetch Github data:', e)
|
|
98
|
+
return null
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
numberFormat(value: number): string {
|
|
103
|
+
return Intl.NumberFormat('en-us', {
|
|
104
|
+
notation: 'compact',
|
|
105
|
+
maximumFractionDigits: 1
|
|
106
|
+
}).format(value)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async connectedCallback() {
|
|
110
|
+
if (!this.dataset.repo) return
|
|
111
|
+
try {
|
|
112
|
+
const data = await this.fetchGithub(this.dataset.repo)
|
|
113
|
+
if (!data) return
|
|
114
|
+
;(this.querySelector('#gh-stars') as HTMLElement).textContent = this.numberFormat(
|
|
115
|
+
data.stargazers_count
|
|
116
|
+
)
|
|
117
|
+
;(this.querySelector('#gh-forks') as HTMLElement).textContent = this.numberFormat(
|
|
118
|
+
data.forks
|
|
119
|
+
)
|
|
120
|
+
;(this.querySelector('#gh-language') as HTMLElement).textContent = data.language || 'N/A'
|
|
121
|
+
;(this.querySelector('#gh-description') as HTMLElement).textContent =
|
|
122
|
+
typeof data.description === 'string'
|
|
123
|
+
? data.description.replace(/:[a-zA-Z0-9_]+:/g, '')
|
|
124
|
+
: 'Description not set'
|
|
125
|
+
|
|
126
|
+
const licenseEl = this.querySelector('#gh-license') as HTMLElement
|
|
127
|
+
if (data.license?.spdx_id) {
|
|
128
|
+
licenseEl.textContent = data.license.spdx_id
|
|
129
|
+
} else {
|
|
130
|
+
licenseEl.classList.add('no-license')
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const avatarEl = this.querySelector('#gh-avatar') as HTMLElement
|
|
134
|
+
if (avatarEl) {
|
|
135
|
+
avatarEl.style.backgroundImage = `url(${data.owner.avatar_url})`
|
|
136
|
+
avatarEl.style.backgroundColor = 'transparent'
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
this.classList.remove('loading')
|
|
140
|
+
} catch (e) {
|
|
141
|
+
console.error('Error setting Github data:', e)
|
|
142
|
+
;(this.querySelector('#gh-description') as HTMLElement).textContent = 'Failed to fetch data'
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
customElements.define('github-card', GithubCard)
|
|
148
|
+
</script>
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { Image } from 'astro:assets'
|
|
3
|
+
|
|
4
|
+
import { parseOpenGraph } from '../../plugins/link-preview'
|
|
5
|
+
import { cn } from '../../utils'
|
|
6
|
+
|
|
7
|
+
export interface Props {
|
|
8
|
+
/** URL to fetch Open Graph data. */
|
|
9
|
+
href: string
|
|
10
|
+
/** Hide any image or video even if set in the OpenGraph data. */
|
|
11
|
+
hideMedia?: boolean
|
|
12
|
+
zoomable?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const { href, hideMedia = false, zoomable = true } = Astro.props
|
|
16
|
+
|
|
17
|
+
const meta = await parseOpenGraph(href)
|
|
18
|
+
const domain = meta?.url ? new URL(meta.url).hostname.replace('www.', '') : ''
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
{
|
|
22
|
+
meta && meta.title ? (
|
|
23
|
+
<div class='not-prose link-preview-container my-2 flex justify-center sm:my-4'>
|
|
24
|
+
<article
|
|
25
|
+
class:list={[
|
|
26
|
+
'link-preview flex flex-col overflow-hidden rounded-lg border max-sm:max-w-sm sm:flex-row',
|
|
27
|
+
{
|
|
28
|
+
'link-preview--has-video max-sm:max-w-none sm:flex-col':
|
|
29
|
+
!hideMedia && meta.video && meta.videoType,
|
|
30
|
+
'link-preview--no-media': hideMedia || !((meta.video && meta.videoType) || meta.image)
|
|
31
|
+
}
|
|
32
|
+
]}
|
|
33
|
+
>
|
|
34
|
+
{hideMedia ? null : meta.video && meta.videoType ? (
|
|
35
|
+
<video controls preload='metadata' width='1200' height='630'>
|
|
36
|
+
<source src={meta.video} type={meta.videoType} />
|
|
37
|
+
</video>
|
|
38
|
+
) : meta.image ? (
|
|
39
|
+
<Image
|
|
40
|
+
class={cn('m-0 sm:max-w-60', zoomable && 'zoomable')}
|
|
41
|
+
src={meta.image}
|
|
42
|
+
alt={meta.imageAlt || ''}
|
|
43
|
+
width='1200'
|
|
44
|
+
height='630'
|
|
45
|
+
/>
|
|
46
|
+
) : null}
|
|
47
|
+
<a
|
|
48
|
+
class='group font-normal text-muted-foreground no-underline hover:text-muted-foreground'
|
|
49
|
+
href={href}
|
|
50
|
+
target='_blank'
|
|
51
|
+
>
|
|
52
|
+
<div class='link-preview__content flex h-full flex-col gap-y-1 px-3 py-2 transition-colors group-hover:bg-muted sm:px-5 sm:py-4'>
|
|
53
|
+
<header class='line-clamp-1 font-medium text-foreground transition-colors group-hover:text-primary'>
|
|
54
|
+
{meta.title}
|
|
55
|
+
</header>
|
|
56
|
+
<p class='link-preview__description line-clamp-2'>
|
|
57
|
+
{meta.description}{' '}
|
|
58
|
+
{domain && <small class='link-preview__domain ml-1'>({domain})</small>}
|
|
59
|
+
</p>
|
|
60
|
+
</div>
|
|
61
|
+
</a>
|
|
62
|
+
</article>
|
|
63
|
+
</div>
|
|
64
|
+
) : (
|
|
65
|
+
<div class='link-preview link-preview--no-metadata'>
|
|
66
|
+
<a {href} target='_blank'>
|
|
67
|
+
{href}
|
|
68
|
+
</a>
|
|
69
|
+
</div>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
<style>
|
|
74
|
+
/* Default styles */
|
|
75
|
+
.link-preview img,
|
|
76
|
+
.link-preview video {
|
|
77
|
+
aspect-ratio: 1200 / 630;
|
|
78
|
+
width: 100%;
|
|
79
|
+
height: auto;
|
|
80
|
+
object-fit: cover;
|
|
81
|
+
}
|
|
82
|
+
</style>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
// https://github.com/francoischalifour/medium-zoom
|
|
3
|
+
import config from 'virtual:config'
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
selector?: string
|
|
7
|
+
background?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { selector = config.integ.mediumZoom.selector, background = 'hsl(var(--background) / 0.8)' } =
|
|
11
|
+
Astro.props
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<script is:inline src={`${config.npmCDN}/medium-zoom/dist/pure/medium-zoom.min.umd.js`}></script>
|
|
15
|
+
<script is:inline define:vars={{ selector, background }}>
|
|
16
|
+
mediumZoom(selector, { background })
|
|
17
|
+
</script>
|
|
18
|
+
<style is:global>
|
|
19
|
+
.medium-zoom-overlay {
|
|
20
|
+
position: fixed;
|
|
21
|
+
top: 0;
|
|
22
|
+
right: 0;
|
|
23
|
+
bottom: 0;
|
|
24
|
+
left: 0;
|
|
25
|
+
opacity: 0;
|
|
26
|
+
transition: opacity 0.3s;
|
|
27
|
+
will-change: opacity;
|
|
28
|
+
}
|
|
29
|
+
.medium-zoom--opened .medium-zoom-overlay {
|
|
30
|
+
cursor: pointer;
|
|
31
|
+
cursor: zoom-out;
|
|
32
|
+
opacity: 1;
|
|
33
|
+
z-index: 999;
|
|
34
|
+
}
|
|
35
|
+
.medium-zoom-image {
|
|
36
|
+
cursor: pointer;
|
|
37
|
+
cursor: zoom-in;
|
|
38
|
+
transition: transform 0.3s cubic-bezier(0.2, 0, 0.2, 1) !important;
|
|
39
|
+
}
|
|
40
|
+
.medium-zoom-image--hidden {
|
|
41
|
+
visibility: hidden;
|
|
42
|
+
}
|
|
43
|
+
.medium-zoom-image--opened {
|
|
44
|
+
position: relative;
|
|
45
|
+
cursor: pointer;
|
|
46
|
+
cursor: zoom-out;
|
|
47
|
+
will-change: transform;
|
|
48
|
+
z-index: 999;
|
|
49
|
+
}
|
|
50
|
+
</style>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
import config from 'virtual:config'
|
|
3
|
+
|
|
4
|
+
const { content } = Astro.props
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<div
|
|
8
|
+
id='qrcode-container'
|
|
9
|
+
aria-expanded='false'
|
|
10
|
+
class='absolute z-10 -mt-2 box-content max-h-0 overflow-hidden rounded-xl border bg-muted p-4 opacity-0 transition-all duration-300 ease-in-out *:my-0 aria-expanded:max-h-[256px] aria-expanded:translate-y-4 aria-expanded:opacity-100'
|
|
11
|
+
>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<script is:inline src={`${config.npmCDN}/qrcodejs/qrcode.min.js`}></script>
|
|
15
|
+
<script is:inline define:vars={{ content }}>
|
|
16
|
+
const renderContent = content ?? window.location.href
|
|
17
|
+
// Load qrcode
|
|
18
|
+
function loadqrcode(qrcodeContainer) {
|
|
19
|
+
if (!qrcodeContainer) throw new Error('qrcode container not found')
|
|
20
|
+
if (qrcodeContainer.innerHTML !== '') return
|
|
21
|
+
new QRCode(qrcodeContainer, renderContent)
|
|
22
|
+
}
|
|
23
|
+
const getQRCode = document.getElementById('get-qrcode')
|
|
24
|
+
|
|
25
|
+
const qrcodeContainer = document.getElementById('qrcode-container')
|
|
26
|
+
if (!qrcodeContainer) throw new Error('qrcode container not found')
|
|
27
|
+
getQRCode?.addEventListener('click', () => {
|
|
28
|
+
if (qrcodeContainer.ariaExpanded === 'true') {
|
|
29
|
+
qrcodeContainer.ariaExpanded = 'false'
|
|
30
|
+
} else {
|
|
31
|
+
loadqrcode(qrcodeContainer)
|
|
32
|
+
qrcodeContainer.ariaExpanded = 'true'
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
</script>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { cn } from '../../utils'
|
|
3
|
+
|
|
4
|
+
const { class: className } = Astro.props
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<quote-component class={cn('not-prose inline-block', className)}>
|
|
8
|
+
<div
|
|
9
|
+
class='flex flex-row items-center gap-x-3 rounded-full border border-border px-4 py-2 text-sm shadow-sm'
|
|
10
|
+
>
|
|
11
|
+
<span class='relative flex items-center justify-center'>
|
|
12
|
+
<span
|
|
13
|
+
class='absolute size-2 animate-ping rounded-full border border-green-400 bg-green-400 opacity-75'
|
|
14
|
+
></span>
|
|
15
|
+
<span class='size-2 rounded-full bg-green-400'></span>
|
|
16
|
+
</span>
|
|
17
|
+
<p id='quote-sentence' class='font-medium text-muted-foreground'>Loading...</p>
|
|
18
|
+
</div>
|
|
19
|
+
</quote-component>
|
|
20
|
+
|
|
21
|
+
<script>
|
|
22
|
+
import config from 'virtual:config'
|
|
23
|
+
|
|
24
|
+
const { quote } = config.integ
|
|
25
|
+
|
|
26
|
+
class Quote extends HTMLElement {
|
|
27
|
+
constructor() {
|
|
28
|
+
super()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
render(sentence: string) {
|
|
32
|
+
const quoteEl = this.querySelector('#quote-sentence') as HTMLElement
|
|
33
|
+
if (!quoteEl) return
|
|
34
|
+
quoteEl.innerText = sentence
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
connectedCallback() {
|
|
38
|
+
fetch(quote.server)
|
|
39
|
+
.then((response) => response.json())
|
|
40
|
+
.then((data) => this.render(quote.target(data)))
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
customElements.define('quote-component', Quote)
|
|
44
|
+
</script>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Web content render
|
|
2
|
+
export { default as Quote } from './Quote.astro'
|
|
3
|
+
export { default as GithubCard } from './GithubCard.astro'
|
|
4
|
+
export { default as LinkPreview } from './LinkPreview.astro'
|
|
5
|
+
|
|
6
|
+
// Data transformer
|
|
7
|
+
export { default as QRCode } from './QRCode.astro'
|
|
8
|
+
export { default as MediumZoom } from './MediumZoom.astro'
|
|
9
|
+
|
|
10
|
+
// Individual server integration
|
|
11
|
+
export { default as Comment } from './Comment.astro'
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
// https://github.com/withastro/starlight/blob/main/packages/starlight/user-components/Aside.astro
|
|
3
|
+
import { AstroError } from 'astro/errors'
|
|
4
|
+
|
|
5
|
+
import { cn } from '../../utils'
|
|
6
|
+
|
|
7
|
+
const asideVariants = ['note', 'tip', 'caution', 'danger'] as const
|
|
8
|
+
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'
|
|
13
|
+
} as const
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
type?: (typeof asideVariants)[number]
|
|
17
|
+
title?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let { type = 'note', title } = Astro.props
|
|
21
|
+
|
|
22
|
+
if (!asideVariants.includes(type)) {
|
|
23
|
+
throw new AstroError(
|
|
24
|
+
'Invalid `type` prop passed to the `<Aside>` component.\n',
|
|
25
|
+
`Received: ${JSON.stringify(type)}\n` +
|
|
26
|
+
`Expected one of ${asideVariants.map((i) => JSON.stringify(i)).join(', ')}`
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!title) {
|
|
31
|
+
title = type.toUpperCase()
|
|
32
|
+
}
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
<aside aria-label={title} class='aside my-3 overflow-hidden rounded-xl border'>
|
|
36
|
+
<div
|
|
37
|
+
class={cn('aside-container border-l-8 border-primary px-4 py-3 bg-primary', `aside-${type}`)}
|
|
38
|
+
>
|
|
39
|
+
<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>
|
|
43
|
+
{title}
|
|
44
|
+
</p>
|
|
45
|
+
<div class='aside-content mt-2'>
|
|
46
|
+
<slot />
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</aside>
|
|
50
|
+
|
|
51
|
+
<style>
|
|
52
|
+
.aside > .aside-container {
|
|
53
|
+
--tw-bg-opacity: 0.07;
|
|
54
|
+
|
|
55
|
+
&.aside-tip {
|
|
56
|
+
--primary: 234 60% 60%;
|
|
57
|
+
}
|
|
58
|
+
&.aside-caution {
|
|
59
|
+
--primary: 41 90% 50%;
|
|
60
|
+
}
|
|
61
|
+
&.aside-danger {
|
|
62
|
+
--primary: 339 90% 60%;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.aside-content {
|
|
66
|
+
& > :first-child {
|
|
67
|
+
margin-top: 0;
|
|
68
|
+
}
|
|
69
|
+
& > :last-child {
|
|
70
|
+
margin-bottom: 0;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
</style>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { cn } from '../../utils'
|
|
3
|
+
|
|
4
|
+
const { as: Tag = 'a', class: className, title, href, style = 'button', ...props } = Astro.props
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<Tag
|
|
8
|
+
class={cn(
|
|
9
|
+
'group inline-flex items-center gap-x-1 rounded-lg bg-muted border border-border px-2 py-1 text-sm text-muted-foreground transition-all hover:bg-primary-foreground no-underline',
|
|
10
|
+
className,
|
|
11
|
+
!href && 'cursor-default',
|
|
12
|
+
style === 'pill' && 'rounded-xl'
|
|
13
|
+
)}
|
|
14
|
+
href={href}
|
|
15
|
+
data-astro-prefetch
|
|
16
|
+
{...props}
|
|
17
|
+
>
|
|
18
|
+
<slot name='before'>
|
|
19
|
+
{
|
|
20
|
+
style === 'back' && (
|
|
21
|
+
<svg
|
|
22
|
+
xmlns='http://www.w3.org/2000/svg'
|
|
23
|
+
width='16'
|
|
24
|
+
height='16'
|
|
25
|
+
viewBox='0 0 24 24'
|
|
26
|
+
fill='none'
|
|
27
|
+
stroke-width='2.5'
|
|
28
|
+
stroke-linecap='round'
|
|
29
|
+
stroke-linejoin='round'
|
|
30
|
+
class='stroke-muted-foreground group-hover:stroke-primary'
|
|
31
|
+
>
|
|
32
|
+
<line
|
|
33
|
+
x1='19'
|
|
34
|
+
y1='12'
|
|
35
|
+
x2='5'
|
|
36
|
+
y2='12'
|
|
37
|
+
class='translate-x-3 scale-x-0 transition-all duration-300 ease-in-out group-hover:translate-x-0 group-hover:scale-x-100'
|
|
38
|
+
/>
|
|
39
|
+
<polyline
|
|
40
|
+
points='12 19 5 12 12 5'
|
|
41
|
+
class='translate-x-1 transition-all duration-300 ease-in-out group-hover:translate-x-0'
|
|
42
|
+
/>
|
|
43
|
+
</svg>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
</slot>
|
|
47
|
+
<slot>
|
|
48
|
+
{title && <p class='my-0'>{title}</p>}
|
|
49
|
+
</slot>
|
|
50
|
+
<slot name='after'>
|
|
51
|
+
{
|
|
52
|
+
style === 'ahead' && (
|
|
53
|
+
<svg
|
|
54
|
+
xmlns='http://www.w3.org/2000/svg'
|
|
55
|
+
width='16'
|
|
56
|
+
height='16'
|
|
57
|
+
viewBox='0 0 24 24'
|
|
58
|
+
fill='none'
|
|
59
|
+
stroke-width='2.5'
|
|
60
|
+
stroke-linecap='round'
|
|
61
|
+
stroke-linejoin='round'
|
|
62
|
+
class='stroke-muted-foreground group-hover:stroke-primary'
|
|
63
|
+
>
|
|
64
|
+
<line
|
|
65
|
+
x1='5'
|
|
66
|
+
y1='12'
|
|
67
|
+
x2='19'
|
|
68
|
+
y2='12'
|
|
69
|
+
class='translate-x-4 scale-x-0 transition-all duration-300 ease-in-out group-hover:translate-x-1 group-hover:scale-x-100'
|
|
70
|
+
/>
|
|
71
|
+
<polyline
|
|
72
|
+
points='12 5 19 12 12 19'
|
|
73
|
+
class='translate-x-0 transition-all duration-300 ease-in-out group-hover:translate-x-1'
|
|
74
|
+
/>
|
|
75
|
+
</svg>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
</slot>
|
|
79
|
+
</Tag>
|