tangled
alpha
login
or
join now
e.xyehr.cn
/
standard.site
forked from
standard.site/standard.site
0
fork
atom
Standard.site landing page built in Next.js
0
fork
atom
overview
issues
pulls
pipelines
Replace MarkdownButton with DocsPageMenu dropdown
aka.dad
1 month ago
d9085f2d
e4fdafb6
verified
This commit was signed with the committer's
known signature
.
aka.dad
SSH Key Fingerprint:
SHA256:PdzfUZ1lVRNyVHvD1/Qf90Kh5UImYQq1TxqaDtaDqSY=
+101
-43
4 changed files
expand all
collapse all
unified
split
app
components
Footer.tsx
docs
DocsPageMenu.tsx
MarkdownButton.tsx
index.ts
+1
-5
app/components/Footer.tsx
···
1
1
-
import { BlueskyLogo, PdslsLogo, StandardSiteLogo, TangledLogo, MarkdownButton } from '@/app/components'
1
1
+
import { BlueskyLogo, PdslsLogo, StandardSiteLogo, TangledLogo } from '@/app/components'
2
2
3
3
export function Footer() {
4
4
return (
···
42
42
</footer>
43
43
44
44
<section className="hidden md:flex shrink-0 w-32"></section>
45
45
-
46
46
-
<div className="absolute bottom-8 right-8">
47
47
-
<MarkdownButton />
48
48
-
</div>
49
45
</div>
50
46
)
51
47
}
+99
app/components/docs/DocsPageMenu.tsx
···
1
1
+
'use client'
2
2
+
3
3
+
import { useState, useRef, useEffect } from 'react'
4
4
+
import { usePathname } from 'next/navigation'
5
5
+
6
6
+
export function DocsPageMenu() {
7
7
+
const [open, setOpen] = useState(false)
8
8
+
const [copied, setCopied] = useState(false)
9
9
+
const menuRef = useRef<HTMLDivElement>(null)
10
10
+
const pathname = usePathname()
11
11
+
12
12
+
const slug = (() => {
13
13
+
const path = pathname.split(/[?#]/)[0]
14
14
+
if (!path.startsWith('/docs/')) return null
15
15
+
const parts = path.split('/').filter(Boolean)
16
16
+
if (parts.length <= 1) return null
17
17
+
return parts.slice(1).join('/')
18
18
+
})()
19
19
+
20
20
+
useEffect(() => {
21
21
+
if (!open) return
22
22
+
23
23
+
function handleClickOutside(e: MouseEvent) {
24
24
+
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
25
25
+
setOpen(false)
26
26
+
}
27
27
+
}
28
28
+
29
29
+
document.addEventListener('mousedown', handleClickOutside)
30
30
+
return () => document.removeEventListener('mousedown', handleClickOutside)
31
31
+
}, [open])
32
32
+
33
33
+
if (!slug) return null
34
34
+
35
35
+
const handleCopyMarkdown = async () => {
36
36
+
try {
37
37
+
const res = await fetch(`/api/docs/markdown/${slug}`)
38
38
+
const markdown = await res.text()
39
39
+
await navigator.clipboard.writeText(markdown)
40
40
+
setCopied(true)
41
41
+
setTimeout(() => {
42
42
+
setCopied(false)
43
43
+
setOpen(false)
44
44
+
}, 1500)
45
45
+
} catch {
46
46
+
setOpen(false)
47
47
+
}
48
48
+
}
49
49
+
50
50
+
return (
51
51
+
<div ref={menuRef} className="relative">
52
52
+
<button
53
53
+
onClick={() => setOpen(!open)}
54
54
+
className="flex items-center justify-center text-muted hover:text-base-content transition-colors p-1 rounded-md hover:bg-base-200 mt-1 size-8"
55
55
+
aria-label="Page options"
56
56
+
>
57
57
+
<svg className="size-4" fill="currentColor" viewBox="0 0 20 20">
58
58
+
<path d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z" />
59
59
+
</svg>
60
60
+
</button>
61
61
+
62
62
+
{open && (
63
63
+
<div className="absolute right-0 top-full mt-2 p-0.5 bg-base-200 border border-border grid gap-px rounded-xl shadow-lg z-20 min-w-48">
64
64
+
<button
65
65
+
onClick={handleCopyMarkdown}
66
66
+
className="w-full flex items-center gap-2 px-3 py-2 text-sm text-muted hover:text-base-content hover:bg-base-300 rounded-lg transition-colors"
67
67
+
>
68
68
+
{copied ? (
69
69
+
<>
70
70
+
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
71
71
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
72
72
+
</svg>
73
73
+
Copied!
74
74
+
</>
75
75
+
) : (
76
76
+
<>
77
77
+
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
78
78
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
79
79
+
</svg>
80
80
+
Copy as Markdown
81
81
+
</>
82
82
+
)}
83
83
+
</button>
84
84
+
<a
85
85
+
href={`/api/docs/markdown/${slug}`}
86
86
+
target="_blank"
87
87
+
rel="noopener noreferrer"
88
88
+
className="w-full flex items-center gap-2 px-3 py-2 text-sm text-muted hover:text-base-content hover:bg-base-300 rounded-lg transition-colors"
89
89
+
>
90
90
+
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
91
91
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
92
92
+
</svg>
93
93
+
View raw Markdown
94
94
+
</a>
95
95
+
</div>
96
96
+
)}
97
97
+
</div>
98
98
+
)
99
99
+
}
-37
app/components/docs/MarkdownButton.tsx
···
1
1
-
'use client'
2
2
-
3
3
-
import { usePathname } from 'next/navigation'
4
4
-
5
5
-
interface MarkdownButtonProps {
6
6
-
slug?: string
7
7
-
}
8
8
-
9
9
-
export function MarkdownButton({ slug: providedSlug }: MarkdownButtonProps) {
10
10
-
const pathname = usePathname()
11
11
-
12
12
-
// Detect slug from pathname if not provided
13
13
-
const slug = providedSlug || (() => {
14
14
-
const path = pathname.split(/[?#]/)[0] // Remove query and hash
15
15
-
if (!path.startsWith('/docs/')) return null
16
16
-
const parts = path.split('/').filter(Boolean)
17
17
-
if (parts.length <= 1) return null // Only /docs/ with no slug
18
18
-
return parts.slice(1).join('/')
19
19
-
})()
20
20
-
21
21
-
if (!slug) return null
22
22
-
23
23
-
return (
24
24
-
<a
25
25
-
href={`/api/docs/markdown/${slug}`}
26
26
-
target="_blank"
27
27
-
rel="noopener noreferrer"
28
28
-
className="bg-base-100 text-muted hover:text-base-content hover:bg-base-300 px-3 py-2 rounded-lg border border-border transition-all flex items-center gap-2 text-xs h-fit"
29
29
-
title="View raw markdown"
30
30
-
>
31
31
-
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
32
32
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
33
33
-
</svg>
34
34
-
Markdown
35
35
-
</a>
36
36
-
)
37
37
-
}
+1
-1
app/components/docs/index.ts
···
3
3
export { Table } from './Table'
4
4
export { LinkCard } from './LinkCard'
5
5
export { StandardSite } from './StandardSite'
6
6
-
export { MarkdownButton } from './MarkdownButton'
7
6
export { ClickableHeading } from './ClickableHeading'
7
7
+
export { DocsPageMenu } from './DocsPageMenu'