diff --git a/.vscode/settings.json b/.vscode/settings.json index 731200e..fbb8ede 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,9 +4,11 @@ "codemirror", "conve", "Keymap", + "lezer", "nextjs", "rehype", "signup", - "svix" + "svix", + "tailwindcss" ] } diff --git a/convex/_generated/api.d.ts b/convex/_generated/api.d.ts index 295087d..8f182c9 100644 --- a/convex/_generated/api.d.ts +++ b/convex/_generated/api.d.ts @@ -15,6 +15,7 @@ import type { FunctionReference, } from "convex/server"; import type * as http from "../http"; +import type * as posts from "../posts"; import type * as users from "../users"; /** @@ -27,6 +28,7 @@ import type * as users from "../users"; */ declare const fullApi: ApiFromModules<{ http: typeof http; + posts: typeof posts; users: typeof users; }>; export declare const api: FilterApi< diff --git a/convex/posts.ts b/convex/posts.ts new file mode 100644 index 0000000..75397a5 --- /dev/null +++ b/convex/posts.ts @@ -0,0 +1,33 @@ +import { v } from 'convex/values'; +import { mutation } from './_generated/server'; +import { userQuery } from './users'; + +export const createPost = mutation({ + args: { + clerkUserId: v.string(), + content: v.array(v.string()), + parentPostId: v.optional(v.id('posts')), + }, + handler: async (ctx, args) => { + const userIdentity = await ctx.auth.getUserIdentity(); + const user = await userQuery(ctx, args.clerkUserId); + + if (userIdentity === null) { + return 'USER_NOT_AUTHORIZED'; + } + + if (user === null) { + return 'USER_NOT_FOUND'; + } + + if (userIdentity.subject !== args.clerkUserId) { + return 'CANNOT_POST_ON_BEHALF_OF_ANOTHER_USER'; + } + + return await ctx.db.insert('posts', { + userId: user._id, + content: args.content, + parentPostId: args.parentPostId, + }); + }, +}); diff --git a/convex/schema.ts b/convex/schema.ts index 01303f1..7065f6c 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -1,19 +1,15 @@ import { defineSchema, defineTable } from 'convex/server'; import { v } from 'convex/values'; - -export default defineSchema({ - users: defineTable({ - clerkUser: v.any(), - }).index('by_clerk_id', ['clerkUser.id']), - posts: defineTable({ - userId: v.id('users'), - content: v.array(v.string()), - likes: v.array(v.id('users')), - replies: v.array( - v.object({ - userId: v.id('users'), - content: v.array(v.string()), - }) - ), - }), -}); +export default defineSchema( + { + users: defineTable({ + clerkUser: v.any(), + }).index('by_clerk_id', ['clerkUser.id']), + posts: defineTable({ + parentPostId: v.optional(v.id('posts')), + userId: v.id('users'), + content: v.array(v.string()), + }).index('by_user_id', ['userId']), + }, + { schemaValidation: false } +); diff --git a/convex/users.ts b/convex/users.ts index 308257a..c4136ed 100644 --- a/convex/users.ts +++ b/convex/users.ts @@ -44,6 +44,19 @@ export const deleteUser = internalMutation({ return; } + const userPosts = await ctx.db + .query('posts') + .withIndex('by_user_id', q => q.eq('userId', userRecord._id)) + .collect(); + + const userPostDeletePromises = []; + + for (const post of userPosts) { + userPostDeletePromises.push(ctx.db.delete(post._id)); + } + + await Promise.all(userPostDeletePromises); + await ctx.db.delete(userRecord._id); }, }); diff --git a/src/app/posts/add/page.tsx b/src/app/posts/add/page.tsx index 79007f9..41d4eb8 100644 --- a/src/app/posts/add/page.tsx +++ b/src/app/posts/add/page.tsx @@ -2,7 +2,7 @@ import Editor from '@/components/Editor'; export default function AddPost() { return ( -
+
); diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx index c5bd19a..b21b671 100644 --- a/src/components/Editor.tsx +++ b/src/components/Editor.tsx @@ -1,34 +1,38 @@ 'use client'; import { useCodeMirror } from '@/hooks'; -import { darkTheme } from '@/lib/codemirror'; +import { basicDark } from '@/lib/codemirror'; +import { useUser } from '@clerk/nextjs'; import { indentWithTab } from '@codemirror/commands'; import { markdown, markdownLanguage } from '@codemirror/lang-markdown'; import { languages } from '@codemirror/language-data'; -import { Compartment, EditorState } from '@codemirror/state'; +import { Compartment, EditorState, Text } from '@codemirror/state'; import { EditorView, keymap } from '@codemirror/view'; import { basicSetup } from 'codemirror'; +import { useMutation } from 'convex/react'; import { useTheme } from 'next-themes'; -import { useEffect, useState } from 'react'; -import ReactMarkdown from 'react-markdown'; -import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; -import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; -import remarkGfm from 'remark-gfm'; +import { useRouter } from 'next/navigation'; +import { FormEvent, useEffect, useState } from 'react'; +import { api } from '../../convex/_generated/api'; +import PostContent from './PostContent'; export default function Editor() { + const user = useUser(); const { theme } = useTheme(); const editorTheme = new Compartment(); - const [currentDoc, setCurrentDoc] = useState(''); + const [currentDoc, setCurrentDoc] = useState(['']); + const createPost = useMutation(api.posts.createPost); + const router = useRouter(); const doc = ['']; const extensions = [ basicSetup, markdown({ base: markdownLanguage, codeLanguages: languages }), EditorState.tabSize.of(2), - editorTheme.of([]), + editorTheme.of(basicDark), keymap.of([indentWithTab]), EditorView.updateListener.of(vu => { - setCurrentDoc(vu.state.doc.toString()); + setCurrentDoc(vu.state.doc.toJSON()); }), ]; @@ -42,18 +46,48 @@ export default function Editor() { if (theme === 'dark') { editorView.dispatch({ - effects: editorTheme.reconfigure([darkTheme]), + effects: editorTheme.reconfigure([]), }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [theme, editorView]); + async function handleSubmit(e: FormEvent) { + e.preventDefault(); + try { + if (user.isSignedIn !== true) { + router.push('/login'); + return; + } + + const result = await createPost({ + clerkUserId: user.user.id, + content: currentDoc, + }); + + switch (result) { + case 'USER_NOT_FOUND': + case 'USER_NOT_AUTHORIZED': + case 'CANNOT_POST_ON_BEHALF_OF_ANOTHER_USER': + throw Error('Unable to create post'); + default: + router.push('/'); + return; + } + } catch (error) { + console.log(error); + } + } + return ( -
+
-
+
- - {String(children).replace(/\n$/, '')} - - ) : ( - - {children} - - ); - }, - }} +
- {currentDoc} - + +
-
- +
+
); diff --git a/src/components/MarkdownContent.css b/src/components/MarkdownContent.css new file mode 100644 index 0000000..a532911 --- /dev/null +++ b/src/components/MarkdownContent.css @@ -0,0 +1,1098 @@ +.dark .markdown-content { + color-scheme: dark; + --color-prettylights-syntax-comment: #8b949e; + --color-prettylights-syntax-constant: #79c0ff; + --color-prettylights-syntax-entity: #d2a8ff; + --color-prettylights-syntax-storage-modifier-import: #c9d1d9; + --color-prettylights-syntax-entity-tag: #7ee787; + --color-prettylights-syntax-keyword: #ff7b72; + --color-prettylights-syntax-string: #a5d6ff; + --color-prettylights-syntax-variable: #ffa657; + --color-prettylights-syntax-brackethighlighter-unmatched: #f85149; + --color-prettylights-syntax-invalid-illegal-text: #f0f6fc; + --color-prettylights-syntax-invalid-illegal-bg: #8e1519; + --color-prettylights-syntax-carriage-return-text: #f0f6fc; + --color-prettylights-syntax-carriage-return-bg: #b62324; + --color-prettylights-syntax-string-regexp: #7ee787; + --color-prettylights-syntax-markup-list: #f2cc60; + --color-prettylights-syntax-markup-heading: #1f6feb; + --color-prettylights-syntax-markup-italic: #c9d1d9; + --color-prettylights-syntax-markup-bold: #c9d1d9; + --color-prettylights-syntax-markup-deleted-text: #ffdcd7; + --color-prettylights-syntax-markup-deleted-bg: #67060c; + --color-prettylights-syntax-markup-inserted-text: #aff5b4; + --color-prettylights-syntax-markup-inserted-bg: #033a16; + --color-prettylights-syntax-markup-changed-text: #ffdfb6; + --color-prettylights-syntax-markup-changed-bg: #5a1e02; + --color-prettylights-syntax-markup-ignored-text: #c9d1d9; + --color-prettylights-syntax-markup-ignored-bg: #1158c7; + --color-prettylights-syntax-meta-diff-range: #d2a8ff; + --color-prettylights-syntax-brackethighlighter-angle: #8b949e; + --color-prettylights-syntax-sublimelinter-gutter-mark: #484f58; + --color-prettylights-syntax-constant-other-reference-link: #a5d6ff; + --color-fg-default: #c9d1d9; + --color-fg-muted: #8b949e; + --color-fg-subtle: #6e7681; + --color-canvas-default: #2f333a; + --color-canvas-subtle: #2f333a; + --color-border-default: #30363d; + --color-border-muted: #21262d; + --color-neutral-muted: rgba(110, 118, 129, 0.4); + --color-accent-fg: #58a6ff; + --color-accent-emphasis: #1f6feb; + --color-attention-subtle: rgba(187, 128, 9, 0.15); + --color-danger-fg: #f85149; +} + +.light .markdown-content { + color-scheme: light; + --color-prettylights-syntax-comment: #6e7781; + --color-prettylights-syntax-constant: #0550ae; + --color-prettylights-syntax-entity: #8250df; + --color-prettylights-syntax-storage-modifier-import: #24292f; + --color-prettylights-syntax-entity-tag: #116329; + --color-prettylights-syntax-keyword: #cf222e; + --color-prettylights-syntax-string: #0a3069; + --color-prettylights-syntax-variable: #953800; + --color-prettylights-syntax-brackethighlighter-unmatched: #82071e; + --color-prettylights-syntax-invalid-illegal-text: #f6f8fa; + --color-prettylights-syntax-invalid-illegal-bg: #82071e; + --color-prettylights-syntax-carriage-return-text: #f6f8fa; + --color-prettylights-syntax-carriage-return-bg: #cf222e; + --color-prettylights-syntax-string-regexp: #116329; + --color-prettylights-syntax-markup-list: #3b2300; + --color-prettylights-syntax-markup-heading: #0550ae; + --color-prettylights-syntax-markup-italic: #24292f; + --color-prettylights-syntax-markup-bold: #24292f; + --color-prettylights-syntax-markup-deleted-text: #82071e; + --color-prettylights-syntax-markup-deleted-bg: #ffebe9; + --color-prettylights-syntax-markup-inserted-text: #116329; + --color-prettylights-syntax-markup-inserted-bg: #dafbe1; + --color-prettylights-syntax-markup-changed-text: #953800; + --color-prettylights-syntax-markup-changed-bg: #ffd8b5; + --color-prettylights-syntax-markup-ignored-text: #eaeef2; + --color-prettylights-syntax-markup-ignored-bg: #0550ae; + --color-prettylights-syntax-meta-diff-range: #8250df; + --color-prettylights-syntax-brackethighlighter-angle: #57606a; + --color-prettylights-syntax-sublimelinter-gutter-mark: #8c959f; + --color-prettylights-syntax-constant-other-reference-link: #0a3069; + --color-fg-default: #24292f; + --color-fg-muted: #57606a; + --color-fg-subtle: #6e7781; + --color-canvas-default: #ffffff; + --color-canvas-subtle: #f6f8fa; + --color-border-default: #d0d7de; + --color-border-muted: hsla(210, 18%, 87%, 1); + --color-neutral-muted: rgba(175, 184, 193, 0.2); + --color-accent-fg: #0969da; + --color-accent-emphasis: #0969da; + --color-attention-subtle: #fff8c5; + --color-danger-fg: #cf222e; +} + +.markdown-content { + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; + margin: 0; + color: var(--color-fg-default); + background-color: var(--color-canvas-default); + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; + font-size: 16px; + line-height: 1.5; + word-wrap: break-word; +} + +.markdown-content .octicon { + display: inline-block; + fill: currentColor; + vertical-align: text-bottom; +} + +.markdown-content h1:hover .anchor .octicon-link:before, +.markdown-content h2:hover .anchor .octicon-link:before, +.markdown-content h3:hover .anchor .octicon-link:before, +.markdown-content h4:hover .anchor .octicon-link:before, +.markdown-content h5:hover .anchor .octicon-link:before, +.markdown-content h6:hover .anchor .octicon-link:before { + width: 16px; + height: 16px; + content: ' '; + display: inline-block; + background-color: currentColor; + -webkit-mask-image: url("data:image/svg+xml,"); + mask-image: url("data:image/svg+xml,"); +} + +.markdown-content details, +.markdown-content figcaption, +.markdown-content figure { + display: block; +} + +.markdown-content summary { + display: list-item; +} + +.markdown-content [hidden] { + display: none !important; +} + +.markdown-content a { + background-color: transparent; + color: var(--color-accent-fg); + text-decoration: none; +} + +.markdown-content abbr[title] { + border-bottom: none; + text-decoration: underline dotted; +} + +.markdown-content b, +.markdown-content strong { + font-weight: var(--base-text-weight-semibold, 600); +} + +.markdown-content dfn { + font-style: italic; +} + +.markdown-content h1 { + margin: .67em 0; + font-weight: var(--base-text-weight-semibold, 600); + padding-bottom: .3em; + font-size: 2em; + border-bottom: 1px solid var(--color-border-muted); +} + +.markdown-content mark { + background-color: var(--color-attention-subtle); + color: var(--color-fg-default); +} + +.markdown-content small { + font-size: 90%; +} + +.markdown-content sub, +.markdown-content sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +.markdown-content sub { + bottom: -0.25em; +} + +.markdown-content sup { + top: -0.5em; +} + +.markdown-content img { + border-style: none; + max-width: 100%; + box-sizing: content-box; + background-color: var(--color-canvas-default); +} + +.markdown-content code, +.markdown-content kbd, +.markdown-content pre, +.markdown-content samp { + font-family: monospace; + font-size: 1em; +} + +.markdown-content figure { + margin: 1em 40px; +} + +.markdown-content hr { + box-sizing: content-box; + overflow: hidden; + background: transparent; + border-bottom: 1px solid var(--color-border-muted); + height: .25em; + padding: 0; + margin: 24px 0; + background-color: var(--color-border-default); + border: 0; +} + +.markdown-content input { + font: inherit; + margin: 0; + overflow: visible; + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +.markdown-content [type=button], +.markdown-content [type=reset], +.markdown-content [type=submit] { + -webkit-appearance: button; +} + +.markdown-content [type=checkbox], +.markdown-content [type=radio] { + box-sizing: border-box; + padding: 0; +} + +.markdown-content [type=number]::-webkit-inner-spin-button, +.markdown-content [type=number]::-webkit-outer-spin-button { + height: auto; +} + +.markdown-content [type=search]::-webkit-search-cancel-button, +.markdown-content [type=search]::-webkit-search-decoration { + -webkit-appearance: none; +} + +.markdown-content ::-webkit-input-placeholder { + color: inherit; + opacity: .54; +} + +.markdown-content ::-webkit-file-upload-button { + -webkit-appearance: button; + font: inherit; +} + +.markdown-content a:hover { + text-decoration: underline; +} + +.markdown-content ::placeholder { + color: var(--color-fg-subtle); + opacity: 1; +} + +.markdown-content hr::before { + display: table; + content: ""; +} + +.markdown-content hr::after { + display: table; + clear: both; + content: ""; +} + +.markdown-content table { + border-spacing: 0; + border-collapse: collapse; + display: block; + width: max-content; + max-width: 100%; + overflow: auto; +} + +.markdown-content td, +.markdown-content th { + padding: 0; +} + +.markdown-content details summary { + cursor: pointer; +} + +.markdown-content details:not([open])>*:not(summary) { + display: none !important; +} + +.markdown-content a:focus, +.markdown-content [role=button]:focus, +.markdown-content input[type=radio]:focus, +.markdown-content input[type=checkbox]:focus { + outline: 2px solid var(--color-accent-fg); + outline-offset: -2px; + box-shadow: none; +} + +.markdown-content a:focus:not(:focus-visible), +.markdown-content [role=button]:focus:not(:focus-visible), +.markdown-content input[type=radio]:focus:not(:focus-visible), +.markdown-content input[type=checkbox]:focus:not(:focus-visible) { + outline: solid 1px transparent; +} + +.markdown-content a:focus-visible, +.markdown-content [role=button]:focus-visible, +.markdown-content input[type=radio]:focus-visible, +.markdown-content input[type=checkbox]:focus-visible { + outline: 2px solid var(--color-accent-fg); + outline-offset: -2px; + box-shadow: none; +} + +.markdown-content a:not([class]):focus, +.markdown-content a:not([class]):focus-visible, +.markdown-content input[type=radio]:focus, +.markdown-content input[type=radio]:focus-visible, +.markdown-content input[type=checkbox]:focus, +.markdown-content input[type=checkbox]:focus-visible { + outline-offset: 0; +} + +.markdown-content kbd { + display: inline-block; + padding: 3px 5px; + font: 11px ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace; + line-height: 10px; + color: var(--color-fg-default); + vertical-align: middle; + background-color: var(--color-canvas-subtle); + border: solid 1px var(--color-neutral-muted); + border-bottom-color: var(--color-neutral-muted); + border-radius: 6px; + box-shadow: inset 0 -1px 0 var(--color-neutral-muted); +} + +.markdown-content h1, +.markdown-content h2, +.markdown-content h3, +.markdown-content h4, +.markdown-content h5, +.markdown-content h6 { + margin-top: 24px; + margin-bottom: 16px; + font-weight: var(--base-text-weight-semibold, 600); + line-height: 1.25; +} + +.markdown-content h2 { + font-weight: var(--base-text-weight-semibold, 600); + padding-bottom: .3em; + font-size: 1.5em; + border-bottom: 1px solid var(--color-border-muted); +} + +.markdown-content h3 { + font-weight: var(--base-text-weight-semibold, 600); + font-size: 1.25em; +} + +.markdown-content h4 { + font-weight: var(--base-text-weight-semibold, 600); + font-size: 1em; +} + +.markdown-content h5 { + font-weight: var(--base-text-weight-semibold, 600); + font-size: .875em; +} + +.markdown-content h6 { + font-weight: var(--base-text-weight-semibold, 600); + font-size: .85em; + color: var(--color-fg-muted); +} + +.markdown-content p { + margin-top: 0; + margin-bottom: 10px; +} + +.markdown-content blockquote { + margin: 0; + padding: 0 1em; + color: var(--color-fg-muted); + border-left: .25em solid var(--color-border-default); +} + +.markdown-content ul, +.markdown-content ol { + margin-top: 0; + margin-bottom: 0; + padding-left: 2em; +} + +.markdown-content ol ol, +.markdown-content ul ol { + list-style-type: lower-roman; +} + +.markdown-content ul ul ol, +.markdown-content ul ol ol, +.markdown-content ol ul ol, +.markdown-content ol ol ol { + list-style-type: lower-alpha; +} + +.markdown-content dd { + margin-left: 0; +} + +.markdown-content tt, +.markdown-content code, +.markdown-content samp { + font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace; + font-size: 12px; +} + +.markdown-content pre { + margin-top: 0; + margin-bottom: 0; + font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace; + font-size: 12px; + word-wrap: normal; +} + +.markdown-content .octicon { + display: inline-block; + overflow: visible !important; + vertical-align: text-bottom; + fill: currentColor; +} + +.markdown-content input::-webkit-outer-spin-button, +.markdown-content input::-webkit-inner-spin-button { + margin: 0; + -webkit-appearance: none; + appearance: none; +} + +.markdown-content::before { + display: table; + content: ""; +} + +.markdown-content::after { + display: table; + clear: both; + content: ""; +} + +.markdown-content>*:first-child { + margin-top: 0 !important; +} + +.markdown-content>*:last-child { + margin-bottom: 0 !important; +} + +.markdown-content a:not([href]) { + color: inherit; + text-decoration: none; +} + +.markdown-content .absent { + color: var(--color-danger-fg); +} + +.markdown-content .anchor { + float: left; + padding-right: 4px; + margin-left: -20px; + line-height: 1; +} + +.markdown-content .anchor:focus { + outline: none; +} + +.markdown-content p, +.markdown-content blockquote, +.markdown-content ul, +.markdown-content ol, +.markdown-content dl, +.markdown-content table, +.markdown-content pre, +.markdown-content details { + margin-top: 0; + margin-bottom: 16px; +} + +.markdown-content blockquote>:first-child { + margin-top: 0; +} + +.markdown-content blockquote>:last-child { + margin-bottom: 0; +} + +.markdown-content h1 .octicon-link, +.markdown-content h2 .octicon-link, +.markdown-content h3 .octicon-link, +.markdown-content h4 .octicon-link, +.markdown-content h5 .octicon-link, +.markdown-content h6 .octicon-link { + color: var(--color-fg-default); + vertical-align: middle; + visibility: hidden; +} + +.markdown-content h1:hover .anchor, +.markdown-content h2:hover .anchor, +.markdown-content h3:hover .anchor, +.markdown-content h4:hover .anchor, +.markdown-content h5:hover .anchor, +.markdown-content h6:hover .anchor { + text-decoration: none; +} + +.markdown-content h1:hover .anchor .octicon-link, +.markdown-content h2:hover .anchor .octicon-link, +.markdown-content h3:hover .anchor .octicon-link, +.markdown-content h4:hover .anchor .octicon-link, +.markdown-content h5:hover .anchor .octicon-link, +.markdown-content h6:hover .anchor .octicon-link { + visibility: visible; +} + +.markdown-content h1 tt, +.markdown-content h1 code, +.markdown-content h2 tt, +.markdown-content h2 code, +.markdown-content h3 tt, +.markdown-content h3 code, +.markdown-content h4 tt, +.markdown-content h4 code, +.markdown-content h5 tt, +.markdown-content h5 code, +.markdown-content h6 tt, +.markdown-content h6 code { + padding: 0 .2em; + font-size: inherit; +} + +.markdown-content summary h1, +.markdown-content summary h2, +.markdown-content summary h3, +.markdown-content summary h4, +.markdown-content summary h5, +.markdown-content summary h6 { + display: inline-block; +} + +.markdown-content summary h1 .anchor, +.markdown-content summary h2 .anchor, +.markdown-content summary h3 .anchor, +.markdown-content summary h4 .anchor, +.markdown-content summary h5 .anchor, +.markdown-content summary h6 .anchor { + margin-left: -40px; +} + +.markdown-content summary h1, +.markdown-content summary h2 { + padding-bottom: 0; + border-bottom: 0; +} + +.markdown-content ul.no-list, +.markdown-content ol.no-list { + padding: 0; + list-style-type: none; +} + +.markdown-content ol[type=a] { + list-style-type: lower-alpha; +} + +.markdown-content ol[type=A] { + list-style-type: upper-alpha; +} + +.markdown-content ol[type=i] { + list-style-type: lower-roman; +} + +.markdown-content ol[type=I] { + list-style-type: upper-roman; +} + +.markdown-content ol[type="1"] { + list-style-type: decimal; +} + +.markdown-content div>ol:not([type]) { + list-style-type: decimal; +} + +.markdown-content ul ul, +.markdown-content ul ol, +.markdown-content ol ol, +.markdown-content ol ul { + margin-top: 0; + margin-bottom: 0; +} + +.markdown-content li>p { + margin-top: 16px; +} + +.markdown-content li+li { + margin-top: .25em; +} + +.markdown-content dl { + padding: 0; +} + +.markdown-content dl dt { + padding: 0; + margin-top: 16px; + font-size: 1em; + font-style: italic; + font-weight: var(--base-text-weight-semibold, 600); +} + +.markdown-content dl dd { + padding: 0 16px; + margin-bottom: 16px; +} + +.markdown-content table th { + font-weight: var(--base-text-weight-semibold, 600); +} + +.markdown-content table th, +.markdown-content table td { + padding: 6px 13px; + border: 1px solid var(--color-border-default); +} + +.markdown-content table tr { + background-color: var(--color-canvas-default); + border-top: 1px solid var(--color-border-muted); +} + +.markdown-content table tr:nth-child(2n) { + background-color: var(--color-canvas-subtle); +} + +.markdown-content table img { + background-color: transparent; +} + +.markdown-content img[align=right] { + padding-left: 20px; +} + +.markdown-content img[align=left] { + padding-right: 20px; +} + +.markdown-content .emoji { + max-width: none; + vertical-align: text-top; + background-color: transparent; +} + +.markdown-content span.frame { + display: block; + overflow: hidden; +} + +.markdown-content span.frame>span { + display: block; + float: left; + width: auto; + padding: 7px; + margin: 13px 0 0; + overflow: hidden; + border: 1px solid var(--color-border-default); +} + +.markdown-content span.frame span img { + display: block; + float: left; +} + +.markdown-content span.frame span span { + display: block; + padding: 5px 0 0; + clear: both; + color: var(--color-fg-default); +} + +.markdown-content span.align-center { + display: block; + overflow: hidden; + clear: both; +} + +.markdown-content span.align-center>span { + display: block; + margin: 13px auto 0; + overflow: hidden; + text-align: center; +} + +.markdown-content span.align-center span img { + margin: 0 auto; + text-align: center; +} + +.markdown-content span.align-right { + display: block; + overflow: hidden; + clear: both; +} + +.markdown-content span.align-right>span { + display: block; + margin: 13px 0 0; + overflow: hidden; + text-align: right; +} + +.markdown-content span.align-right span img { + margin: 0; + text-align: right; +} + +.markdown-content span.float-left { + display: block; + float: left; + margin-right: 13px; + overflow: hidden; +} + +.markdown-content span.float-left span { + margin: 13px 0 0; +} + +.markdown-content span.float-right { + display: block; + float: right; + margin-left: 13px; + overflow: hidden; +} + +.markdown-content span.float-right>span { + display: block; + margin: 13px auto 0; + overflow: hidden; + text-align: right; +} + +.markdown-content code, +.markdown-content tt { + padding: .2em .4em; + margin: 0; + font-size: 85%; + white-space: break-spaces; + background-color: var(--color-neutral-muted); + border-radius: 6px; +} + +.markdown-content code br, +.markdown-content tt br { + display: none; +} + +.markdown-content del code { + text-decoration: inherit; +} + +.markdown-content samp { + font-size: 85%; +} + +.markdown-content pre code { + font-size: 100%; +} + +.markdown-content pre>code { + padding: 0; + margin: 0; + word-break: normal; + white-space: pre; + background: transparent; + border: 0; +} + +.markdown-content .highlight { + margin-bottom: 16px; +} + +.markdown-content .highlight pre { + margin-bottom: 0; + word-break: normal; +} + +.markdown-content .highlight pre, +.markdown-content pre { + padding: 0 16px; + overflow: auto; + font-size: 85%; + line-height: 1.45; + background-color: var(--color-canvas-subtle); + border-radius: 6px; +} + +.markdown-content pre code, +.markdown-content pre tt { + display: inline; + max-width: auto; + padding: 0; + margin: 0; + overflow: visible; + line-height: inherit; + word-wrap: normal; + background-color: transparent; + border: 0; +} + +.markdown-content .csv-data td, +.markdown-content .csv-data th { + padding: 5px; + overflow: hidden; + font-size: 12px; + line-height: 1; + text-align: left; + white-space: nowrap; +} + +.markdown-content .csv-data .blob-num { + padding: 10px 8px 9px; + text-align: right; + background: var(--color-canvas-default); + border: 0; +} + +.markdown-content .csv-data tr { + border-top: 0; +} + +.markdown-content .csv-data th { + font-weight: var(--base-text-weight-semibold, 600); + background: var(--color-canvas-subtle); + border-top: 0; +} + +.markdown-content [data-footnote-ref]::before { + content: "["; +} + +.markdown-content [data-footnote-ref]::after { + content: "]"; +} + +.markdown-content .footnotes { + font-size: 12px; + color: var(--color-fg-muted); + border-top: 1px solid var(--color-border-default); +} + +.markdown-content .footnotes ol { + padding-left: 16px; +} + +.markdown-content .footnotes ol ul { + display: inline-block; + padding-left: 16px; + margin-top: 16px; +} + +.markdown-content .footnotes li { + position: relative; +} + +.markdown-content .footnotes li:target::before { + position: absolute; + top: -8px; + right: -8px; + bottom: -8px; + left: -24px; + pointer-events: none; + content: ""; + border: 2px solid var(--color-accent-emphasis); + border-radius: 6px; +} + +.markdown-content .footnotes li:target { + color: var(--color-fg-default); +} + +.markdown-content .footnotes .data-footnote-backref g-emoji { + font-family: monospace; +} + +.markdown-content .pl-c { + color: var(--color-prettylights-syntax-comment); +} + +.markdown-content .pl-c1, +.markdown-content .pl-s .pl-v { + color: var(--color-prettylights-syntax-constant); +} + +.markdown-content .pl-e, +.markdown-content .pl-en { + color: var(--color-prettylights-syntax-entity); +} + +.markdown-content .pl-smi, +.markdown-content .pl-s .pl-s1 { + color: var(--color-prettylights-syntax-storage-modifier-import); +} + +.markdown-content .pl-ent { + color: var(--color-prettylights-syntax-entity-tag); +} + +.markdown-content .pl-k { + color: var(--color-prettylights-syntax-keyword); +} + +.markdown-content .pl-s, +.markdown-content .pl-pds, +.markdown-content .pl-s .pl-pse .pl-s1, +.markdown-content .pl-sr, +.markdown-content .pl-sr .pl-cce, +.markdown-content .pl-sr .pl-sre, +.markdown-content .pl-sr .pl-sra { + color: var(--color-prettylights-syntax-string); +} + +.markdown-content .pl-v, +.markdown-content .pl-smw { + color: var(--color-prettylights-syntax-variable); +} + +.markdown-content .pl-bu { + color: var(--color-prettylights-syntax-brackethighlighter-unmatched); +} + +.markdown-content .pl-ii { + color: var(--color-prettylights-syntax-invalid-illegal-text); + background-color: var(--color-prettylights-syntax-invalid-illegal-bg); +} + +.markdown-content .pl-c2 { + color: var(--color-prettylights-syntax-carriage-return-text); + background-color: var(--color-prettylights-syntax-carriage-return-bg); +} + +.markdown-content .pl-sr .pl-cce { + font-weight: bold; + color: var(--color-prettylights-syntax-string-regexp); +} + +.markdown-content .pl-ml { + color: var(--color-prettylights-syntax-markup-list); +} + +.markdown-content .pl-mh, +.markdown-content .pl-mh .pl-en, +.markdown-content .pl-ms { + font-weight: bold; + color: var(--color-prettylights-syntax-markup-heading); +} + +.markdown-content .pl-mi { + font-style: italic; + color: var(--color-prettylights-syntax-markup-italic); +} + +.markdown-content .pl-mb { + font-weight: bold; + color: var(--color-prettylights-syntax-markup-bold); +} + +.markdown-content .pl-md { + color: var(--color-prettylights-syntax-markup-deleted-text); + background-color: var(--color-prettylights-syntax-markup-deleted-bg); +} + +.markdown-content .pl-mi1 { + color: var(--color-prettylights-syntax-markup-inserted-text); + background-color: var(--color-prettylights-syntax-markup-inserted-bg); +} + +.markdown-content .pl-mc { + color: var(--color-prettylights-syntax-markup-changed-text); + background-color: var(--color-prettylights-syntax-markup-changed-bg); +} + +.markdown-content .pl-mi2 { + color: var(--color-prettylights-syntax-markup-ignored-text); + background-color: var(--color-prettylights-syntax-markup-ignored-bg); +} + +.markdown-content .pl-mdr { + font-weight: bold; + color: var(--color-prettylights-syntax-meta-diff-range); +} + +.markdown-content .pl-ba { + color: var(--color-prettylights-syntax-brackethighlighter-angle); +} + +.markdown-content .pl-sg { + color: var(--color-prettylights-syntax-sublimelinter-gutter-mark); +} + +.markdown-content .pl-corl { + text-decoration: underline; + color: var(--color-prettylights-syntax-constant-other-reference-link); +} + +.markdown-content g-emoji { + display: inline-block; + min-width: 1ch; + font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + font-size: 1em; + font-style: normal !important; + font-weight: var(--base-text-weight-normal, 400); + line-height: 1; + vertical-align: -0.075em; +} + +.markdown-content g-emoji img { + width: 1em; + height: 1em; +} + +.markdown-content .task-list-item { + list-style-type: none; +} + +.markdown-content .task-list-item label { + font-weight: var(--base-text-weight-normal, 400); +} + +.markdown-content .task-list-item.enabled label { + cursor: pointer; +} + +.markdown-content .task-list-item+.task-list-item { + margin-top: 4px; +} + +.markdown-content .task-list-item .handle { + display: none; +} + +.markdown-content .task-list-item-checkbox { + margin: 0 .2em .25em -1.4em; + vertical-align: middle; +} + +.markdown-content .contains-task-list:dir(rtl) .task-list-item-checkbox { + margin: 0 -1.6em .25em .2em; +} + +.markdown-content .contains-task-list { + position: relative; +} + +.markdown-content .contains-task-list:hover .task-list-item-convert-container, +.markdown-content .contains-task-list:focus-within .task-list-item-convert-container { + display: block; + width: auto; + height: 24px; + overflow: visible; + clip: auto; +} + +.markdown-content ::-webkit-calendar-picker-indicator { + filter: invert(50%); +} \ No newline at end of file diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 7ec3a83..3fc5cbc 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -1,6 +1,7 @@ import { SignedIn, SignedOut, UserButton } from '@clerk/nextjs'; import Link from 'next/link'; import { BiSolidMessageSquareAdd } from 'react-icons/bi'; +import ProfileLink from './ProfileLink'; import ThemeButton from './ThemeButton'; export default function Navbar() { @@ -30,7 +31,7 @@ export default function Navbar() { Following
  • - Profile +
  • + + {String(children).replace(/\n$/, '')} + + ) : ( + + {children} + + ); + }, + }} + > + {content} + +
  • + ); +} diff --git a/src/components/ProfileLink.tsx b/src/components/ProfileLink.tsx new file mode 100644 index 0000000..e620bcd --- /dev/null +++ b/src/components/ProfileLink.tsx @@ -0,0 +1,14 @@ +'use client'; + +import { useUser } from '@clerk/nextjs'; +import Link from 'next/link'; + +export default function ProfileLink() { + const user = useUser(); + + if (user.isSignedIn !== true) { + return Profile; + } + + return Profile; +} diff --git a/src/lib/codemirror/index.ts b/src/lib/codemirror/index.ts index 4b4027f..acbe295 100644 --- a/src/lib/codemirror/index.ts +++ b/src/lib/codemirror/index.ts @@ -1,80 +1,207 @@ -import { HighlightStyle } from '@codemirror/language'; +import { HighlightStyle, syntaxHighlighting } from '@codemirror/language'; +import { Extension } from '@codemirror/state'; import { EditorView } from '@codemirror/view'; import { tags as t } from '@lezer/highlight'; -export const darkTheme = EditorView.theme( +const base00 = '#24272d'; +const base01 = '#DDDDDD'; +const base02 = '#B9D2FF'; +const base03 = '#b0b0b0'; +const base05 = '#e0e0e0'; +const base06 = '#808080'; +const base07 = '#000000'; +const base08 = '#A54543'; +const base09 = '#fc6d24'; +const base0A = '#fda331'; +const base0B = '#8abeb7'; +const base0C = '#b5bd68'; +const base0D = '#6fb3d2'; +const base0E = '#cc99cc'; +const base0F = '#6987AF'; + +const invalid = base09; +const darkBackground = base00; +const highlightBackground = base02 + '30'; +const background = base00; +const tooltipBackground = base01; +const selection = '#202325'; +const cursor = base01; + +const basicDarkTheme = EditorView.theme( { '&': { - backgroundColor: '#24272d', - color: '#d0d9e4', + height: '100%', + color: base01, + backgroundColor: background, + borderRadius: '0.375rem', + padding: '0.5rem 0', + }, + '.cm-gutter': { height: '100%', }, '.cm-content': { - caretColor: '#f8f8f0', + caretColor: cursor, + height: '100%', }, - '.cm-content, .cm-gutter': { height: '100%' }, - '.cm-scroller': { overflow: 'auto' }, - '.cm-cursor, .cm-dropCursor': { - borderLeftColor: '#f8f8f0', + '.cm-scroller': { + overflow: 'auto', }, - '&.cm-focused': { - outline: 'none', + '.cm-cursor, .cm-dropCursor': { borderLeftColor: cursor }, + '&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection': + { backgroundColor: selection }, + '.cm-panels': { backgroundColor: darkBackground, color: base03 }, + '.cm-panels.cm-panels-top': { borderBottom: '2px solid black' }, + '.cm-panels.cm-panels-bottom': { borderTop: '2px solid black' }, + '.cm-searchMatch': { + backgroundColor: base02, + outline: `1px solid ${base03}`, + color: base07, }, - '&.cm-focused .cm-selectionBackground .cm-selectionBackground, .cm-content ::selection': - { - backgroundColor: '#2f333a', - }, - '.cm-activeLine': { - backgroundColor: 'transparent', + '.cm-searchMatch.cm-searchMatch-selected': { + backgroundColor: base05, + color: base07, + }, + '.cm-activeLine': { backgroundColor: highlightBackground }, + '.cm-selectionMatch': { backgroundColor: highlightBackground }, + '&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': { + outline: `1px solid ${base03}`, + }, + '&.cm-focused .cm-matchingBracket': { + backgroundColor: base02, + color: base07, }, '.cm-gutters': { - border: 'none', - backgroundColor: '#24272d', - color: 'd0d9e4', + borderRight: `none`, + color: base06, + backgroundColor: darkBackground, }, '.cm-activeLineGutter': { + backgroundColor: highlightBackground, + }, + '.cm-foldPlaceholder': { backgroundColor: 'transparent', + border: 'none', + color: base02, + }, + '.cm-tooltip': { + border: 'none', + backgroundColor: tooltipBackground, + }, + '.cm-tooltip .cm-tooltip-arrow:before': { + borderTopColor: 'transparent', + borderBottomColor: 'transparent', + }, + '.cm-tooltip .cm-tooltip-arrow:after': { + borderTopColor: tooltipBackground, + borderBottomColor: tooltipBackground, + }, + '.cm-tooltip-autocomplete': { + '& > ul > li[aria-selected]': { + backgroundColor: highlightBackground, + color: base03, + }, }, }, { dark: true } ); -export const highlightStyle = HighlightStyle.define([ +const basicDarkHighlightStyle = HighlightStyle.define([ + { tag: t.keyword, color: base0A }, { - tag: t.comment, - color: '#6272a4', + tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName], + color: base0C, + }, + { tag: [t.variableName], color: base0D }, + { tag: [t.function(t.variableName)], color: base0A }, + { tag: [t.labelName], color: base09 }, + { + tag: [t.color, t.constant(t.name), t.standard(t.name)], + color: base0A, + }, + { tag: [t.definition(t.name), t.separator], color: base0E }, + { tag: [t.brace], color: base0E }, + { + tag: [t.annotation], + color: invalid, }, { - tag: [t.string, t.special(t.brace)], - color: '#f1fa8c', + tag: [t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], + color: base0A, }, { - tag: [t.number, t.self, t.bool, t.null], - color: '#bd93f9', + tag: [t.typeName, t.className], + color: base0D, }, { - tag: [t.keyword, t.operator], - color: '#ff79c6', + tag: [t.operator, t.operatorKeyword], + color: base0E, }, { - tag: [t.definitionKeyword, t.typeName], - color: '#8be9fd', + tag: [t.tagName], + color: base0A, }, { - tag: t.definition(t.typeName), - color: '#f8f8f2', + tag: [t.squareBracket], + color: base0E, }, { - tag: [ - t.className, - t.definition(t.propertyName), - t.function(t.variableName), - t.attributeName, - ], - color: '#50fa7b', + tag: [t.angleBracket], + color: base0E, }, { - tag: [t.heading], - color: '#50fa7b', + tag: [t.attributeName], + color: base0D, }, + { + tag: [t.regexp], + color: base0A, + }, + { + tag: [t.quote], + color: base01, + }, + { tag: [t.string], color: base0C }, + { + tag: t.link, + color: base0F, + textDecoration: 'underline', + textUnderlinePosition: 'under', + }, + { + tag: [t.url, t.escape, t.special(t.string)], + color: base0B, + }, + { tag: [t.meta], color: base08 }, + { tag: [t.comment], color: base06, fontStyle: 'italic' }, + { tag: t.monospace, color: base01 }, + { tag: t.strong, fontWeight: 'bold', color: base0A }, + { tag: t.emphasis, fontStyle: 'italic', color: base0D }, + { tag: t.strikethrough, textDecoration: 'line-through' }, + { tag: t.heading, fontWeight: 'bold', color: base01 }, + { tag: t.special(t.heading1), fontWeight: 'bold', color: base01 }, + { tag: t.heading1, fontWeight: 'bold', color: base01 }, + { + tag: [t.heading2, t.heading3, t.heading4], + fontWeight: 'bold', + color: base01, + }, + { + tag: [t.heading5, t.heading6], + color: base01, + }, + { tag: [t.atom, t.bool, t.special(t.variableName)], color: base0B }, + { + tag: [t.processingInstruction, t.inserted], + color: base0B, + }, + { + tag: [t.contentSeparator], + color: base0D, + }, + { tag: t.invalid, color: base02, borderBottom: `1px dotted ${invalid}` }, ]); + +export const basicDark: Extension = [ + basicDarkTheme, + syntaxHighlighting(basicDarkHighlightStyle), +];