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
-
import { BlueskyLogo, PdslsLogo, StandardSiteLogo, TangledLogo, MarkdownButton } from '@/app/components'
2
3
export function Footer() {
4
return (
···
42
</footer>
43
44
<section className="hidden md:flex shrink-0 w-32"></section>
45
-
46
-
<div className="absolute bottom-8 right-8">
47
-
<MarkdownButton />
48
-
</div>
49
</div>
50
)
51
}
···
1
+
import { BlueskyLogo, PdslsLogo, StandardSiteLogo, TangledLogo } from '@/app/components'
2
3
export function Footer() {
4
return (
···
42
</footer>
43
44
<section className="hidden md:flex shrink-0 w-32"></section>
0
0
0
0
45
</div>
46
)
47
}
+99
app/components/docs/DocsPageMenu.tsx
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
'use client'
2
+
3
+
import { useState, useRef, useEffect } from 'react'
4
+
import { usePathname } from 'next/navigation'
5
+
6
+
export function DocsPageMenu() {
7
+
const [open, setOpen] = useState(false)
8
+
const [copied, setCopied] = useState(false)
9
+
const menuRef = useRef<HTMLDivElement>(null)
10
+
const pathname = usePathname()
11
+
12
+
const slug = (() => {
13
+
const path = pathname.split(/[?#]/)[0]
14
+
if (!path.startsWith('/docs/')) return null
15
+
const parts = path.split('/').filter(Boolean)
16
+
if (parts.length <= 1) return null
17
+
return parts.slice(1).join('/')
18
+
})()
19
+
20
+
useEffect(() => {
21
+
if (!open) return
22
+
23
+
function handleClickOutside(e: MouseEvent) {
24
+
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
25
+
setOpen(false)
26
+
}
27
+
}
28
+
29
+
document.addEventListener('mousedown', handleClickOutside)
30
+
return () => document.removeEventListener('mousedown', handleClickOutside)
31
+
}, [open])
32
+
33
+
if (!slug) return null
34
+
35
+
const handleCopyMarkdown = async () => {
36
+
try {
37
+
const res = await fetch(`/api/docs/markdown/${slug}`)
38
+
const markdown = await res.text()
39
+
await navigator.clipboard.writeText(markdown)
40
+
setCopied(true)
41
+
setTimeout(() => {
42
+
setCopied(false)
43
+
setOpen(false)
44
+
}, 1500)
45
+
} catch {
46
+
setOpen(false)
47
+
}
48
+
}
49
+
50
+
return (
51
+
<div ref={menuRef} className="relative">
52
+
<button
53
+
onClick={() => setOpen(!open)}
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
+
aria-label="Page options"
56
+
>
57
+
<svg className="size-4" fill="currentColor" viewBox="0 0 20 20">
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
+
</svg>
60
+
</button>
61
+
62
+
{open && (
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
+
<button
65
+
onClick={handleCopyMarkdown}
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
+
>
68
+
{copied ? (
69
+
<>
70
+
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
71
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
72
+
</svg>
73
+
Copied!
74
+
</>
75
+
) : (
76
+
<>
77
+
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
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
+
</svg>
80
+
Copy as Markdown
81
+
</>
82
+
)}
83
+
</button>
84
+
<a
85
+
href={`/api/docs/markdown/${slug}`}
86
+
target="_blank"
87
+
rel="noopener noreferrer"
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
+
>
90
+
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
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
+
</svg>
93
+
View raw Markdown
94
+
</a>
95
+
</div>
96
+
)}
97
+
</div>
98
+
)
99
+
}
-37
app/components/docs/MarkdownButton.tsx
···
1
-
'use client'
2
-
3
-
import { usePathname } from 'next/navigation'
4
-
5
-
interface MarkdownButtonProps {
6
-
slug?: string
7
-
}
8
-
9
-
export function MarkdownButton({ slug: providedSlug }: MarkdownButtonProps) {
10
-
const pathname = usePathname()
11
-
12
-
// Detect slug from pathname if not provided
13
-
const slug = providedSlug || (() => {
14
-
const path = pathname.split(/[?#]/)[0] // Remove query and hash
15
-
if (!path.startsWith('/docs/')) return null
16
-
const parts = path.split('/').filter(Boolean)
17
-
if (parts.length <= 1) return null // Only /docs/ with no slug
18
-
return parts.slice(1).join('/')
19
-
})()
20
-
21
-
if (!slug) return null
22
-
23
-
return (
24
-
<a
25
-
href={`/api/docs/markdown/${slug}`}
26
-
target="_blank"
27
-
rel="noopener noreferrer"
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
-
title="View raw markdown"
30
-
>
31
-
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
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
-
</svg>
34
-
Markdown
35
-
</a>
36
-
)
37
-
}
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
+1
-1
app/components/docs/index.ts
···
3
export { Table } from './Table'
4
export { LinkCard } from './LinkCard'
5
export { StandardSite } from './StandardSite'
6
-
export { MarkdownButton } from './MarkdownButton'
7
export { ClickableHeading } from './ClickableHeading'
0
···
3
export { Table } from './Table'
4
export { LinkCard } from './LinkCard'
5
export { StandardSite } from './StandardSite'
0
6
export { ClickableHeading } from './ClickableHeading'
7
+
export { DocsPageMenu } from './DocsPageMenu'