create-next-mdx-blog-app 2.1.1 → 2.1.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 +10 -1
- package/package.json +1 -1
- package/src/app/sample-blog-post-page/page.tsx +5 -1
- package/src/components/ArticleAuthorBio.tsx +1 -1
- package/src/components/ArticleCoverImage.tsx +1 -1
- package/src/components/ArticleHeader.tsx +9 -2
- package/src/components/BackToTopButton.tsx +34 -0
- package/src/components/CodeSandboxClient.tsx +1 -1
- package/src/components/CodeSandboxFeaturesSection.tsx +1 -1
- package/src/components/CopyLinkButton.tsx +38 -0
- package/src/components/DynamicArticle.tsx +6 -2
- package/src/components/MDXRemoteArticle.tsx +1 -1
- package/src/components/ReadingProgressBar.tsx +34 -0
- package/src/components/SandpackEditor.tsx +2 -2
- package/src/components/SocialShareButtons.tsx +34 -0
- package/src/components/StaticArticle.tsx +1 -1
- package/src/components/chat/chat-input.tsx +3 -3
- package/src/components/chat/chat-interface.tsx +1 -1
- package/src/components/chat/chat-messages.tsx +1 -1
- package/src/components/chat/chat-tool-result.tsx +1 -1
- package/src/components/customMDXComponents/CodeBlock.tsx +2 -2
- package/src/components/customMDXComponents/GistCodeBlock.tsx +1 -1
- package/src/components/customMDXComponents/GistCopyButton.tsx +2 -2
- package/src/components/customMDXComponents/GitHubGist.tsx +1 -1
- package/src/components/customMDXComponents/MDXImage.tsx +1 -1
- package/src/utils/constants/SocialShareConstants.ts +20 -0
- package/src/utils/constants/index.ts +1 -0
- package/src/utils/types/SocialShareButtonsType.ts +13 -0
- package/src/utils/types/index.ts +1 -0
package/README.md
CHANGED
|
@@ -59,7 +59,7 @@ npx create-next-mdx-blog-app .
|
|
|
59
59
|
### Package Information
|
|
60
60
|
|
|
61
61
|
- **Package Name**: `create-next-mdx-blog-app`
|
|
62
|
-
- **Version**: `2.1.
|
|
62
|
+
- **Version**: `2.1.3`
|
|
63
63
|
- **License**: MIT
|
|
64
64
|
- **Homepage**: [https://www.npmjs.com/package/create-next-mdx-blog-app](https://www.npmjs.com/package/create-next-mdx-blog-app/)
|
|
65
65
|
|
|
@@ -161,6 +161,15 @@ The project comes with its own `MDXImage` component that utilizes the Next.js bu
|
|
|
161
161
|
|
|
162
162
|
Images are displayed inside a styled container with a **green glow border** and a **hover scale effect** consistent with the rest of the matrix-green design system.
|
|
163
163
|
|
|
164
|
+
### Article Header Actions
|
|
165
|
+
A **Copy Link** button (`src/components/CopyLinkButton.tsx`) is rendered in the article header, writing the current page URL to the clipboard on click and displaying a brief "Copied!" confirmation via icon swap. **Social Share** buttons (`src/components/SocialShareButtons.tsx`) sit alongside it, opening pre-filled share dialogs for X (Twitter), LinkedIn, and Reddit in a new tab using the article title and URL.
|
|
166
|
+
|
|
167
|
+
### Reading Progress Bar
|
|
168
|
+
A thin green bar (`src/components/ReadingProgressBar.tsx`) is fixed to the top of the viewport on all article pages, growing from left to right as the reader scrolls through the article. It uses a passive scroll listener for zero performance impact and includes `role="progressbar"` ARIA attributes for accessibility.
|
|
169
|
+
|
|
170
|
+
### Back to Top Button
|
|
171
|
+
A floating circular button (`src/components/BackToTopButton.tsx`) appears in the bottom-right corner of the screen once the reader has scrolled more than 400px down the page. Clicking it smoothly scrolls back to the top. The button is hidden when not needed and matches the green colour scheme of the rest of the UI.
|
|
172
|
+
|
|
164
173
|
## 🖥️ Code Sandbox
|
|
165
174
|
The project includes an interactive in-browser code execution environment powered by **Sandpack** (<b>route</b>: `/code-sandbox`).
|
|
166
175
|
|
package/package.json
CHANGED
|
@@ -3,6 +3,8 @@ import ArticleAuthorBio from "@/components/ArticleAuthorBio";
|
|
|
3
3
|
import ArticleHeader from "@/components/ArticleHeader";
|
|
4
4
|
import { ArticleAuthorInfoList, ArticleHeaderInfoList } from "@/utils/constants";
|
|
5
5
|
import StaticArticle from "@/components/StaticArticle";
|
|
6
|
+
import ReadingProgressBar from "@/components/ReadingProgressBar";
|
|
7
|
+
import BackToTopButton from "@/components/BackToTopButton";
|
|
6
8
|
import type { Metadata } from 'next';
|
|
7
9
|
|
|
8
10
|
// Generate metadata for the Sample Blog Post page
|
|
@@ -21,7 +23,9 @@ export const metadata: Metadata = {
|
|
|
21
23
|
// Utilizes the Static Article custom component
|
|
22
24
|
const SampleBlogPostPage = () => {
|
|
23
25
|
return (
|
|
24
|
-
<div className="min-h-screen flex flex-col bg-black">
|
|
26
|
+
<div className="min-h-screen flex flex-col bg-black">
|
|
27
|
+
<ReadingProgressBar />
|
|
28
|
+
<BackToTopButton />
|
|
25
29
|
<main className="flex-grow px-4 py-8">
|
|
26
30
|
<div className="max-w-4xl mx-auto">
|
|
27
31
|
<ArticleHeader articleHeaderInformation={ArticleHeaderInfoList} />
|
|
@@ -5,7 +5,7 @@ import Image from "next/image";
|
|
|
5
5
|
import type { ArticleAuthorInfoType } from "@/utils/types";
|
|
6
6
|
|
|
7
7
|
// Article Author Bio Section component
|
|
8
|
-
export default function ArticleAuthorBio({ authorInformation }: { authorInformation: ArticleAuthorInfoType }) {
|
|
8
|
+
export default function ArticleAuthorBio({ authorInformation }: { authorInformation: ArticleAuthorInfoType }): React.JSX.Element {
|
|
9
9
|
return (
|
|
10
10
|
<div className="glass-card p-4 sm:p-6 mb-8 sm:mb-12">
|
|
11
11
|
<div className="flex flex-col sm:flex-row items-center sm:items-start space-y-4 sm:space-y-0">
|
|
@@ -2,7 +2,7 @@ import Image from "next/image";
|
|
|
2
2
|
import type { ArticleCoverImageInfoType } from "@/utils/types";
|
|
3
3
|
|
|
4
4
|
// Article Cover Image Section component
|
|
5
|
-
export default function ArticleCoverImage(coverImageInformation: ArticleCoverImageInfoType) {
|
|
5
|
+
export default function ArticleCoverImage(coverImageInformation: ArticleCoverImageInfoType): React.JSX.Element {
|
|
6
6
|
return (
|
|
7
7
|
<div className="rounded-lg overflow-hidden mb-4">
|
|
8
8
|
<Image
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { Avatar } from "@radix-ui/react-avatar";
|
|
2
2
|
import { Badge } from "./ui/badge";
|
|
3
3
|
import ArticleCoverImage from "./ArticleCoverImage";
|
|
4
|
+
import CopyLinkButton from "./CopyLinkButton";
|
|
5
|
+
import SocialShareButtons from "./SocialShareButtons";
|
|
4
6
|
import type { ArticleHeaderInfoType } from "@/utils/types";
|
|
5
7
|
import Image from "next/image";
|
|
6
8
|
|
|
7
9
|
// Article Header custom component
|
|
8
|
-
export default function ArticleHeader({ articleHeaderInformation } : { articleHeaderInformation: ArticleHeaderInfoType }) {
|
|
10
|
+
export default function ArticleHeader({ articleHeaderInformation } : { articleHeaderInformation: ArticleHeaderInfoType }): React.JSX.Element {
|
|
9
11
|
return (
|
|
10
12
|
<div className="mb-8">
|
|
11
13
|
<Badge className="mb-3 bg-green-900/60 text-green-100 border border-green-500/50">
|
|
@@ -35,8 +37,13 @@ export default function ArticleHeader({ articleHeaderInformation } : { articleHe
|
|
|
35
37
|
</div>
|
|
36
38
|
</div>
|
|
37
39
|
</div>
|
|
40
|
+
{/* Copy link and social share actions */}
|
|
41
|
+
<div className="flex flex-col sm:flex-row sm:items-center gap-3 mb-4 sm:mb-6">
|
|
42
|
+
<CopyLinkButton />
|
|
43
|
+
<SocialShareButtons articleTitle={ articleHeaderInformation.articleTitle } />
|
|
44
|
+
</div>
|
|
38
45
|
{/* Cover image */}
|
|
39
|
-
<ArticleCoverImage coverImageURL={ articleHeaderInformation.articleCoverImageURL.coverImageURL } />
|
|
46
|
+
<ArticleCoverImage coverImageURL={ articleHeaderInformation.articleCoverImageURL.coverImageURL } />
|
|
40
47
|
</div>
|
|
41
48
|
)
|
|
42
49
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { ArrowUp } from 'lucide-react';
|
|
5
|
+
|
|
6
|
+
// Floating button that appears after scrolling down and returns the user to the top of the page
|
|
7
|
+
export default function BackToTopButton(): React.JSX.Element | null {
|
|
8
|
+
const [visible, setVisible] = useState<boolean>(false);
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const onScroll = (): void => {
|
|
12
|
+
setVisible(window.scrollY > 400);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
window.addEventListener('scroll', onScroll, { passive: true });
|
|
16
|
+
return () => window.removeEventListener('scroll', onScroll);
|
|
17
|
+
}, []);
|
|
18
|
+
|
|
19
|
+
const scrollToTop = (): void => {
|
|
20
|
+
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
if (!visible) return null;
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<button
|
|
27
|
+
onClick={scrollToTop}
|
|
28
|
+
className="fixed bottom-6 right-6 z-50 p-3 rounded-full bg-green-500 text-black shadow-lg hover:bg-green-400 transition-colors duration-200"
|
|
29
|
+
aria-label="Back to top"
|
|
30
|
+
>
|
|
31
|
+
<ArrowUp size={20} strokeWidth={2.5} />
|
|
32
|
+
</button>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -11,7 +11,7 @@ import { JS_DEFAULT, TS_DEFAULT, JS_EXAMPLES, TS_EXAMPLES } from '@/utils/consta
|
|
|
11
11
|
type Runtime = 'javascript' | 'typescript';
|
|
12
12
|
|
|
13
13
|
// Client boundary for the Code Sandbox page — owns all interactive state
|
|
14
|
-
export default function CodeSandboxClient() {
|
|
14
|
+
export default function CodeSandboxClient(): React.JSX.Element {
|
|
15
15
|
const [runtime, setRuntime] = useState<Runtime>('javascript');
|
|
16
16
|
const [sandpackKey, setSandpackKey] = useState<number>(0);
|
|
17
17
|
const [sandpackCode, setSandpackCode] = useState<string>(JS_DEFAULT);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Copy, Play, Download, Shield } from "lucide-react";
|
|
2
2
|
|
|
3
|
-
export default function CodeSandboxFeaturesSection() {
|
|
3
|
+
export default function CodeSandboxFeaturesSection(): React.JSX.Element {
|
|
4
4
|
return (
|
|
5
5
|
<section className="pb-8 sm:pb-12">
|
|
6
6
|
<h3 className="text-2xl sm:text-3xl font-bold mb-6 sm:mb-8 matrix-glow text-center text-green-300">Sandbox Features</h3>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { Copy, Check } from 'lucide-react';
|
|
5
|
+
import { Button } from '@/components/ui/button';
|
|
6
|
+
|
|
7
|
+
// Client Component — requires navigator.clipboard and window APIs
|
|
8
|
+
export default function CopyLinkButton(): React.JSX.Element {
|
|
9
|
+
const [copied, setCopied] = useState<boolean>(false);
|
|
10
|
+
|
|
11
|
+
const handleCopy = async (): Promise<void> => {
|
|
12
|
+
try {
|
|
13
|
+
await navigator.clipboard.writeText(window.location.href);
|
|
14
|
+
setCopied(true);
|
|
15
|
+
setTimeout(() => setCopied(false), 2000);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// Clipboard API unavailable — silently ignore
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<Button
|
|
24
|
+
variant="outline"
|
|
25
|
+
size="sm"
|
|
26
|
+
onClick={handleCopy}
|
|
27
|
+
className="border-green-500/40 bg-green-900/20 text-green-300 hover:bg-green-800/40 hover:text-green-200 transition-colors text-xs w-full sm:w-auto"
|
|
28
|
+
aria-label="Copy article link to clipboard"
|
|
29
|
+
>
|
|
30
|
+
{copied ? (
|
|
31
|
+
<Check className="h-4 w-4" aria-hidden="true" />
|
|
32
|
+
) : (
|
|
33
|
+
<Copy className="h-4 w-4" aria-hidden="true" />
|
|
34
|
+
)}
|
|
35
|
+
{copied ? 'Copied!' : 'Copy Link'}
|
|
36
|
+
</Button>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -3,10 +3,12 @@ import ArticleAuthorBio from "@/components/ArticleAuthorBio";
|
|
|
3
3
|
import ArticleHeader from "@/components/ArticleHeader";
|
|
4
4
|
import { ArticleAuthorInfoList, ArticleHeaderInfoList } from "@/utils/constants";
|
|
5
5
|
import MDXRemoteArticle from "./MDXRemoteArticle";
|
|
6
|
+
import ReadingProgressBar from "@/components/ReadingProgressBar";
|
|
7
|
+
import BackToTopButton from "@/components/BackToTopButton";
|
|
6
8
|
import { fetchArticle } from "@/utils/functions";
|
|
7
9
|
|
|
8
10
|
// Custom Dynamic Article component encompasses loading article content stored in a Supabase database
|
|
9
|
-
export default async function DynamicArticle({ slug } : { slug: string }) {
|
|
11
|
+
export default async function DynamicArticle({ slug } : { slug: string }): Promise<React.JSX.Element> {
|
|
10
12
|
// Make a call to the back-end and fetch article information
|
|
11
13
|
const articleData = await fetchArticle(slug);
|
|
12
14
|
|
|
@@ -15,7 +17,9 @@ export default async function DynamicArticle({ slug } : { slug: string }) {
|
|
|
15
17
|
}
|
|
16
18
|
else {
|
|
17
19
|
return (
|
|
18
|
-
<div className="min-h-screen flex flex-col bg-black">
|
|
20
|
+
<div className="min-h-screen flex flex-col bg-black">
|
|
21
|
+
<ReadingProgressBar />
|
|
22
|
+
<BackToTopButton />
|
|
19
23
|
<main className="flex-grow px-4 py-8">
|
|
20
24
|
<div className="max-w-4xl mx-auto">
|
|
21
25
|
<ArticleHeader articleHeaderInformation={ArticleHeaderInfoList} />
|
|
@@ -4,7 +4,7 @@ import matter from 'gray-matter';
|
|
|
4
4
|
|
|
5
5
|
// Pass in the MDX remote article content as a string
|
|
6
6
|
// Utilize the gray matter library to separate article content from article metadata (front-matter)
|
|
7
|
-
export default function MDXRemoteArticle({ content }: { content: string }) {
|
|
7
|
+
export default function MDXRemoteArticle({ content }: { content: string }): React.JSX.Element {
|
|
8
8
|
const { content: mdxContent } = matter(content);
|
|
9
9
|
|
|
10
10
|
// MDX Remote used to capture article data from database
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
|
|
5
|
+
// Thin fixed progress bar at the top of the page indicating scroll depth through the article
|
|
6
|
+
export default function ReadingProgressBar(): React.JSX.Element {
|
|
7
|
+
const [progress, setProgress] = useState<number>(0);
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const updateProgress = (): void => {
|
|
11
|
+
const scrollTop = window.scrollY;
|
|
12
|
+
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
|
|
13
|
+
const pct = docHeight > 0 ? (scrollTop / docHeight) * 100 : 0;
|
|
14
|
+
setProgress(pct);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
window.addEventListener('scroll', updateProgress, { passive: true });
|
|
18
|
+
updateProgress();
|
|
19
|
+
|
|
20
|
+
return () => window.removeEventListener('scroll', updateProgress);
|
|
21
|
+
}, []);
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div
|
|
25
|
+
className="fixed top-0 left-0 z-50 h-1 bg-green-500 transition-all duration-75 ease-out"
|
|
26
|
+
style={{ width: `${progress}%` }}
|
|
27
|
+
role="progressbar"
|
|
28
|
+
aria-valuenow={Math.round(progress)}
|
|
29
|
+
aria-valuemin={0}
|
|
30
|
+
aria-valuemax={100}
|
|
31
|
+
aria-label="Reading progress"
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -43,7 +43,7 @@ const matrixTheme: SandpackTheme = {
|
|
|
43
43
|
},
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
-
function EditorToolbar({ mainFile, template }: { mainFile: string; template: 'vanilla' | 'vanilla-ts' }) {
|
|
46
|
+
function EditorToolbar({ mainFile, template }: { mainFile: string; template: 'vanilla' | 'vanilla-ts' }): React.JSX.Element {
|
|
47
47
|
const { sandpack, dispatch } = useSandpack();
|
|
48
48
|
const isTS = template === 'vanilla-ts';
|
|
49
49
|
|
|
@@ -113,7 +113,7 @@ export interface SandpackEditorProps {
|
|
|
113
113
|
template: 'vanilla' | 'vanilla-ts';
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
export default function SandpackEditor({ initialCode, template }: SandpackEditorProps) {
|
|
116
|
+
export default function SandpackEditor({ initialCode, template }: SandpackEditorProps): React.JSX.Element {
|
|
117
117
|
const mainFile = template === 'vanilla-ts' ? '/index.ts' : '/index.js';
|
|
118
118
|
|
|
119
119
|
return (
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Button } from '@/components/ui/button';
|
|
4
|
+
import type SocialShareButtonsType from '@/utils/types/SocialShareButtonsType';
|
|
5
|
+
import type { SocialPlatform } from '@/utils/types/SocialShareButtonsType';
|
|
6
|
+
import { PLATFORM_CONFIG } from '@/utils/constants/SocialShareConstants';
|
|
7
|
+
|
|
8
|
+
// Client Component — requires window APIs for share URL construction and popup
|
|
9
|
+
export default function SocialShareButtons({ articleTitle }: SocialShareButtonsType): React.JSX.Element {
|
|
10
|
+
const handleShare = (platform: SocialPlatform): void => {
|
|
11
|
+
const encodedUrl = encodeURIComponent(window.location.href);
|
|
12
|
+
const encodedTitle = encodeURIComponent(articleTitle);
|
|
13
|
+
const shareUrl = PLATFORM_CONFIG[platform].buildUrl(encodedUrl, encodedTitle);
|
|
14
|
+
window.open(shareUrl, '_blank', 'noopener,noreferrer');
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div className="flex items-center gap-2 flex-wrap w-full sm:w-auto">
|
|
19
|
+
<span className="text-xs sm:text-sm text-green-400/70">Share:</span>
|
|
20
|
+
{(Object.keys(PLATFORM_CONFIG) as SocialPlatform[]).map((platform) => (
|
|
21
|
+
<Button
|
|
22
|
+
key={platform}
|
|
23
|
+
variant="outline"
|
|
24
|
+
size="sm"
|
|
25
|
+
onClick={() => handleShare(platform)}
|
|
26
|
+
className="border-green-500/40 bg-green-900/20 text-green-300 hover:bg-green-800/40 hover:text-green-200 transition-colors text-xs px-3"
|
|
27
|
+
aria-label={`Share on ${PLATFORM_CONFIG[platform].label}`}
|
|
28
|
+
>
|
|
29
|
+
{PLATFORM_CONFIG[platform].label}
|
|
30
|
+
</Button>
|
|
31
|
+
))}
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -3,7 +3,7 @@ import ArticleContent from "@/markdown/ArticleContent.mdx";
|
|
|
3
3
|
|
|
4
4
|
// Custom Static Article component to be used for loading article content stored locally
|
|
5
5
|
// Article Header information will be hardcoded and stored locally, the following is simply an example
|
|
6
|
-
const StaticArticle = () => {
|
|
6
|
+
const StaticArticle = (): React.JSX.Element => {
|
|
7
7
|
return (
|
|
8
8
|
<div className="glass-card p-8 mb-8">
|
|
9
9
|
<ArticleContent />
|
|
@@ -7,11 +7,11 @@ import { Send } from "lucide-react";
|
|
|
7
7
|
import type { ChatInputType } from "@/utils/types";
|
|
8
8
|
|
|
9
9
|
// Chat Input — matrix themed
|
|
10
|
-
export function ChatInput({ isLoading, onSendMessage }: ChatInputType) {
|
|
10
|
+
export function ChatInput({ isLoading, onSendMessage }: ChatInputType): React.JSX.Element {
|
|
11
11
|
const [input, setInput] = useState("");
|
|
12
12
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
13
13
|
|
|
14
|
-
function handleKeyDown(e: React.KeyboardEvent<HTMLTextAreaElement>) {
|
|
14
|
+
function handleKeyDown(e: React.KeyboardEvent<HTMLTextAreaElement>): void {
|
|
15
15
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
16
16
|
e.preventDefault();
|
|
17
17
|
if (input.trim() && !isLoading) {
|
|
@@ -21,7 +21,7 @@ export function ChatInput({ isLoading, onSendMessage }: ChatInputType) {
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
function handleSubmit(e: React.FormEvent) {
|
|
24
|
+
function handleSubmit(e: React.FormEvent): void {
|
|
25
25
|
e.preventDefault();
|
|
26
26
|
if (input.trim() && !isLoading) {
|
|
27
27
|
onSendMessage(input);
|
|
@@ -11,7 +11,7 @@ import { Bot, RotateCcw } from "lucide-react";
|
|
|
11
11
|
const transport = new TextStreamChatTransport({ api: "/api/chat" });
|
|
12
12
|
|
|
13
13
|
// Chat Interface — no sidebar, no persistence, matrix themed
|
|
14
|
-
export function ChatInterface() {
|
|
14
|
+
export function ChatInterface(): React.JSX.Element {
|
|
15
15
|
const { messages, sendMessage, status, error, setMessages } = useChat({ transport });
|
|
16
16
|
|
|
17
17
|
const isLoading = status === "submitted" || status === "streaming";
|
|
@@ -14,7 +14,7 @@ const SUGGESTIONS = [
|
|
|
14
14
|
];
|
|
15
15
|
|
|
16
16
|
// Chat Messages — matrix themed, auto-scrolling
|
|
17
|
-
export function ChatMessages({ messages, isLoading, error }: ChatMessagesType) {
|
|
17
|
+
export function ChatMessages({ messages, isLoading, error }: ChatMessagesType): React.JSX.Element {
|
|
18
18
|
const bottomRef = useRef<HTMLDivElement>(null);
|
|
19
19
|
|
|
20
20
|
useEffect(() => {
|
|
@@ -5,13 +5,13 @@ import { toast } from 'sonner';
|
|
|
5
5
|
|
|
6
6
|
// Custom code block component for handling code in MDX files
|
|
7
7
|
// Visual Studio Code Dark Plus theme with copy functionality
|
|
8
|
-
const CodeBlock = ({ className = '', children }: { className?: string; children: string }) => {
|
|
8
|
+
const CodeBlock = ({ className = '', children }: { className?: string; children: string }): React.JSX.Element => {
|
|
9
9
|
const match = /language-(\w+)/.exec(className || '');
|
|
10
10
|
const language = match?.[1] || 'text';
|
|
11
11
|
const code = String(children).trim();
|
|
12
12
|
|
|
13
13
|
// Copy content and receive a toast message based on action
|
|
14
|
-
const copyToClipboard = async () => {
|
|
14
|
+
const copyToClipboard = async (): Promise<void> => {
|
|
15
15
|
try {
|
|
16
16
|
await navigator.clipboard.writeText(code);
|
|
17
17
|
toast.success('Code copied!', {
|
|
@@ -6,7 +6,7 @@ interface GistCodeBlockProps {
|
|
|
6
6
|
language: string;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export default function GistCodeBlock({ content, language }: GistCodeBlockProps) {
|
|
9
|
+
export default function GistCodeBlock({ content, language }: GistCodeBlockProps): React.JSX.Element {
|
|
10
10
|
return (
|
|
11
11
|
<SyntaxHighlighter
|
|
12
12
|
language={language}
|
|
@@ -6,8 +6,8 @@ interface GistCopyButtonProps {
|
|
|
6
6
|
mdxGHGistURL: string;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export default function GistCopyButton({ content, mdxGHGistURL }: GistCopyButtonProps) {
|
|
10
|
-
const copyToClipboard = async () => {
|
|
9
|
+
export default function GistCopyButton({ content, mdxGHGistURL }: GistCopyButtonProps): React.JSX.Element {
|
|
10
|
+
const copyToClipboard = async (): Promise<void> => {
|
|
11
11
|
try {
|
|
12
12
|
await navigator.clipboard.writeText(content);
|
|
13
13
|
toast.success('GitHub Gist copied!', {
|
|
@@ -3,7 +3,7 @@ import GistCopyButton from './GistCopyButton';
|
|
|
3
3
|
import GistCodeBlock from './GistCodeBlock';
|
|
4
4
|
import { GITHUB_USERNAME, GITHUB_GIST_LANGUAGE_MAP, GIST_BASE_URL } from '@/utils/constants';
|
|
5
5
|
|
|
6
|
-
export default async function GitHubGist({ id, figCaptionText }: GitHubGistType) {
|
|
6
|
+
export default async function GitHubGist({ id, figCaptionText }: GitHubGistType): Promise<React.JSX.Element> {
|
|
7
7
|
try {
|
|
8
8
|
const headers: HeadersInit = {
|
|
9
9
|
'Accept': 'application/vnd.github.v3+json',
|
|
@@ -3,7 +3,7 @@ import type { MDXImageType } from "@/utils/types";
|
|
|
3
3
|
|
|
4
4
|
// MDXImage custom component
|
|
5
5
|
// Utilizes the built-in Next.js Image component as well as the figcaption element
|
|
6
|
-
export default function MDXImage(imageProperties: MDXImageType) {
|
|
6
|
+
export default function MDXImage(imageProperties: MDXImageType): React.JSX.Element {
|
|
7
7
|
return (
|
|
8
8
|
<figure className='text-center my-6'>
|
|
9
9
|
<div className="relative inline-block p-4 bg-gray-900/30 rounded-lg border-2 border-green-500 shadow-[0_0_15px_rgba(34,197,94,0.3)] transition-all duration-300 hover:shadow-[0_0_25px_rgba(34,197,94,0.5)] hover:scale-[1.02]">
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { PlatformConfig, SocialPlatform } from "@/utils/types/SocialShareButtonsType";
|
|
2
|
+
|
|
3
|
+
// Social share platform configuration
|
|
4
|
+
export const PLATFORM_CONFIG: Record<SocialPlatform, PlatformConfig> = {
|
|
5
|
+
twitter: {
|
|
6
|
+
label: 'X',
|
|
7
|
+
buildUrl: (encodedUrl, encodedTitle) =>
|
|
8
|
+
`https://twitter.com/intent/tweet?url=${encodedUrl}&text=${encodedTitle}`,
|
|
9
|
+
},
|
|
10
|
+
linkedin: {
|
|
11
|
+
label: 'LinkedIn',
|
|
12
|
+
buildUrl: (encodedUrl) =>
|
|
13
|
+
`https://www.linkedin.com/sharing/share-offsite/?url=${encodedUrl}`,
|
|
14
|
+
},
|
|
15
|
+
reddit: {
|
|
16
|
+
label: 'Reddit',
|
|
17
|
+
buildUrl: (encodedUrl, encodedTitle) =>
|
|
18
|
+
`https://reddit.com/submit?url=${encodedUrl}&title=${encodedTitle}`,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Social Share Buttons types
|
|
2
|
+
export type SocialPlatform = 'twitter' | 'linkedin' | 'reddit';
|
|
3
|
+
|
|
4
|
+
// Social Platform configuration
|
|
5
|
+
export interface PlatformConfig {
|
|
6
|
+
label: string;
|
|
7
|
+
buildUrl: (encodedUrl: string, encodedTitle: string) => string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Social Share Button export
|
|
11
|
+
export default interface SocialShareButtonsType {
|
|
12
|
+
articleTitle: string;
|
|
13
|
+
}
|
package/src/utils/types/index.ts
CHANGED
|
@@ -9,3 +9,4 @@ export type { default as MDXImageType } from "./MDXImageType";
|
|
|
9
9
|
export type { default as SandboxExampleType } from "./SandboxExampleType";
|
|
10
10
|
export type { default as ToolConfigType } from "./ToolConfigType";
|
|
11
11
|
export type { default as ToolInvocationPartType } from "./ToolInvocationPartType";
|
|
12
|
+
export type { default as SocialShareButtonsType, SocialPlatform, PlatformConfig } from "./SocialShareButtonsType";
|