diff --git a/.gitignore b/.gitignore
index 3dba35c6..2d455eba 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,7 +7,7 @@
.pnp.js
.yarn/install-state.gz
-
+src-tauri/
# testing
/coverage
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 00000000..bd98ef09
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,5 @@
+.next/
+public/
+styles/
+*.md
+out/
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 00000000..88ebf1bf
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,10 @@
+{
+ "printWidth": 100,
+ "trailingComma": "es5",
+ "tabWidth": 2,
+ "semi": false,
+ "singleQuote": true,
+ "jsxSingleQuote": true,
+ "maxLineLength": 80,
+ "endOfLine": "lf"
+}
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..273ce37a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,25 @@
+MIT License
+
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
index 6c0bba70..b185bb27 100644
--- a/README.md
+++ b/README.md
@@ -1,32 +1,14 @@
-# TinyMind
+## Cofe
-TinyMind is a website that lets you write and sync your blog posts, short thoughts, and memos by signing in with GitHub. Here's how it works:
+Cofe is designed to be a simple and easy-to-use blog and memo taking app, originally forked from [tinymind](https://github.com/mazzzystar/tinymind).
-1. We create a public repo called "tinymind-blog" in your GitHub account.
-2. When you write anything on our webpage, it automatically commits to your `yourname/tinymind-blog` repo.
-3. This ensures a seamless way to create content and maintain data persistence.
+
-## Data Privacy & Permissions
+### HOW TO RUN
-We only have write access to your public repositories. Your privacy matters:
+Register a new OAuth App on Github, and get the `GITHUB_ID` and `GITHUB_SECRET`,
+then run the following command to start the blog:
-- Content stored only in your GitHub repo
-- No data kept on our servers
-- You have full control through your GitHub account
-
-## TODO
-
-- [ ] Create a page to showcase all public writers using TinyMind (creator list)
-- [ ] Implement shareable user main pages (like https://tinywind.me/mazzzystar)
-
-## Tech Stack
-
-Built with Next.js, React, TypeScript, NextAuth.js, and Tailwind CSS.
-
-## Contribute
-
-Contributions are welcome! Feel free to submit a Pull Request.
-
-## License
-
-[Your chosen license here]
+```bash
+ GITHUB_USERNAME='metrue' GITHUB_ID='GITHUB_ID' GITHUB_SECRET='GITHUB_SECRET' NEXTAUTH_SECRET='NEXTAUTH_SECRET' npm run de
+```
diff --git a/api/github/route.ts b/api/github/route.ts
new file mode 100644
index 00000000..0f6b4f8d
--- /dev/null
+++ b/api/github/route.ts
@@ -0,0 +1,101 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { createBlogPost, createMemo, deleteBlogPost, deleteMemo, updateBlogPost, updateMemo } from '@/lib/githubApi';
+
+import { authOptions } from "@/lib/auth";
+import { createGitHubAPIClient } from '@/lib/client';
+import { getServerSession } from "next-auth/next";
+
+export const dynamic = 'force-dynamic'; // Disable caching for this route
+export const revalidate = 60; // Revalidate every 60 seconds
+
+// Add cache control headers
+const headers = {
+ 'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=30',
+ 'Content-Type': 'application/json',
+};
+
+export async function POST(request: NextRequest) {
+ try {
+ console.log('POST request received');
+ const session = await getServerSession(authOptions);
+
+ if (!session || !session.accessToken) {
+ console.log('No valid session found');
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401, headers });
+ }
+
+ const { action, ...data } = await request.json();
+ console.log('Action:', action);
+ console.log('Data:', JSON.stringify(data, null, 2));
+
+ switch (action) {
+ case 'createBlogPost':
+ await createBlogPost(data.title, data.content, session.accessToken);
+ return NextResponse.json({ message: 'Blog post created successfully' }, { headers });
+ case 'updateBlogPost':
+ await updateBlogPost(data.id, data.title, data.content, session.accessToken);
+ return NextResponse.json({ message: 'Blog post updated successfully' }, { headers });
+ case 'deleteBlogPost':
+ await deleteBlogPost(data.id, session.accessToken);
+ return NextResponse.json({ message: 'Blog post deleted successfully' }, { headers });
+ case 'createMemo':
+ await createMemo(data.content, data.image, session.accessToken);
+ return NextResponse.json({ message: 'Memo created successfully' }, { headers });
+ case 'updateMemo':
+ await updateMemo(data.id, data.content, session.accessToken);
+ return NextResponse.json({ message: 'Memo updated successfully' }, { headers });
+ case 'deleteMemo':
+ await deleteMemo(data.id, session.accessToken);
+ return NextResponse.json({ message: 'Memo deleted successfully' }, { headers });
+ default:
+ return NextResponse.json({ error: 'Invalid action' }, { status: 400, headers });
+ }
+ } catch (error) {
+ console.error('Error in /api/github POST:', error);
+ if (error instanceof Error) {
+ console.error('Error stack:', error.stack);
+ return NextResponse.json({ error: error.message, stack: error.stack }, { status: 500, headers });
+ }
+ return NextResponse.json({ error: 'An unexpected error occurred' }, { status: 500, headers });
+ }
+}
+
+export async function GET(request: NextRequest) {
+ try {
+ const session = await getServerSession(authOptions);
+
+ if (!session || !session.accessToken) {
+ console.log('No valid session found');
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401, headers });
+ }
+
+ const { searchParams } = new URL(request.url);
+ const action = searchParams.get('action');
+ const id = searchParams.get('id');
+
+ const client = createGitHubAPIClient(session.accessToken)
+
+ switch (action) {
+ case 'getBlogPosts':
+ const posts = await client.getBlogPosts();
+ return NextResponse.json(posts, { headers });
+ case 'getBlogPost':
+ if (!id) {
+ return NextResponse.json({ error: 'Missing id parameter' }, { status: 400, headers });
+ }
+ const post = await client.getBlogPost(`${id}.md`);
+ return NextResponse.json(post, { headers });
+ case 'getMemos':
+ const memos = await client.getMemos()
+ return NextResponse.json(memos, { headers });
+ default:
+ return NextResponse.json({ error: 'Invalid action' }, { status: 400, headers });
+ }
+ } catch (error) {
+ console.error('Error in /api/github GET:', error);
+ if (error instanceof Error) {
+ return NextResponse.json({ error: error.message }, { status: 500, headers });
+ }
+ return NextResponse.json({ error: 'An unexpected error occurred' }, { status: 500, headers });
+ }
+}
diff --git a/app/api/github/route.ts b/app/api/github/route.ts
deleted file mode 100644
index 545171da..00000000
--- a/app/api/github/route.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-import { NextRequest, NextResponse } from 'next/server';
-import { getServerSession } from "next-auth/next";
-import { authOptions } from "@/lib/auth";
-import { createBlogPost, createThought, getBlogPosts, getThoughts } from '@/lib/githubApi';
-
-export async function POST(request: NextRequest) {
- try {
- console.log('POST request received');
- const session = await getServerSession(authOptions);
-
- if (!session || !session.accessToken) {
- console.log('No valid session found');
- return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
- }
-
- const { action, ...data } = await request.json();
- console.log('Action:', action);
- console.log('Data:', JSON.stringify(data, null, 2));
-
- switch (action) {
- case 'createBlogPost':
- await createBlogPost(data.title, data.content, session.accessToken);
- return NextResponse.json({ message: 'Blog post created successfully' });
- case 'createThought':
- await createThought(data.content, data.image, session.accessToken);
- return NextResponse.json({ message: 'Thought created successfully' });
- default:
- return NextResponse.json({ error: 'Invalid action' }, { status: 400 });
- }
- } catch (error) {
- console.error('Error in /api/github POST:', error);
- if (error instanceof Error) {
- console.error('Error stack:', error.stack);
- return NextResponse.json({ error: error.message, stack: error.stack }, { status: 500 });
- }
- return NextResponse.json({ error: 'An unexpected error occurred' }, { status: 500 });
- }
-}
-
-export async function GET(request: NextRequest) {
- try {
- const session = await getServerSession(authOptions);
-
- if (!session || !session.accessToken) {
- console.log('No valid session found');
- return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
- }
-
- const { searchParams } = new URL(request.url);
- const action = searchParams.get('action');
-
- switch (action) {
- case 'getBlogPosts':
- const posts = await getBlogPosts(session.accessToken);
- return NextResponse.json(posts);
- case 'getThoughts':
- const thoughts = await getThoughts(session.accessToken);
- return NextResponse.json(thoughts);
- default:
- return NextResponse.json({ error: 'Invalid action' }, { status: 400 });
- }
- } catch (error) {
- console.error('Error in /api/github GET:', error);
- if (error instanceof Error) {
- return NextResponse.json({ error: error.message }, { status: 500 });
- }
- return NextResponse.json({ error: 'An unexpected error occurred' }, { status: 500 });
- }
-}
\ No newline at end of file
diff --git a/app/blog/[id]/component.tsx b/app/blog/[id]/component.tsx
new file mode 100644
index 00000000..34feabf1
--- /dev/null
+++ b/app/blog/[id]/component.tsx
@@ -0,0 +1,145 @@
+'use client'
+
+import { useState } from 'react'
+import { useRouter } from 'next/navigation'
+import { BlogPostContent } from '@/components/BlogPostContent'
+import type { BlogPost } from '@/lib/types'
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu'
+import { Button } from '@/components/ui/button'
+import { AiOutlineEllipsis } from 'react-icons/ai'
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+ DialogFooter,
+} from '@/components/ui/dialog'
+import { useSession } from 'next-auth/react'
+import { useTranslations } from 'next-intl'
+import { useToast } from '@/components/ui/use-toast'
+
+function removeFrontmatter(content: string): string {
+ const frontmatterRegex = /^---\n([\s\S]*?)\n---\n/
+ return content.replace(frontmatterRegex, '')
+}
+
+function decodeContent(content: string): string {
+ try {
+ return decodeURIComponent(content)
+ } catch (error) {
+ console.error('Error decoding content:', error)
+ return content
+ }
+}
+
+export const PostContainer = ({ post }: { post: BlogPost }) => {
+ const [isDeleting, setIsDeleting] = useState(false)
+ const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false)
+ const router = useRouter()
+ const { toast } = useToast()
+ // eslint-disable-next-line
+ const { data: session, status } = useSession()
+
+ const t = useTranslations('HomePage')
+
+ const decodedTitle = decodeContent(post.title)
+ const decodedContent = decodeContent(post.content)
+ const contentWithoutFrontmatter = removeFrontmatter(decodedContent)
+
+ const handleDeleteBlogPost = async () => {
+ if (!session?.accessToken) {
+ console.error('No access token available')
+ return
+ }
+
+ setIsDeleting(true)
+ try {
+ const response = await fetch('/api/github', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ action: 'deleteBlogPost',
+ id: post.id,
+ }),
+ })
+
+ if (!response.ok) {
+ throw new Error('Failed to delete blog post')
+ }
+
+ toast({
+ title: t('success'),
+ description: t('blogPostDeleted'),
+ duration: 3000,
+ })
+
+ setTimeout(() => {
+ router.push('/blog')
+ }, 500)
+ } catch (error) {
+ console.error('Error deleting blog post:', error)
+ toast({
+ title: t('error'),
+ description: t('blogPostDeleteFailed'),
+ variant: 'destructive',
+ duration: 3000,
+ })
+ } finally {
+ setIsDeleting(false)
+ setIsDeleteDialogOpen(false)
+ }
+ }
+
+ const headerContent = (
+ <>
+
+
+
+
+
+ router.push(`/editor?type=blog&id=${post.id}`)}>
+ {t('edit')}
+
+ setIsDeleteDialogOpen(true)}>
+ {t('delete')}
+
+
+
+
+ >
+ )
+
+ return (
+
+ )
+}
diff --git a/app/blog/[id]/page.tsx b/app/blog/[id]/page.tsx
index 6a74e442..7007e24d 100644
--- a/app/blog/[id]/page.tsx
+++ b/app/blog/[id]/page.tsx
@@ -1,63 +1,32 @@
-import { getServerSession } from "next-auth/next";
-import { authOptions } from "@/lib/auth";
-import { getBlogPost } from "@/lib/githubApi";
-import Link from "next/link";
-import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
-import { format } from "date-fns";
+import { PostContainer } from './component'
+import { getServerSession } from 'next-auth/next'
+import { authOptions } from '@/lib/auth'
+import { createGitHubAPIClient } from '@/lib/client'
+import fs from 'fs'
+import path from 'path'
-function decodeTitle(title: string): string {
- try {
- return decodeURIComponent(title);
- } catch {
- return title;
- }
-}
+export async function generateStaticParams() {
+ const blogDirectory = path.join(process.cwd(), 'data/blog')
+ const filenames = fs.readdirSync(blogDirectory)
-export default async function BlogPost({ params }: { params: { id: string } }) {
- const session = await getServerSession(authOptions);
+ // Generate params for each blog post based on filenames
+ const params = filenames.map((filename) => ({
+ id: encodeURIComponent(filename.replace(/\.md$/, '')), // Ensure non-ASCII slugs are encoded
+ }))
- if (!session) {
- return (
-
-
- Please sign in to view this post
-
- Sign in
-
-
-
- );
- }
+ return params
+}
- const post = await getBlogPost(params.id, session.accessToken as string);
+export default async function Page({ params }: { params: { id: string } }) {
+ const session = await getServerSession(authOptions)
+ const username = process.env.GITHUB_USERNAME ?? ''
+ const client = createGitHubAPIClient(session?.accessToken ?? '')
+ const posts = await client.getBlogPosts(username)
+ const post = posts.find((p) => p.id === decodeURIComponent(params.id))
if (!post) {
- return (
-
-
- Post not found
-
-
- );
+ return
Post not found
}
- const decodedTitle = decodeTitle(post.title);
- const formattedDate = format(new Date(post.date), "MMMM d, yyyy");
-
- return (
-
-
- {decodedTitle}
- {formattedDate}
-
-
-
-
-
- );
+ return
}
diff --git a/app/blog/page.tsx b/app/blog/page.tsx
index 787ede2e..24da0ee9 100644
--- a/app/blog/page.tsx
+++ b/app/blog/page.tsx
@@ -1,18 +1,18 @@
-import { getServerSession } from "next-auth/next";
-import { authOptions } from "@/lib/auth";
import BlogList from "@/components/BlogList";
-import { getBlogPosts } from "@/lib/githubApi";
-import GitHubSignInButton from "@/components/GitHubSignInButton";
+import { authOptions } from "@/lib/auth";
+import { createGitHubAPIClient } from '@/lib/client'
+import { getServerSession } from "next-auth/next";
+
+export const revalidate = 60;
export default async function BlogPage() {
const session = await getServerSession(authOptions);
- if (!session || !session.accessToken) {
- return ;
- }
+ const client = createGitHubAPIClient(session?.accessToken ?? '');
+ const username = process.env.GITHUB_USERNAME ?? '';
try {
- const posts = await getBlogPosts(session.accessToken);
+ const posts = await client.getBlogPosts(username ?? '');
return ;
} catch (error) {
console.error("Error fetching blog posts:", error);
diff --git a/app/editor/page.tsx b/app/editor/page.tsx
index 78718117..21d5b519 100644
--- a/app/editor/page.tsx
+++ b/app/editor/page.tsx
@@ -1,14 +1,27 @@
-import { getServerSession } from "next-auth/next";
-import { authOptions } from "@/lib/auth";
import EditorComponent from "@/components/Editor";
import GitHubSignInButton from "@/components/GitHubSignInButton";
+import { authOptions } from "@/lib/auth";
+import { getServerSession } from "next-auth/next";
-export default async function EditorPage() {
+export default async function EditorPage({
+ searchParams,
+}: {
+ searchParams: { type?: string };
+}) {
const session = await getServerSession(authOptions);
+ const defaultType = searchParams.type === "blog" ? "blog" : "memo";
if (!session) {
- return ;
+
+ const username = process.env.GITHUB_USERNAME;
+ if (!username) {
+ return ;
+ }
}
- return ;
+ return (
+
+
+
+ );
}
diff --git a/app/favicon.ico b/app/favicon.ico
index 9778fdeb..f53dec7a 100644
Binary files a/app/favicon.ico and b/app/favicon.ico differ
diff --git a/app/fonts/GeistMonoVF.woff b/app/fonts/GeistMonoVF.woff
deleted file mode 100644
index f2ae185c..00000000
Binary files a/app/fonts/GeistMonoVF.woff and /dev/null differ
diff --git a/app/fonts/GeistVF.woff b/app/fonts/GeistVF.woff
deleted file mode 100644
index 1b62daac..00000000
Binary files a/app/fonts/GeistVF.woff and /dev/null differ
diff --git a/app/globals.css b/app/globals.css
index 46947be4..f16fbd4c 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -1,66 +1,71 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
+
@layer base {
:root {
- --background: 0 0% 100%;
- --foreground: 0 0% 3.9%;
- --card: 0 0% 100%;
- --card-foreground: 0 0% 3.9%;
- --popover: 0 0% 100%;
- --popover-foreground: 0 0% 3.9%;
- --primary: 0 0% 9%;
- --primary-foreground: 0 0% 98%;
- --secondary: 0 0% 96.1%;
- --secondary-foreground: 0 0% 9%;
- --muted: 0 0% 96.1%;
- --muted-foreground: 0 0% 45.1%;
- --accent: 0 0% 96.1%;
- --accent-foreground: 0 0% 9%;
- --destructive: 0 84.2% 60.2%;
- --destructive-foreground: 0 0% 98%;
- --border: 0 0% 89.8%;
- --input: 0 0% 89.8%;
- --ring: 0 0% 3.9%;
- --chart-1: 12 76% 61%;
- --chart-2: 173 58% 39%;
- --chart-3: 197 37% 24%;
- --chart-4: 43 74% 66%;
- --chart-5: 27 87% 67%;
- --radius: 0.5rem
+ --background: #fdfdfd;
+ --foreground: #303030;
+ --card: #fdfdfd;
+ --card-foreground: #303030;
+ --popover: #fdfdfd;
+ --popover-foreground: #303030;
+ --primary: #2357cd;
+ --primary-foreground: #fdfdfd;
+ --secondary: #f5f5f5;
+ --secondary-foreground: #404040;
+ --muted: #f5f5f5;
+ --muted-foreground: #707070;
+ --accent: #f5f5f5;
+ --accent-foreground: #404040;
+ --destructive: #652222;
+ --destructive-foreground: #fdfdfd;
+ --border: #ccc;
+ --input: #ccc;
+ --ring: #2357cd;
+ --radius: 0.4rem;
}
+
.dark {
- --background: 0 0% 3.9%;
- --foreground: 0 0% 98%;
- --card: 0 0% 3.9%;
- --card-foreground: 0 0% 98%;
- --popover: 0 0% 3.9%;
- --popover-foreground: 0 0% 98%;
- --primary: 0 0% 98%;
- --primary-foreground: 0 0% 9%;
- --secondary: 0 0% 14.9%;
- --secondary-foreground: 0 0% 98%;
- --muted: 0 0% 14.9%;
- --muted-foreground: 0 0% 63.9%;
- --accent: 0 0% 14.9%;
- --accent-foreground: 0 0% 98%;
- --destructive: 0 62.8% 30.6%;
- --destructive-foreground: 0 0% 98%;
- --border: 0 0% 14.9%;
- --input: 0 0% 14.9%;
- --ring: 0 0% 83.1%;
- --chart-1: 220 70% 50%;
- --chart-2: 160 60% 45%;
- --chart-3: 30 80% 55%;
- --chart-4: 280 65% 60%;
- --chart-5: 340 75% 55%
+ --background: #151615;
+ --foreground: #dbdfdf;
+ --card: #151615;
+ --card-foreground: #dbdfdf;
+ --popover: #151615;
+ --popover-foreground: #dbdfdf;
+ --primary: #6eb8ff;
+ --primary-foreground: #151615;
+ --secondary: #3a3b3b;
+ --secondary-foreground: #bfc5c5;
+ --muted: #3a3b3b;
+ --muted-foreground: #b4b6b8;
+ --accent: #3a3b3b;
+ --accent-foreground: #bfc5c5;
+ --destructive: #652222;
+ --destructive-foreground: #dbdfdf;
+ --border: #414141;
+ --input: #414141;
+ --ring: #6eb8ff;
}
}
+
@layer base {
* {
@apply border-border;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
}
+
body {
@apply bg-background text-foreground;
+ line-height: 1.62em;
+ margin: 0;
+ font-size: 18px;
+ word-wrap: break-word;
}
-}
\ No newline at end of file
+
+ a {
+ @apply text-primary;
+ text-underline-offset: 3px;
+ text-decoration-thickness: 0.5px;
+ }
+}
diff --git a/app/layout.tsx b/app/layout.tsx
index 06b34d7d..cb203dfc 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,58 +1,110 @@
-import type { Metadata } from "next";
-import { Inter } from "next/font/google";
-import "./globals.css";
-import Header from "@/components/Header";
-import { SessionProvider } from "../components/SessionProvider";
-import Link from "next/link";
-import { FiPlus } from "react-icons/fi";
-import { Button } from "@/components/ui/button";
-import Script from "next/script";
-import Footer from "@/components/Footer";
-
-const inter = Inter({ subsets: ["latin"] });
-
-export const metadata: Metadata = {
- title: "TinyMind - Write and sync your blog & memo data with GitHub",
- description:
- "Create a GitHub account and write blogs, thoughts, and notes using this website. Your data will be automatically saved in a GitHub repository, which means your data will never be lost as long as GitHub exists.p",
-};
-
-export default function RootLayout({
- children,
-}: {
- children: React.ReactNode;
-}) {
+import './globals.css'
+
+import { getLocale, getMessages, getTranslations } from 'next-intl/server'
+
+import CreateButton from '@/components/CreateButton'
+import Head from 'next/head'
+import Header from '@/components/Header'
+import { Memo } from '@/lib/types'
+import type { Metadata } from 'next'
+import { NextIntlClientProvider } from 'next-intl'
+import Script from 'next/script'
+import { SessionProvider } from '../components/SessionProvider'
+import { Toaster } from '@/components/ui/toaster'
+import { authOptions } from '@/lib/auth'
+import { createGitHubAPIClient } from '@/lib/client'
+import { getIconUrls } from '@/lib/githubApi'
+import { getServerSession } from 'next-auth/next'
+import { gowun_wodum } from '@/components/ui/font'
+
+export async function generateMetadata(): Promise {
+ const t = await getTranslations('metadata')
+ const session = await getServerSession(authOptions)
+
+ const title =
+ t('title') ||
+ 'Cofe - Write and sync your blog posts & memos with one-click GitHub sign-in'
+ const description =
+ t('description') ||
+ 'Write and preserve your blogs, memos, and notes effortlessly. Sign in with GitHub to automatically sync your content to your own repository, ensuring your ideas are safely stored as long as GitHub exists.'
+
+ const { iconPath } = await getIconPaths(session?.accessToken)
+
+ return {
+ title,
+ description,
+ manifest: '/manifest.json',
+ openGraph: {
+ title,
+ description,
+ images: [{ url: iconPath, width: 512, height: 512, alt: 'App Logo' }],
+ type: 'website',
+ },
+ twitter: {
+ card: 'summary_large_image',
+ title,
+ description,
+ images: [iconPath],
+ },
+ }
+}
+
+export default async function RootLayout({ children }: { children: React.ReactNode }) {
+ const locale = await getLocale()
+ const messages = await getMessages()
+ const session = await getServerSession(authOptions)
+ const username = process.env.GITHUB_USERNAME ?? ''
+
+ const memos = await createGitHubAPIClient(session?.accessToken || '').getMemos(username ?? '')
+ let latestMemo: Memo | undefined
+ if (memos.length > 0) {
+ latestMemo = memos.sort(
+ (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
+ )[0]
+ }
+
+ const { iconPath } = await getIconPaths(session?.accessToken)
+
return (
-
-
-
-
-
-
-
- {children}
-
-
-
+ gtag('config', 'G-5WTLPRB4YS');
+ `}
+
+
+
+
+ {children}
+
+
+
+
- );
+ )
+}
+
+async function getIconPaths(accessToken: string | undefined) {
+ const defaultIconPath = '/icon.jpg'
+ const defaultAppleTouchIconPath = '/icon-144.jpg'
+
+ if (accessToken) {
+ const iconUrls = await getIconUrls(accessToken)
+ return iconUrls
+ }
+
+ return {
+ iconPath: defaultIconPath,
+ appleTouchIconPath: defaultAppleTouchIconPath,
+ }
}
diff --git a/app/memos/page.tsx b/app/memos/page.tsx
new file mode 100644
index 00000000..64664a68
--- /dev/null
+++ b/app/memos/page.tsx
@@ -0,0 +1,32 @@
+import GitHubSignInButton from '@/components/GitHubSignInButton'
+import MemosList from '@/components/MemosList'
+import PublicMemosList from '@/components/PublicMemosList'
+import { authOptions } from '@/lib/auth'
+import { createGitHubAPIClient } from '@/lib/client'
+import { getServerSession } from 'next-auth/next'
+
+export const revalidate = 60
+
+export default async function MemosPage() {
+ const session = await getServerSession(authOptions)
+ const username = process.env.GITHUB_USERNAME ?? ''
+
+ if (!session || !session.accessToken) {
+ if (username) {
+ const blogPosts = await createGitHubAPIClient(session?.accessToken ?? '').getMemos(
+ process.env.GITHUB_USERNAME ?? ''
+ )
+ return (
+
+ )
+ } else {
+ return
+ }
+ }
+
+ return
+}
diff --git a/app/page.tsx b/app/page.tsx
index b060229b..860f4ac4 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,12 +1,11 @@
-import ThoughtsList from "@/components/ThoughtsList";
+import React from 'react';
+import BlogPage from './blog/page';
export default function Home() {
return (
-
-
-
-
-
+
+
+
);
diff --git a/app/thoughts/page.tsx b/app/thoughts/page.tsx
deleted file mode 100644
index e93faa32..00000000
--- a/app/thoughts/page.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import { getServerSession } from "next-auth/next";
-import { authOptions } from "@/lib/auth";
-import ThoughtsList from "@/components/ThoughtsList";
-import GitHubSignInButton from "@/components/GitHubSignInButton";
-
-export default async function ThoughtsPage() {
- const session = await getServerSession(authOptions);
-
- if (!session || !session.accessToken) {
- return ;
- }
-
- return ;
-}
diff --git a/app/unavailable/page.tsx b/app/unavailable/page.tsx
new file mode 100644
index 00000000..a22d4091
--- /dev/null
+++ b/app/unavailable/page.tsx
@@ -0,0 +1,11 @@
+export default function UnavailablePage() {
+ return (
+
+
Unsupported Region
+
+ If you see this message, it means Cofe is not supported in your
+ region. Please use a VPN or proxy to access Cofe.
+
+
+ );
+}
diff --git a/app/api/auth/[...nextauth]/route.ts b/auth/[...nextauth]/route.ts
similarity index 100%
rename from app/api/auth/[...nextauth]/route.ts
rename to auth/[...nextauth]/route.ts
diff --git a/components/BlogCard.tsx b/components/BlogCard.tsx
new file mode 100644
index 00000000..30164f52
--- /dev/null
+++ b/components/BlogCard.tsx
@@ -0,0 +1,38 @@
+"use client";
+
+import { BlogPost } from "@/lib/types";
+import Link from "next/link";
+
+export const BlogCard = ({ post }: { post: BlogPost }) => (
+
+
+
+
+
+ {post.title}
+
+
+
+ {formatDate(post.date)}
+
+
+
+
+);
+
+function formatDate(dateString: string): string {
+ const date = new Date(dateString);
+ return date.toISOString().split("T")[0]; // Returns YYYY-MM-DD format
+}
diff --git a/components/BlogList.tsx b/components/BlogList.tsx
index a3508bdc..52f0dc91 100644
--- a/components/BlogList.tsx
+++ b/components/BlogList.tsx
@@ -1,76 +1,49 @@
-"use client";
+'use client'
-import Link from "next/link";
-import { BlogPost } from "@/lib/githubApi";
+import { BlogCard } from './BlogCard'
+import { BlogPost } from '@/lib/types'
+import { Button } from '@/components/ui/button'
+import { useRouter } from 'next/navigation'
+import { useTranslations } from 'next-intl'
-function decodeTitle(title: string): string {
- try {
- return decodeURIComponent(title);
- } catch {
- return title;
- }
-}
-
-function extractContentPreview(content: string): string {
- const contentWithoutFrontmatter = content
- .replace(/^---[\s\S]*?---/, "")
- .trim();
- return (
- contentWithoutFrontmatter.slice(0, 150) +
- (contentWithoutFrontmatter.length > 150 ? "..." : "")
- );
-}
+export default function BlogList({ posts }: { posts: BlogPost[] }) {
+ const router = useRouter()
+ const t = useTranslations('HomePage')
-function formatDate(dateString: string): string {
- const date = new Date(dateString);
- return date
- .toLocaleString("en-GB", {
- year: "numeric",
- month: "2-digit",
- day: "2-digit",
- hour: "2-digit",
- minute: "2-digit",
- second: "2-digit",
- hour12: false,
- })
- .replace(/\//g, "/")
- .replace(",", "");
-}
+ if (posts.length === 0) {
+ return (
+
+
{t('noBlogPostsYet')}
+
+
+ )
+ }
-export default function BlogList({ posts }: { posts: BlogPost[] }) {
- const sortedPosts = [...posts].sort(
- (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()
- );
+ const sorted = [...posts].sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
return (
-
- {sortedPosts.length === 0 ? (
-
No blog posts found.
- ) : (
-
- )}
+
+
+
+ {sorted
+ .filter((_, index) => index % 2 === 0)
+ .map((post) => (
+
+ ))}
+
+
+ {sorted
+ .filter((_, index) => index % 2 !== 0)
+ .map((post) => (
+
+ ))}
+
+
- );
+ )
}
diff --git a/components/BlogPostContent.tsx b/components/BlogPostContent.tsx
new file mode 100644
index 00000000..61ebb786
--- /dev/null
+++ b/components/BlogPostContent.tsx
@@ -0,0 +1,114 @@
+'use client'
+
+import 'katex/dist/katex.min.css'
+
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
+
+import Giscus from '@giscus/react'
+import React from 'react'
+import ReactMarkdown from 'react-markdown'
+import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
+import { format } from 'date-fns'
+import rehypeKatex from 'rehype-katex'
+import remarkGfm from 'remark-gfm'
+import remarkMath from 'remark-math'
+import { tomorrow } from 'react-syntax-highlighter/dist/esm/styles/prism'
+
+interface BlogPostContentProps {
+ title: string
+ date: string
+ content: string
+ headerContent?: React.ReactNode
+}
+
+export function BlogPostContent({ title, date, content, headerContent }: BlogPostContentProps) {
+ return (
+
+
+
+ {title}
+
+
+ {headerContent}
+
+
+
+
) {
+ const match = /language-(\w+)/.exec(className || '')
+ return !inline && match ? (
+
+ {String(children).replace(/\n$/, '')}
+
+ ) : (
+
+ {children}
+
+ )
+ },
+ a: ({ children, ...props }) => (
+
+ {children}
+
+ ),
+ blockquote: ({ children }) => (
+
+ {children}
+
+ ),
+ img: ({ children, ...props }) => (
+
+
+ {children && {children}}
+
+ ),
+ }}
+ >
+ {content}
+
+
+
+
+
+ )
+}
diff --git a/components/CreateButton.tsx b/components/CreateButton.tsx
new file mode 100644
index 00000000..441a6924
--- /dev/null
+++ b/components/CreateButton.tsx
@@ -0,0 +1,43 @@
+"use client";
+
+import { AbstractIntlMessages } from "next-intl";
+import { FiPlus } from "react-icons/fi";
+import GitHubSignInButton from "./GitHubSignInButton";
+import Link from "next/link";
+import { usePathname } from "next/navigation";
+import { useSession } from "next-auth/react";
+
+export default function CreateButton({
+ messages,
+}: {
+ messages: AbstractIntlMessages;
+}) {
+ const pathname = usePathname();
+ const { data: session } = useSession();
+
+ const isMemosPage = pathname === "/" || pathname === "/memos";
+ const isBlogPage = pathname === "/blog";
+ const createLink = isBlogPage ? "/editor?type=blog" : "/editor?type=memo";
+
+ if (!session) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ {isMemosPage
+ ? (messages.createNewMemo as string)
+ : (messages.createNewBlogPost as string)}
+
+
+ );
+}
diff --git a/components/Editor.tsx b/components/Editor.tsx
index a4a406a8..91e79b78 100644
--- a/components/Editor.tsx
+++ b/components/Editor.tsx
@@ -1,22 +1,119 @@
"use client";
-import { useState } from "react";
-import { useRouter } from "next/navigation";
+import "katex/dist/katex.min.css";
+
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
+import { useCallback, useEffect, useState } from "react";
+import { useRouter, useSearchParams } from "next/navigation";
+
import { Button } from "@/components/ui/button";
+import { CgImage } from "react-icons/cg";
+import { GrInfo } from "react-icons/gr";
import { Input } from "@/components/ui/input";
-import { Textarea } from "@/components/ui/textarea";
-import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Label } from "@/components/ui/label";
-import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
-import { Loader2 } from "lucide-react"; // Import Loader2 icon
+import { Loader2 } from "lucide-react";
+import React from "react";
+import ReactMarkdown from "react-markdown";
+import SyntaxHighlighter from "react-syntax-highlighter";
+import { Textarea } from "@/components/ui/textarea";
+import { Tooltip } from "react-tooltip";
+import { createGitHubAPIClient } from "@/lib/client"
+import rehypeKatex from "rehype-katex";
+import remarkGfm from "remark-gfm";
+import remarkMath from "remark-math";
+import { tomorrow } from "react-syntax-highlighter/dist/esm/styles/prism";
+import { uploadImage } from "@/lib/githubApi";
+import { useDropzone } from "react-dropzone";
+import { useSession } from "next-auth/react";
+import { useToast } from "@/components/ui/use-toast";
+import { useTranslations } from "next-intl";
+
+function removeFrontmatter(content: string): string {
+ const frontmatterRegex = /^---\n([\s\S]*?)\n---\n/;
+ return content.replace(frontmatterRegex, "");
+}
-export default function Editor() {
+export default function Editor({
+ defaultType = "memo",
+}: {
+ defaultType?: "memo" | "blog";
+}) {
+ const router = useRouter();
+ const searchParams = useSearchParams();
const [content, setContent] = useState("");
const [title, setTitle] = useState("");
- const [type, setType] = useState("thought");
- const router = useRouter();
+ const [type, setType] = useState(
+ (searchParams.get("type") as "memo" | "blog") || defaultType
+ );
+ const [isPreview, setIsPreview] = useState(false);
const [isLoading, setIsLoading] = useState(false);
+ const [isImageUploading, setIsImageUploading] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
+ const t = useTranslations("HomePage");
+ const { data: session } = useSession();
+ const [editingMemoId, setEditingMemoId] = useState
(null);
+ const { toast } = useToast();
+ const [cursorPosition, setCursorPosition] = useState(null);
+
+ const fetchMemo = useCallback(
+ async (id: string) => {
+ if (!session?.accessToken) return;
+ try {
+ const memos = await createGitHubAPIClient(session.accessToken).getMemos()
+ const memo = memos.find((t) => t.id === id);
+ if (memo) {
+ setContent(memo.content);
+ }
+ } catch (error) {
+ console.error("Error fetching memo:", error);
+ }
+ },
+ [session?.accessToken]
+ );
+
+ const fetchBlogPost = useCallback(
+ async (id: string) => {
+ if (!session?.accessToken) return;
+ try {
+ const response = await fetch(`/api/github?action=getBlogPost&id=${id}`);
+ if (!response.ok) {
+ throw new Error("Failed to fetch blog post");
+ }
+ const blogPost = await response.json();
+ setTitle(blogPost.title);
+ setContent(removeFrontmatter(blogPost.content));
+ setEditingMemoId(id);
+ } catch (error) {
+ console.error("Error fetching blog post:", error);
+ }
+ },
+ [session?.accessToken]
+ );
+
+ useEffect(() => {
+ const params = new URLSearchParams(window.location.search);
+ params.set("type", type);
+ router.push(`/editor?${params.toString()}`);
+
+ const id = searchParams.get("id");
+
+ if (id) {
+ setEditingMemoId(id);
+ if (type === "blog") {
+ fetchBlogPost(id);
+ } else if (type === "memo") {
+ fetchMemo(id);
+ }
+ }
+ }, [type, router, searchParams, fetchMemo, fetchBlogPost]);
+
+ const handleTypeChange = (value: "blog" | "memo") => {
+ setType(value);
+ if (value === "blog") {
+ setEditingMemoId(null);
+ }
+ };
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
@@ -29,40 +126,214 @@ export default function Editor() {
"Content-Type": "application/json",
},
body: JSON.stringify({
- action: type === "blog" ? "createBlogPost" : "createThought",
+ action:
+ type === "blog"
+ ? editingMemoId
+ ? "updateBlogPost"
+ : "createBlogPost"
+ : editingMemoId
+ ? "updateMemo"
+ : "createMemo",
+ id: editingMemoId,
title,
content,
}),
});
if (!response.ok) {
- throw new Error("Failed to publish");
+ throw new Error(t("failedPublish"));
}
setIsSuccess(true);
+ toast({
+ title: t("success"),
+ description: editingMemoId
+ ? `${type === "blog" ? t("blogPostUpdated") : t("memoUpdated")}`
+ : `${type === "blog" ? t("blogPostCreated") : t("memoCreated")}`,
+ duration: 3000,
+ });
setTimeout(() => {
- router.push("/");
- }, 2000); // Redirect after 2 seconds
+ if (type === "blog") {
+ router.push("/blog");
+ } else {
+ router.push("/memos");
+ }
+ }, 2000);
} catch (error) {
console.error("Error publishing:", error);
- alert("Failed to publish. Please try again.");
+ toast({
+ title: t("error"),
+ description: t("failedPublish"),
+ variant: "destructive",
+ duration: 3000,
+ });
} finally {
setIsLoading(false);
}
};
+ const handleImageUpload = useCallback(
+ async (file: File) => {
+ if (!session?.accessToken) {
+ toast({
+ title: t("error"),
+ description: t("notAuthenticated"),
+ variant: "destructive",
+ duration: 3000,
+ });
+ return;
+ }
+
+ setIsImageUploading(true);
+ try {
+ const imageUrl = await uploadImage(file, session.accessToken);
+ const imageMarkdown = ``;
+
+ if (cursorPosition !== null) {
+ const newContent =
+ content.slice(0, cursorPosition) +
+ imageMarkdown +
+ content.slice(cursorPosition);
+ setContent(newContent);
+ } else {
+ setContent((prevContent) => prevContent + "\n\n" + imageMarkdown);
+ }
+
+ toast({
+ title: t("success"),
+ description: t("imageUploaded"),
+ duration: 3000,
+ });
+ } catch (error) {
+ console.error("Error uploading image:", error);
+ toast({
+ title: t("error"),
+ description: t("imageUploadFailed"),
+ variant: "destructive",
+ duration: 3000,
+ });
+ } finally {
+ setIsImageUploading(false);
+ }
+ },
+ [session?.accessToken, cursorPosition, content, toast, t]
+ );
+
+ const onDrop = useCallback(
+ async (acceptedFiles: File[]) => {
+ for (const file of acceptedFiles) {
+ if (file.type.startsWith("image/")) {
+ await handleImageUpload(file);
+ } else {
+ toast({
+ title: t("error"),
+ description: t("onlyImagesAllowed"),
+ variant: "default",
+ duration: 3000,
+ });
+ }
+ }
+ },
+ [handleImageUpload, toast, t]
+ );
+
+ const { getRootProps, getInputProps, isDragActive } = useDropzone({
+ onDrop,
+ noClick: true,
+ noKeyboard: true,
+ accept: {
+ "image/*": [],
+ },
+ });
+
+ const handlePaste = useCallback(
+ async (e: React.ClipboardEvent) => {
+ if (!session?.accessToken) {
+ toast({
+ title: t("error"),
+ description: t("notAuthenticated"),
+ variant: "destructive",
+ duration: 3000,
+ });
+ return;
+ }
+
+ const items = Array.from(e.clipboardData.items);
+ for (const item of items) {
+ if (item.type.startsWith("image/")) {
+ e.preventDefault();
+ setIsImageUploading(true);
+
+ try {
+ const file = item.getAsFile();
+ if (!file) continue;
+
+ const imageUrl = await uploadImage(file, session.accessToken);
+ const imageMarkdown = ``;
+
+ if (cursorPosition !== null) {
+ const newContent =
+ content.slice(0, cursorPosition) +
+ imageMarkdown +
+ content.slice(cursorPosition);
+ setContent(newContent);
+ } else {
+ setContent((prevContent) => prevContent + "\n\n" + imageMarkdown);
+ }
+
+ toast({
+ title: t("success"),
+ description: t("imageUploaded"),
+ duration: 3000,
+ });
+ } catch (error) {
+ console.error("Error uploading pasted image:", error);
+ toast({
+ title: t("error"),
+ description: t("imageUploadFailed"),
+ variant: "destructive",
+ duration: 3000,
+ });
+ } finally {
+ setIsImageUploading(false);
+ }
+ }
+ }
+ },
+ [session?.accessToken, cursorPosition, content, toast, t]
+ );
+
return (
-
-
-
- Create {type === "blog" ? "Blog Post" : "Thought"}
-
+
+ {(isLoading || isImageUploading) && (
+
+
+
+ )}
+
+
+
+ {type === "blog" ? t("createBlogPost") : t("createMemo")}
+
+ {t("publicContentWarning")}
+
+
+ {t("publicContentTooltip")}
+
+
+
+
-
+
diff --git a/components/Footer.tsx b/components/Footer.tsx
deleted file mode 100644
index 8dd4ef76..00000000
--- a/components/Footer.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-"use client";
-
-import { getUserLogin } from "@/lib/githubApi";
-import { useSession } from "next-auth/react";
-import Link from "next/link";
-import { useEffect, useState } from "react";
-
-const Footer = () => {
- const { data: session } = useSession();
- const [userLogin, setUserLogin] = useState(null);
-
- useEffect(() => {
- if (session?.accessToken) {
- getUserLogin(session.accessToken).then(setUserLogin);
- }
- }, [session]);
-
- if (!session || !session.user?.name || !userLogin) {
- return null;
- }
-
- const owner = userLogin;
- const repo = "tinymind-blog";
-
- return (
-
- );
-};
-
-export default Footer;
diff --git a/components/GitHubSignInButton.tsx b/components/GitHubSignInButton.tsx
index 178cee2d..0973549d 100644
--- a/components/GitHubSignInButton.tsx
+++ b/components/GitHubSignInButton.tsx
@@ -1,32 +1,17 @@
"use client";
import { signIn } from "next-auth/react";
-import { Button } from "@/components/ui/button"; // Add this import
+import { Button } from "@/components/ui/button";
import { Github } from "lucide-react";
const GitHubSignInButton = () => {
return (
-
-
-
How it works:
-
- -
- We create a public "tinymind-blog" repo in your GitHub.
-
- -
- Your new blog & thoughts will be automatically committed to this
- repo.
-
- - Your data is stored only on GitHub, independent of this site.
-
-
-
-
-
-
+
);
};
diff --git a/components/Header.tsx b/components/Header.tsx
index 7ce1eddf..0a919fbe 100644
--- a/components/Header.tsx
+++ b/components/Header.tsx
@@ -1,42 +1,121 @@
-"use client";
+'use client'
-import Link from "next/link";
-import { Button } from "@/components/ui/button";
-import { useState } from "react";
-import { usePathname } from "next/navigation";
+import { useEffect, useState } from 'react'
-export default function Header() {
- const pathname = usePathname();
- const [activeTab, setActiveTab] = useState(
- pathname === "/blog" ? "blog" : "thoughts"
- );
+import Image from 'next/image'
+import Link from 'next/link'
+import { Memo } from '@/lib/types'
+import { getUserLogin } from '@/lib/githubApi'
+import { useSession } from 'next-auth/react'
+
+interface HeaderProps {
+ latestMemo?: Memo
+ username?: string
+ iconUrl?: string
+}
+
+const getRelativeTimeString = (timestamp: string): string => {
+ const diff = Date.now() - new Date(timestamp).getTime()
+ const hours = Math.floor(diff / (1000 * 60 * 60))
+ const days = Math.floor(hours / 24)
+ const years = Math.floor(days / 365)
+
+ if (years > 0) return `${years} year${years > 1 ? 's' : ''} ago`
+ if (days > 0) return `${days} day${days > 1 ? 's' : ''} ago`
+ return `${hours} hour${hours > 1 ? 's' : ''} ago`
+}
+
+const Avatar = ({ src, alt, href }: { src: string; alt: string; href: string }) => (
+
+
+
+)
+
+const UserInfo = ({
+ displayName,
+ latestMemo,
+}: {
+ displayName: string
+ latestMemo: Memo | null
+}) => (
+
+
{displayName}
+
+
+ {latestMemo?.content
+ ? latestMemo.content.length > 24
+ ? `${latestMemo.content.substring(0, 24)} ...`
+ : latestMemo.content
+ : 'No status'}
+
+ •
+
+ {typeof window !== 'undefined' && window.location.pathname !== '/memos' && (
+ <>
+ •
+
+ more
+
+ >
+ )}
+
+
+)
+
+export default function Header({ username, iconUrl, latestMemo }: HeaderProps) {
+ // eslint-disable-next-line
+ const { data: session, status } = useSession()
+ const [userLogin, setUserLogin] = useState(null)
+ const [avatarUrl, setAvatarUrl] = useState('/icon.jpg')
+
+ useEffect(() => {
+ const updateAvatarUrl = async () => {
+ if (username) {
+ setAvatarUrl(`https://github.com/${username}.png`)
+ } else if (session?.accessToken) {
+ const login = await getUserLogin(session.accessToken)
+ setUserLogin(login)
+ setAvatarUrl(`https://github.com/${login}.png`)
+ }
+ }
+ updateAvatarUrl()
+ }, [session, username])
+
+ useEffect(() => {
+ if (iconUrl && iconUrl !== '/icon.jpg') {
+ setAvatarUrl(iconUrl)
+ }
+ }, [iconUrl])
+
+ const navigationPath = '/'
+ const displayName = userLogin || username || 'Anonymous'
return (
-
-
-
-
-
+
+
+
+
+
- );
+ )
}
diff --git a/components/MemosList.tsx b/components/MemosList.tsx
new file mode 100644
index 00000000..e50bd70d
--- /dev/null
+++ b/components/MemosList.tsx
@@ -0,0 +1,312 @@
+'use client'
+
+import 'katex/dist/katex.min.css'
+
+import {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from '@/components/ui/dialog'
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu'
+import { useEffect, useState } from 'react'
+
+import { AiOutlineEllipsis } from 'react-icons/ai'
+import { Button } from '@/components/ui/button'
+import GitHubSignInButton from './GitHubSignInButton'
+import { Memo } from '@/lib/types'
+import ReactMarkdown from 'react-markdown'
+import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
+import { createGitHubAPIClient } from '@/lib/client'
+import { getRelativeTimeString } from '@/lib/utils'
+import rehypeKatex from 'rehype-katex'
+import remarkGfm from 'remark-gfm'
+import remarkMath from 'remark-math'
+import { tomorrow } from 'react-syntax-highlighter/dist/esm/styles/prism'
+import { useRouter } from 'next/navigation'
+import { useSession } from 'next-auth/react'
+import { useToast } from '@/components/ui/use-toast'
+import { useTranslations } from 'next-intl'
+
+interface MemoCardProps {
+ memo: Memo
+ onDelete: (id: string) => void
+ onEdit: (id: string) => void
+}
+
+// TODO fix following
+export const MemoCard = ({ memo, onDelete, onEdit }: MemoCardProps) => {
+ const t = useTranslations('HomePage')
+
+ return (
+
+
+
+
+ {getRelativeTimeString(memo.timestamp)}
+
+
+
+
+
+
+ onDelete(memo.id)}>
+ {t('delete')}
+
+ onEdit(memo.id)}>{t('edit')}
+
+
+
+
) {
+ const match = /language-(\w+)/.exec(className || '')
+ return !inline && match ? (
+
+ {String(children).replace(/\n$/, '')}
+
+ ) : (
+
+ {children}
+
+ )
+ },
+ a: ({ children, ...props }) => (
+
+ {children}
+
+ ),
+ blockquote: ({ children }) => (
+ {children}
+ ),
+ }}
+ >
+ {memo.content}
+
+
+
+ )
+}
+
+interface MemosListProps {
+ username: string
+}
+
+export default function MemosList({ username }: MemosListProps) {
+ const [memos, setMemos] = useState
([])
+ const [error, setError] = useState(null)
+ const [isLoading, setIsLoading] = useState(true)
+ const [memoToDelete, setMemoToDelete] = useState(null)
+ const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false)
+ const { data: session, status } = useSession()
+ const router = useRouter()
+ const t = useTranslations('HomePage')
+ const { toast } = useToast()
+
+ useEffect(() => {
+ async function fetchMemos() {
+ if (status === 'loading') return
+ if (status === 'unauthenticated') {
+ setIsLoading(false)
+ }
+
+ try {
+ const fetchedMemos = await createGitHubAPIClient(session?.accessToken ?? '').getMemos(
+ username
+ )
+ setMemos(fetchedMemos)
+ setError(null)
+ } catch (error) {
+ console.error('Error fetching memos:', error)
+ if (
+ error instanceof Error &&
+ (error.message.includes('Bad credentials') ||
+ error.message.includes('Failed to get authenticated user'))
+ ) {
+ setError('authentication_failed')
+ } else {
+ setMemos([])
+ }
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ fetchMemos()
+ }, [session, status, username])
+
+ const handleDeleteMemo = async (id: string) => {
+ if (!session?.accessToken) {
+ console.error('No access token available')
+ return
+ }
+
+ try {
+ const response = await fetch('/api/github', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ action: 'deleteMemo',
+ id: id,
+ }),
+ })
+
+ if (!response.ok) {
+ throw new Error('Failed to delete memo')
+ }
+
+ setMemos(memos.filter((memo) => memo.id !== id))
+
+ toast({
+ title: t('success'),
+ description: t('memoDeleted'),
+ duration: 3000,
+ })
+ } catch (error) {
+ console.error('Error deleting memo:', error)
+ toast({
+ title: t('error'),
+ description: t('memoDeleteFailed'),
+ variant: 'destructive',
+ duration: 3000,
+ })
+ } finally {
+ setMemoToDelete(null)
+ setIsDeleteDialogOpen(false)
+ }
+ }
+
+ const handleEdit = (id: string) => {
+ router.push(`/editor?type=memo&id=${id}`)
+ }
+
+ const handleDelete = (id: string) => {
+ setMemoToDelete(id)
+ setIsDeleteDialogOpen(true)
+ }
+
+ if (status === 'unauthenticated' || error === 'authentication_failed') {
+ if (!username) {
+ return
+ }
+ }
+
+ if (error && error !== 'authentication_failed') {
+ return {error}
+ }
+
+ if (isLoading) {
+ return (
+
+
{t('readingFromGithub')}
+
+ )
+ }
+
+ if (memos.length === 0) {
+ return (
+
+
{t('noMemosYet')}
+
+
+ )
+ }
+
+ return (
+
+
+
+ {memos
+ .filter((_, index) => index % 2 !== 0)
+ .map((memo) => (
+
+ ))}
+
+
+ {memos
+ .filter((_, index) => index % 2 === 0)
+ .map((memo) => (
+
+ ))}
+
+
+
+
+ )
+}
diff --git a/components/PublicMemosList.tsx b/components/PublicMemosList.tsx
new file mode 100644
index 00000000..1c1c9572
--- /dev/null
+++ b/components/PublicMemosList.tsx
@@ -0,0 +1,56 @@
+"use client";
+
+import "katex/dist/katex.min.css";
+
+import { useEffect, useState } from "react";
+
+import { Memo } from "@/lib/types";
+import { MemoCard } from "./MemosList"; // Import MemoCard from MemosList
+import { formatTimestamp } from "@/lib/utils";
+
+type FormattedMemo = Memo & { formattedTimestamp: string };
+
+export default function PublicMemosList({
+ memos,
+}: {
+ memos: Memo[];
+}) {
+ const [formattedMemos, setFormattedMemos] = useState<
+ FormattedMemo[]
+ >([]);
+
+ useEffect(() => {
+ const formatted = memos.map((memo) => ({
+ ...memo,
+ formattedTimestamp: formatTimestamp(memo.timestamp),
+ }));
+ setFormattedMemos(formatted);
+ }, [memos]);
+
+ return (
+
+
+
+ {formattedMemos.filter((_, index) => index % 2 !== 0).map((memo) => (
+ {}}
+ onEdit={() => {}}
+ />
+ ))}
+
+
+ {memos.filter((_, index) => index % 2 === 0).map((memo) => (
+ {}}
+ onEdit={() => {}}
+ />
+ ))}
+
+
+
+ );
+}
diff --git a/components/TabNavigation.tsx b/components/TabNavigation.tsx
deleted file mode 100644
index 766f29d8..00000000
--- a/components/TabNavigation.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-"use client";
-
-import { useState } from "react";
-import Link from "next/link";
-
-export default function TabNavigation() {
- const [activeTab, setActiveTab] = useState("blog");
-
- return (
-
-
-
-
-
-
-
-
- );
-}
diff --git a/components/ThoughtsList.tsx b/components/ThoughtsList.tsx
deleted file mode 100644
index df208b45..00000000
--- a/components/ThoughtsList.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-"use client";
-
-import { useState, useEffect } from "react";
-import { useSession } from "next-auth/react";
-import { getThoughts, Thought } from "@/lib/githubApi";
-import GitHubSignInButton from "./GitHubSignInButton";
-import { useRouter } from "next/navigation";
-
-export default function ThoughtsList() {
- const [thoughts, setThoughts] = useState([]);
- const [error, setError] = useState(null);
- const { data: session, status } = useSession();
- const router = useRouter();
-
- useEffect(() => {
- async function fetchThoughts() {
- if (status === "loading") return;
- if (status === "unauthenticated") {
- setError("Please log in to view thoughts");
- return;
- }
- if (!session?.accessToken) {
- setError("Access token not available");
- return;
- }
-
- try {
- const fetchedThoughts = await getThoughts(session.accessToken);
- setThoughts(fetchedThoughts);
- setError(null); // Clear any previous errors
- } catch (error) {
- console.error("Error fetching thoughts:", error);
- if (
- error instanceof Error &&
- (error.message.includes("Bad credentials") ||
- error.message.includes("Failed to get authenticated user"))
- ) {
- setError("authentication_failed");
- } else {
- // Instead of setting an error, we'll just leave the thoughts array empty
- setThoughts([]);
- }
- }
- }
-
- fetchThoughts();
- }, [session, status]);
-
- if (status === "unauthenticated" || error === "authentication_failed") {
- return ;
- }
-
- if (error && error !== "authentication_failed") {
- return {error}
;
- }
-
- return (
-
- {thoughts.length === 0 ? (
-
-
-
- ) : (
-
- {thoughts.map((thought) => (
-
-
{thought.content}
- {/* {thought.image && (
-

- )} */}
-
- {new Date(thought.timestamp).toLocaleString()}
-
-
- ))}
-
- )}
-
- );
-}
diff --git a/components/ui/card.tsx b/components/ui/card.tsx
index 77e9fb78..53b4550a 100644
--- a/components/ui/card.tsx
+++ b/components/ui/card.tsx
@@ -9,7 +9,7 @@ const Card = React.forwardRef<
,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
+
+const DialogContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+ {children}
+
+
+ Close
+
+
+
+));
+DialogContent.displayName = DialogPrimitive.Content.displayName;
+
+const DialogHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+);
+DialogHeader.displayName = "DialogHeader";
+
+const DialogFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+);
+DialogFooter.displayName = "DialogFooter";
+
+const DialogTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+DialogTitle.displayName = DialogPrimitive.Title.displayName;
+
+const DialogDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+));
+DialogDescription.displayName = DialogPrimitive.Description.displayName;
+
+export {
+ Dialog,
+ DialogPortal,
+ DialogOverlay,
+ DialogTrigger,
+ DialogClose,
+ DialogContent,
+ DialogHeader,
+ DialogFooter,
+ DialogTitle,
+ DialogDescription,
+};
diff --git a/components/ui/dropdown-menu.tsx b/components/ui/dropdown-menu.tsx
new file mode 100644
index 00000000..242b07a6
--- /dev/null
+++ b/components/ui/dropdown-menu.tsx
@@ -0,0 +1,205 @@
+"use client"
+
+import * as React from "react"
+import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
+import {
+ CheckIcon,
+ ChevronRightIcon,
+ DotFilledIcon,
+} from "@radix-ui/react-icons"
+
+import { cn } from "@/lib/utils"
+
+const DropdownMenu = DropdownMenuPrimitive.Root
+
+const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
+
+const DropdownMenuGroup = DropdownMenuPrimitive.Group
+
+const DropdownMenuPortal = DropdownMenuPrimitive.Portal
+
+const DropdownMenuSub = DropdownMenuPrimitive.Sub
+
+const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
+
+const DropdownMenuSubTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, children, ...props }, ref) => (
+
+ {children}
+
+
+))
+DropdownMenuSubTrigger.displayName =
+ DropdownMenuPrimitive.SubTrigger.displayName
+
+const DropdownMenuSubContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DropdownMenuSubContent.displayName =
+ DropdownMenuPrimitive.SubContent.displayName
+
+const DropdownMenuContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, sideOffset = 4, ...props }, ref) => (
+
+
+
+))
+DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
+
+const DropdownMenuItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+
+))
+DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
+
+const DropdownMenuCheckboxItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, checked, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+DropdownMenuCheckboxItem.displayName =
+ DropdownMenuPrimitive.CheckboxItem.displayName
+
+const DropdownMenuRadioItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
+
+const DropdownMenuLabel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+
+))
+DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
+
+const DropdownMenuSeparator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
+
+const DropdownMenuShortcut = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => {
+ return (
+
+ )
+}
+DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
+
+export {
+ DropdownMenu,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuCheckboxItem,
+ DropdownMenuRadioItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuGroup,
+ DropdownMenuPortal,
+ DropdownMenuSub,
+ DropdownMenuSubContent,
+ DropdownMenuSubTrigger,
+ DropdownMenuRadioGroup,
+}
diff --git a/components/ui/font.ts b/components/ui/font.ts
new file mode 100644
index 00000000..9b8919e3
--- /dev/null
+++ b/components/ui/font.ts
@@ -0,0 +1,14 @@
+import { Inria_Sans } from 'next/font/google';
+import { Gowun_Dodum } from 'next/font/google';
+
+export const inria_sans = Inria_Sans({
+ weight: '400',
+ subsets: ['latin'],
+ display: 'swap',
+});
+
+export const gowun_wodum = Gowun_Dodum({
+ weight: '400',
+ subsets: ['latin'],
+ display: 'swap',
+});
\ No newline at end of file
diff --git a/components/ui/toast.tsx b/components/ui/toast.tsx
new file mode 100644
index 00000000..cc4e0ab2
--- /dev/null
+++ b/components/ui/toast.tsx
@@ -0,0 +1,129 @@
+"use client"
+
+import * as React from "react"
+import { Cross2Icon } from "@radix-ui/react-icons"
+import * as ToastPrimitives from "@radix-ui/react-toast"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const ToastProvider = ToastPrimitives.Provider
+
+const ToastViewport = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+ToastViewport.displayName = ToastPrimitives.Viewport.displayName
+
+const toastVariants = cva(
+ "group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
+ {
+ variants: {
+ variant: {
+ default: "border bg-background text-foreground",
+ destructive:
+ "destructive group border-destructive bg-destructive text-destructive-foreground",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+const Toast = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef &
+ VariantProps
+>(({ className, variant, ...props }, ref) => {
+ return (
+
+ )
+})
+Toast.displayName = ToastPrimitives.Root.displayName
+
+const ToastAction = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+ToastAction.displayName = ToastPrimitives.Action.displayName
+
+const ToastClose = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+))
+ToastClose.displayName = ToastPrimitives.Close.displayName
+
+const ToastTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+ToastTitle.displayName = ToastPrimitives.Title.displayName
+
+const ToastDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+ToastDescription.displayName = ToastPrimitives.Description.displayName
+
+type ToastProps = React.ComponentPropsWithoutRef
+
+type ToastActionElement = React.ReactElement
+
+export {
+ type ToastProps,
+ type ToastActionElement,
+ ToastProvider,
+ ToastViewport,
+ Toast,
+ ToastTitle,
+ ToastDescription,
+ ToastClose,
+ ToastAction,
+}
diff --git a/components/ui/toaster.tsx b/components/ui/toaster.tsx
new file mode 100644
index 00000000..ab247e75
--- /dev/null
+++ b/components/ui/toaster.tsx
@@ -0,0 +1,46 @@
+"use client";
+
+import { useToast } from "@/components/ui/use-toast";
+import {
+ Toast,
+ ToastClose,
+ ToastDescription,
+ ToastProvider,
+ ToastTitle,
+ ToastViewport,
+} from "@/components/ui/toast";
+import { BsCheckCircleFill } from "react-icons/bs";
+import { MdError } from "react-icons/md";
+
+export function Toaster() {
+ const { toasts } = useToast();
+
+ return (
+
+ {toasts.map(function ({ id, title, description, action, ...props }) {
+ const { variant } = props;
+ return (
+
+
+
+ {variant === "destructive" ? (
+
+ ) : (
+
+ )}
+
+ {title && {title}}
+
+ {description && (
+
{description}
+ )}
+
+ {action}
+
+
+ );
+ })}
+
+
+ );
+}
diff --git a/components/ui/use-toast.ts b/components/ui/use-toast.ts
new file mode 100644
index 00000000..8ca831bf
--- /dev/null
+++ b/components/ui/use-toast.ts
@@ -0,0 +1,191 @@
+import * as React from "react"
+
+import type {
+ ToastActionElement,
+ ToastProps,
+} from "@/components/ui/toast"
+
+const TOAST_LIMIT = 1
+const TOAST_REMOVE_DELAY = 3000 // Adjust the delay as needed
+
+type ToasterToast = ToastProps & {
+ id: string
+ title?: React.ReactNode
+ description?: React.ReactNode
+ action?: ToastActionElement
+}
+
+const actionTypes = {
+ ADD_TOAST: "ADD_TOAST",
+ UPDATE_TOAST: "UPDATE_TOAST",
+ DISMISS_TOAST: "DISMISS_TOAST",
+ REMOVE_TOAST: "REMOVE_TOAST",
+} as const
+
+let count = 0
+
+function genId() {
+ count = (count + 1) % Number.MAX_VALUE
+ return count.toString()
+}
+
+type ActionType = typeof actionTypes
+
+type Action =
+ | {
+ type: ActionType["ADD_TOAST"]
+ toast: ToasterToast
+ }
+ | {
+ type: ActionType["UPDATE_TOAST"]
+ toast: Partial
+ }
+ | {
+ type: ActionType["DISMISS_TOAST"]
+ toastId?: ToasterToast["id"]
+ }
+ | {
+ type: ActionType["REMOVE_TOAST"]
+ toastId?: ToasterToast["id"]
+ }
+
+interface State {
+ toasts: ToasterToast[]
+}
+
+const toastTimeouts = new Map>()
+
+const addToRemoveQueue = (toastId: string) => {
+ if (toastTimeouts.has(toastId)) {
+ return
+ }
+
+ const timeout = setTimeout(() => {
+ toastTimeouts.delete(toastId)
+ dispatch({
+ type: "REMOVE_TOAST",
+ toastId: toastId,
+ })
+ }, TOAST_REMOVE_DELAY)
+
+ toastTimeouts.set(toastId, timeout)
+}
+
+export const reducer = (state: State, action: Action): State => {
+ switch (action.type) {
+ case "ADD_TOAST":
+ return {
+ ...state,
+ toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
+ }
+
+ case "UPDATE_TOAST":
+ return {
+ ...state,
+ toasts: state.toasts.map((t) =>
+ t.id === action.toast.id ? { ...t, ...action.toast } : t
+ ),
+ }
+
+ case "DISMISS_TOAST": {
+ const { toastId } = action
+
+ // ! Side effects ! - This could be extracted into a dismissToast() action,
+ // but I'll keep it here for simplicity
+ if (toastId) {
+ addToRemoveQueue(toastId)
+ } else {
+ state.toasts.forEach((toast) => {
+ addToRemoveQueue(toast.id)
+ })
+ }
+
+ return {
+ ...state,
+ toasts: state.toasts.map((t) =>
+ t.id === toastId || toastId === undefined
+ ? {
+ ...t,
+ open: false,
+ }
+ : t
+ ),
+ }
+ }
+ case "REMOVE_TOAST":
+ if (action.toastId === undefined) {
+ return {
+ ...state,
+ toasts: [],
+ }
+ }
+ return {
+ ...state,
+ toasts: state.toasts.filter((t) => t.id !== action.toastId),
+ }
+ }
+}
+
+const listeners: Array<(state: State) => void> = []
+
+let memoryState: State = { toasts: [] }
+
+function dispatch(action: Action) {
+ memoryState = reducer(memoryState, action)
+ listeners.forEach((listener) => {
+ listener(memoryState)
+ })
+}
+
+type Toast = Omit
+
+function toast({ ...props }: Toast) {
+ const id = genId()
+
+ const update = (props: ToasterToast) =>
+ dispatch({
+ type: "UPDATE_TOAST",
+ toast: { ...props, id },
+ })
+ const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
+
+ dispatch({
+ type: "ADD_TOAST",
+ toast: {
+ ...props,
+ id,
+ open: true,
+ onOpenChange: (open) => {
+ if (!open) dismiss()
+ },
+ },
+ })
+
+ return {
+ id: id,
+ dismiss,
+ update,
+ }
+}
+
+function useToast() {
+ const [state, setState] = React.useState(memoryState)
+
+ React.useEffect(() => {
+ listeners.push(setState)
+ return () => {
+ const index = listeners.indexOf(setState)
+ if (index > -1) {
+ listeners.splice(index, 1)
+ }
+ }
+ }, [state])
+
+ return {
+ ...state,
+ toast,
+ dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
+ }
+}
+
+export { useToast, toast }
\ No newline at end of file
diff --git a/data/.gitkeep b/data/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/data/assets/1727016079577.jpg b/data/assets/1727016079577.jpg
new file mode 100644
index 00000000..b7936f26
Binary files /dev/null and b/data/assets/1727016079577.jpg differ
diff --git a/data/assets/images/2024-12-03/.gitkeep b/data/assets/images/2024-12-03/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/data/assets/images/2024-12-03/1733256504772.jpg b/data/assets/images/2024-12-03/1733256504772.jpg
new file mode 100644
index 00000000..13ac49b4
Binary files /dev/null and b/data/assets/images/2024-12-03/1733256504772.jpg differ
diff --git a/data/assets/images/2024-12-12/.gitkeep b/data/assets/images/2024-12-12/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/data/assets/images/2024-12-12/1734033798997.png b/data/assets/images/2024-12-12/1734033798997.png
new file mode 100644
index 00000000..1540417a
Binary files /dev/null and b/data/assets/images/2024-12-12/1734033798997.png differ
diff --git a/data/assets/images/2024-12-21/.gitkeep b/data/assets/images/2024-12-21/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/data/assets/images/2024-12-21/1734785902890.jpeg b/data/assets/images/2024-12-21/1734785902890.jpeg
new file mode 100644
index 00000000..32590103
Binary files /dev/null and b/data/assets/images/2024-12-21/1734785902890.jpeg differ
diff --git a/data/assets/images/2024-12-23/.gitkeep b/data/assets/images/2024-12-23/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/data/assets/images/2024-12-23/1734990512413.jpeg b/data/assets/images/2024-12-23/1734990512413.jpeg
new file mode 100644
index 00000000..7bafb49e
Binary files /dev/null and b/data/assets/images/2024-12-23/1734990512413.jpeg differ
diff --git a/data/assets/images/2024-12-23/1734991833894.jpeg b/data/assets/images/2024-12-23/1734991833894.jpeg
new file mode 100644
index 00000000..23251730
Binary files /dev/null and b/data/assets/images/2024-12-23/1734991833894.jpeg differ
diff --git a/data/assets/images/2024-12-23/1734993188114.jpeg b/data/assets/images/2024-12-23/1734993188114.jpeg
new file mode 100644
index 00000000..068ee537
Binary files /dev/null and b/data/assets/images/2024-12-23/1734993188114.jpeg differ
diff --git a/data/assets/images/2024-12-23/1734993280313.jpeg b/data/assets/images/2024-12-23/1734993280313.jpeg
new file mode 100644
index 00000000..4032f479
Binary files /dev/null and b/data/assets/images/2024-12-23/1734993280313.jpeg differ
diff --git a/data/assets/images/2024-12-23/1734993317984.jpeg b/data/assets/images/2024-12-23/1734993317984.jpeg
new file mode 100644
index 00000000..53b89217
Binary files /dev/null and b/data/assets/images/2024-12-23/1734993317984.jpeg differ
diff --git a/data/assets/images/2024-12-23/1734993411252.jpeg b/data/assets/images/2024-12-23/1734993411252.jpeg
new file mode 100644
index 00000000..56188584
Binary files /dev/null and b/data/assets/images/2024-12-23/1734993411252.jpeg differ
diff --git a/data/assets/images/2024-12-23/1734993425042.jpeg b/data/assets/images/2024-12-23/1734993425042.jpeg
new file mode 100644
index 00000000..cdf5db61
Binary files /dev/null and b/data/assets/images/2024-12-23/1734993425042.jpeg differ
diff --git a/data/assets/images/2024-12-23/1734993480342.jpeg b/data/assets/images/2024-12-23/1734993480342.jpeg
new file mode 100644
index 00000000..bb6c82a5
Binary files /dev/null and b/data/assets/images/2024-12-23/1734993480342.jpeg differ
diff --git a/data/assets/images/2024-12-23/1734993562660.jpeg b/data/assets/images/2024-12-23/1734993562660.jpeg
new file mode 100644
index 00000000..e946b5f2
Binary files /dev/null and b/data/assets/images/2024-12-23/1734993562660.jpeg differ
diff --git a/data/assets/images/2024-12-23/1734993625024.jpeg b/data/assets/images/2024-12-23/1734993625024.jpeg
new file mode 100644
index 00000000..ec8552d1
Binary files /dev/null and b/data/assets/images/2024-12-23/1734993625024.jpeg differ
diff --git a/data/assets/images/2024-12-23/1734993677103.jpeg b/data/assets/images/2024-12-23/1734993677103.jpeg
new file mode 100644
index 00000000..7de17a7e
Binary files /dev/null and b/data/assets/images/2024-12-23/1734993677103.jpeg differ
diff --git a/data/assets/images/2024-12-23/1734993893285.jpeg b/data/assets/images/2024-12-23/1734993893285.jpeg
new file mode 100644
index 00000000..8a99243b
Binary files /dev/null and b/data/assets/images/2024-12-23/1734993893285.jpeg differ
diff --git a/data/assets/images/2024-12-23/1734993996131.jpeg b/data/assets/images/2024-12-23/1734993996131.jpeg
new file mode 100644
index 00000000..d63f836e
Binary files /dev/null and b/data/assets/images/2024-12-23/1734993996131.jpeg differ
diff --git a/data/assets/images/2024-12-23/1734994035144.jpeg b/data/assets/images/2024-12-23/1734994035144.jpeg
new file mode 100644
index 00000000..856ca771
Binary files /dev/null and b/data/assets/images/2024-12-23/1734994035144.jpeg differ
diff --git a/data/assets/images/2024-12-23/1734994064317.jpeg b/data/assets/images/2024-12-23/1734994064317.jpeg
new file mode 100644
index 00000000..b43daeea
Binary files /dev/null and b/data/assets/images/2024-12-23/1734994064317.jpeg differ
diff --git a/data/assets/images/2024-12-23/1734994128679.jpeg b/data/assets/images/2024-12-23/1734994128679.jpeg
new file mode 100644
index 00000000..6e5fc271
Binary files /dev/null and b/data/assets/images/2024-12-23/1734994128679.jpeg differ
diff --git a/data/assets/images/2024-12-23/1734994189257.jpeg b/data/assets/images/2024-12-23/1734994189257.jpeg
new file mode 100644
index 00000000..30ac80be
Binary files /dev/null and b/data/assets/images/2024-12-23/1734994189257.jpeg differ
diff --git a/data/assets/images/2024-12-23/1734994521799.jpeg b/data/assets/images/2024-12-23/1734994521799.jpeg
new file mode 100644
index 00000000..2dc3917a
Binary files /dev/null and b/data/assets/images/2024-12-23/1734994521799.jpeg differ
diff --git a/data/assets/images/2024-12-23/1734994530669.jpeg b/data/assets/images/2024-12-23/1734994530669.jpeg
new file mode 100644
index 00000000..e8f35c9d
Binary files /dev/null and b/data/assets/images/2024-12-23/1734994530669.jpeg differ
diff --git a/data/assets/images/2024-12-24/.gitkeep b/data/assets/images/2024-12-24/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/data/assets/images/2024-12-24/1735032434591.jpeg b/data/assets/images/2024-12-24/1735032434591.jpeg
new file mode 100644
index 00000000..83b1dd53
Binary files /dev/null and b/data/assets/images/2024-12-24/1735032434591.jpeg differ
diff --git a/data/assets/images/2024-12-24/1735068363518.jpeg b/data/assets/images/2024-12-24/1735068363518.jpeg
new file mode 100644
index 00000000..83b1dd53
Binary files /dev/null and b/data/assets/images/2024-12-24/1735068363518.jpeg differ
diff --git a/data/assets/images/Cofe-app.png b/data/assets/images/Cofe-app.png
new file mode 100644
index 00000000..4baa6024
Binary files /dev/null and b/data/assets/images/Cofe-app.png differ
diff --git a/data/blog/.gitkeep b/data/blog/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git "a/data/blog/\344\270\212\346\265\267\345\210\260\351\230\277\345\247\206\346\226\257\347\211\271\344\270\271.md" "b/data/blog/\344\270\212\346\265\267\345\210\260\351\230\277\345\247\206\346\226\257\347\211\271\344\270\271.md"
new file mode 100644
index 00000000..3e19e4a5
--- /dev/null
+++ "b/data/blog/\344\270\212\346\265\267\345\210\260\351\230\277\345\247\206\346\226\257\347\211\271\344\270\271.md"
@@ -0,0 +1,114 @@
+---
+title: 上海到阿姆斯特丹
+date: 2024-09-27T17:56:10.267Z
+---
+
+## 前言
+
+从上海来到阿姆斯特丹也一周了,两个小朋友,两个大人,一个决定,年初做这个决定,也曾有过很多迟疑,然而当决定做好了之后,两个相隔万里的城市,也仅仅是一张飞机票的距离。
+
+
+
+## 决定
+
+我很早就有在小朋友们的小学阶段,带他们去国外生活一段时间的想法,一方面是自己想去体验世界,一方面也是想让小朋友在小时候能感受不同的文化氛围。而现在是一个很好的时机,姐姐三年级,弟弟一年级,多少能自己照顾点自己,而同时又能相互陪伴。
+
+当然更重要的是我和老婆的决定,我们两个人都在上海呆十年左右,工作暂时还算轻松稳定,有房无贷,我们两对于物质也没有啥过高的要求,但是你知道,在上海,无论是否有生活压力,每天的日程都是比较紧凑的。对于小朋友的教育,虽然心里有所期待,但是整体不是虎爸虎妈,健康快乐,健全人格为主。对于生活环境,我们都是小镇青年,从小学,中学,大学,到工作,也不断的从一个城市到一个城市,基本上对于各种环境也有一定的容忍度,自认为可以适应各种环境变化和文化差异。
+
+不过显然这还是一个不小的决定,而且这个决定会给很多人带来变化:
+
+**自己**: 还是原来的公司,原来的团队,原来的工作内容,只是换一个城市,需要去适应新的工作氛围,需要重新去经营和老板,以及身边的同事的关系。
+
+**小朋友**: 他们需要去适应新的学校,新的教育方式,结交新的小朋友,对他们来说也是很大的挑战。但是和他们沟通之后,他们也拥抱我们的决定。
+
+**伴侣**: 而我老婆也同样需要面对的新的挑战,陌生的环境,而且如果辞去当前的工作,意味她需要全部从零开始,她很坦白的和她老板坦白自己的想法,她老板也也很理解和宽容,可以不用辞职,去阿姆斯特丹远程工作两个月,尝试自己是否适应那边的生活,然后在做决定。
+
+**父母**: 我们两个人的服务年龄都在60岁上下,目前能照顾自己,虽然他们都不是很同意我们去那么远的地方去工作生活,但是也拗不过我们的决定,我有妹妹,我老婆有弟弟,他们也能在紧急的时候帮我们照顾他们。一方面在上海的时候,他们也帮我们照顾小朋友很多年了,也想给他们自己的时间,让他们有自己的生活,我爸就特别想把车练好,然后开车走一走中国.
+
+所以整个决定就变成了:**让我们去阿姆斯特丹生活一段时间吧,小朋友去当地国际学校,妈妈不辞职,远程工作一段时间,爸爸原职位不变,只是换城市工作。**
+
+## 流程
+
+决定做好了之后,流程就比较简单了,和老板提出自己想去阿姆斯特丹工作的想法,然后老板和对应的老板沟通商议,然后确定新 Offer,HR 发新的合同,签订合同之后就开始 Relocation 的流程了,这部分公司有专门的 Global Mobility 团队来负责。所以只需要按照他们的步骤来一步步进行就好了。
+
+### 签证申请
+
+公司有专门的服务公司来帮忙完成这一部分,也就是向荷兰移民局(IND) 申请高技术移民签证和家庭成员的居留签证。这个过程还是需要一定的时间,当然对于我们来说,就是根据他们的材料清单去准备材料即可。如果一切都顺利,就收到到 IND 的 Approval Letter,上面有所有成员的 V Number, 有了这个之后,就可以预约荷兰大使馆或者领事馆去递签证了,使馆和领事馆递签也需要带必要的文件,递签顺利的换,几天之后就可以收到盖好签证的护照。有了护照就可以开始后面的流程了。
+
+### 海运
+
+公司有安排专门的搬家公司帮我们全程完成搬家,非常专业的搬家服务,他们会先和你预约一个时间进行初步的沟通,了解大致需要海运的东西,可能是为后期的海运打包工具,和打包人员安排做准备。然后预约一个现场打包的时间,当天他们会全部帮你进行打包和搬运上他们的车,你只需要告诉他们需要打包和搬运的物品即可,基本上不需要你动手。
+
+基本上如果你想把整个家从上海复制到阿姆斯特丹几乎没有什么问题,只是我们一切从简,只海运那些我们需要的东西,比如一箱书籍,厨具,三辆自行车,衣物和棉被,刚买不久的床垫,以及不少的其他东西。
+
+### 机票
+
+基本只要拿到了签好的护照,就可以让公司帮忙预定机票了,我们的时间比较的紧凑,拿到护照之后只有不到十天的时候来进行海运的安排和飞机票的购买了,不过最后好在一切都很顺利,公司定的机票的日期前一天,海运正好弄完。如果没有让公司购买指定的航司,公司一般会优先预定KLM的航班。
+
+## 出发
+
+前一天刚刚打包完海运,今天一大早就出发去浦东机场了,所有的安排都是紧凑的,四个大大的箱子,四个些许有点忐忑又有点其他的心。而且姐姐努力上进的心,竟然说要在机场参加全国青少年信息素养大赛 Python 编程挑战赛的复赛。所以就有这张我办理登机牌时候,姐姐还在努力写程序的照片.
+
+
+
+### 飞行
+
+我们乘坐的是 KLM 从上海浦东直飞阿姆斯特丹史基浦机场的航班,飞机基本满员,总共飞了13个小时50分钟,餐食还凑合,看看电影,玩玩游戏,睡睡觉,不知不觉也就到了。
+
+### 落地
+
+周六上海早上 11:30 出发,落地阿姆斯特丹的时间是下午的 19:05 分,跟着人群按照路标直接走到通关口即可,通关也很顺利,就简单问一下来阿姆斯特丹的目的,以及时间,看到我拿到的工作签证之后,盖个章便出来,去取行李。
+
+公司有有安排好的出租车服务,取完行李按照公司提前发的路线图去等司机来接,看到小红书有不少朋友提到被出关开行李检查的经历,我们并没有发生,我也没有看到同飞机的任何人有被查,也许只是幸运。我们到了指定的候车点之后,直接打了司机的电话,天还很亮,上海三十几度的温度,到了阿姆斯特丹大约不到二十度,幸好我们都带来外套。几分钟之后司机就到了,司机很友善帮忙行李放到后备箱,很熟悉送到了公司安排的酒店。
+
+### 酒店
+
+公司为海外调到阿姆斯特丹工作的员工安排四周(单身)或者六周(家庭)的临时酒店,并且在酒店的接待处可以拿到一个小礼包,里面有一张公交卡, 这边是 OV Card, 和当地小零食,这个公交卡是为了在我们办理自己的各种卡之前方便出行用的。
+
+我们的酒店位于 Cruiquiuseiland 的 YAYS Amsterdam Docklands, 入住之前短信和邮件都以及发送给我相关的入住信息,直接通过 pin code 就可以入住,一个两室的格局,可以做饭。当天到酒店时候,已经是晚上八点多了,所以洗漱结束之后就休息了。
+
+## 第一周
+
+这一整周天气都很好。这也是我选择七月来荷兰的原因,不想一开始进入 hard 模式。当然我这一周的主要任务有三个: 熟悉当地,BSN注册,居留证办理,银行卡办理。
+
+### 第一天
+
+因为正好是周六过来的,所以周日可以好好休息一天,调整一下时差。第二天早上,我们六点多就醒了,然后洗漱之后,就出门到附近走走,显然一大早除了运动的人,没有什么太多人,路上的人挺友善的打招呼,我们有点饿,不过基本上所有的店在 Google 地图上(还是 Yelp) 都显示尚未营业。在公交车车站,我们问一个老奶奶附近的超市在哪儿,是否已经开门,她英文挺好的,指路并且提到他们开门挺早,而且不远,她几乎每天都去。我们顺着她的方向走大约几百米,果然就到了。
+
+超市和国内超市区别不大,不过大部分的商品信息多数是荷兰语。虽然很早,但是已经也有人采购东西,我们主要是购买一些面包,牛奶,蔬菜,和肉类,打算有空的时候做做饭,不了解的东西我都是直接用英文询问其他人,他们英文也挺好。
+
+在我们回酒店的路途中,陆陆续续来来回回的人就多一些了,很多骑自行车的人,而路边也超多的自行车,不时就有人打招呼。
+
+### 购物和交通
+
+购物我基本上就直接去酒店附近的 Albert Heijin 或者 Jumbo,基本和国内的大超市也差多,生活用品基本上都可以买到,肉奶蛋类和国内价格似乎无差,蔬菜类会贵一些。如果你有一张 contactless 的 VISA 信用卡,大超市似乎应该都支持支付。不过我很失误,我只带了招商银行的mini信用卡,不支持 contactless, 基本上刷不了,幸好我带了一点欧元出来。
+
+阿姆斯特丹的交通还是很便利的,有很多的选择: Train, Tram, Bus, Metro, Ferry, Bus, 当然最最最最最重要,还是 Bicycle 啦,自行车真的太重要了在这里,一方面是公共交通真的不便宜,另一方面,它真的应该是最方便的交通工具了。
+
+### BSN 注册 和 IND 居留卡
+
+公司的服务团队在我到阿姆斯特丹之前已经帮我预约好了 BSN 注册和 IND,所以我只需要按时间去到阿姆斯特丹外籍服务中心,现在叫IN Amsterdam。为了按时到达,我们早上吃饭早饭,就一起做上公交车,然后换乘电车到达了 IN Amsterdam。
+
+因为我们家四个人都要办理,而且有小朋友,文件还是满多的,整个过程花了大约2个小时,办事人员也很友善,不过还是很顺利的,我们拿到了自己的 BSN 以及居留卡,从此就是合法阿姆斯特丹打工仔了。
+
+
+
+在办理完 BSN 注册和居留卡之后我们在 IN Asterdam 的楼里吃午饭,没有想到,他们家还挺好吃,虽然略微有些贵,小朋友们很喜欢,我们吃的也很满意。
+
+### 银行卡
+
+公司把办理银行卡的时间约在 BSN 注册的下午,从 IN Amsterdam 到公司预约的银行 ABN AMRO 不是很远,公交车一会就到了,办理也很顺利,简单的信息了解,前几个字,就完成了我们两个人联合账户的初步开通,不过当场并不能拿到卡,需要审批完了之后后面直接邮寄到我的地址。
+
+
+
+## 初印象
+
+一周过去了,我也完成在阿姆斯特丹正常生活必备的一些步骤,新生活也慢慢展开了。阿姆斯特丹这一周给我的初印象大致是,
+
+
+
+大家较为友好,也许是因为我接触的人还很少,但是整体来说,路上时不时都会有人友善的打招呼,遇到问题咨询的时候,大家也很耐心的解答。我们在路上掉了运动器材的包包,人们也很友善的提醒,我有一次在超市掉了二十欧纸币,也有人拿到外面找我归还;
+
+英文的普及率很高,接触过的所有人都可以很流利的说英文,无论是公共部门的办事人员,还是路边的店员,或者是路人,无聊时上了年纪的老人,还是偶遇的年轻人,英文沟通都非常的顺畅。
+
+城市比我想象中的漂亮,河道很多,建筑也很有特点,走在路上时不时都会有小惊喜,很可惜我们海运的三辆自行车还没有到,不然真的想好好骑行整个城市。
\ No newline at end of file
diff --git "a/data/blog/\345\215\216\344\270\272\351\270\277\350\222\231-harmaryos-next-\347\272\277\344\270\213\346\264\273\345\212\250\345\260\217\350\256\260.md" "b/data/blog/\345\215\216\344\270\272\351\270\277\350\222\231-harmaryos-next-\347\272\277\344\270\213\346\264\273\345\212\250\345\260\217\350\256\260.md"
new file mode 100644
index 00000000..d6cfff53
--- /dev/null
+++ "b/data/blog/\345\215\216\344\270\272\351\270\277\350\222\231-harmaryos-next-\347\272\277\344\270\213\346\264\273\345\212\250\345\260\217\350\256\260.md"
@@ -0,0 +1,180 @@
+---
+title: 华为鸿蒙 HarmaryOS Next 线下活动小记
+date: 2024-04-21T05:58:06.454Z
+---
+
+
+
+
+## 偶然决定
+
+很久都没有参加线下活动了,周末的时间基本上,要么陪小朋友运动,要么陪小朋友写作业或者准备比赛,要么陪夫人练车。很偶然的在朋友圈刷到华为鸿蒙的线下开发者活动,心血来潮就报名了,捡一捡线下开发的感觉,也是一个很好的了解鸿蒙生态的一个机会吧。
+
+
+
+我甚至都没有详细看活动议程,甚至都没有想过是不是需要做一点准备,而本人也确实没有一点鸿蒙开发的经验,甚至 Android 开发也没有正经弄过,也就是维护两个小 iOS 应用。但是不妨碍我参加一个以鸿蒙为主题的开发者比赛活动呀,没准比赛中可以完成一个最小可用产品,顺利的开启自己的鸿蒙生涯呢。
+
+## 活动当天
+
+### 早上
+周日和夫人安排好小朋友的学习和活动安排,提醒夫人关注好小朋友的事项安排重点之后,我就出发去活动了,原来活动在徐汇漕河泾的字节办公楼,我从杨浦江湾体育场这边过去,相当于从上海的北边到南边,还是蛮远。想到地铁还要转车,算了咖啡直接打车吧。
+
+上了车想到如果要组队,要不就随机叫个朋友一起吧,所以发个消息给 @Chester ,Checster 也很干脆,一起来玩了。等我到场地时候,活动已经开始了,迟到了十几分钟。Chester 比我迟到更多了,我们完全没有注意到活动的议程,其实人家的流程还是紧凑的。
+
+
+
+早上主要主办方和华为工作人员对活动和鸿蒙的一些介绍,我们两人在期间对于今天要做什么进行一些头脑风暴,"Cross OS HandOff" 这个想法很自然的就到我的脑海中,简单的讨论之后我们就决定做这个想法了。跨生态互操作,想想就很好玩,当然也想想就好难,不过我想着做一个PoC应该问题不大。
+
+### 午餐
+
+大约到了十二点多开始午餐,午餐很一般,略过.
+
+### 下午
+
+#### 设计
+
+下午就是写代码的时间啦. 我们基本上采用了 "Demo Driven" 的开发方案。首先当然是定义我们的目标了。
+
+**我的愿景: 移动设备用户在不同生态(iOS, Android, HarmonyOS等)的设备之间可以做到无缝的持续性操作.**
+
+*场景1*: 比如我在的的 iPhone 手机上的记事本上正在创作,突然想要切换我的 HarmonyOS 笔记本上继续我的创作,键盘输入让我创作更快捷。这时候如果有了 Cross OS 的 HandOff, 它将自动把我 iPhone 上的记事本的状态变成 HarmonyOS 上记事本的应用状态,我直接在 HarmonyOS无缝的继续我的创作。
+
+*场景2*: 比如我在的 HarmonyOS 的笔记本上上的地图软件上查询我到目的地路线,现在我准备出门打车,那么我就可以通过 Cross OS HandOff 的功能在我的 iOS iPhone 手机上的地图应用继续的操作。
+
+这些都是我最初想法想达到的愿景,但是想到现在也就是不到两个小时的时间了,我们如何实现一个 demo 来阐述我们的理念呢。其实要达到我们的目标,主要完成两个主要的部分:
+
+> 为了更好的表达,我们把两个不同的生态上的应用分别进行简单的编号. 在运行着 iOS 系统的 iPhone 设备的某应用程序,我们编号 A. 在运行着 HarmonyOS 系统的鸿蒙设备的某同类型的应用程序,我们编号 B.
+
+* 应用状态快照
+
+为了实现 A 到 B 的 HandOff, 我们需要先将此刻的 A 的状态进行快照。
+
+* 数据传输
+
+为了实现在 B 上继续用户在 A 上的操作,我们需要将上一步的 A 的应用程序状态快照传输到 B 的设备上。
+
+* 应用状态恢复
+
+当运行在 B 的设备收到 A 的状态快照之后,将状态快照恢复成 B 可以识别的状态,并且应用到 B 应用上。
+
+但是很显然,在两个小时里我们是无法完成一个完善的方案的,所以我们最后定下来一个方案实现 PoC 并且很大概率可以完成现场 demo 的方案。
+
+> 两个设备通过局域网进行通信,然后可以选择一个 Mac 应用,在Mac 进行一些一系列操作之后,然后我们转移到 HarmonyOS 设备上时,我们可以在同一个应用(或者同类型)的应用继续我们之前在 Mac 上的工作,保持完整的持续性.
+
+这样我们就很完整的展示我们的概念,**然而**现实环境还是残酷,[^7b0daa3b-0af8-450f-824d-52aea73be8f7-1714018784523]在现场华为工程师的协助下,我们仍然没有办法成功在我们的 Mac 上能够完成 DevEcho - Studio 上运行一个 HarmonyOS 的模拟器,所以为了在短暂时间内有一个像样的 demo,我们选择一个简单的场景:
+
+> 我在我的 Mac 上运行一个剪贴板管理 uPaste,我的队友也在他的 Mac 上运行这个 uPaste, 然后我会在我的 Mac 上进行一系列的复制粘贴操作,在不依赖 Mac 系统的内置同步功能的情况下,这些操作会自动在另外一台 Mac 的 uPaste 应用上自动同步.
+
+#### 实现
+
+* 数据传输
+
+我们俩对于网络部分的知识确实有些捉襟见肘,想着无论是基于蓝牙,还是 WiFi 都可以,至少不能直接使用公网来传输数据。简单搜索一通之后,我们感觉无论是使用 WiFi Direct 还是蓝牙,感觉我们都无法这两个小时内 demo 出完整的流程,所以我们决定:直接在同一个局域网内,两个设备通过 Socket 来进行数据通信.
+
+* 应用数据状态发送
+
+因为我们选择了最简单的剪贴板管理应用,所以应用状态就就很简单了: 用户当前的剪贴板内容. 所以我们只需要在适当的时候(用户进行 HandOff 操作的时候)进行一个剪贴板内容发送即可.
+
+* 应用数据状态恢复
+
+而在目标设备上,我们只需要将接收到剪贴板数据,写入到当前设备的剪贴板即可。
+
+这样这个演示的流程就简单可控了,在清楚阐述我们的设计思路的同时也确保最低概率的演示翻车.
+
+所以整个演示就变成了几段非常简单的代码而已。
+
+监听并且收到数据之后直接写入剪贴板.
+
+```py
+import socket
+
+class Server:
+ def __init__(self, host, port):
+ self.host = host
+ self.port = port
+ self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+
+ def start(self):
+ self.server_socket.bind((self.host, self.port))
+ self.server_socket.listen()
+ while True:
+ client_socket, client_address = self.server_socket.accept()
+ self.handle_client(client_socket)
+
+ def handle_client(self, client_socket):
+ while True:
+ data = client_socket.recv(1024).decode()
+ if not data:
+ break
+ client_socket.close()
+
+if __name__ == "__main__":
+ HOST = '172.20.10.15' # Listen on all available interfaces
+ PORT = 8080
+ server = Server(HOST, PORT)
+ server.start()
+
+```
+
+持续从剪贴板读取数据,如果有变化通过 socket 发送剪贴板内容。
+
+```py
+import socket
+import subprocess
+
+def get_clipboard_content():
+ process = subprocess.Popen(['pbpaste'], stdout=subprocess.PIPE)
+ output, _ = process.communicate()
+ return output.decode('utf-8')
+
+class Client:
+ def __init__(self, host, port):
+ self.host = host
+ self.port = port
+ self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+
+ def send_message(self, message):
+ self.client_socket.connect((self.host, self.port))
+ self.client_socket.sendall(message.encode())
+ self.client_socket.close()
+
+if __name__ == "__main__":
+ HOST = '172.20.10.5' # IP address of the receiver
+ PORT = 8080
+
+ tmp = get_clipboard_content()
+ while True:
+ source = get_clipboard_content()
+ if source and source != tmp:
+ client = Client(HOST, PORT)
+ client.send_message(source)
+ tmp = source
+
+```
+
+上面的代码就是当时我们 demo 所使用的代码,你可能会发现非常多的改进点,但是这就是 demo,让流程走起来比代码的完善更重要,一切的改动的出发点应该都是让 demo 更加完整流畅,能让观众和评审更容易理解和参与。
+
+
+#### Demo
+
+可能是周末写字楼物业不开空调还是怎么的,活动的会议室非常的热,我们只能跑到外面的开放空间写代码。我们一个个的解决掉项目中的问题,感觉似乎从演示的层面上差不多的时候,工作人员跑过来说“你们怎么还在这呢,演示都开始了”,后来我们才了解到,原来最终的作品其实不需要一个可以运行的代码,只要一份演示的 ppt 即可,并且演示是需要报名的,只有十个名额,显然像我们这样,人家都开始演示,我们都还在调试代码的团队,显然就没有机会了。
+
+然而在我们回到会议室,看着别人的演示,觉得既没有特别惊艳的创意,也没有硬核的技术展示的时候,我们琢磨着全部参加完整个流程似乎太晚回家了,打算伺机离开的时候,评委说:“我们再增加两个展示名额吧”,看来评委也希望可以看到更多的项目,更多的可能性。我们想着既然做都做了,展示成果,也算今天没有白来。所以立即就举手上台展示了.
+
+玩完没有想到,展示的效果还挺好,现场观众的反应和评委的评价都比较的积极,还是蛮开心。
+
+> 小插曲: 不知道为啥,当我们使用场地的局域网的时候,socket 连接不成功,最后我们只能通过我们手机开热点形式来完成我们的展示.
+
+
+
+#### 投票和获奖
+
+万万没想想到,最后我们获得票数第二名的成绩,当然这也包括我们自己的投票😂,但是很开心,希望我们在后面的六月份的决赛中[^aC5taW5naGVAZ21haWwuY29t-1721555382700],也可以也能收获幸运。
+
+
+
+
+
+[^7b0daa3b-0af8-450f-824d-52aea73be8f7-1714018784523]: 华为如果可以在开发者体验方面再进一步,将极大有助于生态的繁荣.
+
+[^aC5taW5naGVAZ21haWwuY29t-1721555382700]: 很可惜,由于客观原因,我们没有能前往东莞参加决赛.
diff --git "a/data/blog/\350\245\277\347\217\255\347\211\231\344\270\203\345\244\251\347\232\204\346\227\205\350\241\214.md" "b/data/blog/\350\245\277\347\217\255\347\211\231\344\270\203\345\244\251\347\232\204\346\227\205\350\241\214.md"
new file mode 100644
index 00000000..4469043a
--- /dev/null
+++ "b/data/blog/\350\245\277\347\217\255\347\211\231\344\270\203\345\244\251\347\232\204\346\227\205\350\241\214.md"
@@ -0,0 +1,124 @@
+---
+title: 西班牙七天的旅行
+date: 2024-12-24T19:01:37.622Z
+---
+
+
+
+如果你在欧洲的公司工作,十二月份的很多时候,如果安排多人的会议,大概率你是找不齐所有人的,因为十二月份是大家休假的集中期,所以有些公司也会在这个期间有一个长达两三周的
+`Change Freeze`. 我也选择在这段时间休假,不过休假旅行的地点是小朋友们定的,姐姐(惜惜)和弟弟(佑佑)最终选择了西班牙:巴塞罗那和马德里。
+
+> 本来也想着马德里之后去葡萄牙的里斯本和波尔多,然后再回阿姆斯特丹,但是想着虽然当前虽然天气很好,但是可能户外游泳还是有点冷,而且第一次一个人带着姐姐和弟弟两人旅行,所以就想等着妈妈到了之后,春天的时候再安排一次。
+
+## 准备和出发
+
+### 交通
+
+选定了城市之后,一切就很简单了,我来负责购买机票和预定酒店,惜惜和佑佑通过看Youtube的相关频道来决定我们的旅行攻略。从阿姆斯特丹到巴塞罗那的航班很多,我们当然是选择便宜的航班出行了,选了几家所谓的经济航班,最后我们我们的交通方案是:
+
+* 阿姆斯特丹到巴塞罗那, 乘坐Vueling (伏林航空),三个人的价格大约三百欧元。
+* 巴塞罗那到马德里,乘坐火车,成人价格是六十五欧,儿童是七欧。
+* 马德里回阿姆斯特丹,乘坐AirEuropa, 三个人的价格不到三百欧元。
+* 城市内的交通,公交车或者出租车都很方便。
+
+我们三个人轻装出行,各自背自己的背包,而且我们的背包都符合航班的免费行李额度,一般是 `30cm * 40cm * 50cm`.
+
+### 酒店
+
+由于带小朋友旅行,加上我本身对民宿也没有啥选择经验(唯一一次住Airbnb还是七八年前出差洛杉矶的时候公司帮忙预定), 所以无论在国内还是国外旅行,我们都是住酒店,一般的策略是如果需要定三家房间以上的,比如家庭旅行带着父母,妹妹一家一起的,我就会选择经济一些的连锁酒店比如全季,这些酒店体验也很不错,标准化的流程和设施,很省心也很省钱. 如果人少的旅行,选择就会宽裕一点,而这次我们分别选择了:
+
+* 巴塞罗那的 SB Lacica hotel, 离沙滩很近,早餐也不错,公共交通也超方便。
+* 马德里的 Radisson Blu,早餐种类偏少,但是离各个景点都是步行可达,而且周边无论餐厅,街区,店铺非常丰富。
+
+两家酒店价格有些许区别,不过都在两百到三百欧之间, 不过惜惜和佑佑一致的结论是更喜欢 Radisson Blu, 他们觉得这个酒店设计品味和服务更好,比如惜惜说浴室的开关和淋浴头的分离让她不会不小心淋到水,前台办理入住有专门的凳子,以及糖果和饮料。小朋友的视角确实很独特,当然我本人也更喜欢 Radisson Blu。
+
+### 相关证件
+
+我荷兰居留卡都带在身上,事实证明也是必要的,发现有的地方需要看居留卡,有的地方则只需要护照即可。
+
+### 其他
+
+搞定了交通和酒店之后,其实其他的东西没有什么需要特别注意的了,比如,
+
+* **支付**,欧洲基本上都支持移动支付,我Apple Pay绑定了ABN的银行卡,我在欧洲所有的支付基本上都是直接刷手机, 不用对这二维码扫来扫去,也挺方便的。
+* **公交车**, 也直接上车刷手机(绑定了银行卡,应该Visa也可以)。
+* **出租车**,我一般都是直接在路边招手拦车,下车手机支付.
+* **网约车**,Uber 和 Bolt 都是可以用的,但是我很少用.
+* **地图**,Google Map 确实离不开,因为无论是去景点,还是选餐厅,都离不开它。
+
+## 巴塞罗那
+
+* **阳光和沙滩**
+
+阳光很灿烂,感觉真的好温暖,其实看数据,气温也就是比阿姆斯特丹高不大十度,但是体感温暖舒适很多,而且沙滩很干净,海水很蓝,人也不多,穿着简单的衣服,躺在沙滩上,看着小朋友玩沙玩水,看着人们玩沙滩排球。还蛮惬意的.
+
+
+* **圣家堂(Sagrada Familia)**
+
+我自己艺术修养很浅,接近于零,但是当看到这壮观的建筑,还是感觉到强烈的震撼,在外面观赏,建筑高耸入云,无数个形态各异,而又和充满故事的雕像,就算像这样没有太多西班牙以及宗教知识的人也带来强烈的视觉和心里冲击。
+
+
+走进里面,宗教色彩斑斓的窗,沿着高高的柱子往上,看到是设计精细的各种形状,好吧,没有艺术功底确实找不到合适的描述方法。只能说"我虽然看不懂,但是我大受震撼"。
+
+
+* **毕加索博物馆(Museu Picasso)**
+
+这个博物馆展示了毕加索各个时期的各种作品,门票小朋友免费,大人十来欧,我们租了导览器,这样能够让自己多少了解一下作品背后的故事,不然确实对于我这样的绘画和艺术零基础的人来确实也只是看看颜色了。这里展出了超级多的毕加索作品,也算大饱眼福了,当然小朋友们看了大部分之后,就走不动了,所以其实我们并没有完全看完所有的作品。
+
+
+* **凯旋门**
+
+凯旋门是我们离开毕加索博物馆,打算去吃完饭的时候路过,我们坐在草地上边,惜惜和佑佑喂了好一会鸽子,甚至还有一些鹦鹉,蛮有意思的。凯旋门本身没有太多让我印象深刻的地方,如果不是佑佑告诉我,我都不知道巴塞罗那也有凯旋门,以为只有巴黎有凯旋门。
+
+* **巴塞罗那大学(Universitat de Barcelona)**
+
+我们养成了旅行习惯,那就是每到一个城市旅行,我们都会去当地的大学校园去参观参观,所以来到了欧洲之后,我们参观了不少大学,不过目前主要还是局限在荷兰的大学,像代尔夫特理工大学,阿姆斯特丹大学,阿姆斯特丹自由大学,来顿大学等等。希望随着探索越来越多的欧洲城市,我们可以去参观更多的大学. 我们专门安排了一个早上来参观巴塞罗那大学,确实也是值得的,很美的大学。偶遇了一家三口华人也在参观,小朋友还分发一些糖果给惜惜。
+
+
+* **庞培法布拉大学(Universitat Pompeu Fabra)**
+
+在巴塞罗那的最后一天,因为下午的火车去马德里,饱睡一觉之后,吃好早餐之后,所以早上有几个小时的时间,我们决定去一趟UPF, 我们跟着Google Map 去到写着UPF的一个栋看着超级古老的建筑时候,惜惜走进去问了一下工作人员,他们说这里是办公楼,目前不提供参观,不过工作人员很友好的走到外面,拿着一张他们的校园地图给我讲解,推荐我们去附近的一个校区参观。我们按照他给的地图,走了大约二十分钟,终于走到了,不过这个校区较小,我们逛了一会就打车去火车站了。
+
+## 马德里
+
+从巴塞罗那来马德里的火车大约是两个小时十来分钟,还是非常的快的,马德里的火车是"疯狂动物城"的原型地,但是其实我们没有停留来参观。而且发生了一个神奇的事情,当火车停下来,我们下了火车之后,我打开 Google Map 打算去酒店的时候,看到距离竟然是 120+ 公里,我一下惊呆,难道我下错车站了么?所以赶紧去到售票机去看看马德里的火车票,翻了一会竟然找不到对应的火车站,问了一下傍边的人,他们都不太会英文,后来看到一个华人面孔的哥们,赶紧找他咨询,他中文还凑合,才发现我们并没有下错,这里就是马德里的火车站,我这时候再打开 Google Map 一看,发现这时候距离到酒店就900米,怎么又正确了呢,我说怎么找不到火车站呢,原来我所在位置没有啥问题,可能是之前手机 GPS 出现了一些问题.
+
+
+来到了马德里,体感上确实比巴塞罗那凉一些,走到酒店的一路上,马德里给我的初印象还是不错,街道干净,交错的小巷子,人们喝酒聊天,氛围也很好。
+
+* **普拉多博物馆(Prado Museum)**
+
+事实证明,这个酒店定的很成功,一觉睡到天亮,吃完早餐我们就出发去普拉多博物馆,距离非常的近,步行几分钟就到了,普拉多博物馆是西班牙最大的美术馆,普拉多拥有的杰作数除了涵盖欧洲所有流派和所有名家之外,还几乎囊括了哥雅的全部精品。博物馆确实很大,我们也购买了语音导览,跟着导览图听着语音介绍,整体体验还是可以的,参观的人不少,所以有的作品前面会站的很多人。每次参观艺术作品,我都总恨自己的艺术修养怎么那么低,一方面在整个接受教育的生涯中,没有接受过系统的艺术教育,另一方面自己也没有花时间去阅读艺术书籍学习提高自己的艺术鉴赏能力。真的惭愧呀,希望未来可以多花一些时间在这些方面,做一个会欣赏的人. 普拉多博物馆是不允许拍照的,所以没有留下什么照片,仅仅在管外拍几张。
+
+* **丽池公园 (EI Retiro)**
+
+从普拉多博物馆走几分钟就到丽池公园,这个公园听说是皇室园林,不过也对外开放了一两百年了,园子还是蛮大的,没事坐在晒晒太阳,喂喂鸟,喝喝酒,聊聊天,听艺人演奏音乐,看人们跳跳舞,还是蛮享受的。
+
+
+* **马德里皇宫**
+
+马德里皇宫的门票,最好还是提前一些买比较合适,我提前几天看已经没有票,最后我是在 Booking.com 直接买了 skip line with guide 的票,不过导游的英文需要竖起耳朵来听才能听懂,所以我听着还勉强能够跟得上他的讲解,惜惜和佑佑听起来就有点费劲,好在整个皇宫导游带着游下来,大约也就一个小时。不过皇宫本身还是值得一看的,虽然和我们故宫比起来规模还是小很多的,但是也是非常辉煌气派, 而且装饰风格和设计细节也很大不同, 值得一逛。
+
+
+* **康普顿斯大学(Universidad Complutense de Madrid)**
+
+作为西班牙最知名的大学,我们也是安排最后一天早上来参观,大学位于马德里大学城,和国外很多大学一样,康普顿斯大学其实也没有所谓的大学校门,但是你可以看到各个学院的名字,清晰的知道是哪儿, 我也没有走到作家三毛曾经就读的文学院,我们也是走马观花的走了走,就打车去机场,准备回阿姆斯特丹了。
+
+
+## 那些有意思的瞬间
+
+* **指挥大爷?**
+
+在排队安检进入候机厅的时候,惜惜和佑佑也不知道什么时候学习的荷兰语生日歌曲,边排队边唱了起来,服务员大爷主动当起乐队指挥,双手舞动了起来,还蛮有意思的。
+
+* **走错了航站楼?**
+
+从康普顿斯大学打车去马德里机场的时候,目的地写成了Terminal 4, 但实际上是 Terminal 3, 其实路上快到的时候我就发现了自己写错了,但是司机大叔英文真的是一句都不懂,到了 Terminal 4 之后,我看从 Terminal 4 到 Terminal 3 需要10几分钟的免费大巴,五分钟一趟,我看离起飞也就是35分钟,肯定是来不及,从第一辆车下来之后,我赶紧拦了另外一辆车,还好这个小哥们年轻,能说一些英文,在确定了我走错了航站楼之后,让我们上车了,到了 Terminal 3 之后,尴尬的事情是我没有任何现金,而他也没有收钱的机器,他本来也无所谓了,我说我有Uber, Bolt, Uber 或者 Paypal都可以,他说那你用 Paypal 转吧,我说转多少你说个数,他说随意就好哈哈哈。最后幸好 Terminal 3 没有像虹桥或者浦东的航站楼那样需要走很久才到登机口,我们过了安检,走几分钟就到了登机口了,也正好大家在排队登机,一切刚刚好。
+
+* **录播客**
+
+因为我很爱听播客,所以小朋友们潜移默化的也对播客这种内容也有些概念,所以当我提议可以录一个播客来记录他们的旅行,他们还是蛮兴奋的,我们三个人一起很快就确定了名字: ["西柚课间"](https://www.xiaoyuzhoufm.com/podcast/63f9f6ec75918da323982e2c). 前面三期我帮他们串串场,最后一期他们两单独录制了,听着还挺开心的。
+
+## 最后
+
+第一次和两个小朋友单独旅行,小朋友确实长大,也学会简单的做一点准备,学会在机场大屏幕寻找自己的航班,航站楼,和登机口,也学会慢慢欣赏城市风景和艺术作品。佑佑和惜惜已经开始规划春假的旅行了,瑞士,德国,和意大利之间选择,期待中.
diff --git a/data/memos.json b/data/memos.json
new file mode 100644
index 00000000..d19672a6
--- /dev/null
+++ b/data/memos.json
@@ -0,0 +1,77 @@
+[
+ {
+ "id": "1735423806669",
+ "content": "测试",
+ "timestamp": "2024-12-28T22:10:06.669Z"
+ },
+ {
+ "id": "1735423436612",
+ "content": "Cofe is born.",
+ "timestamp": "2024-12-28T22:03:56.612Z"
+ },
+ {
+ "id": "1735414490353",
+ "content": "为了写好设计文档,我甚至看完了reddit上所有的相关讨论.",
+ "timestamp": "2024-12-28T19:34:50.353Z"
+ },
+ {
+ "id": "1734785725135",
+ "content": "马德里的普拉多博物馆真的蛮棒的,可惜自己的艺术修养太低.\n",
+ "timestamp": "2024-12-21T12:55:25.135Z"
+ },
+ {
+ "id": "1734209939395",
+ "content": "adventofcode 目前为止两个问题困住只能看其他人的讨论才能解决。。。。",
+ "timestamp": "2024-12-14T20:58:59.395Z"
+ },
+ {
+ "id": "1734033804531",
+ "content": "\n\n",
+ "timestamp": "2024-12-12T20:03:24.531Z"
+ },
+ {
+ "id": "1733914604093",
+ "content": "这世界呀真的太多样了.",
+ "timestamp": "2024-12-11T10:56:44.093Z"
+ },
+ {
+ "id": "1733328968321",
+ "content": "一定不要不要做小朋友开心快乐的绊脚石呀",
+ "timestamp": "2024-12-04T16:16:08.321Z"
+ },
+ {
+ "id": "1733168761006",
+ "content": "advent of code Day 2, keep going.",
+ "timestamp": "2024-12-02T19:46:01.006Z"
+ },
+ {
+ "id": "1732864717245",
+ "content": "惜惜连做梦说话都说英文,不知道是慢慢融入了环境还是整体说英文有点困扰了",
+ "timestamp": "2024-11-29T07:18:37.245Z"
+ },
+ {
+ "id": "1732779219018",
+ "content": "这两天早上给小朋友买公交车票,司机都说不用买了,每天省两欧元.",
+ "timestamp": "2024-11-28T07:33:39.018Z"
+ },
+ {
+ "id": "1732736017482",
+ "content": "越来越发现,表达能力已经成为自己职业往下走的主要要阻碍因素了, 几个方面需要努力了:\n\n* 更好的英文能力\n* 更强的故事表达能力\n\n努力加油吧.",
+ "timestamp": "2024-04-08T17:04:37.482Z"
+ },
+ {
+ "id": "1732733932443",
+ "content": "主动,真的会给我们带来更多可能.\n\n最近深度参与了公司的应届生招聘,今天有一件事情真的让我些许有些触动. 有一位候选人在小红书给我提交了简历之后,她很主动的加了我的联系方式,会主动的咨询一些信息,我也会回复合适的信息. 因为好些时间,她收不到OA邀请,她就不断咨询进度,其实我提交了简历之后,我也是无法了解到具体的进度的,所以我也无法回复她清晰的状态,只能告诉她耐心等等看,如果合适HR会联系她的. 但是候选人过几天收不到,她又多次的咨询,我没有办法,只能和HR确认一下是不是已经刷掉了,经过和HR的沟通才发现其中有些信息差异到底她没有过筛选,最后核实了一下,重新联系她,并且开始走流程.\n\n有的时候,只有自己足够重视我们自己,机会才降临.",
+ "timestamp": "2024-04-18T10:12:52.443Z"
+ },
+ {
+ "id": "1732727123715",
+ "content": " 写代码还是最开心的事情呀.",
+ "timestamp": "2024-11-27T17:05:23.715Z"
+ },
+ {
+ "id": "1726906068282",
+ "content": "Winter is coming.",
+ "timestamp": "2024-09-21T08:07:48.282Z"
+ }
+]
\ No newline at end of file
diff --git a/i18n/messages/ar.json b/i18n/messages/ar.json
new file mode 100644
index 00000000..1ac741e4
--- /dev/null
+++ b/i18n/messages/ar.json
@@ -0,0 +1,59 @@
+{
+ "HomePage": {
+ "blog": "مدونة",
+ "memos": "أفكار",
+ "noMemosYet": "لا توجد أفكار بعد",
+ "createMemo": "إنشاء فكرة",
+ "createBlogPost": "إنشاء منشور مدونة",
+ "publish": "نشر",
+ "dataStoredIn": "البيانات مخزنة في:",
+ "writeContent": "اكتب المحتوى هنا...(يدعم Markdown)",
+ "enterTitle": "أدخل العنوان هنا...",
+ "howItWorksTitle": "كيف يعمل:",
+ "howItWorksStep1": "قم بتفويض Tinymind للوصول إلى مستودعات GitHub العامة الخاصة بك.",
+ "howItWorksStep2": "يقوم Tinymind بإنشاء مستودع عام باسم \"tinymind-blog\" في حساب GitHub الخاص بك.",
+ "howItWorksStep3": "أي شيء تكتبه على Tinymind يتم التزامه في هذا المستودع.",
+ "howItWorksStep4": "يتم تخزين بياناتك في مستودع GitHub الخاص بك، بشكل منفصل عن Tinymind.",
+ "signInWithGitHub": "تسجيل الدخول باستخدام GitHub",
+ "redirecting": "إعادة التوجيه...",
+ "publishing": "جارٍ النشر...",
+ "successPublished": "تم النشر بنجاح! قد يستغرق لأمر ما يصل إلى 30 ثانية للظهور. قم بالتحديث إذا لم تره على الفور.",
+ "failedPublish": "فشل النشر",
+ "readingFromGithub": "قراءة البيانات من GitHub...",
+ "publicContentWarning": "كل المحتوى عام.",
+ "write": "كتابة",
+ "preview": "معاينة",
+ "noBlogPostsYet": "لا توجد منشورات مدونة بعد",
+ "edit": "تعديل",
+ "delete": "حذف",
+ "confirmDelete": "تأكيد الحذف",
+ "undoAction": "يمكن التراجع عن هذا الإجراء بفضل Git. تحقق من سجل التغييات في GitHub لاستعادة فكرتك.",
+ "success": "نجاح",
+ "error": "خطأ",
+ "memoDeleted": "تم حذف الفكرة بنجاح",
+ "memoDeleteFailed": "فشل في حذف الفكرة",
+ "blogPostDeleted": "تم حذف منشور المدونة بنجاح",
+ "blogPostDeleteFailed": "فشل في حذف منشور المدونة",
+ "memoCreated": "تم إنشاء الفكرة بنجاح",
+ "memoUpdated": "تم تحديث الفكرة بنجاح",
+ "blogPostCreated": "تم إنشاء منشور المدونة بنجاح",
+ "blogPostUpdated": "ت تحديث منشور لمدونة بنجاح",
+ "cancel": "إلغاء",
+ "imageUploaded": "تم رفع الصورة بنجاح",
+ "imageUploadFailed": "فشل في رفع الصورة",
+ "onlyImagesAllowed": "يُسمح فقط بملفات الصور",
+ "dropImageHere": "أسقط الصورة هنا",
+ "notAuthenticated": "أنت غير مصادق عليه. يرجى تسجيل الدخول.",
+ "publicContentTooltip": "نطلب فقط إذن القراءة/الكتابة للمستودع العام، لذا لا يمكننا إنشاء مستودعات خاصة.",
+ "myHomepage": "صفحتي الرئيسية العامة",
+ "dataStoredOnGithub": "البيانات المخزنة على GitHub",
+ "blogTitle": " مدونة Cofe الخاصة بـ",
+ "blogShortTitle": " مدونة",
+ "blogDescription": "اكتب وزامن المدونة بصيغة Markdown مع تخزين البيانات في GitHub.",
+ "demoPage": "صفحة العرض التوضيحي"
+ },
+ "metadata": {
+ "title": "Cofe - اكتب وزامن منشورات مدونتك وأفكارك بتسجيل دخول GitHub بنقرة واحدة",
+ "description": "اكتب وزامن المدونة بصيغة Markdown مع تخزين البيانات في GitHub."
+ }
+}
diff --git a/i18n/messages/de.json b/i18n/messages/de.json
new file mode 100644
index 00000000..b2e6705e
--- /dev/null
+++ b/i18n/messages/de.json
@@ -0,0 +1,59 @@
+{
+ "HomePage": {
+ "blog": "Blog",
+ "memos": "Gedanken",
+ "noMemosYet": "Noch keine Gedanken",
+ "createMemo": "Gedanke erstellen",
+ "createBlogPost": "Blogbeitrag erstellen",
+ "publish": "Veröffentlichen",
+ "dataStoredIn": "Daten gespeichert in:",
+ "writeContent": "Inhalt hier eingeben...(Markdown unterstützt)",
+ "enterTitle": "Titel hier eingeben...",
+ "howItWorksTitle": "So funktioniert es:",
+ "howItWorksStep1": "Autorisieren Sie Tinymind, auf Ihre öffentlichen GitHub-Repositories zuzugreifen.",
+ "howItWorksStep2": "Tinymind erstellt ein öffentliches Repository namens \"tinymind-blog\" in Ihrem GitHub-Konto.",
+ "howItWorksStep3": "Alles, was Sie auf Tinymind schreiben, wird in dieses Repository übertragen.",
+ "howItWorksStep4": "Ihre Daten werden in Ihrem GitHub-Repository gespeichert, getrennt von Tinymind.",
+ "signInWithGitHub": "Mit GitHub anmelden",
+ "redirecting": "Weiterleitung...",
+ "publishing": "Veröffentlichung...",
+ "successPublished": "Erfolgreich veröffentlicht! Es kann bis zu 30 Sekunden dauern, bis es angezeigt wird. Aktualisieren Sie die Seite, wenn Sie es nicht sofort sehen.",
+ "failedPublish": "Veröffentlichung fehlgeschlagen",
+ "readingFromGithub": "Lese Daten von GitHub...",
+ "publicContentWarning": "Alle Inhalte sind öffentlich.",
+ "write": "Schreiben",
+ "preview": "Vorschau",
+ "noBlogPostsYet": "Noch keine Blogbeiträge",
+ "edit": "Bearbeiten",
+ "delete": "Löschen",
+ "confirmDelete": "Löschen bestätigen",
+ "undoAction": "Diese Aktion kann dank Git rückgängig gemacht werden. Überprüfen Sie Ihren GitHub-Commit-Verlauf, um Ihre Idee wiederherzustellen.",
+ "success": "Erfolg",
+ "error": "Fehler",
+ "memoDeleted": "Gedanke erfolgreich gelöscht",
+ "memoDeleteFailed": "Löschen des Gedankens fehlgeschlagen",
+ "blogPostDeleted": "Blogbeitrag erfolgreich gelöscht",
+ "blogPostDeleteFailed": "Löschen des Blogbeitrags fehlgeschlagen",
+ "memoCreated": "Gedanke erfolgreich erstellt",
+ "memoUpdated": "Gedanke erfolgreich aktualisiert",
+ "blogPostCreated": "Blogbeitrag erfolgreich erstellt",
+ "blogPostUpdated": "Blogbeitrag erfolgreich aktualisiert",
+ "cancel": "Abbrechen",
+ "imageUploaded": "Bild erfolgreich hochgeladen",
+ "imageUploadFailed": "Bild konnte nicht hochgeladen werden",
+ "onlyImagesAllowed": "Nur Bilddateien sind erlaubt",
+ "dropImageHere": "Bild hier ablegen",
+ "notAuthenticated": "Sie sind nicht authentifiziert. Bitte melden Sie sich an.",
+ "publicContentTooltip": "Wir fordern nur öffentliche Repository-Lese-/Schreibberechtigung an, daher können wir keine privaten Repositories erstellen.",
+ "myHomepage": "Meine öffentliche Homepage",
+ "dataStoredOnGithub": "Daten auf GitHub gespeichert",
+ "blogTitle": "s Cofe Blog",
+ "blogShortTitle": "s Blog",
+ "blogDescription": "Schreiben und synchronisieren Sie Blogs in Markdown mit in GitHub gespeicherten Daten.",
+ "demoPage": "Demo-Seite"
+ },
+ "metadata": {
+ "title": "Cofe - Blogbeiträge & Gedanken mit GitHub schreiben.",
+ "description": "Schreiben und synchronisieren Sie Blogbeiträge & Gedanken in Markdown mit in GitHub gespeicherten Daten."
+ }
+ }
diff --git a/i18n/messages/en.json b/i18n/messages/en.json
new file mode 100644
index 00000000..b1ebdcbe
--- /dev/null
+++ b/i18n/messages/en.json
@@ -0,0 +1,55 @@
+{
+ "HomePage": {
+ "blog": "Blog",
+ "memos": "Memo",
+ "noMemosYet": "No memos yet",
+ "readingFromGithub": "Reading data from GitHub...",
+ "createMemo": "Create a memo",
+ "createBlogPost": "Create a blog post",
+ "publish": "Publish",
+ "redirecting": "Redirecting...",
+ "publishing": "Publishing...",
+ "successPublished": "Successfully published! It may take up to 30 seconds to appear. Refresh if you don't see it immediately.",
+ "failedPublish": "Failed to publish",
+ "dataStoredIn": "Data stored in:",
+ "writeContent": "Write your content here...(Markdown supported)",
+ "enterTitle": "Enter your title here...",
+
+ "howItWorksTitle": "How it works:",
+ "howItWorksStep1": "Authorize Tinymind to access your public GitHub repositories.",
+ "howItWorksStep2": "Tinymind creates a public repository named \"tinymind-blog\" in your GitHub account.",
+ "howItWorksStep3": "Anything you write on Tinymind is committed to this repository.",
+ "howItWorksStep4": "Your data is stored on your GitHub repository, separate from Tinymind.",
+ "signInWithGitHub": "Sign in with GitHub",
+ "publicContentWarning": "All content is public.",
+ "write": "Write",
+ "preview": "Preview",
+ "noBlogPostsYet": "No blog posts yet",
+ "edit": "Edit",
+ "delete": "Delete",
+ "confirmDelete": "Confirm Delete",
+ "undoAction": "This action can be undone thanks to Git. Check your GitHub commit history to restore your memo.",
+ "success": "Success",
+ "error": "Error",
+ "memoDeleted": "Memo deleted successfully",
+ "memoDeleteFailed": "Failed to delete memo",
+ "blogPostDeleted": "Blog post deleted successfully",
+ "blogPostDeleteFailed": "Failed to delete blog post",
+ "memoCreated": "Memo created successfully",
+ "memoUpdated": "Memo updated successfully",
+ "blogPostCreated": "Blog post created successfully",
+ "blogPostUpdated": "Blog post updated successfully",
+ "cancel": "Cancel",
+ "publicContentTooltip": "We only request public repository read/write permission, so we can't create private repositories.",
+ "myHomepage": "My Public Homepage",
+ "dataStoredOnGithub": "Data Stored on GitHub",
+ "blogTitle": "'s Cofe Blog",
+ "blogShortTitle": "'s Blog",
+ "blogDescription": "Write and sync blog in Markdown with data stored in GitHub.",
+ "demoPage": "Demo Page"
+ },
+ "metadata": {
+ "title": "Cofe - Write blog & memos with GitHub.",
+ "description": "Write and sync blog posts & memos in Markdown with data stored in GitHub."
+ }
+ }
diff --git a/i18n/messages/es.json b/i18n/messages/es.json
new file mode 100644
index 00000000..884ed178
--- /dev/null
+++ b/i18n/messages/es.json
@@ -0,0 +1,59 @@
+{
+ "HomePage": {
+ "blog": "Blog",
+ "memos": "Pensamientos",
+ "noMemosYet": "Aún no hay pensamientos",
+ "createMemo": "Crear un pensamiento",
+ "createBlogPost": "Crear una entrada de blog",
+ "publish": "Publicar",
+ "dataStoredIn": "Datos almacenados en:",
+ "writeContent": "Escribe tu contenido aquí...(Markdown soportado)",
+ "enterTitle": "Introduce tu título aquí...",
+ "howItWorksTitle": "Cómo funciona:",
+ "howItWorksStep1": "Autoriza a Tinymind para acceder a tus repositorios públicos de GitHub.",
+ "howItWorksStep2": "Tinymind crea un repositorio público llamado \"tinymind-blog\" en tu cuenta de GitHub.",
+ "howItWorksStep3": "Todo lo que escribas en Tinymind se guarda en este repositorio.",
+ "howItWorksStep4": "Tus datos se almacenan en tu repositorio de GitHub, separado de Tinymind.",
+ "signInWithGitHub": "Iniciar sesión con GitHub",
+ "redirecting": "Redirigiendo...",
+ "publishing": "Publicando...",
+ "successPublished": "¡Publicado con éxito! Puede tardar hasta 30 segundos en aparecer. Actualiza si no lo ves de inmediato.",
+ "failedPublish": "Error al publicar",
+ "readingFromGithub": "Leyendo datos de GitHub...",
+ "publicContentWarning": "Todo el contenido es público.",
+ "write": "Escribir",
+ "preview": "Vista previa",
+ "noBlogPostsYet": "Aún no hay entradas de blog",
+ "edit": "Editar",
+ "delete": "Eliminar",
+ "confirmDelete": "Confirmar eliminación",
+ "undoAction": "Esta acción se puede deshacer gracias a Git. Revisa el historial de commits de GitHub para restaurar tu pensamiento.",
+ "success": "Éxito",
+ "error": "Error",
+ "memoDeleted": "Pensamiento eliminado con éxito",
+ "memoDeleteFailed": "Error al eliminar el pensamiento",
+ "blogPostDeleted": "Entrada de blog eliminada con éxito",
+ "blogPostDeleteFailed": "Error al eliminar la entrada de blog",
+ "memoCreated": "Pensamiento creado con éxito",
+ "memoUpdated": "Pensamiento actualizado con éxito",
+ "blogPostCreated": "Entrada de blog creada con éxito",
+ "blogPostUpdated": "Entrada de blog actualizada con éxito",
+ "cancel": "Cancelar",
+ "imageUploaded": "Imagen subida con éxito",
+ "imageUploadFailed": "Error al subir la imagen",
+ "onlyImagesAllowed": "Solo se permiten archivos de imagen",
+ "dropImageHere": "Suelta la imagen aquí",
+ "notAuthenticated": "No estás autenticado. Por favor, inicia sesión.",
+ "publicContentTooltip": "Solo solicitamos permiso de lectura/escritura para repositorios públicos, por lo que no podemos crear repositorios privados.",
+ "myHomepage": "Mi página de inicio pública",
+ "dataStoredOnGithub": "Datos almacenados en GitHub",
+ "blogTitle": " Blog Cofe de",
+ "blogShortTitle": " Blog de",
+ "blogDescription": "Escribe y sincroniza blogs en Markdown con datos almacenados en GitHub.",
+ "demoPage": "Página de Demostración"
+ },
+ "metadata": {
+ "title": "Cofe - Escribe blog y pensamientos con GitHub.",
+ "description": "Escribe y sincroniza entradas de blog y pensamientos en Markdown con datos almacenados en GitHub."
+ }
+}
diff --git a/i18n/messages/fr.json b/i18n/messages/fr.json
new file mode 100644
index 00000000..89b62bf6
--- /dev/null
+++ b/i18n/messages/fr.json
@@ -0,0 +1,59 @@
+{
+ "HomePage": {
+ "blog": "Blog",
+ "memos": "Pensées",
+ "noMemosYet": "Pas encore de pensées",
+ "createMemo": "Créer une pensée",
+ "createBlogPost": "Créer un article",
+ "publish": "Publier",
+ "dataStoredIn": "Données stockées dans :",
+ "writeContent": "Écrivez votre contenu ici...(Markdown supporté)",
+ "enterTitle": "Entrez votre titre ici...",
+ "howItWorksTitle": "Comment ça marche :",
+ "howItWorksStep1": "Autorisez Tinymind à accéder à vos dépôts GitHub publics.",
+ "howItWorksStep2": "Tinymind crée un dépôt public nommé \"tinymind-blog\" dans votre compte GitHub.",
+ "howItWorksStep3": "Tout ce que vous écrivez sur Tinymind est enregistré dans ce dépôt.",
+ "howItWorksStep4": "Vos données sont stockées sur GitHub, indépendamment de ce site.",
+ "signInWithGitHub": "Connexion avec GitHub",
+ "redirecting": "Redirection...",
+ "publishing": "Publication...",
+ "successPublished": "Publié avec succès ! Cela peut prendre jusqu'à 30 secondes pour apparaître. Actualisez si vous ne le voyez pas immédiatement.",
+ "failedPublish": "Échec de la publication",
+ "readingFromGithub": "Lecture des données depuis GitHub...",
+ "publicContentWarning": "Tout le contenu est public.",
+ "write": "Écrire",
+ "preview": "Aperçu",
+ "noBlogPostsYet": "Pas encore d'articles de blog",
+ "edit": "Modifier",
+ "delete": "Supprimer",
+ "confirmDelete": "Confirmer la suppression",
+ "undoAction": "Cette action peut être annulée grâce à Git. Consultez l'historique des commits de GitHub pour restaurer votre pensée.",
+ "success": "Succès",
+ "error": "Erreur",
+ "memoDeleted": "Pensée supprimée avec succès",
+ "memoDeleteFailed": "Échec de la suppression de la pensée",
+ "blogPostDeleted": "Article supprimé avec succès",
+ "blogPostDeleteFailed": "Échec de la suppression de l'article",
+ "memoCreated": "Pensée créée avec succès",
+ "memoUpdated": "Pensée mise à jour avec succès",
+ "blogPostCreated": "Article créé avec succès",
+ "blogPostUpdated": "Article mis à jour avec succès",
+ "cancel": "Annuler",
+ "imageUploaded": "Image téléchargée avec succès",
+ "imageUploadFailed": "Échec du téléchargement de l'image",
+ "onlyImagesAllowed": "Seuls les fichiers image sont autorisés",
+ "dropImageHere": "Déposez l'image ici",
+ "notAuthenticated": "Vous n'êtes pas authentifié. Veuillez vous connecter.",
+ "publicContentTooltip": "Nous ne demandons que l'autorisation de lecture/écriture du dépôt public, nous ne pouvons donc pas créer de dépôts privés.",
+ "myHomepage": "Ma page d'accueil publique",
+ "dataStoredOnGithub": "Données stockées sur GitHub",
+ "blogTitle": " Blog Cofe de",
+ "blogShortTitle": " Blog de",
+ "blogDescription": "Écrivez et synchronisez des blogs en Markdown avec des données stockées sur GitHub.",
+ "demoPage": "Page de Démonstration"
+ },
+ "metadata": {
+ "title": "Cofe - Écrivez blog et pensées avec GitHub.",
+ "description": "Écrivez et synchronisez des articles de blog et des pensées en Markdown avec des données stockées sur GitHub."
+ }
+}
diff --git a/i18n/messages/hi.json b/i18n/messages/hi.json
new file mode 100644
index 00000000..ca091d3d
--- /dev/null
+++ b/i18n/messages/hi.json
@@ -0,0 +1,59 @@
+{
+ "HomePage": {
+ "blog": "ब्लॉग",
+ "memos": "विचार",
+ "noMemosYet": "अभी तक कोई विचार नहीं",
+ "createMemo": "विचार बनाएं",
+ "createBlogPost": "पोस्ट बनाएं",
+ "publish": "प्रकाशित करें",
+ "dataStoredIn": "डेटा यहाँ संग्रहित है:",
+ "writeContent": "अपनी सामग्री यहाँ लिखें...(Markdown समर्थित)",
+ "enterTitle": "अपना शीर्षक यहाँ दर्ज करें...",
+ "howItWorksTitle": "यह कैसे काम करता है:",
+ "howItWorksStep1": "Tinymind को अपने सार्वजनिक GitHub रिपॉजिटरीज तक पहुंच की अनुमति दें।",
+ "howItWorksStep2": "Tinymind आपके GitHub खाते में \"tinymind-blog\" नाम का एक सार्वजनिक रिपॉजिटरी बनाता है।",
+ "howItWorksStep3": "Tinymind पर आप जो कुछ भी लिखते हैं, वह इस रिपॉजिटरी में कमिट किया जाता है।",
+ "howItWorksStep4": "आपका डेटा GitHub रिपॉजिटरी में संग्रहीत किया जाता है, Tinymind से अलग।",
+ "signInWithGitHub": "GitHub के साथ साइन इन करें",
+ "redirecting": "पुनर्निर्देशित कर रहा है...",
+ "publishing": "प्रकाशित हो रहा है...",
+ "successPublished": "सफलतापूर्वक प्रकाशित! दिखाई देने में 30 सेकंड तक का समय लग सकता है। यदि तुरंत नहीं दिखता है ो ताज़ा करें।",
+ "failedPublish": "प्रकाशन विफल रहा",
+ "readingFromGithub": "GitHub से डेटा पढ़ रहा है...",
+ "publicContentWarning": "सभी सामग्री सार्वजनिक है।",
+ "publicContentTooltip": "हम केवल सार्वजनिक रिपॉजिटरी पढ़ने/लिखने की अनुमति का अनुरोध करते हैं, इसलिए हम निजी रिपॉजिटरी नहीं बना सकते।",
+ "write": "लिखें",
+ "preview": "पूर्वावलोकन",
+ "noBlogPostsYet": "अभी तक कोई ब्लॉग पोस्ट नहीं",
+ "edit": "संपादित करें",
+ "delete": "हटाएं",
+ "confirmDelete": "हटाने की पुष्टि करें",
+ "undoAction": "इस क्रिया को Git की बदौलत पूर्ववत किया जा सकता है। अपने विचार को पुनर्स्थापित करने के लिए अपने GitHub कमिट इतिहास की जाँच करें।",
+ "success": "सफलता",
+ "error": "त्रुटि",
+ "memoDeleted": "विचार सफलतापूर्वक हटाया गया",
+ "memoDeleteFailed": "विचार को हटाने में विफल",
+ "blogPostDeleted": "ब्लॉग पोस्ट सफलतापूर्वक हटाया गया",
+ "blogPostDeleteFailed": "ब्लॉग पोस्ट को हटाने में विफल",
+ "memoCreated": "विचार सफलतापूर्वक बनाया गया",
+ "memoUpdated": "विचार सफलतापूर्वक अपडेट किया गया",
+ "blogPostCreated": "ब्लॉग पोस्ट सफलतापूर्वक बनाया गया",
+ "blogPostUpdated": "ब्लॉग पोस्ट सफलतापूर्वक अपडेट किया गया",
+ "cancel": "रद्द करें",
+ "imageUploaded": "छवि सफलतापूर्वक अपलोड की गई",
+ "imageUploadFailed": "छवि अपलोड करने में विफल",
+ "onlyImagesAllowed": "केवल छवि फ़ाइलों की अनुमति है",
+ "dropImageHere": "छवि यहाँ छोड़ें",
+ "notAuthenticated": "आप प्रमाणित नहीं हैं। कृपया साइन इन करें।",
+ "myHomepage": "मेरा सार्वजनिक होमपेज",
+ "dataStoredOnGithub": "GitHub पर संग्रहीत डेटा",
+ "blogTitle": " का Cofe ब्लॉग",
+ "blogShortTitle": " का ब्लॉग",
+ "blogDescription": "GitHub में संग्रहीत डेटा के साथ Markdown में ब्लॉग लिखें और सिंक करें।",
+ "demoPage": "डेमो पेज"
+ },
+ "metadata": {
+ "title": "Cofe - GitHub के साथ ब्लॉग और विचार लिखें।",
+ "description": "GitHub में संग्रहीत डेटा के साथ Markdown में ब्लॉग पोस्ट और विचार लिखें और सिंक करें।"
+ }
+}
diff --git a/i18n/messages/id.json b/i18n/messages/id.json
new file mode 100644
index 00000000..db21712e
--- /dev/null
+++ b/i18n/messages/id.json
@@ -0,0 +1,59 @@
+{
+ "HomePage": {
+ "blog": "Blog",
+ "memos": "Pemikiran",
+ "noMemosYet": "Belum ada pemikiran",
+ "createMemo": "Buat pemikiran",
+ "createBlogPost": "Buat postingan blog",
+ "publish": "Terbitkan",
+ "dataStoredIn": "Data disimpan di:",
+ "writeContent": "Tulis konten Anda di sini...(mendukung Markdown)",
+ "enterTitle": "Masukkan judul Anda di sini...",
+ "howItWorksTitle": "Cara kerjanya:",
+ "howItWorksStep1": "Izinkan Tinymind untuk mengakses repositori GitHub publik Anda.",
+ "howItWorksStep2": "Tinymind membuat repositori publik bernama \"tinymind-blog\" di akun GitHub Anda.",
+ "howItWorksStep3": "Apa pun yang Anda tulis di Tinymind akan disimpan ke repositori ini.",
+ "howItWorksStep4": "Data Anda disimpan di repositori GitHub Anda, terpisah dari Tinymind.",
+ "signInWithGitHub": "Masuk dengan GitHub",
+ "redirecting": "Mengalihkan...",
+ "publishing": "Sedang menerbitkan...",
+ "successPublished": "Berhasil diterbitkan! Mungkin perlu waktu hingga 30 detik untuk muncul. Segarkan jika Anda tidak melihatnya segera.",
+ "failedPublish": "Gagal menerbitkan",
+ "readingFromGithub": "Membaca data dari GitHub...",
+ "publicContentWarning": "Semua konten bersifat publik.",
+ "publicContentTooltip": "Kami hanya meminta izin baca/tulis repositori publik, jadi kami tidak dapat membuat repositori pribadi.",
+ "write": "Tulis",
+ "preview": "Pratinjau",
+ "noBlogPostsYet": "Belum ada postingan blog",
+ "edit": "Edit",
+ "delete": "Hapus",
+ "confirmDelete": "Konfirmasi Hapus",
+ "undoAction": "Tindakan ini dapat dibatalkan berkat Git. Periksa riwayat commit GitHub Anda untuk memulihkan pemikiran Anda.",
+ "success": "Sukses",
+ "error": "Kesalahan",
+ "memoDeleted": "Pemikiran berhasil dihapus",
+ "memoDeleteFailed": "Gagal menghapus pemikiran",
+ "blogPostDeleted": "Postingan blog berhasil dihapus",
+ "blogPostDeleteFailed": "Gagal menghapus postingan blog",
+ "memoCreated": "Pemikiran berhasil dibuat",
+ "memoUpdated": "Pemikiran berhasil diperbarui",
+ "blogPostCreated": "Postingan blog berhasil dibuat",
+ "blogPostUpdated": "Postingan blog berhasil diperbarui",
+ "cancel": "Batal",
+ "imageUploaded": "Gambar berhasil diunggah",
+ "imageUploadFailed": "Gagal mengunggah gambar",
+ "onlyImagesAllowed": "Hanya file gambar yang diizinkan",
+ "dropImageHere": "Jatuhkan gambar di sini",
+ "notAuthenticated": "Anda belum diautentikasi. Silakan masuk.",
+ "myHomepage": "Beranda Publik Saya",
+ "dataStoredOnGithub": "Data Disimpan di GitHub",
+ "blogTitle": " Blog Cofe",
+ "blogShortTitle": " Blog",
+ "blogDescription": "Tulis dan sinkronkan blog dalam Markdown dengan data yang disimpan di GitHub.",
+ "demoPage": "Halaman Demo"
+ },
+ "metadata": {
+ "title": "Cofe - Tulis blog & pemikiran dengan GitHub.",
+ "description": "Tulis dan sinkronkan postingan blog & pemikiran dalam Markdown dengan data yang disimpan di GitHub."
+ }
+}
diff --git a/i18n/messages/it.json b/i18n/messages/it.json
new file mode 100644
index 00000000..18503404
--- /dev/null
+++ b/i18n/messages/it.json
@@ -0,0 +1,59 @@
+{
+ "HomePage": {
+ "blog": "Blog",
+ "memos": "Pensieri",
+ "noMemosYet": "Ancora nessun pensiero",
+ "createMemo": "Crea un pensiero",
+ "createBlogPost": "Crea un post sul blog",
+ "publish": "Pubblica",
+ "dataStoredIn": "Dati memorizzati in:",
+ "writeContent": "Scrivi il tuo contenuto qui...(supporta Markdown)",
+ "enterTitle": "Inserisci il tuo titolo qui...",
+ "howItWorksTitle": "Come funziona:",
+ "howItWorksStep1": "Autorizza Tinymind ad accedere ai tuoi repository GitHub pubblici.",
+ "howItWorksStep2": "Tinymind crea un repository pubblico chiamato \"tinymind-blog\" nel tuo account GitHub.",
+ "howItWorksStep3": "Tutto ciò che scrivi su Tinymind viene salvato in questo repository.",
+ "howItWorksStep4": "I tuoi dati sono memorizzati nel tuo repository GitHub, separato da Tinymind.",
+ "signInWithGitHub": "Accedi con GitHub",
+ "redirecting": "Reindirizzamento...",
+ "publishing": "Pubblicazione...",
+ "successPublished": "Pubblicato con successo! Potrebbe richiedere fino a 30 secondi per apparire. Aggiorna se non lo vedi immediatamente.",
+ "failedPublish": "Pubblicazione fallita",
+ "readingFromGithub": "Lettura dei dati da GitHub...",
+ "publicContentWarning": "Tutti i contenuti sono pubblici.",
+ "write": "Scrivi",
+ "preview": "Anteprima",
+ "noBlogPostsYet": "Ancora nessun post sul blog",
+ "edit": "Modifica",
+ "delete": "Elimina",
+ "confirmDelete": "Conferma eliminazione",
+ "undoAction": "Questa azione può essere annullata grazie a Git. Controlla la cronologia dei commit di GitHub per ripristinare il tuo pensiero.",
+ "success": "Successo",
+ "error": "Errore",
+ "memoDeleted": "Pensiero eliminato con successo",
+ "memoDeleteFailed": "Eliminazione del pensiero fallita",
+ "blogPostDeleted": "Post sul blog eliminato con successo",
+ "blogPostDeleteFailed": "Eliminazione del post sul blog fallita",
+ "memoCreated": "Pensiero creato con successo",
+ "memoUpdated": "Pensiero aggiornato con successo",
+ "blogPostCreated": "Post sul blog creato con successo",
+ "blogPostUpdated": "Post sul blog aggiornato con successo",
+ "cancel": "Annulla",
+ "imageUploaded": "Immagine caricata con successo",
+ "imageUploadFailed": "Caricamento dell'immagine fallito",
+ "onlyImagesAllowed": "Sono consentiti solo file immagine",
+ "dropImageHere": "Trascina qui l'immagine",
+ "notAuthenticated": "Non sei autenticato. Per favore, accedi.",
+ "publicContentTooltip": "Richiediamo solo l'autorizzazione di lettura/scrittura del repository pubblico, quindi non possiamo creare repository privati.",
+ "myHomepage": "La mia homepage pubblica",
+ "dataStoredOnGithub": "Dati memorizzati su GitHub",
+ "blogTitle": " Blog Cofe di",
+ "blogShortTitle": " Blog di",
+ "blogDescription": "Scrivi e sincronizza blog in Markdown con dati archiviati su GitHub.",
+ "demoPage": "Pagina Dimostrativa"
+ },
+ "metadata": {
+ "title": "Cofe - Scrivi blog e pensieri con GitHub.",
+ "description": "Scrivi e sincronizza post del blog e pensieri in Markdown con dati archiviati su GitHub."
+ }
+}
diff --git a/i18n/messages/ja.json b/i18n/messages/ja.json
new file mode 100644
index 00000000..37f5d28c
--- /dev/null
+++ b/i18n/messages/ja.json
@@ -0,0 +1,58 @@
+{
+ "HomePage": {
+ "blog": "ブログ",
+ "memos": "アイデア",
+ "noMemosYet": "アイデアはまだありません",
+ "createMemo": "アイデアを作成",
+ "createBlogPost": "ブログ記事を作成",
+ "publish": "公開",
+ "dataStoredIn": "データ保存先:",
+ "writeContent": "内容を入力...(Markdown対応)",
+ "enterTitle": "タイトルを入力...",
+ "howItWorksTitle": "使い方:",
+ "howItWorksStep1": "Tinymindにあなたの公開GitHubリポジトリへのアクセスを許可します。",
+ "howItWorksStep2": "TinymindはあなたのGitHubアカウントに\"tinymind-blog\"という名前の公開リポジトリを作成します。",
+ "howItWorksStep3": "Tinymindで書いたものはすべて、あなたの\"あなたの名前/tinymind-blog\"リポジトリに",
+ "signInWithGitHub": "GitHubでログイン",
+ "redirecting": "リダイレクト中...",
+ "publishing": "公開中...",
+ "successPublished": "公開成功!表示されるまで最大30秒かかる場合があります。すぐに表示されない場合はリフレッシュしてください。",
+ "failedPublish": "公開失敗",
+ "readingFromGithub": "GitHubからデータを読み込んでいます...",
+ "publicContentWarning": "すべてのコンテンツは公開されます。",
+ "write": "書く",
+ "preview": "プレビュー",
+ "noBlogPostsYet": "まだブログ記事がありません",
+ "edit": "編集",
+ "delete": "削除",
+ "confirmDelete": "削除の確認",
+ "undoAction": "この操作はGitのおかげで元に戻すことができます。アイデアを復元するには、GitHubのコミット履歴を確認してください。",
+ "success": "成功",
+ "error": "エラー",
+ "memoDeleted": "アイデアが正常に削除されました",
+ "memoDeleteFailed": "アイデアの削除に失敗しました",
+ "blogPostDeleted": "ブログ記事が正常に削除されました",
+ "blogPostDeleteFailed": "ブログ記事の削除に失敗しました",
+ "memoCreated": "アイデアが正常に作成されました",
+ "memoUpdated": "アイデアが正常に更新されました",
+ "blogPostCreated": "ブログ記事が正常に作成されました",
+ "blogPostUpdated": "ブログ記事が正常に更新されました",
+ "cancel": "キャンセル",
+ "imageUploaded": "画像が正常にアップロードされました",
+ "imageUploadFailed": "画像のアップロードに失敗しました",
+ "onlyImagesAllowed": "画像ファイルのみが許可されています",
+ "dropImageHere": "ここに画像をドロップしてください",
+ "notAuthenticated": "認証されていません。ログインしてください。",
+ "publicContentTooltip": "パブリックリポジトリの読み取り/書き込み権限のみを要求するため、プライベートリポジトリを作成することはできません。",
+ "myHomepage": "私の公開ホームページ",
+ "dataStoredOnGithub": "GitHubに保存されたデータ",
+ "blogTitle": " の Cofe ブログ",
+ "blogShortTitle": " のブログ",
+ "blogDescription": "GitHubに保存されたデータでMarkdownでブログを書いて同期します。",
+ "demoPage": "デモページ"
+ },
+ "metadata": {
+ "title": "Cofe - GitHubでブログと思考を書く。",
+ "description": "GitHubに保存されたデータでMarkdownでブログ投稿と思考を書いて同期します。"
+ }
+}
diff --git a/i18n/messages/ko.json b/i18n/messages/ko.json
new file mode 100644
index 00000000..cf4313b0
--- /dev/null
+++ b/i18n/messages/ko.json
@@ -0,0 +1,59 @@
+{
+ "HomePage": {
+ "blog": "블로그",
+ "memos": "아이디어",
+ "noMemosYet": "아직 아이디어가 없습니다",
+ "createMemo": "아이디어 작성",
+ "createBlogPost": "블로그 작성",
+ "publish": "게시",
+ "dataStoredIn": "데이터 저장 위치:",
+ "writeContent": "여기에 내용을 작성하세요...(Markdown 지원)",
+ "enterTitle": "여기에 제목을 입력하세요...",
+ "howItWorksTitle": "작동 방식:",
+ "howItWorksStep1": "Tinymind에 귀하의 공개 GitHub 저장소에 접근할 수 있는 권한을 부여합니다.",
+ "howItWorksStep2": "Tinymind가 귀하의 GitHub 계정에 \"tinymind-blog\"라는 이름의 공개 저장소를 생성합니다.",
+ "howItWorksStep3": "Tinymind에서 작성한 모든 내용은 이 저장소에 커밋됩니다.",
+ "howItWorksStep4": "귀하의 데이터는 Tinymind와 별개로 GitHub 저장소에 저장됩니다.",
+ "signInWithGitHub": "GitHub로 로그인",
+ "redirecting": "리다이렉팅 중...",
+ "publishing": "게시 중...",
+ "successPublished": "성공적으로 게시되었습니다! 표시되는 데 최대 30초가 걸릴 수 있습니다. 즉시 보이지 않으면 새로고침하세요.",
+ "failedPublish": "게시 실패했습니다",
+ "readingFromGithub": "GitHub에서 데이터를 읽는 중...",
+ "publicContentWarning": "모든 내용은 공개됩니다.",
+ "write": "작성",
+ "preview": "미리보기",
+ "noBlogPostsYet": "아직 블로그 게시물이 없습니다",
+ "edit": "편집",
+ "delete": "삭제",
+ "confirmDelete": "삭제 확인",
+ "undoAction": "이 작업은 Git 덕분에 취소할 수 있습니다. 아이디어를 복원하려면 GitHub 커밋 기록을 확인하세요.",
+ "success": "성공",
+ "error": "오류",
+ "memoDeleted": "아이디어가 성공적으로 삭제되었습니다",
+ "memoDeleteFailed": "아이디어 삭제 실패",
+ "blogPostDeleted": "블로그 게시물이 성공적으로 삭제되었습니다",
+ "blogPostDeleteFailed": "블로그 게시물 삭제 실패",
+ "memoCreated": "아이디어가 성공적으로 생성되었습니다",
+ "memoUpdated": "아이디어가 성공적으로 업데이트되었습니다",
+ "blogPostCreated": "블로그 게시물이 성공적으로 생성되었습니다",
+ "blogPostUpdated": "블로그 게시물이 성공적으로 업데이트되었습니다",
+ "cancel": "취소",
+ "imageUploaded": "이미지가 성공적으로 업로드되었습니다",
+ "imageUploadFailed": "이미지 업로드 실패",
+ "onlyImagesAllowed": "이미지 파일만 허용됩니다",
+ "dropImageHere": "여기에 이미지를 드롭하세요",
+ "notAuthenticated": "인증되지 않았습니다. 로그인해 주세요.",
+ "publicContentTooltip": "공개 저장소 읽기/쓰기 권한만 요청하므로 비공개 저장소를 만들 수 없습니다.",
+ "myHomepage": "내 공개 홈페이지",
+ "dataStoredOnGithub": "GitHub에 저장된 데이터",
+ "blogTitle": "의 Cofe 블로그",
+ "blogShortTitle": "의 블로그",
+ "blogDescription": "GitHub에 저장된 데이터로 Markdown으로 블로그를 작성하고 동기화합니다.",
+ "demoPage": "데모 페이지"
+ },
+ "metadata": {
+ "title": "Cofe - GitHub로 블로그 및 생각 작성하기.",
+ "description": "GitHub에 저장된 데이터로 Markdown으로 블로그 포스트 및 생각을 작성하고 동기화합니다."
+ }
+}
diff --git a/i18n/messages/nl.json b/i18n/messages/nl.json
new file mode 100644
index 00000000..63cc9a32
--- /dev/null
+++ b/i18n/messages/nl.json
@@ -0,0 +1,59 @@
+{
+ "HomePage": {
+ "blog": "Blog",
+ "memos": "Gedachten",
+ "noMemosYet": "Nog geen gedachten",
+ "createMemo": "Maak een gedachte",
+ "createBlogPost": "Maak een blogpost",
+ "publish": "Publiceren",
+ "dataStoredIn": "Gegevens opgeslagen in:",
+ "writeContent": "Schrijf je inhoud hier...(Markdown ondersteund)",
+ "enterTitle": "Voer hier je titel in...",
+ "howItWorksTitle": "Hoe het werkt:",
+ "howItWorksStep1": "Geef Tinymind toestemming om toegang te krijgen tot je openbare GitHub-repositories.",
+ "howItWorksStep2": "Tinymind maakt een openbaar repository genaamd \"tinymind-blog\" in je GitHub-account.",
+ "howItWorksStep3": "Alles wat je op Tinymind schrijft, wordt in dit repository opgeslagen.",
+ "howItWorksStep4": "Je gegevens worden opgeslagen in je GitHub-repository, los van Tinymind.",
+ "signInWithGitHub": "Inloggen met GitHub",
+ "redirecting": "Doorverwijzen...",
+ "publishing": "Publiceren...",
+ "successPublished": "Succesvol gepubliceerd! Het kan tot 30 seconden duren voordat het verschijnt. Ververs als je het niet meteen ziet.",
+ "failedPublish": "Publiceren mislukt",
+ "readingFromGithub": "Gegevens lezen van GitHub...",
+ "publicContentWarning": "Alle inhoud is openbaar.",
+ "write": "Schrijven",
+ "preview": "Voorbeeld",
+ "noBlogPostsYet": "Nog geen blogberichten",
+ "edit": "Bewerken",
+ "delete": "Verwijderen",
+ "confirmDelete": "Verwijdering bevestigen",
+ "undoAction": "Deze actie kan ongedaan worden gemaakt dankzij Git. Controleer je GitHub-commitgeschiedenis om je gedachte te herstellen.",
+ "success": "Succes",
+ "error": "Fout",
+ "memoDeleted": "Gedachte succesvol verwijderd",
+ "memoDeleteFailed": "Verwijderen van gedachte mislukt",
+ "blogPostDeleted": "Blogpost succesvol verwijderd",
+ "blogPostDeleteFailed": "Verwijderen van blogpost mislukt",
+ "memoCreated": "Gedachte succesvol aangemaakt",
+ "memoUpdated": "Gedachte succesvol bijgewerkt",
+ "blogPostCreated": "Blogpost succesvol aangemaakt",
+ "blogPostUpdated": "Blogpost succesvol bijgewerkt",
+ "cancel": "Annuleren",
+ "imageUploaded": "Afbeelding succesvol geüpload",
+ "imageUploadFailed": "Uploaden van afbeelding mislukt",
+ "onlyImagesAllowed": "Alleen afbeeldingsbestanden zijn toegestaan",
+ "dropImageHere": "Sleep de afbeelding hier naartoe",
+ "notAuthenticated": "U bent niet geauthenticeerd. Log alstublieft in.",
+ "publicContentTooltip": "We vragen alleen om openbare repository lees-/schrijfrechten, dus we kunnen geen privérepository's maken.",
+ "myHomepage": "Mijn openbare homepage",
+ "dataStoredOnGithub": "Gegevens opgeslagen op GitHub",
+ "blogTitle": "'s Cofe Blog",
+ "blogShortTitle": "'s Blog",
+ "blogDescription": "Schrijf en synchroniseer blogs in Markdown met gegevens opgeslagen in GitHub.",
+ "demoPage": "Demopagina"
+ },
+ "metadata": {
+ "title": "Cofe - Schrijf blog & gedachten met GitHub.",
+ "description": "Schrijf en synchroniseer blogposts & gedachten in Markdown met gegevens opgeslagen in GitHub."
+ }
+}
diff --git a/i18n/messages/pl.json b/i18n/messages/pl.json
new file mode 100644
index 00000000..8001252a
--- /dev/null
+++ b/i18n/messages/pl.json
@@ -0,0 +1,59 @@
+{
+ "HomePage": {
+ "blog": "Blog",
+ "memos": "Pensamentos",
+ "noMemosYet": "Nenhum pensamento ainda",
+ "createMemo": "Criar um pensamento",
+ "createBlogPost": "Criar um post no blog",
+ "publish": "Publicar",
+ "dataStoredIn": "Dados armazenados em:",
+ "writeContent": "Escreva seu conteúdo aqui...(suporta Markdown)",
+ "enterTitle": "Digite seu título aqui...",
+ "howItWorksTitle": "Como funciona:",
+ "howItWorksStep1": "Autoryzuj Tinymind do dostępu do swoich publicznych repozytoriów GitHub.",
+ "howItWorksStep2": "Tinymind tworzy publiczne repozytorium o nazwie \"tinymind-blog\" w Twoim koncie GitHub.",
+ "howItWorksStep3": "Wszystko, co napiszesz na Tinymind, jest zapisywane w tym repozytorium.",
+ "howItWorksStep4": "Twoje dane są przechowywane w Twoim repozytorium GitHub, oddzielnie od Tinymind.",
+ "signInWithGitHub": "Entrar com o GitHub",
+ "redirecting": "Redirecionando...",
+ "publishing": "Publicando...",
+ "successPublished": "Publicado com sucesso! Pode levar até 30 segundos para aparecer. Atualize se você não o vir imediatamente.",
+ "failedPublish": "Falha ao publicar",
+ "readingFromGithub": "Lendo dados do GitHub...",
+ "publicContentWarning": "Todos os conteúdos são públicos.",
+ "write": "Escrever",
+ "preview": "Visualizar",
+ "noBlogPostsYet": "Nenhum post no blog ainda",
+ "edit": "Editar",
+ "delete": "Excluir",
+ "confirmDelete": "Confirmar exclusão",
+ "undoAction": "Esta ação pode ser desfeita graças ao Git. Verifique o histórico de commits do GitHub para restaurar seu pensamento.",
+ "success": "Sucesso",
+ "error": "Erro",
+ "memoDeleted": "Pensamento excluído com sucesso",
+ "memoDeleteFailed": "Falha ao excluir pensamento",
+ "blogPostDeleted": "Post no blog excluído com sucesso",
+ "blogPostDeleteFailed": "Falha ao excluir post no blog",
+ "memoCreated": "Pensamento criado com sucesso",
+ "memoUpdated": "Pensamento atualizado com sucesso",
+ "blogPostCreated": "Post no blog criado com sucesso",
+ "blogPostUpdated": "Post no blog atualizado com sucesso",
+ "cancel": "Cancelar",
+ "imageUploaded": "Obraz został pomyślnie przesłany",
+ "imageUploadFailed": "Nie udało się przesłać obrazu",
+ "onlyImagesAllowed": "Dozwolone są tylko pliki obrazów",
+ "dropImageHere": "Upuść obraz tutaj",
+ "notAuthenticated": "Nie jesteś uwierzytelniony. Proszę się zalogować.",
+ "publicContentTooltip": "Prosimy tylko o uprawnienia do odczytu/zapisu publicznego repozytorium, więc nie możemy tworzyć prywatnych repozytoriów.",
+ "myHomepage": "Moja publiczna strona główna",
+ "dataStoredOnGithub": "Dane przechowywane na GitHub",
+ "blogTitle": " Blog Cofe",
+ "blogShortTitle": " Blog",
+ "blogDescription": "Pisz i synchronizuj blogi w Markdown z danymi przechowywanymi w GitHub.",
+ "demoPage": "Strona Demonstracyjna"
+ },
+ "metadata": {
+ "title": "Cofe - Pisz blog i myśli z GitHub.",
+ "description": "Pisz i synchronizuj posty na blogu i myśli w Markdown z danymi przechowywanymi w GitHub."
+ }
+}
diff --git a/i18n/messages/pt.json b/i18n/messages/pt.json
new file mode 100644
index 00000000..c194a2cf
--- /dev/null
+++ b/i18n/messages/pt.json
@@ -0,0 +1,59 @@
+{
+ "HomePage": {
+ "blog": "Blog",
+ "memos": "Pensamentos",
+ "noMemosYet": "Ainda não há pensamentos",
+ "createMemo": "Criar um pensamento",
+ "createBlogPost": "Criar uma postagem no blog",
+ "publish": "Publicar",
+ "dataStoredIn": "Dados armazenados em:",
+ "writeContent": "Escreva seu conteúdo aqui...(Markdown suportado)",
+ "enterTitle": "Digite seu título aqui...",
+ "howItWorksTitle": "Como funciona:",
+ "howItWorksStep1": "Autorize o Tinymind a acessar seus repositórios públicos do GitHub.",
+ "howItWorksStep2": "O Tinymind cria um repositório público chamado \"tinymind-blog\" na sua conta do GitHub.",
+ "howItWorksStep3": "Tudo o que você escreve no Tinymind é salvo neste repositório.",
+ "howItWorksStep4": "Seus dados são armazenados no seu repositório do GitHub, separado do Tinymind.",
+ "signInWithGitHub": "Entrar com GitHub",
+ "redirecting": "Redirecionando...",
+ "publishing": "Publicando...",
+ "successPublished": "Publicado com sucesso! Pode levar até 30 segundos para aparecer. Atualize se não aparecer imediatamente.",
+ "failedPublish": "Falha ao publicar",
+ "readingFromGithub": "Lendo dados do GitHub...",
+ "publicContentWarning": "Todo o conteúdo é público.",
+ "write": "Escrever",
+ "preview": "Visualizar",
+ "noBlogPostsYet": "Ainda não há postagens no blog",
+ "edit": "Editar",
+ "delete": "Excluir",
+ "confirmDelete": "Confirmar exclusão",
+ "undoAction": "Esta ação pode ser desfeita graças ao Git. Verifique o histórico de commits do GitHub para restaurar seu pensamento.",
+ "success": "Sucesso",
+ "error": "Erro",
+ "memoDeleted": "Pensamento excluído com sucesso",
+ "memoDeleteFailed": "Falha ao excluir pensamento",
+ "blogPostDeleted": "Postagem no blog excluída com sucesso",
+ "blogPostDeleteFailed": "Falha ao excluir postagem no blog",
+ "memoCreated": "Pensamento criado com sucesso",
+ "memoUpdated": "Pensamento atualizado com sucesso",
+ "blogPostCreated": "Postagem no blog criada com sucesso",
+ "blogPostUpdated": "Postagem no blog atualizada com sucesso",
+ "cancel": "Cancelar",
+ "imageUploaded": "Imagem carregada com sucesso",
+ "imageUploadFailed": "Falha ao carregar a imagem",
+ "onlyImagesAllowed": "Apenas arquivos de imagem são permitidos",
+ "dropImageHere": "Solte a imagem aqui",
+ "notAuthenticated": "Você não está autenticado. Por favor, faça login.",
+ "publicContentTooltip": "Solicitamos apenas permissão de leitura/escrita do repositório público, então não podemos criar repositórios privados.",
+ "myHomepage": "Minha página inicial pública",
+ "dataStoredOnGithub": "Dados armazenados no GitHub",
+ "blogTitle": " Blog Cofe de",
+ "blogShortTitle": " Blog de",
+ "blogDescription": "Escreva e sincronize blogs em Markdown com dados armazenados no GitHub.",
+ "demoPage": "Página de Demonstração"
+ },
+ "metadata": {
+ "title": "Cofe - Escreva blog e pensamentos com GitHub.",
+ "description": "Escreva e sincronize postagens de blog e pensamentos em Markdown com dados armazenados no GitHub."
+ }
+}
diff --git a/i18n/messages/ru.json b/i18n/messages/ru.json
new file mode 100644
index 00000000..8b6d12a3
--- /dev/null
+++ b/i18n/messages/ru.json
@@ -0,0 +1,59 @@
+{
+ "HomePage": {
+ "blog": "Блог",
+ "memos": "Мысли",
+ "noMemosYet": "Пока нет мыслей",
+ "createMemo": "Создать мысль",
+ "createBlogPost": "Создать запись в блоге",
+ "publish": "Опубликовать",
+ "dataStoredIn": "Данные хранятся в:",
+ "writeContent": "Напишите ваш контент здесь...(поддерживается Markdown)",
+ "enterTitle": "Введите ваш заголовок здесь...",
+ "howItWorksTitle": "Как это работает:",
+ "howItWorksStep1": "Авторизуйте Tinymind для доступа к вашим публичным репозиториям GitHub.",
+ "howItWorksStep2": "Tinymind создает публичный репозиторий с именем \"tinymind-blog\" в вашей учетной записи GitHub.",
+ "howItWorksStep3": "Все, что вы пишете на Tinymind, сохраняется в этом репозитории.",
+ "howItWorksStep4": "Ваши данные хранятся в вашем репозитории GitHub, отдельно от Tinymind.",
+ "signInWithGitHub": "Войти с GitHub",
+ "redirecting": "Перенаправление...",
+ "publishing": "Публикация...",
+ "successPublished": "Успешно опубликовано! Это может занять до 30 секунд, чтобы появиться. Обновите страницу, если не видите его сразу.",
+ "failedPublish": "Не удалось опубликовать",
+ "readingFromGithub": "Чтение данных из GitHub...",
+ "publicContentWarning": "Весь контент является публичным.",
+ "write": "Писать",
+ "preview": "Предпросмотр",
+ "noBlogPostsYet": "Пока нет записей в блоге",
+ "edit": "Редактировать",
+ "delete": "Удалить",
+ "confirmDelete": "Подтвердить удаление",
+ "undoAction": "Это действие можно отменить благодаря Git. Проверьте историю коммитов на GitHub, чтобы восстановить свою мысль.",
+ "success": "Успех",
+ "error": "Ошибка",
+ "memoDeleted": "Мысль успешно удалена",
+ "memoDeleteFailed": "Не удалось удалить мысль",
+ "blogPostDeleted": "Запись в блоге успешно удалена",
+ "blogPostDeleteFailed": "Не удалось удалить запись в блоге",
+ "memoCreated": "Мысль успешно создана",
+ "memoUpdated": "Мысль успешно обновлена",
+ "blogPostCreated": "Запись в блоге успешно создана",
+ "blogPostUpdated": "Запись в блоге успешно обновлена",
+ "cancel": "Отмена",
+ "imageUploaded": "Изображение успешно загружено",
+ "imageUploadFailed": "Не удалось загрузить изображение",
+ "onlyImagesAllowed": "Разрешены только файлы изображений",
+ "dropImageHere": "Перетащите изображение сюда",
+ "notAuthenticated": "Вы не аутентифицированы. Пожалуйста, войдите в систему.",
+ "publicContentTooltip": "Мы запрашиваем только разрешение на чтение/запись публичного репозитория, поэтому мы не можем создавать частные репозитории.",
+ "myHomepage": "Моя публичная домашняя страница",
+ "dataStoredOnGithub": "Данные хранятся на GitHub",
+ "blogTitle": " Блог Cofe",
+ "blogShortTitle": " Блог",
+ "blogDescription": "Пишите и синхронизируйте блоги в Markdown с данными, хранящимися в GitHub.",
+ "demoPage": "Демо-страница"
+ },
+ "metadata": {
+ "title": "Cofe - Пишите блог и мысли с GitHub.",
+ "description": "Пишите и синхронизируйте записи в блоге и мысли в Markdown с данными, хранящимися в GitHub."
+ }
+}
diff --git a/i18n/messages/th.json b/i18n/messages/th.json
new file mode 100644
index 00000000..7fc60075
--- /dev/null
+++ b/i18n/messages/th.json
@@ -0,0 +1,59 @@
+{
+ "HomePage": {
+ "blog": "บล็อก",
+ "memos": "ความคิด",
+ "noMemosYet": "ยังไม่มีความคิด",
+ "createMemo": "สร้างความคิด",
+ "createBlogPost": "สร้างโพสต์บล็อก",
+ "publish": "เผยแพร่",
+ "dataStoredIn": "ข้อมูลถูกเก็บไว้ใน:",
+ "writeContent": "เขียนเนื้อหาของคุณที่นี่...(รองรับ Markdown)",
+ "enterTitle": "ป้อนชื่อเรื่องของคุณที่นี่...",
+ "howItWorksTitle": "วิธีการทำงาน:",
+ "howItWorksStep1": "อนุญาตให้ Tinymind เข้าถึงที่เก็บ GitHub สาธารณะของคุณ",
+ "howItWorksStep2": "Tinymind สร้างที่เก็บสาธารณะชื่อ \"tinymind-blog\" ในบัญชี GitHub ของคุณ",
+ "howItWorksStep3": "ทุกสิ่งที่คุณเขียนบน Tinymind จะถูกบันทึกในที่เก็บนี้",
+ "howItWorksStep4": "ข้อมูลของคุณถูกจัดเก็บในที่เก็บ GitHub ของคุณ แยกจาก Tinymind",
+ "signInWithGitHub": "ลงชื่อเข้าใช้ด้วย GitHub",
+ "redirecting": "กำลังเปลี่ยนเส้นทาง...",
+ "publishing": "กำลังเผยแพร่...",
+ "successPublished": "เผยแพร่สำเร็จ! อาจใช้เวลาถึง 30 วินาทีในการปรากฏ หากไม่เห็นทันทีให้รีเฟรช",
+ "failedPublish": "การเผยแพร่ล้มเหลว",
+ "readingFromGithub": "กำลังอ่านข้อมูลจาก GitHub...",
+ "publicContentWarning": "เนื้อหาทั้งหมดเป็นสาธารณะ",
+ "write": "เขียน",
+ "preview": "ดูตัวอย่าง",
+ "noBlogPostsYet": "ยังไม่มีโพสต์บล็อก",
+ "edit": "แก้ไข",
+ "delete": "ลบ",
+ "confirmDelete": "ยืนยันการลบ",
+ "undoAction": "การกร��ทำนี้สามารถยกเลิกได้ด้วย Git ตรวจสอบประวัติการคอมมิตของ GitHub เพื่อกู้คืนความคิดของคุณ",
+ "success": "สำเร็จ",
+ "error": "ข้อผิดพลาด",
+ "memoDeleted": "ลบความคิดสำเร็จ",
+ "memoDeleteFailed": "ลบความคิดล้มเหลว",
+ "blogPostDeleted": "ลบโพสต์บล็อกสำเร็จ",
+ "blogPostDeleteFailed": "ลบโพสต์บล็อกล้มเหลว",
+ "memoCreated": "สร้างความคิดสำเร็จ",
+ "memoUpdated": "อัปเดตความคิดสำเร็จ",
+ "blogPostCreated": "สร้างโพสต์บล็อกสำเร็จ",
+ "blogPostUpdated": "อัปเดตโพสต์บล็อกสำเร็จ",
+ "cancel": "ยกเลิก",
+ "imageUploaded": "อัปโหลดรูปภาพสำเร็จ",
+ "imageUploadFailed": "อัปโหลดรูปภาพล้มเหลว",
+ "onlyImagesAllowed": "อนุญาตเฉพาะไฟล์รูปภาพเท่า��ั้น",
+ "dropImageHere": "วางรูปภาพที่นี",
+ "notAuthenticated": "คุณยังไม่ได้รับการตรวจสอบสิทธิ์ กรุณาเข้าสู่ระบบ",
+ "publicContentTooltip": "เราขออนุญาตอ่าน/เขียนที่เก็บสาธารณะเท่านั้น ดังนั้นเราจึงไม่สามารถสร้างที่เก็บส่วนตัวได้",
+ "myHomepage": "หน้าแรกสาธารณะของฉัน",
+ "dataStoredOnGithub": "ข้อมูลที่เก็บไว้ใน GitHub",
+ "blogTitle": " บล็อก Cofe ของ",
+ "blogShortTitle": " บล็อกของ",
+ "blogDescription": "เขียนและซิงค์บล็อกใน Markdown โดยเก็บข้อมูลไว้ใน GitHub",
+ "demoPage": "หน้าสาธิต"
+ },
+ "metadata": {
+ "title": "Cofe - เขียนบล็อกและความคิดด้วย GitHub",
+ "description": "เขียนและซิงค์โพสต์บล็อกและความคิดใน Markdown โดยเก็บข้อมูลไว้ใน GitHub"
+ }
+}
diff --git a/i18n/messages/tr.json b/i18n/messages/tr.json
new file mode 100644
index 00000000..a33beb1a
--- /dev/null
+++ b/i18n/messages/tr.json
@@ -0,0 +1,59 @@
+{
+ "HomePage": {
+ "blog": "Blog",
+ "memos": "Düşünceler",
+ "noMemosYet": "Henüz düşünce yok",
+ "createMemo": "Bir düşünce oluştur",
+ "createBlogPost": "Bir blog yazısı oluştur",
+ "publish": "Yayınla",
+ "dataStoredIn": "Veriler burada saklanır:",
+ "writeContent": "İçeriğinizi buraya yazın...(Markdown desteklenir)",
+ "enterTitle": "Başlığınızı buraya girin...",
+ "howItWorksTitle": "Nasıl çalışır:",
+ "howItWorksStep1": "Tinymind'in genel GitHub depolarınıza erişmesine izin verin.",
+ "howItWorksStep2": "Tinymind, GitHub hesabınızda \"tinymind-blog\" adlı bir genel depo oluşturur.",
+ "howItWorksStep3": "Tinymind'de yazdığınız her şey bu depoya kaydedilir.",
+ "howItWorksStep4": "Verileriniz Tinymind'den ayrı olarak GitHub deponuzda saklanır.",
+ "signInWithGitHub": "GitHub ile giriş yap",
+ "redirecting": "Yönlendiriliyor...",
+ "publishing": "Yayınlanıyor...",
+ "successPublished": "Başarıyla yayınlandı! Görünmesi 30 saniyeye kadar sürebilir. Hemen görmezseniz yenileyin.",
+ "failedPublish": "Yayınlama başarısız oldu",
+ "readingFromGithub": "GitHub'dan veri okunuyor...",
+ "publicContentWarning": "Tüm içerik herkese açıktır.",
+ "write": "Yaz",
+ "preview": "Önizleme",
+ "noBlogPostsYet": "Henüz blog yazısı yok",
+ "edit": "Düzenle",
+ "delete": "Sil",
+ "confirmDelete": "Silme işlemini onayla",
+ "undoAction": "Bu işlem Git sayesinde geri alınabilir. Düşüncenizi geri yüklemek için GitHub commit geçmişinizi kontrol edin.",
+ "success": "Başarılı",
+ "error": "Hata",
+ "memoDeleted": "Düşünce başarıyla silindi",
+ "memoDeleteFailed": "Düşünce silme başarısız oldu",
+ "blogPostDeleted": "Blog yazısı başarıyla silindi",
+ "blogPostDeleteFailed": "Blog yazısı silme başarısız oldu",
+ "memoCreated": "Düşünce başarıyla oluşturuldu",
+ "memoUpdated": "Düşünce başarıyla güncellendi",
+ "blogPostCreated": "Blog yazısı başarıyla oluşturuldu",
+ "blogPostUpdated": "Blog yazısı başarıyla güncellendi",
+ "cancel": "İptal",
+ "imageUploaded": "Resim başarıyla yüklendi",
+ "imageUploadFailed": "Resim yüklenemedi",
+ "onlyImagesAllowed": "Sadece resim dosyalarına izin verilir",
+ "dropImageHere": "Resmi buraya bırakın",
+ "notAuthenticated": "Kimlik doğrulaması yapılmadı. Lütfen giriş yapın.",
+ "publicContentTooltip": "Sadece genel depo okuma/yazma izni istiyoruz, bu nedenle özel depolar oluşturamıyoruz.",
+ "myHomepage": "Genel Ana Sayfam",
+ "dataStoredOnGithub": "GitHub'da Saklanan Veriler",
+ "blogTitle": " Cofe Blogu",
+ "blogShortTitle": " Blogu",
+ "blogDescription": "GitHub'da depolanan verilerle Markdown'da blog yazın ve senkronize edin.",
+ "demoPage": "Demo Sayfası"
+ },
+ "metadata": {
+ "title": "Cofe - GitHub ile blog ve düşünceler yazın.",
+ "description": "GitHub'da depolanan verilerle Markdown'da blog gönderileri ve düşünceler yazın ve senkronize edin."
+ }
+}
diff --git a/i18n/messages/vi.json b/i18n/messages/vi.json
new file mode 100644
index 00000000..f4ae62dd
--- /dev/null
+++ b/i18n/messages/vi.json
@@ -0,0 +1,59 @@
+{
+ "HomePage": {
+ "blog": "Blog",
+ "memos": "Suy nghĩ",
+ "noMemosYet": "Chưa có suy nghĩ nào",
+ "createMemo": "Tạo suy nghĩ",
+ "createBlogPost": "Tạo bài viết blog",
+ "publish": "Xuất bản",
+ "dataStoredIn": "Dữ liệu được lưu trữ tại:",
+ "writeContent": "Viết nội dung của bạn ở đây...(hỗ trợ Markdown)",
+ "enterTitle": "Nhập tiêu đề của bạn ở đây...",
+ "howItWorksTitle": "Cách hoạt động:",
+ "howItWorksStep1": "Ủy quyền cho Tinymind truy cập vào các kho lưu trữ công khai của GitHub của bạn.",
+ "howItWorksStep2": "Tinymind tạo một kho lưu trữ công khai có tên \"tinymind-blog\" trong tài khoản GitHub của bạn.",
+ "howItWorksStep3": "Bất cứ điều gì bạn viết trên Tinymind sẽ được lưu vào kho lưu trữ này.",
+ "howItWorksStep4": "Dữ liệu của bạn được lưu trữ trong kho lưu trữ GitHub của bạn, tách biệt với Tinymind.",
+ "signInWithGitHub": "Đăng nhập bằng GitHub",
+ "redirecting": "Đang chuyển hướng...",
+ "publishing": "Đang xuất bản...",
+ "successPublished": "Xuất bản thành công! Có thể mất đến 30 giây để xuất hiện. Làm mới nếu bạn không thấy ngay lập tức.",
+ "failedPublish": "Xuất bản thất bại",
+ "readingFromGithub": "Đang đọc dữ liệu từ GitHub...",
+ "publicContentWarning": "Tất cả nội dung đều công khai.",
+ "write": "Viết",
+ "preview": "Xem trước",
+ "noBlogPostsYet": "Chưa có bài viết blog nào",
+ "edit": "Chỉnh sửa",
+ "delete": "Xóa",
+ "confirmDelete": "Xác nhận xóa",
+ "undoAction": "Hành động này có thể được hoàn tác nhờ Git. Kiểm tra lịch sử commit của GitHub để khôi phục suy nghĩ của bạn.",
+ "success": "Thành công",
+ "error": "Lỗi",
+ "memoDeleted": "Suy nghĩ đã được xóa thành công",
+ "memoDeleteFailed": "Xóa suy nghĩ thất bại",
+ "blogPostDeleted": "Bài viết blog đã được xóa thành công",
+ "blogPostDeleteFailed": "Xóa bài viết blog thất bại",
+ "memoCreated": "Suy nghĩ đã được tạo thành công",
+ "memoUpdated": "Suy nghĩ đã được cập nhật thành công",
+ "blogPostCreated": "Bài viết blog đã được tạo thành công",
+ "blogPostUpdated": "Bài viết blog đã được cập nhật thành công",
+ "cancel": "Hủy",
+ "imageUploaded": "Tải lên hình ảnh thành công",
+ "imageUploadFailed": "Tải lên hình ảnh thất bại",
+ "onlyImagesAllowed": "Chỉ cho phép các tệp hình ảnh",
+ "dropImageHere": "Thả hình ảnh vào đây",
+ "notAuthenticated": "Bạn chưa được xác thực. Vui lòng đăng nhập.",
+ "publicContentTooltip": "Chúng tôi chỉ yêu cầu quyền đọc/ghi kho lưu trữ công khai, vì vậy chúng tôi không thể tạo kho lưu trữ riêng tư.",
+ "myHomepage": "Trang chủ công khai của tôi",
+ "dataStoredOnGithub": "Dữ liệu được lưu trữ trên GitHub",
+ "blogTitle": " Blog Cofe của",
+ "blogShortTitle": " Blog của",
+ "blogDescription": "Viết và đồng bộ hóa blog bằng Markdown với dữ liệu được lưu trữ trong GitHub.",
+ "demoPage": "Trang Demo"
+ },
+ "metadata": {
+ "title": "Cofe - Viết blog và suy nghĩ với GitHub.",
+ "description": "Viết và đồng bộ hóa bài viết blog và suy nghĩ bằng Markdown với dữ liệu được lưu trữ trong GitHub."
+ }
+}
diff --git a/i18n/messages/zh-HK.json b/i18n/messages/zh-HK.json
new file mode 100644
index 00000000..5b985ef9
--- /dev/null
+++ b/i18n/messages/zh-HK.json
@@ -0,0 +1,59 @@
+{
+ "HomePage": {
+ "blog": "網誌",
+ "memos": "想法",
+ "noMemosYet": "還沒有想法",
+ "createMemo": "創建想法",
+ "createBlogPost": "創建網誌文章",
+ "publish": "發佈",
+ "dataStoredIn": "數據儲存在:",
+ "writeContent": "在這裡寫下內容...(支援Markdown)",
+ "enterTitle": "在這裡輸入標題...",
+ "howItWorksTitle": "運作原理:",
+ "howItWorksStep1": "授權 Tinymind 訪問您的公共 GitHub 儲存庫。",
+ "howItWorksStep2": "Tinymind 在您的 GitHub 帳戶中創建一個名為 \"tinymind-blog\" 的公共儲存庫。",
+ "howItWorksStep3": "您在 Tinymind 上寫的任何內容都會提交到這個儲存庫。",
+ "howItWorksStep4": "您的數據存儲在您的 GitHub 儲存庫中,與 Tinymind 分開。",
+ "signInWithGitHub": "使用GitHub登入",
+ "redirecting": "重定向中...",
+ "publishing": "發佈中...",
+ "successPublished": "發佈成功!可能需要長達30秒的時間才能顯示。如果沒有立即看到,請重新整理。",
+ "failedPublish": "發佈失敗",
+ "readingFromGithub": "正在從GitHub讀取數據...",
+ "publicContentWarning": "所有都是公開的。",
+ "write": "寫作",
+ "preview": "預覽",
+ "noBlogPostsYet": "還沒有網誌文章",
+ "edit": "編輯",
+ "delete": "刪除",
+ "confirmDelete": "確認刪除",
+ "undoAction": "由於Git的存在,此操作可以撤銷。檢查您的GitHub提交歷史以恢復您的想法。",
+ "success": "成功",
+ "error": "錯誤",
+ "memoDeleted": "想法已成功刪除",
+ "memoDeleteFailed": "刪除想法失敗",
+ "blogPostDeleted": "網誌文章已成功刪除",
+ "blogPostDeleteFailed": "刪除網誌文章失敗",
+ "memoCreated": "想法已成功創建",
+ "memoUpdated": "想法已成功更新",
+ "blogPostCreated": "網誌文章已成功創建",
+ "blogPostUpdated": "網誌文章已成功更新",
+ "cancel": "取消",
+ "imageUploaded": "圖片上傳成功",
+ "imageUploadFailed": "圖片上傳失敗",
+ "onlyImagesAllowed": "只允許圖片檔案",
+ "dropImageHere": "將圖片拖放到這裡",
+ "notAuthenticated": "您尚未通過身份驗證。請登入。",
+ "publicContentTooltip": "我們只請求公共儲存庫的讀/寫權限,因此無���創建私人儲存庫。",
+ "myHomepage": "我的公開主頁",
+ "dataStoredOnGithub": "數據存儲在GitHub",
+ "blogTitle": " 的 Cofe 網誌",
+ "blogShortTitle": " 的網誌",
+ "blogDescription": "使用Markdown編寫網誌並同步,數據存儲在GitHub中。",
+ "demoPage": "示範頁面"
+ },
+ "metadata": {
+ "title": "Cofe - 使用GitHub編寫網誌和想法。",
+ "description": "使用Markdown編寫並同步網誌文章和想法,數據存儲在GitHub中。"
+ }
+}
diff --git a/i18n/messages/zh-TW.json b/i18n/messages/zh-TW.json
new file mode 100644
index 00000000..a1df3659
--- /dev/null
+++ b/i18n/messages/zh-TW.json
@@ -0,0 +1,59 @@
+{
+ "HomePage": {
+ "blog": "部落格",
+ "memos": "想法",
+ "noMemosYet": "還沒有想法",
+ "createMemo": "創建想法",
+ "createBlogPost": "創建部落格文章",
+ "publish": "發佈",
+ "dataStoredIn": "數據存儲在:",
+ "writeContent": "在這裡寫下內容...(支援Markdown)",
+ "enterTitle": "在這裡輸入標題...",
+ "howItWorksTitle": "工作原理:",
+ "howItWorksStep1": "授權 Tinymind 訪問您的公共 GitHub 儲存庫。",
+ "howItWorksStep2": "Tinymind 在您的 GitHub 帳戶中創建一個名為 \"tinymind-blog\" 的公共儲存庫。",
+ "howItWorksStep3": "您在 Tinymind 上寫的任何內容都會提交到這個儲存庫。",
+ "howItWorksStep4": "您的數據存儲在您的 GitHub 儲存庫中,與 Tinymind 分開。",
+ "signInWithGitHub": "使用GitHub登入",
+ "redirecting": "重定向中...",
+ "publishing": "發佈中...",
+ "successPublished": "發佈成功!可能需要長達30秒的時間才能顯示。如果沒有立即看到,請刷新。",
+ "failedPublish": "發佈失敗",
+ "readingFromGithub": "正在從GitHub讀取數據...",
+ "publicContentWarning": "所有內容都是公開的。",
+ "write": "寫作",
+ "preview": "預覽",
+ "noBlogPostsYet": "還沒有部落格文章",
+ "edit": "編輯",
+ "delete": "刪除",
+ "confirmDelete": "確認刪除",
+ "undoAction": "由於Git的存在,此操作可以撤銷。檢查您的GitHub提交歷史以恢復您的想法。",
+ "success": "成功",
+ "error": "錯誤",
+ "memoDeleted": "想法已成功刪除",
+ "memoDeleteFailed": "刪除想法失敗",
+ "blogPostDeleted": "部落格文章已成功刪除",
+ "blogPostDeleteFailed": "刪除部落格文章失敗",
+ "memoCreated": "想法已成功創建",
+ "memoUpdated": "想法已成功更新",
+ "blogPostCreated": "部落格文章已成功創建",
+ "blogPostUpdated": "部落格文章已成功更新",
+ "cancel": "取消",
+ "imageUploaded": "圖片上傳成功",
+ "imageUploadFailed": "圖片上傳失敗",
+ "onlyImagesAllowed": "只允許圖片檔案",
+ "dropImageHere": "將圖片拖放到這裡",
+ "notAuthenticated": "您尚未通過身份驗證。請登入。",
+ "publicContentTooltip": "我們只請求公共儲存庫的讀/寫權限,因此無法創建私人儲存庫。",
+ "myHomepage": "我的公開首頁",
+ "dataStoredOnGithub": "數據存儲在GitHub",
+ "blogTitle": " 的 Cofe 部落格",
+ "blogShortTitle": " 的部落格",
+ "blogDescription": "使用Markdown編寫部落格並同步,數據存儲在GitHub中。",
+ "demoPage": "示範頁面"
+ },
+ "metadata": {
+ "title": "Cofe - 使用GitHub編寫部落格和想法。",
+ "description": "使用Markdown編寫並同步部落格文章和想法,數據存儲在GitHub中。"
+ }
+ }
diff --git a/i18n/messages/zh.json b/i18n/messages/zh.json
new file mode 100644
index 00000000..f3ff797f
--- /dev/null
+++ b/i18n/messages/zh.json
@@ -0,0 +1,59 @@
+{
+ "HomePage": {
+ "blog": "博客",
+ "memos": "想法",
+ "noMemosYet": "还没有想法",
+ "createMemo": "创建想法",
+ "createBlogPost": "创建博客",
+ "publish": "发布",
+ "dataStoredIn": "数据存储在:",
+ "writeContent": "在这里写下内容...(支持Markdown)",
+ "enterTitle": "在这里输入标题...",
+ "howItWorksTitle": "工作原理:",
+ "howItWorksStep1": "授权 Tinymind 访问您的公共 GitHub 仓库。",
+ "howItWorksStep2": "Tinymind 在您的 GitHub 账户中创建一个名为 \"tinymind-blog\" 的公共仓库。",
+ "howItWorksStep3": "您在 Tinymind 上写的任何内容都会提交到这个仓库。",
+ "howItWorksStep4": "您的数据存储在您的 GitHub 仓库中,与 Tinymind 分开。",
+ "signInWithGitHub": "使用GitHub登录",
+ "redirecting": "重定向中...",
+ "publishing": "发布中...",
+ "successPublished": "发布成功!可能需要长达30秒的时间才能显示。如果没有立即看到,请刷新。",
+ "failedPublish": "发布失败",
+ "readingFromGithub": "正在从GitHub读取数据...",
+ "publicContentWarning": "所有内容都是公开的。",
+ "write": "写作",
+ "preview": "预览",
+ "noBlogPostsYet": "还没有博客文章",
+ "edit": "编辑",
+ "delete": "删除",
+ "confirmDelete": "确认删除",
+ "undoAction": "由于Git的存在,此操作可以撤销。检查您的GitHub提交历史以恢复您的想法。",
+ "success": "成功",
+ "error": "错误",
+ "memoDeleted": "想法已成功删除",
+ "memoDeleteFailed": "删除想法失败",
+ "blogPostDeleted": "博客文章已成功删除",
+ "blogPostDeleteFailed": "删除博客文章失败",
+ "memoCreated": "想法已成功创建",
+ "memoUpdated": "想法已成功更新",
+ "blogPostCreated": "博客文章已成功创建",
+ "blogPostUpdated": "博客文章已成功更新",
+ "cancel": "取消",
+ "imageUploaded": "图片上传成功",
+ "imageUploadFailed": "图片上传失败",
+ "onlyImagesAllowed": "只允许图片文件",
+ "dropImageHere": "将图片拖放到这里",
+ "notAuthenticated": "您尚未通过身份验证。请登录。",
+ "publicContentTooltip": "我们只请求公共仓库的读/写权限,所以我们无法创建私有仓库。",
+ "myHomepage": "我的公开主页",
+ "dataStoredOnGithub": "数据存储在GitHub",
+ "blogTitle": " 的 Cofe 博客",
+ "blogShortTitle": " 的博客",
+ "blogDescription": "使用Markdown编写博客并同步,数据存储在GitHub中。",
+ "demoPage": "演示页面"
+ },
+ "metadata": {
+ "title": "Cofe - 使用GitHub编写博客和想法。",
+ "description": "使用Markdown编写并同步博客文章和想法,数据存储在GitHub中。"
+ }
+ }
diff --git a/i18n/request.ts b/i18n/request.ts
new file mode 100644
index 00000000..40543779
--- /dev/null
+++ b/i18n/request.ts
@@ -0,0 +1,30 @@
+import { getRequestConfig } from 'next-intl/server';
+import { headers } from 'next/headers';
+
+export default getRequestConfig(async () => {
+ // Get the Accept-Language header
+ const acceptLanguage = headers().get('Accept-Language');
+
+ // Parse the Accept-Language header to get the preferred language
+ let browserLocale = acceptLanguage ? acceptLanguage.split(',')[0] : 'en';
+
+ // Special handling for Chinese locales
+ if (browserLocale.startsWith('zh')) {
+ if (browserLocale === 'zh-TW' || browserLocale === 'zh-HK') {
+ browserLocale = browserLocale; // Keep zh-TW or zh-HK as is
+ } else {
+ browserLocale = 'zh'; // Default to zh for other Chinese variants
+ }
+ } else {
+ browserLocale = browserLocale.split('-')[0];
+ }
+
+ // Use the browser locale if it's supported, otherwise fallback to 'en'
+ const supportedLocales = ['en', 'zh', 'zh-TW', 'zh-HK', 'ja', 'ko', 'fr', 'de', 'es', 'pt', 'ru', 'ar', 'hi', 'it', 'nl', 'tr', 'pl', 'vi', 'th', 'id'];
+ const selectedLocale = supportedLocales.includes(browserLocale) ? browserLocale : 'en';
+
+ return {
+ locale: selectedLocale,
+ messages: (await import(`./messages/${selectedLocale}.json`)).default
+ };
+});
diff --git a/lib/cache.ts b/lib/cache.ts
new file mode 100644
index 00000000..479c9e13
--- /dev/null
+++ b/lib/cache.ts
@@ -0,0 +1,81 @@
+interface CacheEntry {
+ data: T;
+ expiry: number;
+}
+
+// Universal in-memory cache (server-side only)
+// eslint-disable-next-line
+const serverCache: Record> = {};
+
+// Cache duration in milliseconds (e.g., 5 minutes)
+const CACHE_DURATION = 5 * 60 * 1000;
+
+// Check if running on the server
+const isServer = typeof window === "undefined";
+
+/**
+ * Get cache entry by key
+ */
+function getCache(key: string): CacheEntry | null {
+ if (isServer) {
+ return serverCache[key] || null;
+ } else {
+ const cachedItem = localStorage.getItem(key);
+ return cachedItem ? (JSON.parse(cachedItem) as CacheEntry) : null;
+ }
+}
+
+/**
+ * Set cache entry by key
+ */
+function setCache(key: string, data: T): void {
+ const cacheEntry: CacheEntry = {
+ data,
+ expiry: Date.now() + CACHE_DURATION,
+ };
+
+ if (isServer) {
+ serverCache[key] = cacheEntry;
+ } else {
+ localStorage.setItem(key, JSON.stringify(cacheEntry));
+ }
+}
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+function clearExpiredCache(key: string): void {
+ if (isServer) {
+ delete serverCache[key];
+ } else {
+ localStorage.removeItem(key);
+ }
+}
+
+/**
+ * Universal function to get data from cache or fetch if not available or expired
+ */
+export async function getCachedOrFetch(
+ key: string,
+ fetchFunction: () => Promise
+): Promise {
+ const cachedData = getCache(key);
+
+ // Return cached data if valid
+ if (cachedData && cachedData.expiry > Date.now()) {
+ console.warn(`Returning cached data for key: ${key}`);
+ return cachedData.data;
+ }
+
+ try {
+ console.warn(`Fetching new data for key: ${key}`);
+ const data = await fetchFunction();
+ setCache(key, data);
+ return data;
+ } catch (error) {
+ console.error(`Error fetching data for key: ${key}`, error);
+ if (cachedData) {
+ // Fallback to expired cached data if fetch fails
+ return cachedData.data;
+ }
+ throw error;
+ }
+}
diff --git a/lib/client.ts b/lib/client.ts
new file mode 100644
index 00000000..fe123303
--- /dev/null
+++ b/lib/client.ts
@@ -0,0 +1,128 @@
+import { BlogPost, Memo } from './types'
+
+import { Octokit } from '@octokit/rest'
+import { getCachedOrFetch } from './cache'
+
+const REPO = 'Cofe'
+
+const getFirstImageURLFrom = (content: string): string | null => {
+ const imgRegex = /(https?:\/\/[^\s]+?\.(?:png|jpg|jpeg|gif|webp))/i
+ const match = imgRegex.exec(content)
+ if (match) {
+ const url = match[1]
+ return url.startsWith('https://github') ? `${url}?raw=true` : url
+ }
+ return null
+}
+
+class GitHubAPIClient {
+ private accessToken: string
+
+ constructor(token: string) {
+ this.accessToken = token
+ }
+
+ async getBlogPosts(owner?: string): Promise {
+ const octokit = this.accessToken ? new Octokit({ auth: this.accessToken }) : new Octokit()
+ if (!owner && this.accessToken) {
+ const { data: user } = await octokit.users.getAuthenticated()
+ owner = user.login
+ }
+ return getCachedOrFetch(`${owner}/${REPO}`, async () => {
+ try {
+ const response = await octokit.repos.getContent({
+ owner: owner ?? '',
+ repo: REPO,
+ path: 'data/blog',
+ })
+
+ if (!Array.isArray(response.data)) {
+ console.warn('Unexpected response from GitHub API: data is not an array')
+ return []
+ }
+
+ const posts = await Promise.all(
+ response.data
+ .filter(
+ (file) =>
+ file.type === 'file' && file.name !== '.gitkeep' && file.name.endsWith('.md')
+ )
+ .map(async (file) => {
+ return this.getBlogPost(file.name, owner)
+ })
+ )
+
+ return posts.filter((post): post is BlogPost => post !== undefined)
+ } catch (error) {
+ console.error('Error fetching blog posts:', error)
+ // If the blog directory doesn't exist, return an empty array
+ if (error instanceof Error && 'status' in error && error.status === 404) {
+ console.log('Blog directory does not exist, returning empty array')
+ return []
+ }
+ throw error
+ }
+ })
+ }
+
+ async getBlogPost(name: string, owner?: string): Promise {
+ const octokit = this.accessToken ? new Octokit({ auth: this.accessToken }) : new Octokit()
+ if (!owner) {
+ const { data: user } = await octokit.users.getAuthenticated()
+ owner = user.login
+ }
+
+ return getCachedOrFetch(`${owner}/${REPO}/data/blog/${name}`, async () => {
+ const contentResponse = await octokit.repos.getContent({
+ owner,
+ repo: REPO,
+ path: `data/blog/${name}`,
+ })
+
+ if ('content' in contentResponse.data) {
+ const content = Buffer.from(contentResponse.data.content, 'base64').toString('utf-8')
+ const titleMatch = content.match(/title:\s*(.+)/)
+ const dateMatch = content.match(/date:\s*(.+)/)
+
+ return {
+ id: name.replace('.md', ''),
+ title: titleMatch
+ ? decodeURIComponent(titleMatch[1])
+ : decodeURIComponent(name.replace('.md', '')),
+ content,
+ imageUrl: getFirstImageURLFrom(content),
+ date: dateMatch ? new Date(dateMatch[1]).toISOString() : new Date().toISOString(),
+ }
+ }
+ })
+ }
+
+ async getMemos(owner?: string): Promise {
+ const octokit = this.accessToken ? new Octokit({ auth: this.accessToken }) : new Octokit()
+ if (!owner) {
+ const { data: user } = await octokit.users.getAuthenticated()
+ owner = user.login
+ }
+ return getCachedOrFetch(`${owner}/${REPO}/data/memos.json`, async () => {
+ try {
+ const response = await octokit.repos.getContent({
+ owner,
+ repo: REPO,
+ path: 'data/memos.json',
+ })
+
+ if (Array.isArray(response.data) || !('content' in response.data)) {
+ return []
+ }
+
+ const content = Buffer.from(response.data.content, 'base64').toString('utf-8')
+ return JSON.parse(content) as Memo[]
+ } catch (error) {
+ console.error('Error fetching public memos:', error)
+ return []
+ }
+ })
+ }
+}
+
+export const createGitHubAPIClient = (token: string) => new GitHubAPIClient(token)
diff --git a/lib/githubApi.ts b/lib/githubApi.ts
index c54c8c09..cef997d3 100644
--- a/lib/githubApi.ts
+++ b/lib/githubApi.ts
@@ -1,46 +1,53 @@
+import { Memo } from './types'
import { Octokit } from '@octokit/rest'
+import path from 'path'
-export interface BlogPost {
- id: string;
- title: string;
- content: string;
- date: string;
-}
+type UpdateFileParams = Parameters[0]
-export type Thought = {
- id: string
- content: string
- timestamp: string
- image?: string
-}
+const REPO = 'Cofe'
function getOctokit(accessToken: string | undefined) {
if (!accessToken) {
- throw new Error('Access token is required');
+ throw new Error('Access token is required')
}
- return new Octokit({ auth: accessToken });
+ return new Octokit({ auth: accessToken })
}
async function getRepoInfo(accessToken: string | undefined) {
if (!accessToken) {
- throw new Error('Access token is required');
+ throw new Error('Access token is required')
}
- const octokit = getOctokit(accessToken);
+ const octokit = getOctokit(accessToken)
try {
- const { data: user } = await octokit.users.getAuthenticated();
+ const { data: user } = await octokit.users.getAuthenticated()
return {
owner: user.login,
- repo: 'tinymind-blog' // You might want to make this configurable
- };
+ repo: REPO,
+ }
} catch (error) {
- console.error('Error getting authenticated user:', error);
- throw new Error('Failed to get authenticated user');
+ console.error('Error getting authenticated user:', error)
+ throw new Error('Failed to get authenticated user')
}
}
async function ensureRepoExists(octokit: Octokit, owner: string, repo: string) {
try {
- await octokit.repos.get({ owner, repo })
+ const { data: repoData } = await octokit.repos.get({ owner, repo })
+
+ // Check if the repository description is empty.
+ if (!repoData.description) {
+ // Get the authenticated user's login
+ const { data: userData } = await octokit.users.getAuthenticated()
+ const userLogin = userData.login
+
+ // Update the repository with the new description
+ await octokit.repos.update({
+ owner,
+ repo,
+ description: `https://tinymind.me/${userLogin}`,
+ })
+ console.log(`Updated repository description to https://tinymind.me/${userLogin}`)
+ }
} catch (error) {
if (error instanceof Error && 'status' in error && error.status === 404) {
await octokit.repos.createForAuthenticatedUser({
@@ -52,20 +59,71 @@ async function ensureRepoExists(octokit: Octokit, owner: string, repo: string) {
throw error
}
}
+
+ // Check if README.md exists and needs updating
+ try {
+ const { data: readmeContent } = await octokit.repos.getContent({
+ owner,
+ repo,
+ path: 'README.md',
+ })
+ console.log('README content:', readmeContent)
+ if ('content' in readmeContent) {
+ const decodedContent = Buffer.from(readmeContent.content, 'base64').toString('utf-8')
+ console.log('README Decoded content:', decodedContent.trim())
+ if (decodedContent.trim() === '' || decodedContent.trim() === '# tinymind-blog') {
+ // README.md is empty or contains only the default repo name, update it
+ await octokit.repos.createOrUpdateFileContents({
+ owner,
+ repo,
+ path: 'README.md',
+ message: 'Update README.md with default content',
+ content: Buffer.from(
+ '# Cofe Blog\n\nWrite blog posts and memos at https://tinymind.me with data stored on GitHub.'
+ ).toString('base64'),
+ sha: readmeContent.sha,
+ })
+ console.log('README.md updated with default content')
+ }
+ }
+ } catch (error) {
+ if (error instanceof Error && 'status' in error && error.status === 404) {
+ // Create README.md if it doesn't exist
+ const content = Buffer.from(
+ 'Write blog posts and memos at https://tinymind.me with data stored on GitHub.'
+ ).toString('base64')
+ await octokit.repos.createOrUpdateFileContents({
+ owner,
+ repo,
+ path: 'README.md',
+ message: 'Initial commit: Add README.md',
+ content,
+ })
+ } else {
+ throw error
+ }
+ }
}
async function ensureContentStructure(octokit: Octokit, owner: string, repo: string) {
- async function createFileIfNotExists(octokit: Octokit, owner: string, repo: string, path: string, message: string, content: string) {
+ async function createFileIfNotExists(
+ octokit: Octokit,
+ owner: string,
+ repo: string,
+ path: string,
+ message: string,
+ content: string
+ ) {
try {
await octokit.repos.getContent({
owner,
repo,
path,
- });
- console.log(`File ${path} already exists.`);
+ })
+ console.log(`File ${path} already exists.`)
} catch (error) {
if (error instanceof Error && 'status' in error && error.status === 404) {
- console.log(`Creating file ${path}...`);
+ console.log(`Creating file ${path}...`)
try {
await octokit.repos.createOrUpdateFileContents({
owner,
@@ -73,26 +131,47 @@ async function ensureContentStructure(octokit: Octokit, owner: string, repo: str
path,
message,
content: Buffer.from(content).toString('base64'), // Encode content to Base64
- });
- console.log(`File ${path} created successfully.`);
+ })
+ console.log(`File ${path} created successfully.`)
} catch (createError) {
- console.error(`Error creating file ${path}:`, createError);
- throw createError;
+ console.error(`Error creating file ${path}:`, createError)
+ throw createError
}
} else {
- console.error(`Error checking file ${path}:`, error);
- throw error;
+ console.error(`Error checking file ${path}:`, error)
+ throw error
}
}
}
try {
- await createFileIfNotExists(octokit, owner, repo, 'content/.gitkeep', 'Initialize content directory', '');
- await createFileIfNotExists(octokit, owner, repo, 'content/blog/.gitkeep', 'Initialize blog directory', '');
- await createFileIfNotExists(octokit, owner, repo, 'content/thoughts.json', 'Initialize thoughts.json', '[]');
+ await createFileIfNotExists(
+ octokit,
+ owner,
+ repo,
+ 'data/.gitkeep',
+ 'Initialize content directory',
+ ''
+ )
+ await createFileIfNotExists(
+ octokit,
+ owner,
+ repo,
+ 'data/blog/.gitkeep',
+ 'Initialize blog directory',
+ ''
+ )
+ await createFileIfNotExists(
+ octokit,
+ owner,
+ repo,
+ 'data/memos.json',
+ 'Initialize memos.json',
+ '[]'
+ )
} catch (error) {
- console.error('Error ensuring content structure:', error);
- throw error;
+ console.error('Error ensuring content structure:', error)
+ throw error
}
}
@@ -101,241 +180,525 @@ async function initializeGitHubStructure(octokit: Octokit, owner: string, repo:
await ensureContentStructure(octokit, owner, repo)
}
-export async function getBlogPosts(accessToken: string): Promise {
- const octokit = getOctokit(accessToken);
- const { owner, repo } = await getRepoInfo(accessToken);
- await initializeGitHubStructure(octokit, owner, repo);
+export async function createBlogPost(
+ title: string,
+ content: string,
+ accessToken: string
+): Promise {
+ const octokit = getOctokit(accessToken)
+ const { owner, repo } = await getRepoInfo(accessToken)
+ await initializeGitHubStructure(octokit, owner, repo)
+
+ const path = `data/blog/${title.toLowerCase().replace(/\s+/g, '-')}.md`
+ const date = new Date().toISOString() // Store full ISO string
+ const fullContent = `---
+title: ${title}
+date: ${date}
+---
+
+${content}`
+
+ await octokit.repos.createOrUpdateFileContents({
+ owner,
+ repo,
+ path,
+ message: `Add blog post: ${title}`,
+ content: Buffer.from(fullContent).toString('base64'),
+ })
+}
+
+export async function createMemo(
+ content: string,
+ image: string | undefined,
+ accessToken: string
+): Promise {
+ console.log('Creating memo...')
+ if (!accessToken) {
+ throw new Error('Access token is required')
+ }
+ const octokit = getOctokit(accessToken)
+ console.log('Octokit instance created')
try {
- const response = await octokit.repos.getContent({
+ const { owner, repo } = await getRepoInfo(accessToken)
+ console.log('Repo info:', { owner, repo })
+
+ await initializeGitHubStructure(octokit, owner, repo)
+ console.log('GitHub structure initialized')
+
+ let memos: Memo[] = []
+ let existingSha: string | undefined
+
+ // Try to fetch existing memos
+ try {
+ console.log('Fetching existing memos...')
+ const response = await octokit.repos.getContent({
+ owner,
+ repo,
+ path: 'data/memos.json',
+ })
+
+ if (!Array.isArray(response.data) && 'content' in response.data) {
+ const existingContent = Buffer.from(response.data.content, 'base64').toString('utf-8')
+ memos = JSON.parse(existingContent) as Memo[]
+ existingSha = response.data.sha
+ console.log('Existing memos fetched')
+ }
+ } catch (error) {
+ if (error instanceof Error && 'status' in error && error.status === 404) {
+ console.log('memos.json does not exist, creating a new file')
+ } else {
+ console.error('Error fetching existing memos:', error)
+ throw error
+ }
+ }
+
+ // Create new memo
+ const newMemo: Memo = {
+ id: Date.now().toString(),
+ content,
+ timestamp: new Date().toISOString(),
+ image,
+ }
+
+ // Add new memo to the beginning of the array
+ memos.unshift(newMemo)
+
+ console.log('Updating memos file...')
+ // Create or update the file with all memos
+ const updateParams: UpdateFileParams = {
owner,
repo,
- path: 'content/blog',
- });
-
- console.log('GitHub API response:', response);
+ path: 'data/memos.json',
+ message: 'Add new memo',
+ content: Buffer.from(JSON.stringify(memos, null, 2)).toString('base64'),
+ }
- if (!Array.isArray(response.data)) {
- console.warn('Unexpected response from GitHub API: data is not an array');
- return [];
+ if (existingSha) {
+ updateParams.sha = existingSha
}
- const posts = await Promise.all(
- response.data
- .filter(file => file.type === 'file' && file.name !== '.gitkeep' && file.name.endsWith('.md'))
- .map(async (file) => {
- try {
- const contentResponse = await octokit.repos.getContent({
- owner,
- repo,
- path: `content/blog/${file.name}`,
- });
-
- if ('content' in contentResponse.data) {
- const content = Buffer.from(contentResponse.data.content, 'base64').toString('utf-8');
-
- // Parse the date from the content
- const dateMatch = content.match(/date:\s*(.+)/);
- const date = dateMatch ? new Date(dateMatch[1]).toISOString() : new Date().toISOString();
-
- // Parse the title from the content
- const titleMatch = content.match(/title:\s*(.+)/);
- const title = titleMatch ? titleMatch[1] : file.name.replace('.md', '');
-
- return {
- id: file.name.replace('.md', ''),
- title,
- content,
- date,
- };
- }
- } catch (error) {
- console.error(`Error fetching content for ${file.name}:`, error);
- }
- })
- );
+ await octokit.repos.createOrUpdateFileContents(updateParams)
- const filteredPosts = posts.filter((post): post is BlogPost => post !== undefined);
- console.log('Filtered posts:', filteredPosts);
- return filteredPosts;
+ console.log('Memo created successfully')
} catch (error) {
- console.error('Error fetching blog posts:', error);
- throw error;
+ console.error('Error creating memo:', error)
+ throw error
}
}
-export async function getBlogPost(id: string, accessToken: string): Promise {
+export async function deleteMemo(id: string, accessToken: string): Promise {
+ console.log('Deleting memo...')
if (!accessToken) {
- throw new Error('Access token is required');
+ throw new Error('Access token is required')
}
- const octokit = getOctokit(accessToken);
- const { owner, repo } = await getRepoInfo(accessToken);
- await initializeGitHubStructure(octokit, owner, repo);
+ const octokit = getOctokit(accessToken)
+ console.log('Octokit instance created')
try {
- // Fetch the file content
- const contentResponse = await octokit.repos.getContent({
+ const { owner, repo } = await getRepoInfo(accessToken)
+ console.log('Repo info:', { owner, repo })
+
+ await initializeGitHubStructure(octokit, owner, repo)
+ console.log('GitHub structure initialized')
+
+ let memos: Memo[] = []
+ let existingSha: string | undefined
+
+ // Fetch existing memos
+ const response = await octokit.repos.getContent({
owner,
repo,
- path: `content/blog/${id}.md`,
- });
+ path: 'data/memos.json',
+ })
- if (Array.isArray(contentResponse.data) || !('content' in contentResponse.data)) {
- throw new Error('Unexpected response from GitHub API');
+ if (!Array.isArray(response.data) && 'content' in response.data) {
+ const existingContent = Buffer.from(response.data.content, 'base64').toString('utf-8')
+ memos = JSON.parse(existingContent) as Memo[]
+ existingSha = response.data.sha
+ console.log('Existing memos fetched')
+ }
+
+ // Find and remove the memo
+ const newMemos = memos.filter((t) => t.id !== id)
+ console.log(newMemos)
+
+ console.log('Updating memos file...')
+ // Update the file with all memos
+ const updateParams: UpdateFileParams = {
+ owner,
+ repo,
+ path: 'data/memos.json',
+ message: 'Delete a memo',
+ content: Buffer.from(JSON.stringify(newMemos, null, 2)).toString('base64'),
+ sha: existingSha,
}
- const content = Buffer.from(contentResponse.data.content, 'base64').toString('utf-8');
+ await octokit.repos.createOrUpdateFileContents(updateParams)
- // Fetch the latest commit for this file
- const commitResponse = await octokit.repos.listCommits({
+ console.log('Memo deleted successfully')
+ } catch (error) {
+ console.error('Error deleting memo:', error)
+ throw error
+ }
+}
+
+export async function updateMemo(
+ id: string,
+ content: string,
+ accessToken: string
+): Promise {
+ console.log('Updating memo...')
+ if (!accessToken) {
+ throw new Error('Access token is required')
+ }
+ const octokit = getOctokit(accessToken)
+ console.log('Octokit instance created')
+
+ try {
+ const { owner, repo } = await getRepoInfo(accessToken)
+ console.log('Repo info:', { owner, repo })
+
+ await initializeGitHubStructure(octokit, owner, repo)
+ console.log('GitHub structure initialized')
+
+ let memos: Memo[] = []
+ let existingSha: string | undefined
+
+ // Fetch existing memos
+ const response = await octokit.repos.getContent({
owner,
repo,
- path: `content/blog/${id}.md`,
- per_page: 1
- });
+ path: 'data/memos.json',
+ })
- if (commitResponse.data.length === 0) {
- throw new Error('No commits found for this file');
+ if (!Array.isArray(response.data) && 'content' in response.data) {
+ const existingContent = Buffer.from(response.data.content, 'base64').toString('utf-8')
+ memos = JSON.parse(existingContent) as Memo[]
+ existingSha = response.data.sha
+ console.log('Existing memos fetched')
}
- const latestCommit = commitResponse.data[0];
+ // Find and update the memo
+ const memoIndex = memos.findIndex((t) => t.id === id)
+ if (memoIndex === -1) {
+ throw new Error('Memo not found')
+ }
- return {
- id,
- title: id,
+ memos[memoIndex] = {
+ ...memos[memoIndex],
content,
- date: latestCommit.commit.author?.date ?? new Date().toISOString(),
- };
+ // Removed the timestamp update to keep the original timestamp
+ }
+
+ console.log('Updating memos file...')
+ // Update the file with all memos
+ const updateParams: UpdateFileParams = {
+ owner,
+ repo,
+ path: 'data/memos.json',
+ message: 'Update memo',
+ content: Buffer.from(JSON.stringify(memos, null, 2)).toString('base64'),
+ sha: existingSha,
+ }
+
+ await octokit.repos.createOrUpdateFileContents(updateParams)
+
+ console.log('Memo updated successfully')
} catch (error) {
- console.error('Error fetching blog post:', error);
- return null;
+ console.error('Error updating memo:', error)
+ throw error
}
}
-export async function getThoughts(accessToken: string | undefined): Promise {
+export async function deleteBlogPost(id: string, accessToken: string): Promise {
+ console.log('Deleting blog post...')
if (!accessToken) {
- throw new Error('Access token is required');
+ throw new Error('Access token is required')
}
- const octokit = getOctokit(accessToken);
- const { owner, repo } = await getRepoInfo(accessToken);
+ const octokit = getOctokit(accessToken)
try {
- const response = await octokit.repos.getContent({
+ const { owner, repo } = await getRepoInfo(accessToken)
+
+ // Decode the ID and create the file path
+ const decodedId = decodeURIComponent(id)
+ const path = `data/blog/${decodedId}.md`
+
+ console.log(`Attempting to delete file: ${path}`)
+
+ // Get the current file to retrieve its SHA
+ const currentFile = await octokit.repos.getContent({
owner,
repo,
- path: 'content/thoughts.json',
+ path,
})
- if (Array.isArray(response.data) || !('content' in response.data)) {
- throw new Error('Unexpected response from GitHub API')
+ if (Array.isArray(currentFile.data) || !('sha' in currentFile.data)) {
+ throw new Error('Unexpected response when fetching current blog post')
}
- const content = Buffer.from(response.data.content, 'base64').toString('utf-8')
- const thoughts = JSON.parse(content) as Thought[]
+ // Delete the blog post file
+ await octokit.repos.deleteFile({
+ owner,
+ repo,
+ path,
+ message: 'Delete blog post',
+ sha: currentFile.data.sha,
+ })
- return thoughts
+ console.log('Blog post deleted successfully')
} catch (error) {
- console.error('Error fetching thoughts:', error)
+ console.error('Error deleting blog post:', error)
throw error
}
}
-export async function createBlogPost(title: string, content: string, accessToken: string): Promise {
- const octokit = getOctokit(accessToken);
- const { owner, repo } = await getRepoInfo(accessToken);
- await initializeGitHubStructure(octokit, owner, repo);
+async function getContent(octokit: Octokit, owner: string, repo: string, path: string) {
+ const response = await octokit.repos.getContent({ owner, repo, path })
+ if (Array.isArray(response.data) || !('content' in response.data)) {
+ throw new Error('Unexpected response from GitHub API')
+ }
+ return {
+ content: Buffer.from(response.data.content, 'base64').toString('utf-8'),
+ sha: response.data.sha,
+ }
+}
- const path = `content/blog/${encodeURIComponent(title.toLowerCase().replace(/\s+/g, '-'))}.md`
- const date = new Date().toISOString() // Store full ISO string
- const fullContent = `---
+async function updateFileContents(
+ octokit: Octokit,
+ owner: string,
+ repo: string,
+ path: string,
+ message: string,
+ content: string,
+ sha?: string
+) {
+ const params: UpdateFileParams = {
+ owner,
+ repo,
+ path,
+ message,
+ content: Buffer.from(content).toString('base64'),
+ }
+ if (sha) {
+ params.sha = sha
+ }
+ await octokit.repos.createOrUpdateFileContents(params)
+}
+
+export async function updateBlogPost(
+ id: string,
+ title: string,
+ content: string,
+ accessToken: string
+): Promise {
+ console.log('Updating blog post...')
+ if (!accessToken) {
+ throw new Error('Access token is required')
+ }
+ const octokit = getOctokit(accessToken)
+
+ try {
+ const { owner, repo } = await getRepoInfo(accessToken)
+
+ // Get the current file to retrieve its SHA and content
+ const { content: existingContent, sha } = await getContent(
+ octokit,
+ owner,
+ repo,
+ `data/blog/${id}.md`
+ )
+ const dateMatch = existingContent.match(/date:\s*(.+)/)
+ const date = dateMatch ? dateMatch[1] : new Date().toISOString()
+
+ const updatedContent = `---
title: ${title}
date: ${date}
---
${content}`
- await octokit.repos.createOrUpdateFileContents({
- owner,
- repo,
- path,
- message: `Add blog post: ${title}`,
- content: Buffer.from(fullContent).toString('base64'),
- })
+ // Update the blog post file
+ await updateFileContents(
+ octokit,
+ owner,
+ repo,
+ `data/blog/${id}.md`,
+ 'Update blog post',
+ updatedContent,
+ sha
+ )
+
+ console.log('Blog post updated successfully')
+ } catch (error) {
+ console.error('Error updating blog post:', error)
+ throw error
+ }
}
-export async function createThought(content: string, image: string | undefined, accessToken: string): Promise {
- console.log('Creating thought...');
+export async function uploadImage(file: File, accessToken: string): Promise {
+ console.log('Uploading image...')
if (!accessToken) {
- throw new Error('Access token is required');
+ throw new Error('Access token is required')
}
- const octokit = getOctokit(accessToken);
- console.log('Octokit instance created');
-
+ const octokit = getOctokit(accessToken)
+ console.log('Octokit instance created')
+
try {
- const { owner, repo } = await getRepoInfo(accessToken);
- console.log('Repo info:', { owner, repo });
+ const { owner, repo } = await getRepoInfo(accessToken)
+ console.log('Repo info:', { owner, repo })
- await initializeGitHubStructure(octokit, owner, repo);
- console.log('GitHub structure initialized');
+ // Get the default branch
+ const { data: repoData } = await octokit.repos.get({ owner, repo })
+ const defaultBranch = repoData.default_branch
+ console.log('Default branch:', defaultBranch)
- let thoughts: Thought[] = [];
- let existingSha: string | undefined;
+ await initializeGitHubStructure(octokit, owner, repo)
+ console.log('GitHub structure initialized')
- // Try to fetch existing thoughts
- try {
- console.log('Fetching existing thoughts...');
- const response = await octokit.repos.getContent({
+ // Generate a unique filename
+ const date = new Date().toISOString().split('T')[0] // YYYY-MM-DD
+ const id = Date.now().toString()
+ const ext = path.extname(file.name)
+ const filename = `${id}${ext}`
+ const filePath = `assets/images/${date}/${filename}`
+
+ // Ensure the directory exists
+ await ensureDirectoryExists(octokit, owner, repo, `assets/images/${date}`)
+
+ // Convert file to base64
+ const content = await fileToBase64(file)
+
+ // Upload the file
+ const response = await octokit.repos.createOrUpdateFileContents({
+ owner,
+ repo,
+ path: filePath,
+ message: `Upload image: ${filename}`,
+ content,
+ })
+ console.log(response)
+
+ console.log('Image uploaded successfully')
+
+ // Modify the returned URL to use the correct format
+ const rawUrl = response.data.content?.download_url
+ if (rawUrl) {
+ const parts = rawUrl.split('/')
+ const username = parts[3]
+ const repo = parts[4]
+ const path = parts.slice(6).join('/')
+
+ return `https://github.com/${username}/${repo}/blob/${defaultBranch}/${path}?raw=true`
+ }
+
+ throw new Error('Failed to get image URL')
+ } catch (error) {
+ console.error('Error uploading image:', error)
+ throw error
+ }
+}
+
+async function ensureDirectoryExists(octokit: Octokit, owner: string, repo: string, path: string) {
+ try {
+ await octokit.repos.getContent({ owner, repo, path })
+ } catch (error) {
+ if (error instanceof Error && 'status' in error && error.status === 404) {
+ // Directory doesn't exist, create it
+ await octokit.repos.createOrUpdateFileContents({
owner,
repo,
- path: 'content/thoughts.json',
- });
+ path: `${path}/.gitkeep`,
+ message: `Create directory: ${path}`,
+ content: '',
+ })
+ } else {
+ throw error
+ }
+ }
+}
- if (!Array.isArray(response.data) && 'content' in response.data) {
- const existingContent = Buffer.from(response.data.content, 'base64').toString('utf-8');
- thoughts = JSON.parse(existingContent) as Thought[];
- existingSha = response.data.sha;
- console.log('Existing thoughts fetched');
- }
- } catch (error) {
- if (error instanceof Error && 'status' in error && error.status === 404) {
- console.log('thoughts.json does not exist, creating a new file');
+async function fileToBase64(file: File): Promise {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader()
+ reader.readAsDataURL(file)
+ reader.onload = () => {
+ if (typeof reader.result === 'string') {
+ // Remove the data URL prefix (e.g., "data:image/png;base64,")
+ resolve(reader.result.split(',')[1])
} else {
- console.error('Error fetching existing thoughts:', error);
- throw error;
+ reject(new Error('Failed to convert file to base64'))
}
}
+ reader.onerror = (error) => reject(error)
+ })
+}
- // Create new thought
- const newThought: Thought = {
- id: Date.now().toString(),
- content,
- timestamp: new Date().toISOString(),
- image,
- };
+export async function getUserLogin(accessToken: string): Promise {
+ const octokit = getOctokit(accessToken)
+ const { data: user } = await octokit.users.getAuthenticated()
+ return user.name ?? user.login
+}
+
+export async function getIconUrls(
+ usernameOrAccessToken: string
+): Promise<{ iconPath: string; appleTouchIconPath: string }> {
+ let owner: string
+ let repo: string
+ let octokit: Octokit | null = null
- // Add new thought to the beginning of the array
- thoughts.unshift(newThought);
+ // Check if the input is an access token or a username
+ if (usernameOrAccessToken.length > 40) {
+ // Assuming access tokens are longer than usernames
+ try {
+ octokit = getOctokit(usernameOrAccessToken)
+ const repoInfo = await getRepoInfo(usernameOrAccessToken)
+ owner = repoInfo.owner
+ repo = repoInfo.repo
+ } catch (error) {
+ console.error('Error getting authenticated user:', error)
+ // Fallback to using the access token as a username
+ owner = usernameOrAccessToken
+ repo = REPO
+ }
+ } else {
+ owner = usernameOrAccessToken
+ repo = REPO
+ }
- console.log('Updating thoughts file...');
- // Create or update the file with all thoughts
- await octokit.repos.createOrUpdateFileContents({
+ const defaultIconPath = `https://github.com/${owner}.png`
+ const defaultAppleTouchIconPath = `https://github.com/${owner}.png`
+
+ let iconPath = defaultIconPath
+ let appleTouchIconPath = defaultAppleTouchIconPath
+
+ if (octokit) {
+ iconPath = await getIconUrl(octokit, owner, repo, 'assets/icon.jpg', defaultIconPath)
+ appleTouchIconPath = await getIconUrl(
+ octokit,
owner,
repo,
- path: 'content/thoughts.json',
- message: 'Add new thought',
- content: Buffer.from(JSON.stringify(thoughts, null, 2)).toString('base64'),
- sha: existingSha,
- });
+ 'assets/icon-144.jpg',
+ defaultAppleTouchIconPath
+ )
+ }
+
+ return { iconPath, appleTouchIconPath }
+}
- console.log('Thought created successfully');
+async function getIconUrl(
+ octokit: Octokit,
+ owner: string,
+ repo: string,
+ path: string,
+ defaultPath: string
+): Promise {
+ try {
+ await octokit.repos.getContent({ owner, repo, path })
+ return `https://github.com/${owner}/${repo}/blob/main/${path}?raw=true`
} catch (error) {
- console.error('Error creating thought:', error);
- throw error;
+ console.warn(`No icon found in ${path}, using default:`, defaultPath)
+ return defaultPath
}
}
-
-export async function getUserLogin(accessToken: string): Promise {
- const octokit = getOctokit(accessToken);
- const { data: user } = await octokit.users.getAuthenticated();
- return user.login;
-}
\ No newline at end of file
diff --git a/lib/types.ts b/lib/types.ts
new file mode 100644
index 00000000..0ad92cd0
--- /dev/null
+++ b/lib/types.ts
@@ -0,0 +1,14 @@
+export interface BlogPost {
+ id: string
+ title: string
+ content: string
+ imageUrl: string | null
+ date: string
+ }
+
+ export type Memo = {
+ id: string
+ content: string
+ timestamp: string
+ image?: string
+ }
diff --git a/lib/utils.ts b/lib/utils.ts
index bd0c391d..685d707a 100644
--- a/lib/utils.ts
+++ b/lib/utils.ts
@@ -1,6 +1,52 @@
-import { clsx, type ClassValue } from "clsx"
+import { type ClassValue, clsx } from "clsx"
+
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
+
+export const formatTimestamp = (timestamp: string) => {
+ const date = new Date(timestamp);
+
+ // Check if the date is valid
+ if (isNaN(date.getTime())) {
+ console.error('Invalid date:', timestamp);
+ return 'Invalid date';
+ }
+
+ // Format the date string in the user's local time zone
+ const localDateString = date.toLocaleString('en-US', {
+ year: 'numeric',
+ month: 'numeric',
+ day: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ second: '2-digit',
+ hour12: false,
+ timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
+ });
+
+ // Get the local time zone offset
+ const offsetMinutes = -date.getTimezoneOffset();
+ const offsetHours = Math.floor(offsetMinutes / 60);
+ const offsetSign = offsetHours >= 0 ? '+' : '-';
+ const absOffsetHours = Math.abs(offsetHours);
+
+ // Reformat the date string to match the desired format
+ const [datePart, timePart] = localDateString.split(', ');
+ const [month, day, year] = datePart.split('/');
+
+ return `${year}/${month}/${day} ${timePart}(UTC${offsetSign}${absOffsetHours})`;
+};
+
+export function getRelativeTimeString(timestamp: string): string {
+ const date = new Date(timestamp);
+ const now = new Date();
+ const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
+
+ if (diffInSeconds < 60) return 'just now';
+ if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m ago`;
+ if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)}h ago`;
+ return `${Math.floor(diffInSeconds / 86400)}d ago`;
+}
diff --git a/next.config.mjs b/next.config.mjs
index cf2ea1fa..942df8bc 100644
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -1,8 +1,23 @@
+import createNextIntlPlugin from 'next-intl/plugin'
+
+const withNextIntl = createNextIntlPlugin()
+
/** @type {import('next').NextConfig} */
+
const nextConfig = {
+ output: 'export',
reactStrictMode: true,
images: {
- domains: ['avatars.githubusercontent.com'],
+ remotePatterns: [
+ {
+ protocol: 'https',
+ hostname: '*',
+ },
+ {
+ protocol: 'http',
+ hostname: '*',
+ },
+ ],
},
async headers() {
return [
@@ -12,11 +27,23 @@ const nextConfig = {
{ key: 'Access-Control-Allow-Credentials', value: 'true' },
{ key: 'Access-Control-Allow-Origin', value: '*' },
{ key: 'Access-Control-Allow-Methods', value: 'GET,DELETE,PATCH,POST,PUT' },
- { key: 'Access-Control-Allow-Headers', value: 'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version' },
+ {
+ key: 'Access-Control-Allow-Headers',
+ value:
+ 'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version',
+ },
],
},
- ];
+ ]
+ },
+ async rewrites() {
+ return [
+ {
+ source: '/manifest.json',
+ destination: '/manifest.json',
+ },
+ ]
},
}
-export default nextConfig
\ No newline at end of file
+export default withNextIntl(nextConfig)
diff --git a/package-lock.json b/package-lock.json
index 9334585d..41e00732 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,44 +1,65 @@
{
- "name": "thoughts-app",
+ "name": "cofe",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "thoughts-app",
+ "name": "cofe",
"version": "0.1.0",
"dependencies": {
"@auth/core": "^0.34.2",
+ "@giscus/react": "^3.0.0",
"@octokit/rest": "^21.0.2",
+ "@radix-ui/react-dialog": "^1.1.1",
+ "@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-radio-group": "^1.2.0",
"@radix-ui/react-slot": "^1.1.0",
+ "@radix-ui/react-toast": "^1.2.1",
+ "@tailwindcss/typography": "^0.5.15",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"date-fns": "^3.6.0",
+ "date-fns-tz": "^3.1.3",
"gray-matter": "^4.0.3",
+ "katex": "^0.16.11",
"lucide-react": "^0.441.0",
"marked": "^14.1.2",
"next": "14.2.11",
"next-auth": "^4.24.7",
+ "next-intl": "^3.19.3",
"react": "^18",
"react-dom": "^18",
+ "react-dropzone": "^14.2.3",
"react-icons": "^5.3.0",
+ "react-katex": "^3.0.1",
+ "react-markdown": "^9.0.1",
+ "react-syntax-highlighter": "^15.5.0",
+ "react-tooltip": "^5.28.0",
+ "rehype-highlight": "^7.0.0",
+ "rehype-katex": "^7.0.1",
+ "rehype-raw": "^7.0.0",
"remark": "^15.0.1",
+ "remark-gfm": "^4.0.0",
"remark-html": "^16.0.1",
+ "remark-math": "^6.0.0",
"tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@shadcn/ui": "^0.0.4",
+ "@types/negotiator": "^0.6.3",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
+ "@types/react-syntax-highlighter": "^15.5.13",
"autoprefixer": "^10.4.20",
"eslint": "^8",
"eslint-config-next": "14.2.11",
"postcss": "^8.4.47",
+ "prettier": "^3.4.2",
"tailwindcss": "^3.4.11",
"typescript": "^5"
}
@@ -86,15 +107,6 @@
}
}
},
- "node_modules/@auth/core/node_modules/cookie": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
- "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/@auth/core/node_modules/jose": {
"version": "5.9.2",
"resolved": "https://registry.npmjs.org/jose/-/jose-5.9.2.tgz",
@@ -198,6 +210,105 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
+ "node_modules/@floating-ui/core": {
+ "version": "1.6.8",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz",
+ "integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.8"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.6.11",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.11.tgz",
+ "integrity": "sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.6.0",
+ "@floating-ui/utils": "^0.2.8"
+ }
+ },
+ "node_modules/@floating-ui/react-dom": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz",
+ "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/dom": "^1.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.8",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz",
+ "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==",
+ "license": "MIT"
+ },
+ "node_modules/@formatjs/ecma402-abstract": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz",
+ "integrity": "sha512-rRqXOqdFmk7RYvj4khklyqzcfQl9vEL/usogncBHRZfZBDOwMGuSRNFl02fu5KGHXdbinju+YXyuR+Nk8xlr/g==",
+ "license": "MIT",
+ "dependencies": {
+ "@formatjs/intl-localematcher": "0.5.4",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@formatjs/fast-memoize": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.0.tgz",
+ "integrity": "sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@formatjs/icu-messageformat-parser": {
+ "version": "2.7.8",
+ "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.8.tgz",
+ "integrity": "sha512-nBZJYmhpcSX0WeJ5SDYUkZ42AgR3xiyhNCsQweFx3cz/ULJjym8bHAzWKvG5e2+1XO98dBYC0fWeeAECAVSwLA==",
+ "license": "MIT",
+ "dependencies": {
+ "@formatjs/ecma402-abstract": "2.0.0",
+ "@formatjs/icu-skeleton-parser": "1.8.2",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@formatjs/icu-skeleton-parser": {
+ "version": "1.8.2",
+ "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.2.tgz",
+ "integrity": "sha512-k4ERKgw7aKGWJZgTarIcNEmvyTVD9FYh0mTrrBMHZ1b8hUu6iOJ4SzsZlo3UNAvHYa+PnvntIwRPt1/vy4nA9Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@formatjs/ecma402-abstract": "2.0.0",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@formatjs/intl-localematcher": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz",
+ "integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@giscus/react": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@giscus/react/-/react-3.0.0.tgz",
+ "integrity": "sha512-hgCjLpg3Wgh8VbTF5p8ZLcIHI74wvDk1VIFv12+eKhenNVUDjgwNg2B1aq/3puyHOad47u/ZSyqiMtohjy/OOA==",
+ "dependencies": {
+ "giscus": "^1.5.0"
+ },
+ "peerDependencies": {
+ "react": "^16 || ^17 || ^18",
+ "react-dom": "^16 || ^17 || ^18"
+ }
+ },
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
@@ -328,6 +439,19 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@lit-labs/ssr-dom-shim": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.1.tgz",
+ "integrity": "sha512-wx4aBmgeGvFmOKucFKY+8VFJSYZxs9poN3SDNQFF6lT6NrQUnHiPB2PWz2sc4ieEcAaYYzN+1uWahEeTq2aRIQ=="
+ },
+ "node_modules/@lit/reactive-element": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.4.tgz",
+ "integrity": "sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==",
+ "dependencies": {
+ "@lit-labs/ssr-dom-shim": "^1.2.0"
+ }
+ },
"node_modules/@next/env": {
"version": "14.2.11",
"resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.11.tgz",
@@ -360,134 +484,6 @@
"node": ">= 10"
}
},
- "node_modules/@next/swc-darwin-x64": {
- "version": "14.2.11",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.11.tgz",
- "integrity": "sha512-lnB0zYCld4yE0IX3ANrVMmtAbziBb7MYekcmR6iE9bujmgERl6+FK+b0MBq0pl304lYe7zO4yxJus9H/Af8jbg==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-arm64-gnu": {
- "version": "14.2.11",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.11.tgz",
- "integrity": "sha512-Ulo9TZVocYmUAtzvZ7FfldtwUoQY0+9z3BiXZCLSUwU2bp7GqHA7/bqrfsArDlUb2xeGwn3ZuBbKtNK8TR0A8w==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-arm64-musl": {
- "version": "14.2.11",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.11.tgz",
- "integrity": "sha512-fH377DnKGyUnkWlmUpFF1T90m0dADBfK11dF8sOQkiELF9M+YwDRCGe8ZyDzvQcUd20Rr5U7vpZRrAxKwd3Rzg==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-x64-gnu": {
- "version": "14.2.11",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.11.tgz",
- "integrity": "sha512-a0TH4ZZp4NS0LgXP/488kgvWelNpwfgGTUCDXVhPGH6pInb7yIYNgM4kmNWOxBFt+TIuOH6Pi9NnGG4XWFUyXQ==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-linux-x64-musl": {
- "version": "14.2.11",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.11.tgz",
- "integrity": "sha512-DYYZcO4Uir2gZxA4D2JcOAKVs8ZxbOFYPpXSVIgeoQbREbeEHxysVsg3nY4FrQy51e5opxt5mOHl/LzIyZBoKA==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-win32-arm64-msvc": {
- "version": "14.2.11",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.11.tgz",
- "integrity": "sha512-PwqHeKG3/kKfPpM6of1B9UJ+Er6ySUy59PeFu0Un0LBzJTRKKAg2V6J60Yqzp99m55mLa+YTbU6xj61ImTv9mg==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-win32-ia32-msvc": {
- "version": "14.2.11",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.11.tgz",
- "integrity": "sha512-0U7PWMnOYIvM74GY6rbH6w7v+vNPDVH1gUhlwHpfInJnNe5LkmUZqhp7FNWeNa5wbVgRcRi1F1cyxp4dmeLLvA==",
- "cpu": [
- "ia32"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@next/swc-win32-x64-msvc": {
- "version": "14.2.11",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.11.tgz",
- "integrity": "sha512-gQpS7mcgovWoaTG1FbS5/ojF7CGfql1Q0ZLsMrhcsi2Sr9HEqsUZ70MPJyaYBXbk6iEAP7UXMD9HC8KY1qNwvA==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -711,6 +707,29 @@
"integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==",
"license": "MIT"
},
+ "node_modules/@radix-ui/react-arrow": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz",
+ "integrity": "sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.0.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-collection": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz",
@@ -767,38 +786,47 @@
}
}
},
- "node_modules/@radix-ui/react-direction": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz",
- "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==",
+ "node_modules/@radix-ui/react-dialog": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.1.tgz",
+ "integrity": "sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==",
"license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.0",
+ "@radix-ui/react-compose-refs": "1.1.0",
+ "@radix-ui/react-context": "1.1.0",
+ "@radix-ui/react-dismissable-layer": "1.1.0",
+ "@radix-ui/react-focus-guards": "1.1.0",
+ "@radix-ui/react-focus-scope": "1.1.0",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-portal": "1.1.1",
+ "@radix-ui/react-presence": "1.1.0",
+ "@radix-ui/react-primitive": "2.0.0",
+ "@radix-ui/react-slot": "1.1.0",
+ "@radix-ui/react-use-controllable-state": "1.1.0",
+ "aria-hidden": "^1.1.1",
+ "react-remove-scroll": "2.5.7"
+ },
"peerDependencies": {
"@types/react": "*",
- "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
}
}
},
- "node_modules/@radix-ui/react-icons": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.0.tgz",
- "integrity": "sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw==",
- "license": "MIT",
- "peerDependencies": {
- "react": "^16.x || ^17.x || ^18.x"
- }
- },
- "node_modules/@radix-ui/react-id": {
+ "node_modules/@radix-ui/react-direction": {
"version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz",
- "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz",
+ "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==",
"license": "MIT",
- "dependencies": {
- "@radix-ui/react-use-layout-effect": "1.1.0"
- },
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
@@ -809,13 +837,232 @@
}
}
},
- "node_modules/@radix-ui/react-label": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.0.tgz",
- "integrity": "sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==",
+ "node_modules/@radix-ui/react-dismissable-layer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.0.tgz",
+ "integrity": "sha512-/UovfmmXGptwGcBQawLzvn2jOfM0t4z3/uKffoBlj724+n3FvBbZ7M0aaBOmkp6pqFYpO4yx8tSVJjx3Fl2jig==",
"license": "MIT",
"dependencies": {
- "@radix-ui/react-primitive": "2.0.0"
+ "@radix-ui/primitive": "1.1.0",
+ "@radix-ui/react-compose-refs": "1.1.0",
+ "@radix-ui/react-primitive": "2.0.0",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "@radix-ui/react-use-escape-keydown": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dropdown-menu": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.1.tgz",
+ "integrity": "sha512-y8E+x9fBq9qvteD2Zwa4397pUVhYsh9iq44b5RD5qu1GMJWBCBuVg1hMyItbc6+zH00TxGRqd9Iot4wzf3OoBQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.0",
+ "@radix-ui/react-compose-refs": "1.1.0",
+ "@radix-ui/react-context": "1.1.0",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-menu": "2.1.1",
+ "@radix-ui/react-primitive": "2.0.0",
+ "@radix-ui/react-use-controllable-state": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-guards": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.0.tgz",
+ "integrity": "sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-scope": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz",
+ "integrity": "sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.0",
+ "@radix-ui/react-primitive": "2.0.0",
+ "@radix-ui/react-use-callback-ref": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-icons": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.0.tgz",
+ "integrity": "sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.x || ^17.x || ^18.x"
+ }
+ },
+ "node_modules/@radix-ui/react-id": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz",
+ "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-label": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.0.tgz",
+ "integrity": "sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.0.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-menu": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.1.tgz",
+ "integrity": "sha512-oa3mXRRVjHi6DZu/ghuzdylyjaMXLymx83irM7hTxutQbD+7IhPKdMdRHD26Rm+kHRrWcrUkkRPv5pd47a2xFQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.0",
+ "@radix-ui/react-collection": "1.1.0",
+ "@radix-ui/react-compose-refs": "1.1.0",
+ "@radix-ui/react-context": "1.1.0",
+ "@radix-ui/react-direction": "1.1.0",
+ "@radix-ui/react-dismissable-layer": "1.1.0",
+ "@radix-ui/react-focus-guards": "1.1.0",
+ "@radix-ui/react-focus-scope": "1.1.0",
+ "@radix-ui/react-id": "1.1.0",
+ "@radix-ui/react-popper": "1.2.0",
+ "@radix-ui/react-portal": "1.1.1",
+ "@radix-ui/react-presence": "1.1.0",
+ "@radix-ui/react-primitive": "2.0.0",
+ "@radix-ui/react-roving-focus": "1.1.0",
+ "@radix-ui/react-slot": "1.1.0",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "aria-hidden": "^1.1.1",
+ "react-remove-scroll": "2.5.7"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popper": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz",
+ "integrity": "sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/react-dom": "^2.0.0",
+ "@radix-ui/react-arrow": "1.1.0",
+ "@radix-ui/react-compose-refs": "1.1.0",
+ "@radix-ui/react-context": "1.1.0",
+ "@radix-ui/react-primitive": "2.0.0",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "@radix-ui/react-use-layout-effect": "1.1.0",
+ "@radix-ui/react-use-rect": "1.1.0",
+ "@radix-ui/react-use-size": "1.1.0",
+ "@radix-ui/rect": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-portal": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.1.tgz",
+ "integrity": "sha512-A3UtLk85UtqhzFqtoC8Q0KvR2GbXF3mtPgACSazajqq6A41mEQgo53iPzY4i6BwDxlIFqWIhiQ2G729n+2aw/g==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.0.0",
+ "@radix-ui/react-use-layout-effect": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
@@ -960,6 +1207,40 @@
}
}
},
+ "node_modules/@radix-ui/react-toast": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.1.tgz",
+ "integrity": "sha512-5trl7piMXcZiCq7MW6r8YYmu0bK5qDpTWz+FdEPdKyft2UixkspheYbjbrLXVN5NGKHFbOP7lm8eD0biiSqZqg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.0",
+ "@radix-ui/react-collection": "1.1.0",
+ "@radix-ui/react-compose-refs": "1.1.0",
+ "@radix-ui/react-context": "1.1.0",
+ "@radix-ui/react-dismissable-layer": "1.1.0",
+ "@radix-ui/react-portal": "1.1.1",
+ "@radix-ui/react-presence": "1.1.0",
+ "@radix-ui/react-primitive": "2.0.0",
+ "@radix-ui/react-use-callback-ref": "1.1.0",
+ "@radix-ui/react-use-controllable-state": "1.1.0",
+ "@radix-ui/react-use-layout-effect": "1.1.0",
+ "@radix-ui/react-visually-hidden": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
@@ -993,6 +1274,24 @@
}
}
},
+ "node_modules/@radix-ui/react-use-escape-keydown": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz",
+ "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-callback-ref": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-use-layout-effect": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz",
@@ -1023,6 +1322,24 @@
}
}
},
+ "node_modules/@radix-ui/react-use-rect": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz",
+ "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/rect": "1.1.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-use-size": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz",
@@ -1041,21 +1358,50 @@
}
}
},
- "node_modules/@rtsao/scc": {
+ "node_modules/@radix-ui/react-visually-hidden": {
"version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
- "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@rushstack/eslint-patch": {
- "version": "1.10.4",
- "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.4.tgz",
- "integrity": "sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@shadcn/ui": {
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.0.tgz",
+ "integrity": "sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.0.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/rect": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz",
+ "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==",
+ "license": "MIT"
+ },
+ "node_modules/@rtsao/scc": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
+ "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rushstack/eslint-patch": {
+ "version": "1.10.4",
+ "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.4.tgz",
+ "integrity": "sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@shadcn/ui": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/@shadcn/ui/-/ui-0.0.4.tgz",
"integrity": "sha512-0dtu/5ApsOZ24qgaZwtif8jVwqol7a4m1x5AxPuM1k5wxhqU7t/qEfBGtaSki1R8VlbTQfCj5PAlO45NKCa7Gg==",
@@ -1114,6 +1460,34 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@tailwindcss/typography": {
+ "version": "0.5.15",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.15.tgz",
+ "integrity": "sha512-AqhlCXl+8grUz8uqExv5OTtgpjuVIwFTSXTrh8y9/pw6q2ek7fJ+Y8ZEVw7EB2DCcuCOtEjf9w3+J3rzts01uA==",
+ "license": "MIT",
+ "dependencies": {
+ "lodash.castarray": "^4.4.0",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.merge": "^4.6.2",
+ "postcss-selector-parser": "6.0.10"
+ },
+ "peerDependencies": {
+ "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20"
+ }
+ },
+ "node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": {
+ "version": "6.0.10",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
+ "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/@types/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
@@ -1129,6 +1503,21 @@
"@types/ms": "*"
}
},
+ "node_modules/@types/estree": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
+ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/estree-jsx": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz",
+ "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "*"
+ }
+ },
"node_modules/@types/hast": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
@@ -1152,6 +1541,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/katex": {
+ "version": "0.16.7",
+ "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz",
+ "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==",
+ "license": "MIT"
+ },
"node_modules/@types/mdast": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
@@ -1167,6 +1562,13 @@
"integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==",
"license": "MIT"
},
+ "node_modules/@types/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/@types/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-JkXTOdKs5MF086b/pt8C3+yVp3iDUwG635L7oCH6HvJvvr6lSUU5oe/gLXnPEfYRROHjJIPgCV6cuAg8gGkntQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/node": {
"version": "20.16.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.5.tgz",
@@ -1181,14 +1583,12 @@
"version": "15.7.12",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==",
- "devOptional": true,
"license": "MIT"
},
"node_modules/@types/react": {
"version": "18.3.5",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.5.tgz",
"integrity": "sha512-WeqMfGJLGuLCqHGYRGHxnKrXcTitc6L/nBUWfWPcTarG3t9PsquqUMuVeXZeca+mglY4Vo5GZjCi0A3Or2lnxA==",
- "devOptional": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
@@ -1205,6 +1605,16 @@
"@types/react": "*"
}
},
+ "node_modules/@types/react-syntax-highlighter": {
+ "version": "15.5.13",
+ "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz",
+ "integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
"node_modules/@types/semver": {
"version": "7.5.8",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
@@ -1212,6 +1622,11 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/trusted-types": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
+ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="
+ },
"node_modules/@types/unist": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
@@ -1544,6 +1959,18 @@
"dev": true,
"license": "Python-2.0"
},
+ "node_modules/aria-hidden": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz",
+ "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/aria-query": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz",
@@ -1729,6 +2156,14 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/attr-accept": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
+ "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/autoprefixer": {
"version": "10.4.20",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
@@ -2078,6 +2513,16 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/character-reference-invalid": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz",
+ "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@@ -2135,6 +2580,12 @@
"node": ">=6"
}
},
+ "node_modules/classnames": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
+ "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
+ "license": "MIT"
+ },
"node_modules/cli-cursor": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz",
@@ -2234,19 +2685,17 @@
"license": "MIT"
},
"node_modules/cookie": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
- "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
- "license": "MIT",
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
+ "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cross-spawn": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
- "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
- "license": "MIT",
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
@@ -2272,7 +2721,6 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "devOptional": true,
"license": "MIT"
},
"node_modules/damerau-levenshtein": {
@@ -2356,6 +2804,15 @@
"url": "https://github.com/sponsors/kossnocorp"
}
},
+ "node_modules/date-fns-tz": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-3.1.3.tgz",
+ "integrity": "sha512-ZfbMu+nbzW0mEzC8VZrLiSWvUIaI3aRHeq33mTe7Y38UctKukgqPR4nTDwcwS4d64Gf8GghnVsroBuMY3eiTeA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "date-fns": "^3.0.0"
+ }
+ },
"node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
@@ -2484,6 +2941,12 @@
"node": ">=6"
}
},
+ "node_modules/detect-node-es": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
+ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
+ "license": "MIT"
+ },
"node_modules/devlop": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
@@ -2568,6 +3031,18 @@
"node": ">=10.13.0"
}
},
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
"node_modules/es-abstract": {
"version": "1.23.3",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz",
@@ -3229,6 +3704,16 @@
"node": ">=4.0"
}
},
+ "node_modules/estree-util-is-identifier-name": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz",
+ "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
@@ -3346,6 +3831,19 @@
"reusify": "^1.0.4"
}
},
+ "node_modules/fault": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz",
+ "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==",
+ "license": "MIT",
+ "dependencies": {
+ "format": "^0.2.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/fetch-blob": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
@@ -3383,6 +3881,17 @@
"node": "^10.12.0 || >=12.0.0"
}
},
+ "node_modules/file-selector": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz",
+ "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==",
+ "dependencies": {
+ "tslib": "^2.4.0"
+ },
+ "engines": {
+ "node": ">= 12"
+ }
+ },
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -3460,6 +3969,14 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/format": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz",
+ "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==",
+ "engines": {
+ "node": ">=0.4.x"
+ }
+ },
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
@@ -3581,6 +4098,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/get-nonce": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
+ "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/get-stream": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
@@ -3625,6 +4151,14 @@
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
+ "node_modules/giscus": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/giscus/-/giscus-1.5.0.tgz",
+ "integrity": "sha512-t3LL0qbSO3JXq3uyQeKpF5CegstGfKX/0gI6eDe1cmnI7D56R7j52yLdzw4pdKrg3VnufwCgCM3FDz7G1Qr6lg==",
+ "dependencies": {
+ "lit": "^3.1.2"
+ }
+ },
"node_modules/glob": {
"version": "10.3.10",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
@@ -3887,6 +4421,126 @@
"node": ">= 0.4"
}
},
+ "node_modules/hast-util-from-dom": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-5.0.0.tgz",
+ "integrity": "sha512-d6235voAp/XR3Hh5uy7aGLbM3S4KamdW0WEgOaU1YoewnuYw4HXb5eRtv9g65m/RFGEfUY1Mw4UqCc5Y8L4Stg==",
+ "license": "ISC",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "hastscript": "^8.0.0",
+ "web-namespaces": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-from-html": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz",
+ "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "devlop": "^1.1.0",
+ "hast-util-from-parse5": "^8.0.0",
+ "parse5": "^7.0.0",
+ "vfile": "^6.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-from-html-isomorphic": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-2.0.0.tgz",
+ "integrity": "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "hast-util-from-dom": "^5.0.0",
+ "hast-util-from-html": "^2.0.0",
+ "unist-util-remove-position": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-from-parse5": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz",
+ "integrity": "sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "devlop": "^1.0.0",
+ "hastscript": "^8.0.0",
+ "property-information": "^6.0.0",
+ "vfile": "^6.0.0",
+ "vfile-location": "^5.0.0",
+ "web-namespaces": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-is-element": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz",
+ "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-parse-selector": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz",
+ "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-raw": {
+ "version": "9.0.4",
+ "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.0.4.tgz",
+ "integrity": "sha512-LHE65TD2YiNsHD3YuXcKPHXPLuYh/gjp12mOfU8jxSrm1f/yJpsb0F/KKljS6U9LJoP0Ux+tCe8iJ2AsPzTdgA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "@ungap/structured-clone": "^1.0.0",
+ "hast-util-from-parse5": "^8.0.0",
+ "hast-util-to-parse5": "^8.0.0",
+ "html-void-elements": "^3.0.0",
+ "mdast-util-to-hast": "^13.0.0",
+ "parse5": "^7.0.0",
+ "unist-util-position": "^5.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0",
+ "web-namespaces": "^2.0.0",
+ "zwitch": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/hast-util-sanitize": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-5.0.1.tgz",
@@ -3925,27 +4579,125 @@
"url": "https://opencollective.com/unified"
}
},
- "node_modules/hast-util-whitespace": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
- "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
+ "node_modules/hast-util-to-jsx-runtime": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.0.tgz",
+ "integrity": "sha512-H/y0+IWPdsLLS738P8tDnrQ8Z+dj12zQQ6WC11TIM21C8WFVoIxcqWXf2H3hiTVZjF1AWqoimGwrTWecWrnmRQ==",
"license": "MIT",
"dependencies": {
- "@types/hast": "^3.0.0"
+ "@types/estree": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "devlop": "^1.0.0",
+ "estree-util-is-identifier-name": "^3.0.0",
+ "hast-util-whitespace": "^3.0.0",
+ "mdast-util-mdx-expression": "^2.0.0",
+ "mdast-util-mdx-jsx": "^3.0.0",
+ "mdast-util-mdxjs-esm": "^2.0.0",
+ "property-information": "^6.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "style-to-object": "^1.0.0",
+ "unist-util-position": "^5.0.0",
+ "vfile-message": "^4.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
- "node_modules/html-void-elements": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz",
- "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==",
+ "node_modules/hast-util-to-parse5": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz",
+ "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==",
"license": "MIT",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/wooorm"
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "devlop": "^1.0.0",
+ "property-information": "^6.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "web-namespaces": "^2.0.0",
+ "zwitch": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-to-text": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz",
+ "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "hast-util-is-element": "^3.0.0",
+ "unist-util-find-after": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-whitespace": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
+ "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hastscript": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-8.0.0.tgz",
+ "integrity": "sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "hast-util-parse-selector": "^4.0.0",
+ "property-information": "^6.0.0",
+ "space-separated-tokens": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/highlight.js": {
+ "version": "11.9.0",
+ "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz",
+ "integrity": "sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/html-url-attributes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.0.tgz",
+ "integrity": "sha512-/sXbVCWayk6GDVg3ctOX6nxaVj7So40FcFAnWlWGNAB1LpYKcV5Cd10APjPjW80O7zYW2MsjBV4zZ7IZO5fVow==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/html-void-elements": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz",
+ "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/human-signals": {
@@ -4035,6 +4787,12 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/inline-style-parser": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz",
+ "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==",
+ "license": "MIT"
+ },
"node_modules/internal-slot": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz",
@@ -4050,6 +4808,51 @@
"node": ">= 0.4"
}
},
+ "node_modules/intl-messageformat": {
+ "version": "10.5.14",
+ "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.5.14.tgz",
+ "integrity": "sha512-IjC6sI0X7YRjjyVH9aUgdftcmZK7WXdHeil4KwbjDnRWjnVitKpAx3rr6t6di1joFp5188VqKcobOPA6mCLG/w==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@formatjs/ecma402-abstract": "2.0.0",
+ "@formatjs/fast-memoize": "2.2.0",
+ "@formatjs/icu-messageformat-parser": "2.7.8",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.0.0"
+ }
+ },
+ "node_modules/is-alphabetical": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
+ "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-alphanumerical": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz",
+ "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==",
+ "license": "MIT",
+ "dependencies": {
+ "is-alphabetical": "^2.0.0",
+ "is-decimal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/is-arguments": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
@@ -4212,6 +5015,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-decimal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz",
+ "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/is-extendable": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
@@ -4280,6 +5093,16 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-hexadecimal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz",
+ "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/is-interactive": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz",
@@ -4674,6 +5497,31 @@
"node": ">=4.0"
}
},
+ "node_modules/katex": {
+ "version": "0.16.11",
+ "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.11.tgz",
+ "integrity": "sha512-RQrI8rlHY92OLf3rho/Ts8i/XvjgguEjOkO1BEXcU3N8BqPpSzBNwV/G0Ukr+P/l3ivvJUE/Fa/CwbS6HesGNQ==",
+ "funding": [
+ "https://opencollective.com/katex",
+ "https://github.com/sponsors/katex"
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "commander": "^8.3.0"
+ },
+ "bin": {
+ "katex": "cli.js"
+ }
+ },
+ "node_modules/katex/node_modules/commander": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
+ "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -4752,6 +5600,34 @@
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"license": "MIT"
},
+ "node_modules/lit": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/lit/-/lit-3.2.1.tgz",
+ "integrity": "sha512-1BBa1E/z0O9ye5fZprPtdqnc0BFzxIxTTOO/tQFmyC/hj1O3jL4TfmLBw0WEwjAokdLwpclkvGgDJwTIh0/22w==",
+ "dependencies": {
+ "@lit/reactive-element": "^2.0.4",
+ "lit-element": "^4.1.0",
+ "lit-html": "^3.2.0"
+ }
+ },
+ "node_modules/lit-element": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.1.1.tgz",
+ "integrity": "sha512-HO9Tkkh34QkTeUmEdNYhMT8hzLid7YlMlATSi1q4q17HE5d9mrrEHJ/o8O2D0cMi182zK1F3v7x0PWFjrhXFew==",
+ "dependencies": {
+ "@lit-labs/ssr-dom-shim": "^1.2.0",
+ "@lit/reactive-element": "^2.0.4",
+ "lit-html": "^3.2.0"
+ }
+ },
+ "node_modules/lit-html": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.2.1.tgz",
+ "integrity": "sha512-qI/3lziaPMSKsrwlxH/xMgikhQ0EGOX2ICU73Bi/YHFvz2j/yMCIrw4+puF2IpQ4+upd3EWbvnHM9+PnJn48YA==",
+ "dependencies": {
+ "@types/trusted-types": "^2.0.2"
+ }
+ },
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -4768,11 +5644,22 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/lodash.castarray": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
+ "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
+ "license": "MIT"
+ },
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
- "dev": true,
"license": "MIT"
},
"node_modules/log-symbols": {
@@ -4827,6 +5714,21 @@
"loose-envify": "cli.js"
}
},
+ "node_modules/lowlight": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.1.0.tgz",
+ "integrity": "sha512-CEbNVoSikAxwDMDPjXlqlFYiZLkDJHwyGu/MfOsJnF3d7f3tds5J3z8s/l9TMXhzfsJCCJEAsD78842mwmg0PQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "devlop": "^1.0.0",
+ "highlight.js": "~11.9.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
@@ -4842,6 +5744,16 @@
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
}
},
+ "node_modules/markdown-table": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz",
+ "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/marked": {
"version": "14.1.2",
"resolved": "https://registry.npmjs.org/marked/-/marked-14.1.2.tgz",
@@ -4854,6 +5766,34 @@
"node": ">= 18"
}
},
+ "node_modules/mdast-util-find-and-replace": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.1.tgz",
+ "integrity": "sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "escape-string-regexp": "^5.0.0",
+ "unist-util-is": "^6.0.0",
+ "unist-util-visit-parents": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
+ "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/mdast-util-from-markdown": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.1.tgz",
@@ -4878,104 +5818,284 @@
"url": "https://opencollective.com/unified"
}
},
- "node_modules/mdast-util-phrasing": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
- "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==",
+ "node_modules/mdast-util-gfm": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.0.0.tgz",
+ "integrity": "sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==",
"license": "MIT",
"dependencies": {
- "@types/mdast": "^4.0.0",
- "unist-util-is": "^6.0.0"
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-gfm-autolink-literal": "^2.0.0",
+ "mdast-util-gfm-footnote": "^2.0.0",
+ "mdast-util-gfm-strikethrough": "^2.0.0",
+ "mdast-util-gfm-table": "^2.0.0",
+ "mdast-util-gfm-task-list-item": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
- "node_modules/mdast-util-to-hast": {
- "version": "13.2.0",
- "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz",
- "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==",
+ "node_modules/mdast-util-gfm-autolink-literal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz",
+ "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==",
"license": "MIT",
"dependencies": {
- "@types/hast": "^3.0.0",
"@types/mdast": "^4.0.0",
- "@ungap/structured-clone": "^1.0.0",
+ "ccount": "^2.0.0",
"devlop": "^1.0.0",
- "micromark-util-sanitize-uri": "^2.0.0",
- "trim-lines": "^3.0.0",
- "unist-util-position": "^5.0.0",
- "unist-util-visit": "^5.0.0",
- "vfile": "^6.0.0"
+ "mdast-util-find-and-replace": "^3.0.0",
+ "micromark-util-character": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
- "node_modules/mdast-util-to-markdown": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.0.tgz",
- "integrity": "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==",
+ "node_modules/mdast-util-gfm-footnote": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.0.0.tgz",
+ "integrity": "sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==",
"license": "MIT",
"dependencies": {
"@types/mdast": "^4.0.0",
- "@types/unist": "^3.0.0",
- "longest-streak": "^3.0.0",
- "mdast-util-phrasing": "^4.0.0",
- "mdast-util-to-string": "^4.0.0",
- "micromark-util-decode-string": "^2.0.0",
- "unist-util-visit": "^5.0.0",
- "zwitch": "^2.0.0"
+ "devlop": "^1.1.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
- "node_modules/mdast-util-to-string": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz",
- "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==",
+ "node_modules/mdast-util-gfm-strikethrough": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz",
+ "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==",
"license": "MIT",
"dependencies": {
- "@types/mdast": "^4.0.0"
+ "@types/mdast": "^4.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
- "node_modules/merge-stream": {
+ "node_modules/mdast-util-gfm-table": {
"version": "2.0.0",
- "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
- "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/merge2": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
- "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz",
+ "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==",
"license": "MIT",
- "engines": {
- "node": ">= 8"
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "markdown-table": "^3.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
- "node_modules/micromark": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz",
- "integrity": "sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==",
- "funding": [
- {
- "type": "GitHub Sponsors",
- "url": "https://github.com/sponsors/unifiedjs"
- },
- {
- "type": "OpenCollective",
- "url": "https://opencollective.com/unified"
- }
- ],
+ "node_modules/mdast-util-gfm-task-list-item": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz",
+ "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-math": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-3.0.0.tgz",
+ "integrity": "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "longest-streak": "^3.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.1.0",
+ "unist-util-remove-position": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-mdx-expression": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz",
+ "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-mdx-jsx": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.3.tgz",
+ "integrity": "sha512-bfOjvNt+1AcbPLTFMFWY149nJz0OjmewJs3LQQ5pIyVGxP4CdOqNVJL6kTaM5c68p8q82Xv3nCyFfUnuEcH3UQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "ccount": "^2.0.0",
+ "devlop": "^1.1.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0",
+ "parse-entities": "^4.0.0",
+ "stringify-entities": "^4.0.0",
+ "unist-util-stringify-position": "^4.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-mdxjs-esm": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz",
+ "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-phrasing": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
+ "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "unist-util-is": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-hast": {
+ "version": "13.2.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz",
+ "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "@ungap/structured-clone": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "trim-lines": "^3.0.0",
+ "unist-util-position": "^5.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-markdown": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.0.tgz",
+ "integrity": "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "longest-streak": "^3.0.0",
+ "mdast-util-phrasing": "^4.0.0",
+ "mdast-util-to-string": "^4.0.0",
+ "micromark-util-decode-string": "^2.0.0",
+ "unist-util-visit": "^5.0.0",
+ "zwitch": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz",
+ "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromark": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz",
+ "integrity": "sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
"license": "MIT",
"dependencies": {
"@types/debug": "^4.0.0",
@@ -5031,6 +6151,146 @@
"micromark-util-types": "^2.0.0"
}
},
+ "node_modules/micromark-extension-gfm": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz",
+ "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==",
+ "license": "MIT",
+ "dependencies": {
+ "micromark-extension-gfm-autolink-literal": "^2.0.0",
+ "micromark-extension-gfm-footnote": "^2.0.0",
+ "micromark-extension-gfm-strikethrough": "^2.0.0",
+ "micromark-extension-gfm-table": "^2.0.0",
+ "micromark-extension-gfm-tagfilter": "^2.0.0",
+ "micromark-extension-gfm-task-list-item": "^2.0.0",
+ "micromark-util-combine-extensions": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-autolink-literal": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz",
+ "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==",
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-footnote": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz",
+ "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==",
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-core-commonmark": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-strikethrough": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz",
+ "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==",
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-classify-character": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-table": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.0.tgz",
+ "integrity": "sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g==",
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-tagfilter": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz",
+ "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==",
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-task-list-item": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz",
+ "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==",
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-math": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz",
+ "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/katex": "^0.16.0",
+ "devlop": "^1.0.0",
+ "katex": "^0.16.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/micromark-factory-destination": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz",
@@ -5480,16 +6740,15 @@
}
},
"node_modules/nanoid": {
- "version": "3.3.7",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
- "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
+ "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
- "license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
@@ -5504,6 +6763,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/next": {
"version": "14.2.11",
"resolved": "https://registry.npmjs.org/next/-/next-14.2.11.tgz",
@@ -5555,14 +6823,13 @@
}
},
"node_modules/next-auth": {
- "version": "4.24.7",
- "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.7.tgz",
- "integrity": "sha512-iChjE8ov/1K/z98gdKbn2Jw+2vLgJtVV39X+rCP5SGnVQuco7QOr19FRNGMIrD8d3LYhHWV9j9sKLzq1aDWWQQ==",
- "license": "ISC",
+ "version": "4.24.11",
+ "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.11.tgz",
+ "integrity": "sha512-pCFXzIDQX7xmHFs4KVH4luCjaCbuPRtZ9oBUjUhOk84mZ9WVPf94n87TxYI4rSRf9HmfHEF8Yep3JrYDVOo3Cw==",
"dependencies": {
"@babel/runtime": "^7.20.13",
"@panva/hkdf": "^1.0.2",
- "cookie": "^0.5.0",
+ "cookie": "^0.7.0",
"jose": "^4.15.5",
"oauth": "^0.9.15",
"openid-client": "^5.4.0",
@@ -5571,17 +6838,170 @@
"uuid": "^8.3.2"
},
"peerDependencies": {
- "next": "^12.2.5 || ^13 || ^14",
+ "@auth/core": "0.34.2",
+ "next": "^12.2.5 || ^13 || ^14 || ^15",
"nodemailer": "^6.6.5",
- "react": "^17.0.2 || ^18",
- "react-dom": "^17.0.2 || ^18"
+ "react": "^17.0.2 || ^18 || ^19",
+ "react-dom": "^17.0.2 || ^18 || ^19"
},
"peerDependenciesMeta": {
+ "@auth/core": {
+ "optional": true
+ },
"nodemailer": {
"optional": true
}
}
},
+ "node_modules/next-auth/node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/next-intl": {
+ "version": "3.19.3",
+ "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-3.19.3.tgz",
+ "integrity": "sha512-BcZmxvsFosuTlju4dc7fVQmM75VNSP2HCTIDB6dXCCi1wNRLJUbdBzDGB7ocWyXodhnLLEIrvji5NU8kxXEAdA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/amannn"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@formatjs/intl-localematcher": "^0.5.4",
+ "negotiator": "^0.6.3",
+ "use-intl": "^3.19.3"
+ },
+ "peerDependencies": {
+ "next": "^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/next/node_modules/@next/swc-darwin-x64": {
+ "version": "14.2.11",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.11.tgz",
+ "integrity": "sha512-lnB0zYCld4yE0IX3ANrVMmtAbziBb7MYekcmR6iE9bujmgERl6+FK+b0MBq0pl304lYe7zO4yxJus9H/Af8jbg==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/next/node_modules/@next/swc-linux-arm64-gnu": {
+ "version": "14.2.11",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.11.tgz",
+ "integrity": "sha512-Ulo9TZVocYmUAtzvZ7FfldtwUoQY0+9z3BiXZCLSUwU2bp7GqHA7/bqrfsArDlUb2xeGwn3ZuBbKtNK8TR0A8w==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/next/node_modules/@next/swc-linux-arm64-musl": {
+ "version": "14.2.11",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.11.tgz",
+ "integrity": "sha512-fH377DnKGyUnkWlmUpFF1T90m0dADBfK11dF8sOQkiELF9M+YwDRCGe8ZyDzvQcUd20Rr5U7vpZRrAxKwd3Rzg==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/next/node_modules/@next/swc-linux-x64-gnu": {
+ "version": "14.2.11",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.11.tgz",
+ "integrity": "sha512-a0TH4ZZp4NS0LgXP/488kgvWelNpwfgGTUCDXVhPGH6pInb7yIYNgM4kmNWOxBFt+TIuOH6Pi9NnGG4XWFUyXQ==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/next/node_modules/@next/swc-linux-x64-musl": {
+ "version": "14.2.11",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.11.tgz",
+ "integrity": "sha512-DYYZcO4Uir2gZxA4D2JcOAKVs8ZxbOFYPpXSVIgeoQbREbeEHxysVsg3nY4FrQy51e5opxt5mOHl/LzIyZBoKA==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/next/node_modules/@next/swc-win32-arm64-msvc": {
+ "version": "14.2.11",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.11.tgz",
+ "integrity": "sha512-PwqHeKG3/kKfPpM6of1B9UJ+Er6ySUy59PeFu0Un0LBzJTRKKAg2V6J60Yqzp99m55mLa+YTbU6xj61ImTv9mg==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/next/node_modules/@next/swc-win32-ia32-msvc": {
+ "version": "14.2.11",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.11.tgz",
+ "integrity": "sha512-0U7PWMnOYIvM74GY6rbH6w7v+vNPDVH1gUhlwHpfInJnNe5LkmUZqhp7FNWeNa5wbVgRcRi1F1cyxp4dmeLLvA==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/next/node_modules/@next/swc-win32-x64-msvc": {
+ "version": "14.2.11",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.11.tgz",
+ "integrity": "sha512-gQpS7mcgovWoaTG1FbS5/ojF7CGfql1Q0ZLsMrhcsi2Sr9HEqsUZ70MPJyaYBXbk6iEAP7UXMD9HC8KY1qNwvA==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
"node_modules/next/node_modules/postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@@ -6063,6 +7483,44 @@
"node": ">=6"
}
},
+ "node_modules/parse-entities": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz",
+ "integrity": "sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "character-entities": "^2.0.0",
+ "character-entities-legacy": "^3.0.0",
+ "character-reference-invalid": "^2.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "is-alphanumerical": "^2.0.0",
+ "is-decimal": "^2.0.0",
+ "is-hexadecimal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/parse-entities/node_modules/@types/unist": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
+ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
+ "license": "MIT"
+ },
+ "node_modules/parse5": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
+ "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
+ "license": "MIT",
+ "dependencies": {
+ "entities": "^4.4.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -6357,12 +7815,36 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/prettier": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
+ "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
+ "dev": true,
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
"node_modules/pretty-format": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz",
"integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==",
"license": "MIT"
},
+ "node_modules/prismjs": {
+ "version": "1.29.0",
+ "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz",
+ "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/prompts": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
@@ -6381,7 +7863,6 @@
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.4.0",
@@ -6454,6 +7935,23 @@
"react": "^18.3.1"
}
},
+ "node_modules/react-dropzone": {
+ "version": "14.2.3",
+ "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz",
+ "integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==",
+ "license": "MIT",
+ "dependencies": {
+ "attr-accept": "^2.2.2",
+ "file-selector": "^0.6.0",
+ "prop-types": "^15.8.1"
+ },
+ "engines": {
+ "node": ">= 10.13"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8 || 18.0.0"
+ }
+ },
"node_modules/react-icons": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.3.0.tgz",
@@ -6467,9 +7965,170 @@
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "dev": true,
"license": "MIT"
},
+ "node_modules/react-katex": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/react-katex/-/react-katex-3.0.1.tgz",
+ "integrity": "sha512-wIUW1fU5dHlkKvq4POfDkHruQsYp3fM8xNb/jnc8dnQ+nNCnaj0sx5pw7E6UyuEdLRyFKK0HZjmXBo+AtXXy0A==",
+ "license": "MIT",
+ "dependencies": {
+ "katex": "^0.16.0"
+ },
+ "peerDependencies": {
+ "prop-types": "^15.8.1",
+ "react": ">=15.3.2 <=18"
+ }
+ },
+ "node_modules/react-markdown": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.0.1.tgz",
+ "integrity": "sha512-186Gw/vF1uRkydbsOIkcGXw7aHq0sZOCRFFjGrr7b9+nVZg4UfA4enXCaxm4fUzecU38sWfrNDitGhshuU7rdg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "devlop": "^1.0.0",
+ "hast-util-to-jsx-runtime": "^2.0.0",
+ "html-url-attributes": "^3.0.0",
+ "mdast-util-to-hast": "^13.0.0",
+ "remark-parse": "^11.0.0",
+ "remark-rehype": "^11.0.0",
+ "unified": "^11.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ },
+ "peerDependencies": {
+ "@types/react": ">=18",
+ "react": ">=18"
+ }
+ },
+ "node_modules/react-remove-scroll": {
+ "version": "2.5.7",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz",
+ "integrity": "sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==",
+ "license": "MIT",
+ "dependencies": {
+ "react-remove-scroll-bar": "^2.3.4",
+ "react-style-singleton": "^2.2.1",
+ "tslib": "^2.1.0",
+ "use-callback-ref": "^1.3.0",
+ "use-sidecar": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-remove-scroll-bar": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz",
+ "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==",
+ "license": "MIT",
+ "dependencies": {
+ "react-style-singleton": "^2.2.1",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-style-singleton": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz",
+ "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==",
+ "license": "MIT",
+ "dependencies": {
+ "get-nonce": "^1.0.0",
+ "invariant": "^2.2.4",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-syntax-highlighter": {
+ "version": "15.5.0",
+ "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz",
+ "integrity": "sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.3.1",
+ "highlight.js": "^10.4.1",
+ "lowlight": "^1.17.0",
+ "prismjs": "^1.27.0",
+ "refractor": "^3.6.0"
+ },
+ "peerDependencies": {
+ "react": ">= 0.14.0"
+ }
+ },
+ "node_modules/react-syntax-highlighter/node_modules/highlight.js": {
+ "version": "10.7.3",
+ "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
+ "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/react-syntax-highlighter/node_modules/lowlight": {
+ "version": "1.20.0",
+ "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz",
+ "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==",
+ "license": "MIT",
+ "dependencies": {
+ "fault": "^1.0.0",
+ "highlight.js": "~10.7.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/react-tooltip": {
+ "version": "5.28.0",
+ "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.28.0.tgz",
+ "integrity": "sha512-R5cO3JPPXk6FRbBHMO0rI9nkUG/JKfalBSQfZedZYzmqaZQgq7GLzF8vcCWx6IhUCKg0yPqJhXIzmIO5ff15xg==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/dom": "^1.6.1",
+ "classnames": "^2.3.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.14.0",
+ "react-dom": ">=16.14.0"
+ }
+ },
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -6528,6 +8187,197 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/refractor": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz",
+ "integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==",
+ "license": "MIT",
+ "dependencies": {
+ "hastscript": "^6.0.0",
+ "parse-entities": "^2.0.0",
+ "prismjs": "~1.27.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/refractor/node_modules/@types/hast": {
+ "version": "2.3.10",
+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz",
+ "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^2"
+ }
+ },
+ "node_modules/refractor/node_modules/@types/unist": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
+ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
+ "license": "MIT"
+ },
+ "node_modules/refractor/node_modules/character-entities": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz",
+ "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/refractor/node_modules/character-entities-legacy": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz",
+ "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/refractor/node_modules/character-reference-invalid": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz",
+ "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/refractor/node_modules/comma-separated-tokens": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz",
+ "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/refractor/node_modules/hast-util-parse-selector": {
+ "version": "2.2.5",
+ "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz",
+ "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/refractor/node_modules/hastscript": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz",
+ "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^2.0.0",
+ "comma-separated-tokens": "^1.0.0",
+ "hast-util-parse-selector": "^2.0.0",
+ "property-information": "^5.0.0",
+ "space-separated-tokens": "^1.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/refractor/node_modules/is-alphabetical": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz",
+ "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/refractor/node_modules/is-alphanumerical": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz",
+ "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==",
+ "license": "MIT",
+ "dependencies": {
+ "is-alphabetical": "^1.0.0",
+ "is-decimal": "^1.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/refractor/node_modules/is-decimal": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz",
+ "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/refractor/node_modules/is-hexadecimal": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz",
+ "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/refractor/node_modules/parse-entities": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz",
+ "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==",
+ "license": "MIT",
+ "dependencies": {
+ "character-entities": "^1.0.0",
+ "character-entities-legacy": "^1.0.0",
+ "character-reference-invalid": "^1.0.0",
+ "is-alphanumerical": "^1.0.0",
+ "is-decimal": "^1.0.0",
+ "is-hexadecimal": "^1.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/refractor/node_modules/prismjs": {
+ "version": "1.27.0",
+ "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz",
+ "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/refractor/node_modules/property-information": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz",
+ "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==",
+ "license": "MIT",
+ "dependencies": {
+ "xtend": "^4.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/refractor/node_modules/space-separated-tokens": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz",
+ "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
@@ -6541,16 +8391,67 @@
"dev": true,
"license": "MIT",
"dependencies": {
- "call-bind": "^1.0.6",
- "define-properties": "^1.2.1",
- "es-errors": "^1.3.0",
- "set-function-name": "^2.0.1"
- },
- "engines": {
- "node": ">= 0.4"
+ "call-bind": "^1.0.6",
+ "define-properties": "^1.2.1",
+ "es-errors": "^1.3.0",
+ "set-function-name": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/rehype-highlight": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/rehype-highlight/-/rehype-highlight-7.0.0.tgz",
+ "integrity": "sha512-QtobgRgYoQaK6p1eSr2SD1i61f7bjF2kZHAQHxeCHAuJf7ZUDMvQ7owDq9YTkmar5m5TSUol+2D3bp3KfJf/oA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "hast-util-to-text": "^4.0.0",
+ "lowlight": "^3.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/rehype-katex": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-7.0.1.tgz",
+ "integrity": "sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/katex": "^0.16.0",
+ "hast-util-from-html-isomorphic": "^2.0.0",
+ "hast-util-to-text": "^4.0.0",
+ "katex": "^0.16.0",
+ "unist-util-visit-parents": "^6.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/rehype-raw": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz",
+ "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "hast-util-raw": "^9.0.0",
+ "vfile": "^6.0.0"
},
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
}
},
"node_modules/remark": {
@@ -6569,6 +8470,24 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/remark-gfm": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.0.tgz",
+ "integrity": "sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-gfm": "^3.0.0",
+ "micromark-extension-gfm": "^3.0.0",
+ "remark-parse": "^11.0.0",
+ "remark-stringify": "^11.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/remark-html": {
"version": "16.0.1",
"resolved": "https://registry.npmjs.org/remark-html/-/remark-html-16.0.1.tgz",
@@ -6586,6 +8505,22 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/remark-math": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz",
+ "integrity": "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-math": "^3.0.0",
+ "micromark-extension-math": "^3.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/remark-parse": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
@@ -6602,6 +8537,23 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/remark-rehype": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.0.tgz",
+ "integrity": "sha512-z3tJrAs2kIs1AqIIy6pzHmAHlF1hWQ+OdY4/hv+Wxe35EhyLKcajL33iUEn3ScxtFox9nUvRufR/Zre8Q08H/g==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "mdast-util-to-hast": "^13.0.0",
+ "unified": "^11.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/remark-stringify": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz",
@@ -7294,6 +9246,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/style-to-object": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz",
+ "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==",
+ "license": "MIT",
+ "dependencies": {
+ "inline-style-parser": "0.2.4"
+ }
+ },
"node_modules/styled-jsx": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz",
@@ -7687,6 +9648,20 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/unist-util-find-after": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz",
+ "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/unist-util-is": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz",
@@ -7713,6 +9688,20 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/unist-util-remove-position": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz",
+ "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-visit": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/unist-util-stringify-position": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
@@ -7812,6 +9801,62 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/use-callback-ref": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz",
+ "integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-intl": {
+ "version": "3.19.3",
+ "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-3.19.3.tgz",
+ "integrity": "sha512-byEeorqdMlZYfTxkPiDzlF22wtcWsAjQYCSWdYhc8E95+bHQi3r/7FJOd8KfkSvm3a5xjoIySlnr5ndhiphCtQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@formatjs/fast-memoize": "^2.2.0",
+ "intl-messageformat": "^10.5.14"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/use-sidecar": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz",
+ "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==",
+ "license": "MIT",
+ "dependencies": {
+ "detect-node-es": "^1.1.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -7841,6 +9886,20 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/vfile-location": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz",
+ "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
"node_modules/vfile-message": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz",
@@ -7865,6 +9924,16 @@
"defaults": "^1.0.3"
}
},
+ "node_modules/web-namespaces": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
+ "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/web-streams-polyfill": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
@@ -8084,6 +10153,15 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4"
+ }
+ },
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
@@ -8134,6 +10212,126 @@
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
+ },
+ "node_modules/@next/swc-darwin-x64": {
+ "version": "14.2.11",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.11.tgz",
+ "integrity": "sha512-lnB0zYCld4yE0IX3ANrVMmtAbziBb7MYekcmR6iE9bujmgERl6+FK+b0MBq0pl304lYe7zO4yxJus9H/Af8jbg==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-gnu": {
+ "version": "14.2.11",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.11.tgz",
+ "integrity": "sha512-Ulo9TZVocYmUAtzvZ7FfldtwUoQY0+9z3BiXZCLSUwU2bp7GqHA7/bqrfsArDlUb2xeGwn3ZuBbKtNK8TR0A8w==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-musl": {
+ "version": "14.2.11",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.11.tgz",
+ "integrity": "sha512-fH377DnKGyUnkWlmUpFF1T90m0dADBfK11dF8sOQkiELF9M+YwDRCGe8ZyDzvQcUd20Rr5U7vpZRrAxKwd3Rzg==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-gnu": {
+ "version": "14.2.11",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.11.tgz",
+ "integrity": "sha512-a0TH4ZZp4NS0LgXP/488kgvWelNpwfgGTUCDXVhPGH6pInb7yIYNgM4kmNWOxBFt+TIuOH6Pi9NnGG4XWFUyXQ==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-musl": {
+ "version": "14.2.11",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.11.tgz",
+ "integrity": "sha512-DYYZcO4Uir2gZxA4D2JcOAKVs8ZxbOFYPpXSVIgeoQbREbeEHxysVsg3nY4FrQy51e5opxt5mOHl/LzIyZBoKA==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-arm64-msvc": {
+ "version": "14.2.11",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.11.tgz",
+ "integrity": "sha512-PwqHeKG3/kKfPpM6of1B9UJ+Er6ySUy59PeFu0Un0LBzJTRKKAg2V6J60Yqzp99m55mLa+YTbU6xj61ImTv9mg==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-ia32-msvc": {
+ "version": "14.2.11",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.11.tgz",
+ "integrity": "sha512-0U7PWMnOYIvM74GY6rbH6w7v+vNPDVH1gUhlwHpfInJnNe5LkmUZqhp7FNWeNa5wbVgRcRi1F1cyxp4dmeLLvA==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-x64-msvc": {
+ "version": "14.2.11",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.11.tgz",
+ "integrity": "sha512-gQpS7mcgovWoaTG1FbS5/ojF7CGfql1Q0ZLsMrhcsi2Sr9HEqsUZ70MPJyaYBXbk6iEAP7UXMD9HC8KY1qNwvA==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
}
}
}
diff --git a/package.json b/package.json
index 1d811ac4..03a272f5 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,5 @@
{
- "name": "thoughts-app",
+ "name": "cofe",
"version": "0.1.0",
"private": true,
"scripts": {
@@ -10,36 +10,57 @@
},
"dependencies": {
"@auth/core": "^0.34.2",
+ "@giscus/react": "^3.0.0",
"@octokit/rest": "^21.0.2",
+ "@radix-ui/react-dialog": "^1.1.1",
+ "@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-radio-group": "^1.2.0",
"@radix-ui/react-slot": "^1.1.0",
+ "@radix-ui/react-toast": "^1.2.1",
+ "@tailwindcss/typography": "^0.5.15",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"date-fns": "^3.6.0",
+ "date-fns-tz": "^3.1.3",
"gray-matter": "^4.0.3",
+ "katex": "^0.16.11",
"lucide-react": "^0.441.0",
"marked": "^14.1.2",
"next": "14.2.11",
"next-auth": "^4.24.7",
+ "next-intl": "^3.19.3",
"react": "^18",
"react-dom": "^18",
+ "react-dropzone": "^14.2.3",
"react-icons": "^5.3.0",
+ "react-katex": "^3.0.1",
+ "react-markdown": "^9.0.1",
+ "react-syntax-highlighter": "^15.5.0",
+ "react-tooltip": "^5.28.0",
+ "rehype-highlight": "^7.0.0",
+ "rehype-katex": "^7.0.1",
+ "rehype-raw": "^7.0.0",
"remark": "^15.0.1",
+ "remark-gfm": "^4.0.0",
"remark-html": "^16.0.1",
+ "remark-math": "^6.0.0",
"tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@shadcn/ui": "^0.0.4",
+ "@types/negotiator": "^0.6.3",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
+ "@types/react-syntax-highlighter": "^15.5.13",
"autoprefixer": "^10.4.20",
"eslint": "^8",
"eslint-config-next": "14.2.11",
"postcss": "^8.4.47",
+ "prettier": "^3.4.2",
"tailwindcss": "^3.4.11",
"typescript": "^5"
}
diff --git a/public/Tinymind-banner.png b/public/Tinymind-banner.png
new file mode 100644
index 00000000..724ff859
Binary files /dev/null and b/public/Tinymind-banner.png differ
diff --git a/public/Tinymind.dmg b/public/Tinymind.dmg
new file mode 100644
index 00000000..7ed6d6c6
Binary files /dev/null and b/public/Tinymind.dmg differ
diff --git a/public/icon-144.jpg b/public/icon-144.jpg
new file mode 100644
index 00000000..85122a1d
Binary files /dev/null and b/public/icon-144.jpg differ
diff --git a/public/icon.jpg b/public/icon.jpg
new file mode 100644
index 00000000..28540e36
Binary files /dev/null and b/public/icon.jpg differ
diff --git a/public/manifest.json b/public/manifest.json
new file mode 100644
index 00000000..889cad1e
--- /dev/null
+++ b/public/manifest.json
@@ -0,0 +1,25 @@
+{
+ "short_name": "Cofe",
+ "name": "Cofe",
+ "icons": [
+ {
+ "src": "icon-144.jpg",
+ "sizes": "144x144",
+ "type": "image/jpeg"
+ },
+ {
+ "src": "icon.jpg",
+ "sizes": "512x512",
+ "type": "image/jpeg"
+ }
+ ],
+ "orientation": "any",
+ "start_url": "/",
+ "theme_color": "#ffffff",
+ "background_color": "#ffffff",
+ "display": "standalone",
+ "description": "Turn GitHub into Your Blog & Memo Hub in Seconds.",
+ "scope": "/",
+ "apple-mobile-web-app-capable": "yes",
+ "apple-mobile-web-app-status-bar-style": "default"
+}
diff --git a/public/tinymind.icns b/public/tinymind.icns
new file mode 100644
index 00000000..329eaa68
Binary files /dev/null and b/public/tinymind.icns differ
diff --git a/tailwind.config.js b/tailwind.config.js
index 5da6d775..c3bd1100 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -60,6 +60,9 @@ const config = {
}
}
},
- plugins: [require("tailwindcss-animate")],
+ plugins: [
+ require('@tailwindcss/typography'),
+ require("tailwindcss-animate"),
+ ],
}
export default config
\ No newline at end of file
diff --git a/tailwind.config.ts b/tailwind.config.ts
index d43da912..80b76033 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -14,6 +14,6 @@ const config: Config = {
},
},
},
- plugins: [],
+ plugins: [require('@tailwindcss/typography'), require("tailwindcss-animate")],
};
export default config;