From 36e0e1921203ca5135d34520accd6d46483f6b35 Mon Sep 17 00:00:00 2001 From: Aslam Date: Thu, 8 Aug 2024 00:57:22 +0700 Subject: [PATCH] Setup (#112) * wip * wip * wip3 * chore: utils * feat: add command * wip * fix: key duplicate * fix: move and check * fix: use react-use instead * fix: sidebar * chore: make dynamic * chore: tablet mode * chore: fix padding * chore: link instead of inbox * fix: use dnd kit * feat: add select component * chore: use atom * refactor: remove dnd provider * feat: disabled drag when sort is not manual * search route * . * feat: accessibility * fix: search * . * . * . * fix: sidebar collapsed * ai search layout * . * . * . * . * ai responsible content * . * . * . * . * . * global topic route * global topic correct route * topic buttons * sidebar search navigation * ai * Update jazz * . * . * . * . * . * learning status * . * . * chore: content header * fix: pointer none when dragging. prevent auto click after drag end * fix: confirm * fix: prevent drag when editing * chore: remove unused fn * fix: check propagation * chore: list * chore: tweak sonner * chore: update stuff * feat: add badge * chore: close edit when create * chore: escape on manage form * refactor: remove learn path * css: responsive item * chore: separate pages and topic * reafactor: remove new-schema * feat(types): extend jazz type so it can be nullable * chore: use new types * fix: missing deps * fix: link * fix: sidebar in layout * fix: quotes * css: use medium instead semi * Actual streaming and rendering markdown response * . * . * . * . * . * . * . * . * . * . * . * . * . * . * . * . * . * . * . * . * . * chore: metadata * feat: la-editor * . * fix: editor and page * . * . * . * . * . * . * fix: remove link * chore: page sidebar * fix: remove 'replace with learning status' * fix: link * fix: link * chore: update schema * chore: use new schema * fix: instead of showing error, just do unique slug * feat: create slug * refactor apply * update package json * fix: schema personal page * chore: editor * feat: pages * fix: metadata * fix: jazz provider * feat: handling data * feat: page detail * chore: server page to id * chore: use id instead of slug * chore: update content header * chore: update link header implementation * refactor: global.css * fix: la editor use animation frame * fix: editor export ref * refactor: page detail * chore: tidy up schema * chore: adapt to new schema * fix: wrap using settimeout * fix: wrap using settimeout * . * . --------- Co-authored-by: marshennikovaolga Co-authored-by: Nikita Co-authored-by: Anselm Co-authored-by: Damian Tarnawski --- .gitignore | 49 +- bun.lockb | Bin 151704 -> 380296 bytes cli/run.ts | 12 + cli/seed.ts | 59 +++ lib/utils.ts | 7 + license | 21 + package.json | 19 +- web/.env.example | 2 + web/app/(pages)/layout.tsx | 15 + web/app/(pages)/links/page.tsx | 5 + web/app/(pages)/pages/[id]/page.tsx | 5 + .../(pages)/profile/_components/wrapper.tsx | 14 + web/app/(pages)/profile/page.tsx | 5 + web/app/(pages)/search/page.tsx | 5 + web/app/(topics)/[topic]/layout.tsx | 14 + web/app/(topics)/[topic]/page.tsx | 5 + web/app/api/metadata/route.test.ts | 101 ++++ web/app/api/metadata/route.ts | 63 +++ web/app/api/search-stream/route.ts | 43 ++ web/app/globals.css | 99 +++- web/app/layout.tsx | 32 +- web/app/page.tsx | 13 +- web/components.json | 17 + web/components/custom/ai-search.tsx | 88 ++++ web/components/custom/content-header.tsx | 61 +++ web/components/custom/demo-auth.tsx | 166 +++++++ web/components/custom/logo.tsx | 57 +++ .../custom/sidebar/partial/page-section.tsx | 114 +++++ .../custom/sidebar/partial/topic-section.tsx | 100 ++++ web/components/custom/sidebar/sidebar.tsx | 179 +++++++ .../components/bubble-menu/bubble-menu.tsx | 60 +++ .../la-editor/components/bubble-menu/index.ts | 1 + .../la-editor/components/ui/icon.tsx | 22 + .../components/ui/popover-wrapper.tsx | 20 + .../la-editor/components/ui/shortcut.tsx | 45 ++ .../components/ui/toolbar-button.tsx | 49 ++ .../extensions/blockquote/blockquote.ts | 13 + .../la-editor/extensions/blockquote/index.ts | 0 .../extensions/bullet-list/bullet-list.ts | 14 + .../la-editor/extensions/bullet-list/index.ts | 1 + .../code-block-lowlight.ts | 17 + .../extensions/code-block-lowlight/index.ts | 1 + .../la-editor/extensions/code/code.ts | 15 + .../la-editor/extensions/code/index.ts | 1 + .../extensions/dropcursor/dropcursor.ts | 13 + .../la-editor/extensions/dropcursor/index.ts | 1 + .../la-editor/extensions/heading/heading.ts | 29 ++ .../la-editor/extensions/heading/index.ts | 1 + .../horizontal-rule/horizontal-rule.ts | 18 + .../extensions/horizontal-rule/index.ts | 1 + web/components/la-editor/extensions/index.ts | 43 ++ .../la-editor/extensions/link/index.ts | 1 + .../la-editor/extensions/link/link.ts | 90 ++++ .../extensions/ordered-list/index.ts | 1 + .../extensions/ordered-list/ordered-list.ts | 14 + .../la-editor/extensions/paragraph/index.ts | 1 + .../extensions/paragraph/paragraph.ts | 14 + .../la-editor/extensions/selection/index.ts | 1 + .../extensions/selection/selection.ts | 36 ++ .../extensions/slash-command/groups.ts | 122 +++++ .../extensions/slash-command/index.ts | 1 + .../extensions/slash-command/menu-list.tsx | 155 +++++++ .../extensions/slash-command/slash-command.ts | 234 ++++++++++ .../extensions/slash-command/types.ts | 26 ++ .../la-editor/extensions/starter-kit.ts | 153 ++++++ .../task-item/components/task-item-view.tsx | 50 ++ .../la-editor/extensions/task-item/index.ts | 1 + .../extensions/task-item/task-item.ts | 64 +++ .../la-editor/extensions/task-list/index.ts | 1 + .../extensions/task-list/task-list.ts | 12 + .../la-editor/hooks/use-text-menu-commands.ts | 30 ++ .../la-editor/hooks/use-text-menu-states.ts | 34 ++ web/components/la-editor/index.ts | 1 + web/components/la-editor/la-editor.tsx | 146 ++++++ web/components/la-editor/lib/utils/index.ts | 14 + .../lib/utils/isCustomNodeSelected.ts | 28 ++ .../la-editor/lib/utils/isTextSelected.ts | 25 + .../la-editor/lib/utils/keyboard.ts | 25 + .../la-editor/lib/utils/platform.ts | 46 ++ web/components/la-editor/styles/index.css | 140 ++++++ .../la-editor/styles/partials/code.css | 86 ++++ .../la-editor/styles/partials/lists.css | 82 ++++ .../la-editor/styles/partials/placeholder.css | 12 + .../la-editor/styles/partials/typography.css | 27 ++ web/components/la-editor/types.ts | 20 + .../routes/globalTopic/globalTopic.tsx | 158 +++++++ web/components/routes/link/form/manage.tsx | 438 ++++++++++++++++++ .../link/form/partial/topic-section.tsx | 74 +++ web/components/routes/link/form/schema.ts | 24 + web/components/routes/link/header.tsx | 126 +++++ web/components/routes/link/list-item.tsx | 191 ++++++++ web/components/routes/link/list.tsx | 232 ++++++++++ web/components/routes/link/wrapper.tsx | 19 + web/components/routes/page/detail/header.tsx | 34 ++ web/components/routes/page/detail/wrapper.tsx | 139 ++++++ web/components/routes/search/header.tsx | 10 + web/components/routes/search/wrapper.tsx | 131 ++++++ web/components/ui/LearningTodoStatus.tsx | 70 +++ web/components/ui/badge.tsx | 28 ++ web/components/ui/breadcrumb.tsx | 90 ++++ web/components/ui/button.tsx | 47 ++ web/components/ui/calendar.tsx | 62 +++ web/components/ui/checkbox.tsx | 28 ++ web/components/ui/command.tsx | 134 ++++++ web/components/ui/context-menu.tsx | 180 +++++++ web/components/ui/dialog.tsx | 97 ++++ web/components/ui/dropdown-menu.tsx | 182 ++++++++ web/components/ui/form.tsx | 138 ++++++ web/components/ui/input.tsx | 22 + web/components/ui/label.tsx | 19 + web/components/ui/popover.tsx | 33 ++ web/components/ui/scroll-area.tsx | 40 ++ web/components/ui/select.tsx | 144 ++++++ web/components/ui/separator.tsx | 22 + web/components/ui/sonner.tsx | 40 ++ web/components/ui/textarea.tsx | 21 + web/components/ui/toggle.tsx | 39 ++ web/components/ui/tooltip.tsx | 30 ++ web/jest.config.ts | 20 + web/jest.setup.ts | 2 + web/lib/providers/confirm-provider.tsx | 12 + web/lib/providers/jazz-provider.tsx | 16 + web/lib/providers/jotai-provider.tsx | 7 + web/lib/providers/theme-provider.tsx | 9 + web/lib/schema/global-link.ts | 34 ++ web/lib/schema/global-topic.ts | 21 + web/lib/schema/index.ts | 72 +++ web/lib/schema/personal-link.ts | 34 ++ web/lib/schema/personal-page.ts | 18 + web/lib/types.ts | 44 ++ web/lib/utils/index.ts | 13 + web/lib/utils/slug.ts | 36 ++ web/lib/utils/urls.ts | 21 + web/next.config.mjs | 14 +- web/package.json | 104 ++++- web/postcss.config.mjs | 8 +- web/public/next.svg | 1 - web/public/vercel.svg | 1 - web/readme.md | 5 + web/store/link.ts | 6 + web/store/sidebar.ts | 7 + web/tailwind.config.ts | 91 +++- web/tsconfig.json | 2 +- 143 files changed, 6967 insertions(+), 101 deletions(-) create mode 100644 cli/run.ts create mode 100644 cli/seed.ts create mode 100644 lib/utils.ts create mode 100644 license create mode 100644 web/.env.example create mode 100644 web/app/(pages)/layout.tsx create mode 100644 web/app/(pages)/links/page.tsx create mode 100644 web/app/(pages)/pages/[id]/page.tsx create mode 100644 web/app/(pages)/profile/_components/wrapper.tsx create mode 100644 web/app/(pages)/profile/page.tsx create mode 100644 web/app/(pages)/search/page.tsx create mode 100644 web/app/(topics)/[topic]/layout.tsx create mode 100644 web/app/(topics)/[topic]/page.tsx create mode 100644 web/app/api/metadata/route.test.ts create mode 100644 web/app/api/metadata/route.ts create mode 100644 web/app/api/search-stream/route.ts create mode 100644 web/components.json create mode 100644 web/components/custom/ai-search.tsx create mode 100644 web/components/custom/content-header.tsx create mode 100644 web/components/custom/demo-auth.tsx create mode 100644 web/components/custom/logo.tsx create mode 100644 web/components/custom/sidebar/partial/page-section.tsx create mode 100644 web/components/custom/sidebar/partial/topic-section.tsx create mode 100644 web/components/custom/sidebar/sidebar.tsx create mode 100644 web/components/la-editor/components/bubble-menu/bubble-menu.tsx create mode 100644 web/components/la-editor/components/bubble-menu/index.ts create mode 100644 web/components/la-editor/components/ui/icon.tsx create mode 100644 web/components/la-editor/components/ui/popover-wrapper.tsx create mode 100644 web/components/la-editor/components/ui/shortcut.tsx create mode 100644 web/components/la-editor/components/ui/toolbar-button.tsx create mode 100644 web/components/la-editor/extensions/blockquote/blockquote.ts create mode 100644 web/components/la-editor/extensions/blockquote/index.ts create mode 100644 web/components/la-editor/extensions/bullet-list/bullet-list.ts create mode 100644 web/components/la-editor/extensions/bullet-list/index.ts create mode 100644 web/components/la-editor/extensions/code-block-lowlight/code-block-lowlight.ts create mode 100644 web/components/la-editor/extensions/code-block-lowlight/index.ts create mode 100644 web/components/la-editor/extensions/code/code.ts create mode 100644 web/components/la-editor/extensions/code/index.ts create mode 100644 web/components/la-editor/extensions/dropcursor/dropcursor.ts create mode 100644 web/components/la-editor/extensions/dropcursor/index.ts create mode 100644 web/components/la-editor/extensions/heading/heading.ts create mode 100644 web/components/la-editor/extensions/heading/index.ts create mode 100644 web/components/la-editor/extensions/horizontal-rule/horizontal-rule.ts create mode 100644 web/components/la-editor/extensions/horizontal-rule/index.ts create mode 100644 web/components/la-editor/extensions/index.ts create mode 100644 web/components/la-editor/extensions/link/index.ts create mode 100644 web/components/la-editor/extensions/link/link.ts create mode 100644 web/components/la-editor/extensions/ordered-list/index.ts create mode 100644 web/components/la-editor/extensions/ordered-list/ordered-list.ts create mode 100644 web/components/la-editor/extensions/paragraph/index.ts create mode 100644 web/components/la-editor/extensions/paragraph/paragraph.ts create mode 100644 web/components/la-editor/extensions/selection/index.ts create mode 100644 web/components/la-editor/extensions/selection/selection.ts create mode 100644 web/components/la-editor/extensions/slash-command/groups.ts create mode 100644 web/components/la-editor/extensions/slash-command/index.ts create mode 100644 web/components/la-editor/extensions/slash-command/menu-list.tsx create mode 100644 web/components/la-editor/extensions/slash-command/slash-command.ts create mode 100644 web/components/la-editor/extensions/slash-command/types.ts create mode 100644 web/components/la-editor/extensions/starter-kit.ts create mode 100644 web/components/la-editor/extensions/task-item/components/task-item-view.tsx create mode 100644 web/components/la-editor/extensions/task-item/index.ts create mode 100644 web/components/la-editor/extensions/task-item/task-item.ts create mode 100644 web/components/la-editor/extensions/task-list/index.ts create mode 100644 web/components/la-editor/extensions/task-list/task-list.ts create mode 100644 web/components/la-editor/hooks/use-text-menu-commands.ts create mode 100644 web/components/la-editor/hooks/use-text-menu-states.ts create mode 100644 web/components/la-editor/index.ts create mode 100644 web/components/la-editor/la-editor.tsx create mode 100644 web/components/la-editor/lib/utils/index.ts create mode 100644 web/components/la-editor/lib/utils/isCustomNodeSelected.ts create mode 100644 web/components/la-editor/lib/utils/isTextSelected.ts create mode 100644 web/components/la-editor/lib/utils/keyboard.ts create mode 100644 web/components/la-editor/lib/utils/platform.ts create mode 100644 web/components/la-editor/styles/index.css create mode 100644 web/components/la-editor/styles/partials/code.css create mode 100644 web/components/la-editor/styles/partials/lists.css create mode 100644 web/components/la-editor/styles/partials/placeholder.css create mode 100644 web/components/la-editor/styles/partials/typography.css create mode 100644 web/components/la-editor/types.ts create mode 100644 web/components/routes/globalTopic/globalTopic.tsx create mode 100644 web/components/routes/link/form/manage.tsx create mode 100644 web/components/routes/link/form/partial/topic-section.tsx create mode 100644 web/components/routes/link/form/schema.ts create mode 100644 web/components/routes/link/header.tsx create mode 100644 web/components/routes/link/list-item.tsx create mode 100644 web/components/routes/link/list.tsx create mode 100644 web/components/routes/link/wrapper.tsx create mode 100644 web/components/routes/page/detail/header.tsx create mode 100644 web/components/routes/page/detail/wrapper.tsx create mode 100644 web/components/routes/search/header.tsx create mode 100644 web/components/routes/search/wrapper.tsx create mode 100644 web/components/ui/LearningTodoStatus.tsx create mode 100644 web/components/ui/badge.tsx create mode 100644 web/components/ui/breadcrumb.tsx create mode 100644 web/components/ui/button.tsx create mode 100644 web/components/ui/calendar.tsx create mode 100644 web/components/ui/checkbox.tsx create mode 100644 web/components/ui/command.tsx create mode 100644 web/components/ui/context-menu.tsx create mode 100644 web/components/ui/dialog.tsx create mode 100644 web/components/ui/dropdown-menu.tsx create mode 100644 web/components/ui/form.tsx create mode 100644 web/components/ui/input.tsx create mode 100644 web/components/ui/label.tsx create mode 100644 web/components/ui/popover.tsx create mode 100644 web/components/ui/scroll-area.tsx create mode 100644 web/components/ui/select.tsx create mode 100644 web/components/ui/separator.tsx create mode 100644 web/components/ui/sonner.tsx create mode 100644 web/components/ui/textarea.tsx create mode 100644 web/components/ui/toggle.tsx create mode 100644 web/components/ui/tooltip.tsx create mode 100644 web/jest.config.ts create mode 100644 web/jest.setup.ts create mode 100644 web/lib/providers/confirm-provider.tsx create mode 100644 web/lib/providers/jazz-provider.tsx create mode 100644 web/lib/providers/jotai-provider.tsx create mode 100644 web/lib/providers/theme-provider.tsx create mode 100644 web/lib/schema/global-link.ts create mode 100644 web/lib/schema/global-topic.ts create mode 100644 web/lib/schema/index.ts create mode 100644 web/lib/schema/personal-link.ts create mode 100644 web/lib/schema/personal-page.ts create mode 100644 web/lib/types.ts create mode 100644 web/lib/utils/index.ts create mode 100644 web/lib/utils/slug.ts create mode 100644 web/lib/utils/urls.ts delete mode 100644 web/public/next.svg delete mode 100644 web/public/vercel.svg create mode 100644 web/readme.md create mode 100644 web/store/link.ts create mode 100644 web/store/sidebar.ts diff --git a/.gitignore b/.gitignore index c4c909d1..b946812b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,46 +1,15 @@ -# dependencies -node_modules -/.pnp -.pnp.js -.yarn/install-state.gz - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc +# base .DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# local env files +.env .env*.local - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts - -dbschema/edgeql-js -dbschema/interfaces.ts - -# other +# ts +node_modules package-lock.json pnpm-lock.yaml -.env -cli/run.ts +.vercel +# next .next-types -.env .next +# other +private +past-* diff --git a/bun.lockb b/bun.lockb index 164158122f0a795b1d856bde4d862a90dd61aca6..dce45b0b1e2f9a3d16e20dd35becb35fa88b9088 100755 GIT binary patch literal 380296 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%0A1aJf=3J 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%0AxCBk6a2S@%Q& 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%0A?L5vz=$e$4`<_>vUEH;Y8JRo)+bqYRX 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 delta 38314 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 W=bT~X;Rc_h05T;2s`l&|*4+SYvwQ;p diff --git a/cli/run.ts b/cli/run.ts new file mode 100644 index 00000000..9d597a10 --- /dev/null +++ b/cli/run.ts @@ -0,0 +1,12 @@ +import { getEnvOrThrow } from "@/lib/utils" + +async function run() { + try { + const OPENAI_API_KEY = getEnvOrThrow("OPENAI_API_KEY") + console.log(OPENAI_API_KEY) + } catch (err) { + console.log(err, "err") + } +} + +await run() diff --git a/cli/seed.ts b/cli/seed.ts new file mode 100644 index 00000000..c88d838d --- /dev/null +++ b/cli/seed.ts @@ -0,0 +1,59 @@ +import { getEnvOrThrow } from "@/lib/utils" +import { LaAccount } from "@/web/lib/schema" +import { startWorker } from "jazz-nodejs" +import { Group, ID } from "jazz-tools" +import { appendFile } from "node:fs/promises" + +const JAZZ_WORKER_SECRET = getEnvOrThrow("JAZZ_WORKER_SECRET") + +async function seed() { + const args = Bun.argv + const command = args[2] + try { + switch (command) { + case undefined: + console.log("No command provided") + break + case "setup": + await setup() + break + case "prod": + await prodSeed() + break + default: + console.log("Unknown command") + break + } + console.log("done") + } catch (err) { + console.error("Error occurred:", err) + } +} + +// sets up jazz global group and writes it to .env +async function setup() { + const { worker } = await startWorker({ + accountID: "co_zhvp7ryXJzDvQagX61F6RCZFJB9", + accountSecret: JAZZ_WORKER_SECRET + }) + const user = (await await LaAccount.createAs(worker, { + creationProps: { name: "nikiv" } + }))! + const publicGlobalGroup = Group.create({ owner: worker }) + publicGlobalGroup.addMember("everyone", "reader") + await appendFile("./.env", `\nJAZZ_PUBLIC_GLOBAL_GROUP=${JSON.stringify(publicGlobalGroup.id)}`) + const adminGlobalGroup = Group.create({ owner: worker }) + adminGlobalGroup.addMember(user, "admin") + await appendFile("./.env", `\nJAZZ_ADMIN_GLOBAL_GROUP=${JSON.stringify(adminGlobalGroup.id)}`) +} + +async function prodSeed() { + const { worker } = await startWorker({ + accountID: "co_zhvp7ryXJzDvQagX61F6RCZFJB9", + accountSecret: JAZZ_WORKER_SECRET + }) + const globalGroup = await Group.load(process.env.JAZZ_PUBLIC_GLOBAL_GROUP as ID, worker, {}) + if (!globalGroup) return // TODO: err + // TODO: complete full seed (connections, topics from old LA) +} +await seed() diff --git a/lib/utils.ts b/lib/utils.ts new file mode 100644 index 00000000..2a660567 --- /dev/null +++ b/lib/utils.ts @@ -0,0 +1,7 @@ +export function getEnvOrThrow(env: string) { + const value = process.env[env] + if (!value) { + throw new Error(`${env} environment variable is not set`) + } + return value +} diff --git a/license b/license new file mode 100644 index 00000000..e4f391fb --- /dev/null +++ b/license @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Learn Anything (learn-anything.xyz) + +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. diff --git a/package.json b/package.json index e379ccc6..e1b4a655 100644 --- a/package.json +++ b/package.json @@ -3,20 +3,29 @@ "scripts": { "dev": "bun web", "web": "cd web && bun dev", - "web:build": "bun run --filter '*' build" - }, - "devDependencies": { - "bun-types": "^1.1.18" + "web:build": "bun run --filter '*' build", + "cli": "bun run --watch cli/run.ts", + "seed": "bun --watch cli/seed.ts" }, "workspaces": [ "web" ], + "dependencies": { + "jazz-nodejs": "^0.7.23", + "react-icons": "^5.2.1" + }, + "devDependencies": { + "bun-types": "^1.1.21" + }, "prettier": { + "plugins": [ + "prettier-plugin-tailwindcss" + ], "useTabs": true, "semi": false, "trailingComma": "none", "printWidth": 120, "arrowParens": "avoid" }, - "private": true + "license": "MIT" } diff --git a/web/.env.example b/web/.env.example new file mode 100644 index 00000000..d0c69440 --- /dev/null +++ b/web/.env.example @@ -0,0 +1,2 @@ +NEXT_PUBLIC_APP_NAME="Learn Anything" +NEXT_PUBLIC_APP_URL=http://localhost:3000 \ No newline at end of file diff --git a/web/app/(pages)/layout.tsx b/web/app/(pages)/layout.tsx new file mode 100644 index 00000000..1c296d0d --- /dev/null +++ b/web/app/(pages)/layout.tsx @@ -0,0 +1,15 @@ +import { Sidebar } from "@/components/custom/sidebar/sidebar" + +export default async function RootLayout({ children }: { children: React.ReactNode }) { + return ( +
+ + +
+
+ {children} +
+
+
+ ) +} diff --git a/web/app/(pages)/links/page.tsx b/web/app/(pages)/links/page.tsx new file mode 100644 index 00000000..a8361cae --- /dev/null +++ b/web/app/(pages)/links/page.tsx @@ -0,0 +1,5 @@ +import { LinkWrapper } from "@/components/routes/link/wrapper" + +export default function LinkPage() { + return +} diff --git a/web/app/(pages)/pages/[id]/page.tsx b/web/app/(pages)/pages/[id]/page.tsx new file mode 100644 index 00000000..a235ccb3 --- /dev/null +++ b/web/app/(pages)/pages/[id]/page.tsx @@ -0,0 +1,5 @@ +import { DetailPageWrapper } from "@/components/routes/page/detail/wrapper" + +export default function DetailPage({ params }: { params: { id: string } }) { + return +} diff --git a/web/app/(pages)/profile/_components/wrapper.tsx b/web/app/(pages)/profile/_components/wrapper.tsx new file mode 100644 index 00000000..8db87158 --- /dev/null +++ b/web/app/(pages)/profile/_components/wrapper.tsx @@ -0,0 +1,14 @@ +"use client" + +import { useAccount } from "@/lib/providers/jazz-provider" + +export const ProfileWrapper = () => { + const account = useAccount() + + return ( +
+

{account.me.profile?.name}

+

Profile Page

+
+ ) +} diff --git a/web/app/(pages)/profile/page.tsx b/web/app/(pages)/profile/page.tsx new file mode 100644 index 00000000..8b4cb3e8 --- /dev/null +++ b/web/app/(pages)/profile/page.tsx @@ -0,0 +1,5 @@ +import { ProfileWrapper } from "./_components/wrapper" + +export default function ProfilePage() { + return +} diff --git a/web/app/(pages)/search/page.tsx b/web/app/(pages)/search/page.tsx new file mode 100644 index 00000000..c6797e3a --- /dev/null +++ b/web/app/(pages)/search/page.tsx @@ -0,0 +1,5 @@ +import { SearchWrapper } from "@/components/routes/search/wrapper" + +export default function ProfilePage() { + return +} diff --git a/web/app/(topics)/[topic]/layout.tsx b/web/app/(topics)/[topic]/layout.tsx new file mode 100644 index 00000000..f1c3e520 --- /dev/null +++ b/web/app/(topics)/[topic]/layout.tsx @@ -0,0 +1,14 @@ +import { Sidebar } from "@/components/custom/sidebar/sidebar" + +export default function TopicsLayout({ children }: { children: React.ReactNode }) { + return ( +
+ +
+
+ {children} +
+
+
+ ) +} diff --git a/web/app/(topics)/[topic]/page.tsx b/web/app/(topics)/[topic]/page.tsx new file mode 100644 index 00000000..3ead540f --- /dev/null +++ b/web/app/(topics)/[topic]/page.tsx @@ -0,0 +1,5 @@ +import GlobalTopic from "@/components/routes/globalTopic/globalTopic" + +export default function GlobalTopicPage({ params }: { params: { topic: string } }) { + return +} diff --git a/web/app/api/metadata/route.test.ts b/web/app/api/metadata/route.test.ts new file mode 100644 index 00000000..83c96baa --- /dev/null +++ b/web/app/api/metadata/route.test.ts @@ -0,0 +1,101 @@ +/** + * @jest-environment node + */ +import { NextRequest } from "next/server" +import axios from "axios" +import { GET } from "./route" + +jest.mock("axios") +const mockedAxios = axios as jest.Mocked + +describe("Metadata Fetcher", () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + it("should return metadata when URL is valid", async () => { + const mockHtml = ` + + + Test Title + + + + + ` + + mockedAxios.get.mockResolvedValue({ data: mockHtml }) + + const req = { + url: process.env.NEXT_PUBLIC_APP_URL + "/api/metadata?url=https://example.com" + } as unknown as NextRequest + + const response = await GET(req) + const data = await response.json() + + expect(response.status).toBe(200) + expect(data).toEqual({ + title: "Test Title", + description: "Test Description", + favicon: "https://example.com/favicon.ico", + url: "https://example.com" + }) + }) + + it("should return an error when URL is missing", async () => { + const req = { + url: process.env.NEXT_PUBLIC_APP_URL + "/api/metadata" + } as unknown as NextRequest + + const response = await GET(req) + const data = await response.json() + + expect(response.status).toBe(400) + expect(data).toEqual({ error: "URL is required" }) + }) + + it("should return default values when fetching fails", async () => { + mockedAxios.get.mockRejectedValue(new Error("Network error")) + + const req = { + url: process.env.NEXT_PUBLIC_APP_URL + "/api/metadata?url=https://example.com" + } as unknown as NextRequest + + const response = await GET(req) + const data = await response.json() + + expect(response.status).toBe(200) + expect(data).toEqual({ + title: "No title available", + description: "No description available", + favicon: null, + url: "https://example.com" + }) + }) + + it("should handle missing metadata gracefully", async () => { + const mockHtml = ` + + + + + ` + + mockedAxios.get.mockResolvedValue({ data: mockHtml }) + + const req = { + url: process.env.NEXT_PUBLIC_APP_URL + "/api/metadata?url=https://example.com" + } as unknown as NextRequest + + const response = await GET(req) + const data = await response.json() + + expect(response.status).toBe(200) + expect(data).toEqual({ + title: "No title available", + description: "No description available", + favicon: null, + url: "https://example.com" + }) + }) +}) diff --git a/web/app/api/metadata/route.ts b/web/app/api/metadata/route.ts new file mode 100644 index 00000000..b0b458d5 --- /dev/null +++ b/web/app/api/metadata/route.ts @@ -0,0 +1,63 @@ +import { NextRequest, NextResponse } from "next/server" +import axios from "axios" +import * as cheerio from "cheerio" + +interface Metadata { + title: string + description: string + favicon: string | null + url: string +} + +const DEFAULT_VALUES = { + TITLE: "No title available", + DESCRIPTION: "No description available", + IMAGE: null, + FAVICON: null +} + +export async function GET(request: NextRequest) { + const { searchParams } = new URL(request.url) + const url = searchParams.get("url") + + if (!url) { + return NextResponse.json({ error: "URL is required" }, { status: 400 }) + } + + try { + const { data } = await axios.get(url, { + timeout: 5000, + headers: { + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" + } + }) + + const $ = cheerio.load(data) + + const metadata: Metadata = { + title: $("title").text() || $('meta[property="og:title"]').attr("content") || DEFAULT_VALUES.TITLE, + description: + $('meta[name="description"]').attr("content") || + $('meta[property="og:description"]').attr("content") || + DEFAULT_VALUES.DESCRIPTION, + favicon: + $('link[rel="icon"]').attr("href") || $('link[rel="shortcut icon"]').attr("href") || DEFAULT_VALUES.FAVICON, + url: url + } + + if (metadata.favicon && !metadata.favicon.startsWith("http")) { + metadata.favicon = new URL(metadata.favicon, url).toString() + } + + return NextResponse.json(metadata) + } catch (error) { + const defaultMetadata: Metadata = { + title: DEFAULT_VALUES.TITLE, + description: DEFAULT_VALUES.DESCRIPTION, + favicon: DEFAULT_VALUES.FAVICON, + url: url + } + return NextResponse.json(defaultMetadata) + } +} diff --git a/web/app/api/search-stream/route.ts b/web/app/api/search-stream/route.ts new file mode 100644 index 00000000..21ca9a50 --- /dev/null +++ b/web/app/api/search-stream/route.ts @@ -0,0 +1,43 @@ +import { NextRequest, NextResponse } from "next/server" + +export async function POST(request: NextRequest) { + let data: unknown + try { + data = (await request.json()) as unknown + } catch (error) { + return new NextResponse("Invalid JSON", { status: 400 }) + } + + if (typeof data !== "object" || !data) { + return new NextResponse("Missing request data", { status: 400 }) + } + + if (!("question" in data) || typeof data.question !== "string") { + return new NextResponse("Missing `question` data field.", { status: 400 }) + } + + const chunks: string[] = [ + "# Hello", + " from th", + "e server", + "\n\n your question", + " was:\n\n", + "> ", + data.question, + "\n\n", + "**good bye!**" + ] + + const stream = new ReadableStream({ + async start(controller) { + for (const chunk of chunks) { + controller.enqueue(chunk) + await new Promise(resolve => setTimeout(resolve, 1000)) + } + + controller.close() + } + }) + + return new NextResponse(stream) +} diff --git a/web/app/globals.css b/web/app/globals.css index 56208c36..0214df23 100644 --- a/web/app/globals.css +++ b/web/app/globals.css @@ -1,28 +1,89 @@ @tailwind base; @tailwind components; @tailwind utilities; - -:root { - --foreground-rgb: 0, 0, 0; - --background-start-rgb: 214, 219, 220; - --background-end-rgb: 255, 255, 255; -} - -@media (prefers-color-scheme: dark) { - :root { - --foreground-rgb: 255, 255, 255; - --background-start-rgb: 0, 0, 0; - --background-end-rgb: 0, 0, 0; - } -} +@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"); body { - color: rgb(var(--foreground-rgb)); - background: linear-gradient(to bottom, transparent, rgb(var(--background-end-rgb))) rgb(var(--background-start-rgb)); + font-family: "Inter", sans-serif; } -@layer utilities { - .text-balance { - text-wrap: balance; +@layer base { + body { + @apply bg-background text-foreground; + font-feature-settings: + "rlig" 1, + "calt" 1; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + overflow-x: hidden; + min-height: 100vh; + line-height: 1.5; + } +} + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 240 10% 3.9%; + --card: 0 0% 100%; + --card-foreground: 240 10% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 240 10% 3.9%; + --primary: 240 5.9% 10%; + --primary-foreground: 0 0% 98%; + --secondary: 240 4.8% 95.9%; + --secondary-foreground: 240 5.9% 10%; + --muted: 240 4.8% 95.9%; + --muted-foreground: 240 3.8% 46.1%; + --accent: 240 4.8% 95.9%; + --accent-foreground: 240 5.9% 10%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 240 5.9% 90%; + --input: 240 5.9% 90%; + --ring: 240 10% 3.9%; + --radius: 0.5rem; + --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%; + } + + .dark { + --background: 240 10% 3.9%; + --foreground: 0 0% 98%; + --card: 240 10% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 240 10% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 240 5.9% 10%; + --secondary: 240 3.7% 15.9%; + --secondary-foreground: 0 0% 98%; + --muted: 240 3.7% 15.9%; + --muted-foreground: 240 5% 64.9%; + --accent: 240 3.7% 15.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 240 3.7% 15.9%; + --input: 240 3.7% 15.9%; + --ring: 240 4.9% 83.9%; + --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%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; } } diff --git a/web/app/layout.tsx b/web/app/layout.tsx index d2910887..3adedfc5 100644 --- a/web/app/layout.tsx +++ b/web/app/layout.tsx @@ -1,8 +1,23 @@ import type { Metadata } from "next" +// import { Inter as FontSans } from "next/font/google" import { Inter } from "next/font/google" +import { cn } from "@/lib/utils" +import { ThemeProvider } from "@/lib/providers/theme-provider" import "./globals.css" +import { JazzProvider } from "@/lib/providers/jazz-provider" +import { JotaiProvider } from "@/lib/providers/jotai-provider" +import { Toaster } from "@/components/ui/sonner" +import { ConfirmProvider } from "@/lib/providers/confirm-provider" -const inter = Inter({ subsets: ["latin"] }) +// const fontSans = FontSans({ +// subsets: ["latin"], +// variable: "--font-sans" +// }) + +const inter = Inter({ + subsets: ["latin"], + variable: "--font-sans" +}) export const metadata: Metadata = { title: "Learn Anything", @@ -15,8 +30,19 @@ export default function RootLayout({ children: React.ReactNode }>) { return ( - - {children} + + + + + + + {children} + + + + + + ) } diff --git a/web/app/page.tsx b/web/app/page.tsx index d9621817..70761f42 100644 --- a/web/app/page.tsx +++ b/web/app/page.tsx @@ -1,5 +1,12 @@ -"use client" +import { Button } from "@/components/ui/button" +import Link from "next/link" -export default function Home() { - return
+export default function HomePage() { + return ( +
+ + + +
+ ) } diff --git a/web/components.json b/web/components.json new file mode 100644 index 00000000..c0a1e61e --- /dev/null +++ b/web/components.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "app/globals.css", + "baseColor": "zinc", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils" + } +} diff --git a/web/components/custom/ai-search.tsx b/web/components/custom/ai-search.tsx new file mode 100644 index 00000000..d0904d51 --- /dev/null +++ b/web/components/custom/ai-search.tsx @@ -0,0 +1,88 @@ +"use client" +import React, { useEffect, useState } from "react" +import * as smd from "streaming-markdown" + +interface AiSearchProps { + searchQuery: string +} + +const AiSearch: React.FC = (props: { searchQuery: string }) => { + const [error, setError] = useState("") + + let root_el = React.useRef(null) + + let [parser, md_el] = React.useMemo(() => { + let md_el = document.createElement("div") + let renderer = smd.default_renderer(md_el) + let parser = smd.parser(renderer) + return [parser, md_el] + }, []) + + useEffect(() => { + if (root_el.current) { + root_el.current.appendChild(md_el) + } + }, [root_el.current, md_el]) + + useEffect(() => { + let question = props.searchQuery + + fetchData() + async function fetchData() { + let response: Response + try { + response = await fetch("/api/search-stream", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ question: question }) + }) + } catch (error) { + console.error("Error fetching data:", error) + setError("Error fetching data") + return + } + + if (!response.body) { + console.error("Response has no body") + setError("Response has no body") + return + } + + const reader = response.body.getReader() + const decoder = new TextDecoder() + + while (true) { + let res = await reader.read() + + if (res.value) { + let text = decoder.decode(res.value) + smd.parser_write(parser, text) + } + + if (res.done) { + smd.parser_end(parser) + break + } + } + } + }, [props.searchQuery, parser]) + + return ( +
+
+
+

✨ This is what I have found:

+
+
+
+

{error}

+ +
+ ) +} + +export default AiSearch diff --git a/web/components/custom/content-header.tsx b/web/components/custom/content-header.tsx new file mode 100644 index 00000000..64c8725c --- /dev/null +++ b/web/components/custom/content-header.tsx @@ -0,0 +1,61 @@ +"use client" + +import React from "react" +import { Separator } from "@/components/ui/separator" +import { Button } from "../ui/button" +import { PanelLeftIcon } from "lucide-react" +import { useAtom } from "jotai" +import { isCollapseAtom, toggleCollapseAtom } from "@/store/sidebar" +import { useMedia } from "react-use" +import { cn } from "@/lib/utils" + +type ContentHeaderProps = Omit, "title"> + +export const ContentHeader = React.forwardRef( + ({ children, className, ...props }, ref) => { + return ( +
+ {children} +
+ ) + } +) + +ContentHeader.displayName = "ContentHeader" + +export const SidebarToggleButton: React.FC = () => { + const [isCollapse] = useAtom(isCollapseAtom) + const [, toggle] = useAtom(toggleCollapseAtom) + const isTablet = useMedia("(max-width: 1024px)") + + if (!isCollapse && !isTablet) return null + + const handleClick = (e: React.MouseEvent) => { + e.preventDefault() + e.stopPropagation() + toggle() + } + + return ( +
+ + +
+ ) +} diff --git a/web/components/custom/demo-auth.tsx b/web/components/custom/demo-auth.tsx new file mode 100644 index 00000000..1de84dd3 --- /dev/null +++ b/web/components/custom/demo-auth.tsx @@ -0,0 +1,166 @@ +import React, { useEffect, useMemo, useState } from "react" +import { BrowserDemoAuth, AuthProvider } from "jazz-browser" +import { Account, CoValueClass, ID } from "jazz-tools" +import { AgentSecret } from "cojson" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" + +// Types +export type AuthState = "loading" | "ready" | "signedIn" + +export type ReactAuthHook = (setJazzAuthState: (state: AuthState) => void) => { + auth: AuthProvider + AuthUI: React.ReactNode + logOut?: () => void +} + +type DemoAuthProps = { + accountSchema?: CoValueClass & typeof Account + appName: string + appHostname?: string + Component?: DemoAuth.Component + seedAccounts?: { + [name: string]: { accountID: ID; accountSecret: AgentSecret } + } +} + +type AuthComponentProps = { + appName: string + loading: boolean + existingUsers: string[] + logInAs: (existingUser: string) => void + signUp: (username: string) => void +} + +// Main DemoAuth function +export function DemoAuth({ + accountSchema = Account as CoValueClass & typeof Account, + appName, + appHostname, + Component = DemoAuth.BasicUI, + seedAccounts +}: DemoAuthProps): ReactAuthHook { + return function useLocalAuth(setJazzAuthState) { + const [authState, setAuthState] = useState("loading") + const [existingUsers, setExistingUsers] = useState([]) + const [logInAs, setLogInAs] = useState<(existingUser: string) => void>(() => () => {}) + const [signUp, setSignUp] = useState<(username: string) => void>(() => () => {}) + const [logOut, setLogOut] = useState<(() => void) | undefined>(undefined) + const [logOutCounter, setLogOutCounter] = useState(0) + + useEffect(() => { + setJazzAuthState(authState) + }, [authState, setJazzAuthState]) + + const auth = useMemo(() => { + return new BrowserDemoAuth( + accountSchema, + { + onReady(next) { + setAuthState("ready") + setExistingUsers(next.existingUsers) + setLogInAs(() => next.logInAs) + setSignUp(() => next.signUp) + }, + onSignedIn(next) { + setAuthState("signedIn") + setLogOut(() => () => { + next.logOut() + setAuthState("loading") + setLogOutCounter(c => c + 1) + }) + } + }, + appName, + seedAccounts + ) + }, []) + + const AuthUI = ( + + ) + + return { auth, AuthUI, logOut } + } +} + +const DemoAuthBasicUI: React.FC = ({ appName, existingUsers, logInAs, signUp }) => { + const [username, setUsername] = useState("") + const darkMode = useDarkMode() + + return ( +
+
+

{appName}

+ + +
+
+ ) +} + +// Helper components +const SignUpForm: React.FC<{ + username: string + setUsername: (value: string) => void + signUp: (username: string) => void + darkMode: boolean +}> = ({ username, setUsername, signUp, darkMode }) => ( +
{ + e.preventDefault() + signUp(username) + }} + className="flex flex-col gap-y-4" + > + setUsername(e.target.value)} + autoComplete="webauthn" + /> + +
+) + +const ExistingUsersList: React.FC<{ + existingUsers: string[] + logInAs: (user: string) => void + darkMode: boolean +}> = ({ existingUsers, logInAs, darkMode }) => ( +
+ {existingUsers.map(user => ( + + ))} +
+) + +// Hooks +const useDarkMode = () => { + const [darkMode, setDarkMode] = useState(false) + + useEffect(() => { + const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)") + setDarkMode(mediaQuery.matches) + + const handler = (e: MediaQueryListEvent) => setDarkMode(e.matches) + mediaQuery.addEventListener("change", handler) + return () => mediaQuery.removeEventListener("change", handler) + }, []) + + return darkMode +} + +// DemoAuth namespace +export namespace DemoAuth { + export type Component = React.FC + export const BasicUI = DemoAuthBasicUI +} diff --git a/web/components/custom/logo.tsx b/web/components/custom/logo.tsx new file mode 100644 index 00000000..11de1826 --- /dev/null +++ b/web/components/custom/logo.tsx @@ -0,0 +1,57 @@ +import * as React from "react" + +interface Logo extends React.SVGProps {} + +export const Logo = ({ className, ...props }: Logo) => { + return ( + + + + + + + + + + + + + + + + + + + + ) +} diff --git a/web/components/custom/sidebar/partial/page-section.tsx b/web/components/custom/sidebar/partial/page-section.tsx new file mode 100644 index 00000000..479abfe3 --- /dev/null +++ b/web/components/custom/sidebar/partial/page-section.tsx @@ -0,0 +1,114 @@ +import { SidebarItem } from "../sidebar" +import { z } from "zod" +import { useAccount } from "@/lib/providers/jazz-provider" +import { Input } from "@/components/ui/input" +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover" +import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from "@/components/ui/form" +import { PlusIcon } from "lucide-react" +import { generateUniqueSlug } from "@/lib/utils" +import { PersonalPage } from "@/lib/schema/personal-page" +import { toast } from "sonner" +import { Button } from "@/components/ui/button" +import { useForm } from "react-hook-form" +import { zodResolver } from "@hookform/resolvers/zod" + +const createPageSchema = z.object({ + title: z.string({ message: "Please enter a valid title" }).min(1, { message: "Please enter a valid title" }) +}) + +type PageFormValues = z.infer + +export const PageSection: React.FC = () => { + const { me } = useAccount() + const personalPages = me.root?.personalPages || [] + + return ( +
+
+
+ Pages + +
+
+ +
+
+ {personalPages.map( + page => page && + )} +
+
+
+ ) +} + +const CreatePageForm: React.FC = () => { + const { me } = useAccount() + + const form = useForm({ + resolver: zodResolver(createPageSchema), + defaultValues: { + title: "" + } + }) + + const onSubmit = (values: PageFormValues) => { + try { + const personalPages = me?.root?.personalPages?.toJSON() || [] + const slug = generateUniqueSlug(personalPages, values.title) + + const newPersonalPage = PersonalPage.create( + { + title: values.title, + slug: slug, + content: "" + }, + { owner: me._owner } + ) + + me.root?.personalPages?.push(newPersonalPage) + + toast.success("Page created successfully") + } catch (error) { + console.error(error) + toast.error("Failed to create page") + } + } + + return ( + + + + + +
+ + ( + + New page + + + + + + )} + /> + + + + +
+
+ ) +} diff --git a/web/components/custom/sidebar/partial/topic-section.tsx b/web/components/custom/sidebar/partial/topic-section.tsx new file mode 100644 index 00000000..b713a50b --- /dev/null +++ b/web/components/custom/sidebar/partial/topic-section.tsx @@ -0,0 +1,100 @@ +import { useState, useEffect, useRef } from "react" +import { usePathname } from "next/navigation" +import Link from "next/link" +import { Button } from "@/components/ui/button" +import { ChevronDown, BookOpen, Bookmark, GraduationCap, Check } from "lucide-react" +import { SidebarItem } from "../sidebar" + +const TOPICS = ["Nix", "Javascript", "Kubernetes", "Figma", "Hiring", "Java", "IOS", "Design"] + +export const TopicSection = () => { + const [showOptions, setShowOptions] = useState(false) + const [selectedStatus, setSelectedStatus] = useState(null) + const sectionRef = useRef(null) + + const learningOptions = [ + { text: "To Learn", icon: , color: "text-white/70" }, + { + text: "Learning", + icon: , + color: "text-[#D29752]" + }, + { + text: "Learned", + icon: , + color: "text-[#708F51]" + } + ] + + const statusSelect = (status: string) => { + setSelectedStatus(status === "Show All" ? null : status) + setShowOptions(false) + } + + useEffect(() => { + const overlayClick = (event: MouseEvent) => { + if (sectionRef.current && !sectionRef.current.contains(event.target as Node)) { + setShowOptions(false) + } + } + + document.addEventListener("mousedown", overlayClick) + return () => { + document.removeEventListener("mousedown", overlayClick) + } + }, []) + + const availableOptions = selectedStatus + ? [ + { + text: "Show All", + icon: , + color: "text-white" + }, + ...learningOptions.filter(option => option.text !== selectedStatus) + ] + : learningOptions + + // const topicClick = (topic: string) => { + // router.push(`/${topic.toLowerCase()}`) + // } + + return ( +
+ + + {showOptions && ( +
+ {availableOptions.map(option => ( + + ))} +
+ )} +
+ {TOPICS.map(topic => ( + + ))} +
+
+ ) +} + +export default TopicSection diff --git a/web/components/custom/sidebar/sidebar.tsx b/web/components/custom/sidebar/sidebar.tsx new file mode 100644 index 00000000..1dd7ec13 --- /dev/null +++ b/web/components/custom/sidebar/sidebar.tsx @@ -0,0 +1,179 @@ +"use client" + +import * as React from "react" +import Link from "next/link" +import { usePathname } from "next/navigation" +import { useMedia } from "react-use" +import { useAtom } from "jotai" +import { LinkIcon, SearchIcon } from "lucide-react" +import { Logo } from "@/components/custom/logo" +import { Button } from "@/components/ui/button" +import { cn } from "@/lib/utils" +import { isCollapseAtom } from "@/store/sidebar" + +import { PageSection } from "./partial/page-section" +import { TopicSection } from "./partial/topic-section" + +interface SidebarContextType { + isCollapsed: boolean + setIsCollapsed: React.Dispatch> +} + +const SidebarContext = React.createContext({ + isCollapsed: false, + setIsCollapsed: () => {} +}) + +const useSidebarCollapse = (isTablet: boolean): [boolean, React.Dispatch>] => { + const [isCollapsed, setIsCollapsed] = useAtom(isCollapseAtom) + const pathname = usePathname() + + React.useEffect(() => { + if (isTablet) setIsCollapsed(true) + }, [pathname, setIsCollapsed, isTablet]) + + React.useEffect(() => { + setIsCollapsed(isTablet) + }, [isTablet, setIsCollapsed]) + + return [isCollapsed, setIsCollapsed] +} + +interface SidebarItemProps { + label: string + url: string + icon?: React.ReactNode + onClick?: () => void + children?: React.ReactNode +} + +export const SidebarItem: React.FC = React.memo(({ label, url, icon, onClick, children }) => { + const pathname = usePathname() + const isActive = pathname === url + + return ( +
+ + {icon && ( + + {icon} + + )} + {label} + {children} + +
+ ) +}) + +const LogoAndSearch: React.FC = React.memo(() => { + const pathname = usePathname() + return ( +
+
+ + + +
+ {pathname === "/search" ? ( + + + + ) : ( + + + + )} +
+
+ ) +}) + +const SidebarContent: React.FC = React.memo(() => { + const { isCollapsed } = React.useContext(SidebarContext) + const isTablet = useMedia("(max-width: 1024px)") + + return ( + + ) +}) + +export const Sidebar: React.FC = () => { + const isTablet = useMedia("(max-width: 1024px)") + const [isCollapsed, setIsCollapsed] = useSidebarCollapse(isTablet) + + const sidebarClasses = cn( + "h-full overflow-hidden transition-all duration-300 ease-in-out", + isCollapsed ? "w-0" : "w-auto min-w-56" + ) + + const sidebarInnerClasses = cn( + "h-full w-auto min-w-56 transition-transform duration-300 ease-in-out", + isCollapsed ? "-translate-x-full" : "translate-x-0" + ) + + const contextValue = React.useMemo(() => ({ isCollapsed, setIsCollapsed }), [isCollapsed, setIsCollapsed]) + + if (isTablet) { + return ( + <> +
setIsCollapsed(true)} + /> +
+
+ + + +
+
+ + ) + } + + return ( +
+
+ + + +
+
+ ) +} + +export default Sidebar diff --git a/web/components/la-editor/components/bubble-menu/bubble-menu.tsx b/web/components/la-editor/components/bubble-menu/bubble-menu.tsx new file mode 100644 index 00000000..e56e30c9 --- /dev/null +++ b/web/components/la-editor/components/bubble-menu/bubble-menu.tsx @@ -0,0 +1,60 @@ +import { useTextmenuCommands } from "../../hooks/use-text-menu-commands" +import { PopoverWrapper } from "../ui/popover-wrapper" +import { useTextmenuStates } from "../../hooks/use-text-menu-states" +import { BubbleMenu as TiptapBubbleMenu, Editor } from "@tiptap/react" +import { ToolbarButton } from "../ui/toolbar-button" +import { Icon } from "../ui/icon" +import * as React from "react" + +export type BubbleMenuProps = { + editor: Editor +} + +export const BubbleMenu = ({ editor }: BubbleMenuProps) => { + const commands = useTextmenuCommands(editor) + const states = useTextmenuStates(editor) + + return ( + + +
+ + + + + + + + + + {/* + + */} + + + + + + + + + + {/* + + */} +
+
+
+ ) +} + +export default BubbleMenu diff --git a/web/components/la-editor/components/bubble-menu/index.ts b/web/components/la-editor/components/bubble-menu/index.ts new file mode 100644 index 00000000..32c445a6 --- /dev/null +++ b/web/components/la-editor/components/bubble-menu/index.ts @@ -0,0 +1 @@ +export * from "./bubble-menu" diff --git a/web/components/la-editor/components/ui/icon.tsx b/web/components/la-editor/components/ui/icon.tsx new file mode 100644 index 00000000..5e0f94d5 --- /dev/null +++ b/web/components/la-editor/components/ui/icon.tsx @@ -0,0 +1,22 @@ +import * as React from "react" +import { cn } from "@/lib/utils" +import { icons } from "lucide-react" + +export type IconProps = { + name: keyof typeof icons + className?: string + strokeWidth?: number + [key: string]: any +} + +export const Icon = React.memo(({ name, className, size, strokeWidth, ...props }: IconProps) => { + const IconComponent = icons[name] + + if (!IconComponent) { + return null + } + + return +}) + +Icon.displayName = "Icon" diff --git a/web/components/la-editor/components/ui/popover-wrapper.tsx b/web/components/la-editor/components/ui/popover-wrapper.tsx new file mode 100644 index 00000000..565e9d07 --- /dev/null +++ b/web/components/la-editor/components/ui/popover-wrapper.tsx @@ -0,0 +1,20 @@ +import * as React from "react" +import { cn } from "@/lib/utils" + +export type PopoverWrapperProps = React.HTMLProps + +export const PopoverWrapper = React.forwardRef( + ({ children, className, ...props }, ref) => { + return ( +
+ {children} +
+ ) + } +) + +PopoverWrapper.displayName = "PopoverWrapper" diff --git a/web/components/la-editor/components/ui/shortcut.tsx b/web/components/la-editor/components/ui/shortcut.tsx new file mode 100644 index 00000000..978a2b4a --- /dev/null +++ b/web/components/la-editor/components/ui/shortcut.tsx @@ -0,0 +1,45 @@ +import * as React from "react" +import { cn } from "@/lib/utils" +import { getShortcutKey } from "../../lib/utils" + +export interface ShortcutKeyWrapperProps extends React.HTMLAttributes { + ariaLabel: string +} + +const ShortcutKeyWrapper = React.forwardRef( + ({ className, ariaLabel, children, ...props }, ref) => { + return ( + + {children} + + ) + } +) + +ShortcutKeyWrapper.displayName = "ShortcutKeyWrapper" + +export interface ShortcutKeyProps extends React.HTMLAttributes { + shortcut: string +} + +const ShortcutKey = React.forwardRef(({ className, shortcut, ...props }, ref) => { + return ( + + {getShortcutKey(shortcut)} + + ) +}) + +ShortcutKey.displayName = "ShortcutKey" + +export const Shortcut = { + Wrapper: ShortcutKeyWrapper, + Key: ShortcutKey +} diff --git a/web/components/la-editor/components/ui/toolbar-button.tsx b/web/components/la-editor/components/ui/toolbar-button.tsx new file mode 100644 index 00000000..8556a35c --- /dev/null +++ b/web/components/la-editor/components/ui/toolbar-button.tsx @@ -0,0 +1,49 @@ +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip" +import { Toggle } from "@/components/ui/toggle" + +import * as React from "react" +import { cn } from "@/lib/utils" +import type { TooltipContentProps } from "@radix-ui/react-tooltip" + +interface ToolbarButtonProps extends React.ComponentPropsWithoutRef { + isActive?: boolean + tooltip?: string + tooltipOptions?: TooltipContentProps +} + +const ToolbarButton = React.forwardRef(function ToolbarButton( + { isActive, children, tooltip, className, tooltipOptions, ...props }, + ref +) { + return ( + + + + + {children} + + + {tooltip && ( + +
{tooltip}
+
+ )} +
+
+ ) +}) + +ToolbarButton.displayName = "ToolbarButton" + +export { ToolbarButton } diff --git a/web/components/la-editor/extensions/blockquote/blockquote.ts b/web/components/la-editor/extensions/blockquote/blockquote.ts new file mode 100644 index 00000000..0db11b0c --- /dev/null +++ b/web/components/la-editor/extensions/blockquote/blockquote.ts @@ -0,0 +1,13 @@ +/* + * Add block-node class to blockquote element + */ +import { mergeAttributes } from "@tiptap/core" +import { Blockquote as TiptapBlockquote } from "@tiptap/extension-blockquote" + +export const Blockquote = TiptapBlockquote.extend({ + renderHTML({ HTMLAttributes }) { + return ["blockquote", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { class: "block-node" }), 0] + } +}) + +export default Blockquote diff --git a/web/components/la-editor/extensions/blockquote/index.ts b/web/components/la-editor/extensions/blockquote/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/web/components/la-editor/extensions/bullet-list/bullet-list.ts b/web/components/la-editor/extensions/bullet-list/bullet-list.ts new file mode 100644 index 00000000..1b2f3efc --- /dev/null +++ b/web/components/la-editor/extensions/bullet-list/bullet-list.ts @@ -0,0 +1,14 @@ +import { BulletList as TiptapBulletList } from "@tiptap/extension-bullet-list" + +export const BulletList = TiptapBulletList.extend({ + addOptions() { + return { + ...this.parent?.(), + HTMLAttributes: { + class: "list-node" + } + } + } +}) + +export default BulletList diff --git a/web/components/la-editor/extensions/bullet-list/index.ts b/web/components/la-editor/extensions/bullet-list/index.ts new file mode 100644 index 00000000..1c27d144 --- /dev/null +++ b/web/components/la-editor/extensions/bullet-list/index.ts @@ -0,0 +1 @@ +export * from "./bullet-list" diff --git a/web/components/la-editor/extensions/code-block-lowlight/code-block-lowlight.ts b/web/components/la-editor/extensions/code-block-lowlight/code-block-lowlight.ts new file mode 100644 index 00000000..63291eb6 --- /dev/null +++ b/web/components/la-editor/extensions/code-block-lowlight/code-block-lowlight.ts @@ -0,0 +1,17 @@ +import { CodeBlockLowlight as TiptapCodeBlockLowlight } from "@tiptap/extension-code-block-lowlight" +import { common, createLowlight } from "lowlight" + +export const CodeBlockLowlight = TiptapCodeBlockLowlight.extend({ + addOptions() { + return { + ...this.parent?.(), + lowlight: createLowlight(common), + defaultLanguage: null, + HTMLAttributes: { + class: "block-node" + } + } + } +}) + +export default CodeBlockLowlight diff --git a/web/components/la-editor/extensions/code-block-lowlight/index.ts b/web/components/la-editor/extensions/code-block-lowlight/index.ts new file mode 100644 index 00000000..4d6759ea --- /dev/null +++ b/web/components/la-editor/extensions/code-block-lowlight/index.ts @@ -0,0 +1 @@ +export * from "./code-block-lowlight" diff --git a/web/components/la-editor/extensions/code/code.ts b/web/components/la-editor/extensions/code/code.ts new file mode 100644 index 00000000..c29c847c --- /dev/null +++ b/web/components/la-editor/extensions/code/code.ts @@ -0,0 +1,15 @@ +import { Code as TiptapCode } from "@tiptap/extension-code" + +export const Code = TiptapCode.extend({ + addOptions() { + return { + ...this.parent?.(), + HTMLAttributes: { + class: "inline", + spellCheck: "false" + } + } + } +}) + +export default Code diff --git a/web/components/la-editor/extensions/code/index.ts b/web/components/la-editor/extensions/code/index.ts new file mode 100644 index 00000000..57a6d399 --- /dev/null +++ b/web/components/la-editor/extensions/code/index.ts @@ -0,0 +1 @@ +export * from "./code" diff --git a/web/components/la-editor/extensions/dropcursor/dropcursor.ts b/web/components/la-editor/extensions/dropcursor/dropcursor.ts new file mode 100644 index 00000000..383b0d0c --- /dev/null +++ b/web/components/la-editor/extensions/dropcursor/dropcursor.ts @@ -0,0 +1,13 @@ +import { Dropcursor as TiptapDropcursor } from "@tiptap/extension-dropcursor" + +export const Dropcursor = TiptapDropcursor.extend({ + addOptions() { + return { + ...this.parent?.(), + width: 2, + class: "ProseMirror-dropcursor border" + } + } +}) + +export default Dropcursor diff --git a/web/components/la-editor/extensions/dropcursor/index.ts b/web/components/la-editor/extensions/dropcursor/index.ts new file mode 100644 index 00000000..aaf0411d --- /dev/null +++ b/web/components/la-editor/extensions/dropcursor/index.ts @@ -0,0 +1 @@ +export * from "./dropcursor" diff --git a/web/components/la-editor/extensions/heading/heading.ts b/web/components/la-editor/extensions/heading/heading.ts new file mode 100644 index 00000000..452cb880 --- /dev/null +++ b/web/components/la-editor/extensions/heading/heading.ts @@ -0,0 +1,29 @@ +/* + * Add heading level validation. decimal (0-9) + * Add heading class to heading element + */ +import { mergeAttributes } from "@tiptap/core" +import TiptapHeading from "@tiptap/extension-heading" +import type { Level } from "@tiptap/extension-heading" + +export const Heading = TiptapHeading.extend({ + addOptions() { + return { + ...this.parent?.(), + levels: [1, 2, 3] as Level[], + HTMLAttributes: { + class: "heading-node" + } + } + }, + + renderHTML({ node, HTMLAttributes }) { + const nodeLevel = parseInt(node.attrs.level, 10) as Level + const hasLevel = this.options.levels.includes(nodeLevel) + const level = hasLevel ? nodeLevel : this.options.levels[0] + + return [`h${level}`, mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0] + } +}) + +export default Heading diff --git a/web/components/la-editor/extensions/heading/index.ts b/web/components/la-editor/extensions/heading/index.ts new file mode 100644 index 00000000..6528f482 --- /dev/null +++ b/web/components/la-editor/extensions/heading/index.ts @@ -0,0 +1 @@ +export * from "./heading" diff --git a/web/components/la-editor/extensions/horizontal-rule/horizontal-rule.ts b/web/components/la-editor/extensions/horizontal-rule/horizontal-rule.ts new file mode 100644 index 00000000..349515df --- /dev/null +++ b/web/components/la-editor/extensions/horizontal-rule/horizontal-rule.ts @@ -0,0 +1,18 @@ +/* + * Wrap the horizontal rule in a div element. + * Also add a keyboard shortcut to insert a horizontal rule. + */ +import { HorizontalRule as TiptapHorizontalRule } from "@tiptap/extension-horizontal-rule" + +export const HorizontalRule = TiptapHorizontalRule.extend({ + addKeyboardShortcuts() { + return { + "Mod-Alt--": () => + this.editor.commands.insertContent({ + type: this.name + }) + } + } +}) + +export default HorizontalRule diff --git a/web/components/la-editor/extensions/horizontal-rule/index.ts b/web/components/la-editor/extensions/horizontal-rule/index.ts new file mode 100644 index 00000000..7cc78542 --- /dev/null +++ b/web/components/la-editor/extensions/horizontal-rule/index.ts @@ -0,0 +1 @@ +export * from "./horizontal-rule" diff --git a/web/components/la-editor/extensions/index.ts b/web/components/la-editor/extensions/index.ts new file mode 100644 index 00000000..da03a983 --- /dev/null +++ b/web/components/la-editor/extensions/index.ts @@ -0,0 +1,43 @@ +import { StarterKit } from "./starter-kit" +import { TaskList } from "./task-list" +import { TaskItem } from "./task-item" +import { HorizontalRule } from "./horizontal-rule" +import { Blockquote } from "./blockquote/blockquote" +import { SlashCommand } from "./slash-command" +import { Heading } from "./heading" +import { Link } from "./link" +import { CodeBlockLowlight } from "./code-block-lowlight" +import { Selection } from "./selection" +import { Code } from "./code" +import { Paragraph } from "./paragraph" +import { BulletList } from "./bullet-list" +import { OrderedList } from "./ordered-list" +import { Dropcursor } from "./dropcursor" + +export interface ExtensionOptions { + placeholder?: string +} + +export const createExtensions = ({ placeholder = "Start typing..." }: ExtensionOptions) => [ + Heading, + Code, + Link, + TaskList, + TaskItem, + Selection, + Paragraph, + Dropcursor, + Blockquote, + BulletList, + OrderedList, + SlashCommand, + HorizontalRule, + CodeBlockLowlight, + StarterKit.configure({ + placeholder: { + placeholder: () => placeholder + } + }) +] + +export default createExtensions diff --git a/web/components/la-editor/extensions/link/index.ts b/web/components/la-editor/extensions/link/index.ts new file mode 100644 index 00000000..6ada303c --- /dev/null +++ b/web/components/la-editor/extensions/link/index.ts @@ -0,0 +1 @@ +export * from "./link" diff --git a/web/components/la-editor/extensions/link/link.ts b/web/components/la-editor/extensions/link/link.ts new file mode 100644 index 00000000..26eef782 --- /dev/null +++ b/web/components/la-editor/extensions/link/link.ts @@ -0,0 +1,90 @@ +import { mergeAttributes } from "@tiptap/core" +import TiptapLink from "@tiptap/extension-link" +import { EditorView } from "@tiptap/pm/view" +import { getMarkRange } from "@tiptap/core" +import { Plugin, TextSelection } from "@tiptap/pm/state" + +export const Link = TiptapLink.extend({ + /* + * Determines whether typing next to a link automatically becomes part of the link. + * In this case, we dont want any characters to be included as part of the link. + */ + inclusive: false, + + /* + * Match all
elements that have an href attribute, except for: + * - elements with a data-type attribute set to button + * - elements with an href attribute that contains 'javascript:' + */ + parseHTML() { + return [{ tag: 'a[href]:not([data-type="button"]):not([href *= "javascript:" i])' }] + }, + + renderHTML({ HTMLAttributes }) { + return ["a", mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0] + }, + + addOptions() { + return { + ...this.parent?.(), + openOnClick: false, + HTMLAttributes: { + class: "link" + } + } + }, + + addProseMirrorPlugins() { + const { editor } = this + + return [ + ...(this.parent?.() || []), + new Plugin({ + props: { + handleKeyDown: (view: EditorView, event: KeyboardEvent) => { + const { selection } = editor.state + + /* + * Handles the 'Escape' key press when there's a selection within the link. + * This will move the cursor to the end of the link. + */ + if (event.key === "Escape" && selection.empty !== true) { + console.log("Link handleKeyDown") + editor.commands.focus(selection.to, { scrollIntoView: false }) + } + + return false + }, + handleClick(view, pos) { + /* + * Marks the entire link when the user clicks on it. + */ + + const { schema, doc, tr } = view.state + const range = getMarkRange(doc.resolve(pos), schema.marks.link) + + if (!range) { + return + } + + const { from, to } = range + const start = Math.min(from, to) + const end = Math.max(from, to) + + if (pos < start || pos > end) { + return + } + + const $start = doc.resolve(start) + const $end = doc.resolve(end) + const transaction = tr.setSelection(new TextSelection($start, $end)) + + view.dispatch(transaction) + } + } + }) + ] + } +}) + +export default Link diff --git a/web/components/la-editor/extensions/ordered-list/index.ts b/web/components/la-editor/extensions/ordered-list/index.ts new file mode 100644 index 00000000..1bf163b5 --- /dev/null +++ b/web/components/la-editor/extensions/ordered-list/index.ts @@ -0,0 +1 @@ +export * from "./ordered-list" diff --git a/web/components/la-editor/extensions/ordered-list/ordered-list.ts b/web/components/la-editor/extensions/ordered-list/ordered-list.ts new file mode 100644 index 00000000..3bc55a58 --- /dev/null +++ b/web/components/la-editor/extensions/ordered-list/ordered-list.ts @@ -0,0 +1,14 @@ +import { OrderedList as TiptapOrderedList } from "@tiptap/extension-ordered-list" + +export const OrderedList = TiptapOrderedList.extend({ + addOptions() { + return { + ...this.parent?.(), + HTMLAttributes: { + class: "list-node" + } + } + } +}) + +export default OrderedList diff --git a/web/components/la-editor/extensions/paragraph/index.ts b/web/components/la-editor/extensions/paragraph/index.ts new file mode 100644 index 00000000..04a77c68 --- /dev/null +++ b/web/components/la-editor/extensions/paragraph/index.ts @@ -0,0 +1 @@ +export * from "./paragraph" diff --git a/web/components/la-editor/extensions/paragraph/paragraph.ts b/web/components/la-editor/extensions/paragraph/paragraph.ts new file mode 100644 index 00000000..f5aa9947 --- /dev/null +++ b/web/components/la-editor/extensions/paragraph/paragraph.ts @@ -0,0 +1,14 @@ +import { Paragraph as TiptapParagraph } from "@tiptap/extension-paragraph" + +export const Paragraph = TiptapParagraph.extend({ + addOptions() { + return { + ...this.parent?.(), + HTMLAttributes: { + class: "text-node" + } + } + } +}) + +export default Paragraph diff --git a/web/components/la-editor/extensions/selection/index.ts b/web/components/la-editor/extensions/selection/index.ts new file mode 100644 index 00000000..c8f61024 --- /dev/null +++ b/web/components/la-editor/extensions/selection/index.ts @@ -0,0 +1 @@ +export * from "./selection" diff --git a/web/components/la-editor/extensions/selection/selection.ts b/web/components/la-editor/extensions/selection/selection.ts new file mode 100644 index 00000000..8d62b26c --- /dev/null +++ b/web/components/la-editor/extensions/selection/selection.ts @@ -0,0 +1,36 @@ +import { Extension } from "@tiptap/core" +import { Plugin, PluginKey } from "@tiptap/pm/state" +import { Decoration, DecorationSet } from "@tiptap/pm/view" + +export const Selection = Extension.create({ + name: "selection", + + addProseMirrorPlugins() { + const { editor } = this + + return [ + new Plugin({ + key: new PluginKey("selection"), + props: { + decorations(state) { + if (state.selection.empty) { + return null + } + + if (editor.isFocused === true) { + return null + } + + return DecorationSet.create(state.doc, [ + Decoration.inline(state.selection.from, state.selection.to, { + class: "selection" + }) + ]) + } + } + }) + ] + } +}) + +export default Selection diff --git a/web/components/la-editor/extensions/slash-command/groups.ts b/web/components/la-editor/extensions/slash-command/groups.ts new file mode 100644 index 00000000..d509c59f --- /dev/null +++ b/web/components/la-editor/extensions/slash-command/groups.ts @@ -0,0 +1,122 @@ +import { Group } from "./types" + +export const GROUPS: Group[] = [ + { + name: "format", + title: "Format", + commands: [ + { + name: "heading1", + label: "Heading 1", + iconName: "Heading1", + description: "High priority section title", + aliases: ["h1"], + shortcuts: ["mod", "alt", "1"], + action: editor => { + editor.chain().focus().setHeading({ level: 1 }).run() + } + }, + { + name: "heading2", + label: "Heading 2", + iconName: "Heading2", + description: "Medium priority section title", + aliases: ["h2"], + shortcuts: ["mod", "alt", "2"], + action: editor => { + editor.chain().focus().setHeading({ level: 2 }).run() + } + }, + { + name: "heading3", + label: "Heading 3", + iconName: "Heading3", + description: "Low priority section title", + aliases: ["h3"], + shortcuts: ["mod", "alt", "3"], + action: editor => { + editor.chain().focus().setHeading({ level: 3 }).run() + } + } + ] + }, + { + name: "list", + title: "List", + commands: [ + { + name: "bulletList", + label: "Bullet List", + iconName: "List", + description: "Unordered list of items", + aliases: ["ul"], + shortcuts: ["mod", "shift", "8"], + action: editor => { + editor.chain().focus().toggleBulletList().run() + } + }, + { + name: "numberedList", + label: "Numbered List", + iconName: "ListOrdered", + description: "Ordered list of items", + aliases: ["ol"], + shortcuts: ["mod", "shift", "7"], + action: editor => { + editor.chain().focus().toggleOrderedList().run() + } + }, + { + name: "taskList", + label: "Task List", + iconName: "ListTodo", + description: "Task list with todo items", + aliases: ["todo"], + shortcuts: ["mod", "shift", "8"], + action: editor => { + editor.chain().focus().toggleTaskList().run() + } + } + ] + }, + { + name: "insert", + title: "Insert", + commands: [ + { + name: "codeBlock", + label: "Code Block", + iconName: "SquareCode", + description: "Code block with syntax highlighting", + shortcuts: ["mod", "alt", "c"], + shouldBeHidden: editor => editor.isActive("columns"), + action: editor => { + editor.chain().focus().setCodeBlock().run() + } + }, + { + name: "horizontalRule", + label: "Divider", + iconName: "Divide", + description: "Insert a horizontal divider", + aliases: ["hr"], + shortcuts: ["mod", "shift", "-"], + action: editor => { + editor.chain().focus().setHorizontalRule().run() + } + }, + { + name: "blockquote", + label: "Blockquote", + iconName: "Quote", + description: "Element for quoting", + shortcuts: ["mod", "shift", "b"], + action: editor => { + editor.chain().focus().setBlockquote().run() + } + } + ] + } +] + +export default GROUPS diff --git a/web/components/la-editor/extensions/slash-command/index.ts b/web/components/la-editor/extensions/slash-command/index.ts new file mode 100644 index 00000000..a7ccb685 --- /dev/null +++ b/web/components/la-editor/extensions/slash-command/index.ts @@ -0,0 +1 @@ +export * from "./slash-command" diff --git a/web/components/la-editor/extensions/slash-command/menu-list.tsx b/web/components/la-editor/extensions/slash-command/menu-list.tsx new file mode 100644 index 00000000..10377d18 --- /dev/null +++ b/web/components/la-editor/extensions/slash-command/menu-list.tsx @@ -0,0 +1,155 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +import { Button } from "@/components/ui/button" +import { Separator } from "@/components/ui/separator" + +import { Command, MenuListProps } from "./types" +import { getShortcutKeys } from "../../lib/utils" +import { Icon } from "../../components/ui/icon" +import { PopoverWrapper } from "../../components/ui/popover-wrapper" +import { Shortcut } from "../../components/ui/shortcut" + +export const MenuList = React.forwardRef((props: MenuListProps, ref) => { + const scrollContainer = React.useRef(null) + const activeItem = React.useRef(null) + const [selectedGroupIndex, setSelectedGroupIndex] = React.useState(0) + const [selectedCommandIndex, setSelectedCommandIndex] = React.useState(0) + + // Anytime the groups change, i.e. the user types to narrow it down, we want to + // reset the current selection to the first menu item + React.useEffect(() => { + setSelectedGroupIndex(0) + setSelectedCommandIndex(0) + }, [props.items]) + + const selectItem = React.useCallback( + (groupIndex: number, commandIndex: number) => { + const command = props.items[groupIndex].commands[commandIndex] + props.command(command) + }, + [props] + ) + + React.useImperativeHandle(ref, () => ({ + onKeyDown: ({ event }: { event: React.KeyboardEvent }) => { + if (event.key === "ArrowDown") { + if (!props.items.length) { + return false + } + + const commands = props.items[selectedGroupIndex].commands + + let newCommandIndex = selectedCommandIndex + 1 + let newGroupIndex = selectedGroupIndex + + if (commands.length - 1 < newCommandIndex) { + newCommandIndex = 0 + newGroupIndex = selectedGroupIndex + 1 + } + + if (props.items.length - 1 < newGroupIndex) { + newGroupIndex = 0 + } + + setSelectedCommandIndex(newCommandIndex) + setSelectedGroupIndex(newGroupIndex) + + return true + } + + if (event.key === "ArrowUp") { + if (!props.items.length) { + return false + } + + let newCommandIndex = selectedCommandIndex - 1 + let newGroupIndex = selectedGroupIndex + + if (newCommandIndex < 0) { + newGroupIndex = selectedGroupIndex - 1 + newCommandIndex = props.items[newGroupIndex]?.commands.length - 1 || 0 + } + + if (newGroupIndex < 0) { + newGroupIndex = props.items.length - 1 + newCommandIndex = props.items[newGroupIndex].commands.length - 1 + } + + setSelectedCommandIndex(newCommandIndex) + setSelectedGroupIndex(newGroupIndex) + + return true + } + + if (event.key === "Enter") { + if (!props.items.length || selectedGroupIndex === -1 || selectedCommandIndex === -1) { + return false + } + + selectItem(selectedGroupIndex, selectedCommandIndex) + + return true + } + + return false + } + })) + + React.useEffect(() => { + if (activeItem.current && scrollContainer.current) { + const offsetTop = activeItem.current.offsetTop + const offsetHeight = activeItem.current.offsetHeight + + scrollContainer.current.scrollTop = offsetTop - offsetHeight + } + }, [selectedCommandIndex, selectedGroupIndex]) + + const createCommandClickHandler = React.useCallback( + (groupIndex: number, commandIndex: number) => { + return () => { + selectItem(groupIndex, commandIndex) + } + }, + [selectItem] + ) + + if (!props.items.length) { + return null + } + + return ( + + {props.items.map((group, groupIndex: number) => ( + + {group.commands.map((command: Command, commandIndex: number) => ( + + ))} + {groupIndex !== props.items.length - 1 && } + + ))} + + ) +}) + +MenuList.displayName = "MenuList" + +export default MenuList diff --git a/web/components/la-editor/extensions/slash-command/slash-command.ts b/web/components/la-editor/extensions/slash-command/slash-command.ts new file mode 100644 index 00000000..f5524ac6 --- /dev/null +++ b/web/components/la-editor/extensions/slash-command/slash-command.ts @@ -0,0 +1,234 @@ +import { Editor, Extension } from "@tiptap/core" +import { ReactRenderer } from "@tiptap/react" +import Suggestion, { SuggestionProps, SuggestionKeyDownProps } from "@tiptap/suggestion" +import { PluginKey } from "@tiptap/pm/state" +import tippy from "tippy.js" + +import { GROUPS } from "./groups" +import { MenuList } from "./menu-list" + +const EXTENSION_NAME = "slashCommand" + +let popup: any + +export const SlashCommand = Extension.create({ + name: EXTENSION_NAME, + priority: 200, + + onCreate() { + popup = tippy("body", { + interactive: true, + trigger: "manual", + placement: "bottom-start", + theme: "slash-command", + maxWidth: "16rem", + offset: [16, 8], + popperOptions: { + strategy: "fixed", + modifiers: [{ name: "flip", enabled: false }] + } + }) + }, + + addProseMirrorPlugins() { + return [ + Suggestion({ + editor: this.editor, + char: "/", + allowSpaces: true, + startOfLine: true, + pluginKey: new PluginKey(EXTENSION_NAME), + + allow: ({ state, range }) => { + const $from = state.doc.resolve(range.from) + const isRootDepth = $from.depth === 1 + const isParagraph = $from.parent.type.name === "paragraph" + const isStartOfNode = $from.parent.textContent?.charAt(0) === "/" + const isInColumn = this.editor.isActive("column") + + const afterContent = $from.parent.textContent?.substring($from.parent.textContent?.indexOf("/")) + const isValidAfterContent = !afterContent?.endsWith(" ") + + return ( + ((isRootDepth && isParagraph && isStartOfNode) || (isInColumn && isParagraph && isStartOfNode)) && + isValidAfterContent + ) + }, + + command: ({ editor, props }: { editor: Editor; props: any }) => { + const { view, state } = editor + const { $head, $from } = view.state.selection + + const end = $from.pos + const from = $head?.nodeBefore + ? end - ($head.nodeBefore.text?.substring($head.nodeBefore.text?.indexOf("/")).length ?? 0) + : $from.start() + + const tr = state.tr.deleteRange(from, end) + view.dispatch(tr) + + props.action(editor) + view.focus() + }, + + items: ({ query }: { query: string }) => { + return GROUPS.map(group => ({ + ...group, + commands: group.commands + .filter(item => { + const labelNormalized = item.label.toLowerCase().trim() + const queryNormalized = query.toLowerCase().trim() + + if (item.aliases) { + const aliases = item.aliases.map(alias => alias.toLowerCase().trim()) + return labelNormalized.includes(queryNormalized) || aliases.includes(queryNormalized) + } + + return labelNormalized.includes(queryNormalized) + }) + .filter(command => (command.shouldBeHidden ? !command.shouldBeHidden(this.editor) : true)) + .map(command => ({ + ...command, + isEnabled: true + })) + })).filter(group => group.commands.length > 0) + }, + + render: () => { + let component: any + let scrollHandler: (() => void) | null = null + + return { + onStart: (props: SuggestionProps) => { + component = new ReactRenderer(MenuList, { + props, + editor: props.editor + }) + + const { view } = props.editor + const editorNode = view.dom as HTMLElement + + const getReferenceClientRect = () => { + if (!props.clientRect) { + return props.editor.storage[EXTENSION_NAME].rect + } + + const rect = props.clientRect() + + if (!rect) { + return props.editor.storage[EXTENSION_NAME].rect + } + + let yPos = rect.y + + if (rect.top + component.element.offsetHeight + 40 > window.innerHeight) { + const diff = rect.top + component.element.offsetHeight - window.innerHeight + 40 + yPos = rect.y - diff + } + + const editorXOffset = editorNode.getBoundingClientRect().x + return new DOMRect(rect.x, yPos, rect.width, rect.height) + } + + scrollHandler = () => { + popup?.[0].setProps({ + getReferenceClientRect + }) + } + + view.dom.parentElement?.addEventListener("scroll", scrollHandler) + + popup?.[0].setProps({ + getReferenceClientRect, + appendTo: () => document.body, + content: component.element + }) + + popup?.[0].show() + }, + + onUpdate(props: SuggestionProps) { + component.updateProps(props) + + const { view } = props.editor + const editorNode = view.dom as HTMLElement + + const getReferenceClientRect = () => { + if (!props.clientRect) { + return props.editor.storage[EXTENSION_NAME].rect + } + + const rect = props.clientRect() + + if (!rect) { + return props.editor.storage[EXTENSION_NAME].rect + } + + return new DOMRect(rect.x, rect.y, rect.width, rect.height) + } + + let scrollHandler = () => { + popup?.[0].setProps({ + getReferenceClientRect + }) + } + + view.dom.parentElement?.addEventListener("scroll", scrollHandler) + + props.editor.storage[EXTENSION_NAME].rect = props.clientRect + ? getReferenceClientRect() + : { + width: 0, + height: 0, + left: 0, + top: 0, + right: 0, + bottom: 0 + } + popup?.[0].setProps({ + getReferenceClientRect + }) + }, + + onKeyDown(props: SuggestionKeyDownProps) { + if (props.event.key === "Escape") { + popup?.[0].hide() + return true + } + + if (!popup?.[0].state.isShown) { + popup?.[0].show() + } + + return component.ref?.onKeyDown(props) + }, + + onExit(props) { + popup?.[0].hide() + if (scrollHandler) { + const { view } = props.editor + view.dom.parentElement?.removeEventListener("scroll", scrollHandler) + } + component.destroy() + } + } + } + }) + ] + }, + + addStorage() { + return { + rect: { + width: 0, + height: 0, + left: 0, + top: 0, + right: 0, + bottom: 0 + } + } + } +}) + +export default SlashCommand diff --git a/web/components/la-editor/extensions/slash-command/types.ts b/web/components/la-editor/extensions/slash-command/types.ts new file mode 100644 index 00000000..8745ac0c --- /dev/null +++ b/web/components/la-editor/extensions/slash-command/types.ts @@ -0,0 +1,26 @@ +import { Editor } from "@tiptap/core" + +import { icons } from "lucide-react" + +export interface Group { + name: string + title: string + commands: Command[] +} + +export interface Command { + name: string + label: string + description: string + aliases?: string[] + shortcuts: string[] + iconName: keyof typeof icons + action: (editor: Editor) => void + shouldBeHidden?: (editor: Editor) => boolean +} + +export interface MenuListProps { + editor: Editor + items: Group[] + command: (command: Command) => void +} diff --git a/web/components/la-editor/extensions/starter-kit.ts b/web/components/la-editor/extensions/starter-kit.ts new file mode 100644 index 00000000..9d55e508 --- /dev/null +++ b/web/components/la-editor/extensions/starter-kit.ts @@ -0,0 +1,153 @@ +import { Extension } from "@tiptap/core" +import { Bold, BoldOptions } from "@tiptap/extension-bold" +import { Document } from "@tiptap/extension-document" +import { Gapcursor } from "@tiptap/extension-gapcursor" +import { HardBreak, HardBreakOptions } from "@tiptap/extension-hard-break" +import { Italic, ItalicOptions } from "@tiptap/extension-italic" +import { ListItem, ListItemOptions } from "@tiptap/extension-list-item" +import { Strike, StrikeOptions } from "@tiptap/extension-strike" +import { Text } from "@tiptap/extension-text" +import { FocusClasses, FocusOptions } from "@tiptap/extension-focus" +import { Typography, TypographyOptions } from "@tiptap/extension-typography" +import { Placeholder, PlaceholderOptions } from "@tiptap/extension-placeholder" +import { History, HistoryOptions } from "@tiptap/extension-history" + +export interface StarterKitOptions { + /** + * If set to false, the bold extension will not be registered + * @example bold: false + */ + bold: Partial | false + + /** + * If set to false, the document extension will not be registered + * @example document: false + */ + document: false + + /** + * If set to false, the gapcursor extension will not be registered + * @example gapcursor: false + */ + gapcursor: false + + /** + * If set to false, the hardBreak extension will not be registered + * @example hardBreak: false + */ + hardBreak: Partial | false + + /** + * If set to false, the history extension will not be registered + * @example history: false + */ + history: Partial | false + + /** + * If set to false, the italic extension will not be registered + * @example italic: false + */ + italic: Partial | false + + /** + * If set to false, the listItem extension will not be registered + * @example listItem: false + */ + listItem: Partial | false + + /** + * If set to false, the strike extension will not be registered + * @example strike: false + */ + strike: Partial | false + + /** + * If set to false, the text extension will not be registered + * @example text: false + */ + text: false + + /** + * If set to false, the typography extension will not be registered + * @example typography: false + */ + typography: Partial | false + + /** + * If set to false, the placeholder extension will not be registered + * @example placeholder: false + */ + + placeholder: Partial | false + + /** + * If set to false, the focus extension will not be registered + * @example focus: false + */ + focus: Partial | false +} + +/** + * The starter kit is a collection of essential editor extensions. + * + * It’s a good starting point for building your own editor. + */ +export const StarterKit = Extension.create({ + name: "starterKit", + + addExtensions() { + const extensions = [] + + if (this.options.bold !== false) { + extensions.push(Bold.configure(this.options?.bold)) + } + + if (this.options.document !== false) { + extensions.push(Document.configure(this.options?.document)) + } + + if (this.options.gapcursor !== false) { + extensions.push(Gapcursor.configure(this.options?.gapcursor)) + } + + if (this.options.hardBreak !== false) { + extensions.push(HardBreak.configure(this.options?.hardBreak)) + } + + if (this.options.history !== false) { + extensions.push(History.configure(this.options?.history)) + } + + if (this.options.italic !== false) { + extensions.push(Italic.configure(this.options?.italic)) + } + + if (this.options.listItem !== false) { + extensions.push(ListItem.configure(this.options?.listItem)) + } + + if (this.options.strike !== false) { + extensions.push(Strike.configure(this.options?.strike)) + } + + if (this.options.text !== false) { + extensions.push(Text.configure(this.options?.text)) + } + + if (this.options.typography !== false) { + extensions.push(Typography.configure(this.options?.typography)) + } + + if (this.options.placeholder !== false) { + extensions.push(Placeholder.configure(this.options?.placeholder)) + } + + if (this.options.focus !== false) { + extensions.push(FocusClasses.configure(this.options?.focus)) + } + + return extensions + } +}) + +export default StarterKit diff --git a/web/components/la-editor/extensions/task-item/components/task-item-view.tsx b/web/components/la-editor/extensions/task-item/components/task-item-view.tsx new file mode 100644 index 00000000..b5deba27 --- /dev/null +++ b/web/components/la-editor/extensions/task-item/components/task-item-view.tsx @@ -0,0 +1,50 @@ +import { NodeViewContent, Editor, NodeViewWrapper } from "@tiptap/react" +import { Icon } from "../../../components/ui/icon" +import { useCallback } from "react" +import { Node as ProseMirrorNode } from "@tiptap/pm/model" +import { Node } from "@tiptap/core" + +interface TaskItemProps { + editor: Editor + node: ProseMirrorNode + updateAttributes: (attrs: Record) => void + extension: Node +} + +export const TaskItemView: React.FC = ({ node, updateAttributes, editor, extension }) => { + const handleChange = useCallback( + (event: React.ChangeEvent) => { + const checked = event.target.checked + + if (!editor.isEditable && !extension.options.onReadOnlyChecked) { + return + } + + if (editor.isEditable) { + updateAttributes({ checked }) + } else if (extension.options.onReadOnlyChecked) { + if (!extension.options.onReadOnlyChecked(node, checked)) { + event.target.checked = !checked + } + } + }, + [editor.isEditable, extension.options, node, updateAttributes] + ) + + return ( + +
+ + + +
+
+ +
+
+ ) +} + +export default TaskItemView diff --git a/web/components/la-editor/extensions/task-item/index.ts b/web/components/la-editor/extensions/task-item/index.ts new file mode 100644 index 00000000..489fdbc2 --- /dev/null +++ b/web/components/la-editor/extensions/task-item/index.ts @@ -0,0 +1 @@ +export * from "./task-item" diff --git a/web/components/la-editor/extensions/task-item/task-item.ts b/web/components/la-editor/extensions/task-item/task-item.ts new file mode 100644 index 00000000..089751b6 --- /dev/null +++ b/web/components/la-editor/extensions/task-item/task-item.ts @@ -0,0 +1,64 @@ +import { ReactNodeViewRenderer } from "@tiptap/react" +import { mergeAttributes } from "@tiptap/core" +import { TaskItemView } from "./components/task-item-view" +import { TaskItem as TiptapTaskItem } from "@tiptap/extension-task-item" + +export const TaskItem = TiptapTaskItem.extend({ + name: "taskItem", + + draggable: true, + + addOptions() { + return { + ...this.parent?.(), + nested: true + } + }, + + addAttributes() { + return { + checked: { + default: false, + keepOnSplit: false, + parseHTML: element => { + const dataChecked = element.getAttribute("data-checked") + return dataChecked === "" || dataChecked === "true" + }, + renderHTML: attributes => ({ + "data-checked": attributes.checked + }) + } + } + }, + + renderHTML({ node, HTMLAttributes }) { + return [ + "li", + mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { + "data-type": this.name + }), + [ + "div", + { class: "taskItem-checkbox-container" }, + [ + "label", + [ + "input", + { + type: "checkbox", + checked: node.attrs.checked ? "checked" : null, + class: "taskItem-checkbox" + } + ] + ] + ], + ["div", { class: "taskItem-content" }, 0] + ] + }, + + addNodeView() { + return ReactNodeViewRenderer(TaskItemView, { + as: "span" + }) + } +}) diff --git a/web/components/la-editor/extensions/task-list/index.ts b/web/components/la-editor/extensions/task-list/index.ts new file mode 100644 index 00000000..5fb119d2 --- /dev/null +++ b/web/components/la-editor/extensions/task-list/index.ts @@ -0,0 +1 @@ +export * from "./task-list" diff --git a/web/components/la-editor/extensions/task-list/task-list.ts b/web/components/la-editor/extensions/task-list/task-list.ts new file mode 100644 index 00000000..423d74e6 --- /dev/null +++ b/web/components/la-editor/extensions/task-list/task-list.ts @@ -0,0 +1,12 @@ +import { TaskList as TiptapTaskList } from "@tiptap/extension-task-list" + +export const TaskList = TiptapTaskList.extend({ + addOptions() { + return { + ...this.parent?.(), + HTMLAttributes: { + class: "list-node" + } + } + } +}) diff --git a/web/components/la-editor/hooks/use-text-menu-commands.ts b/web/components/la-editor/hooks/use-text-menu-commands.ts new file mode 100644 index 00000000..21bddce9 --- /dev/null +++ b/web/components/la-editor/hooks/use-text-menu-commands.ts @@ -0,0 +1,30 @@ +import { Editor } from "@tiptap/react" +import { useCallback } from "react" + +export const useTextmenuCommands = (editor: Editor) => { + const onBold = useCallback(() => editor.chain().focus().toggleBold().run(), [editor]) + const onItalic = useCallback(() => editor.chain().focus().toggleItalic().run(), [editor]) + const onStrike = useCallback(() => editor.chain().focus().toggleStrike().run(), [editor]) + const onCode = useCallback(() => editor.chain().focus().toggleCode().run(), [editor]) + const onCodeBlock = useCallback(() => editor.chain().focus().toggleCodeBlock().run(), [editor]) + const onQuote = useCallback(() => editor.chain().focus().toggleBlockquote().run(), [editor]) + const onLink = useCallback( + (url: string, inNewTab?: boolean) => + editor + .chain() + .focus() + .setLink({ href: url, target: inNewTab ? "_blank" : "" }) + .run(), + [editor] + ) + + return { + onBold, + onItalic, + onStrike, + onCode, + onCodeBlock, + onQuote, + onLink + } +} diff --git a/web/components/la-editor/hooks/use-text-menu-states.ts b/web/components/la-editor/hooks/use-text-menu-states.ts new file mode 100644 index 00000000..69454d12 --- /dev/null +++ b/web/components/la-editor/hooks/use-text-menu-states.ts @@ -0,0 +1,34 @@ +import { Editor } from "@tiptap/react" +import { useCallback } from "react" +import { ShouldShowProps } from "../types" +import { isCustomNodeSelected, isTextSelected } from "../lib/utils" + +export const useTextmenuStates = (editor: Editor) => { + const shouldShow = useCallback( + ({ view, from }: ShouldShowProps) => { + if (!view) { + return false + } + + const domAtPos = view.domAtPos(from || 0).node as HTMLElement + const nodeDOM = view.nodeDOM(from || 0) as HTMLElement + const node = nodeDOM || domAtPos + + if (isCustomNodeSelected(editor, node)) { + return false + } + + return isTextSelected({ editor }) + }, + [editor] + ) + + return { + isBold: editor.isActive("bold"), + isItalic: editor.isActive("italic"), + isStrike: editor.isActive("strike"), + isUnderline: editor.isActive("underline"), + isCode: editor.isActive("code"), + shouldShow + } +} diff --git a/web/components/la-editor/index.ts b/web/components/la-editor/index.ts new file mode 100644 index 00000000..3da90ddd --- /dev/null +++ b/web/components/la-editor/index.ts @@ -0,0 +1 @@ +export * from "./la-editor" diff --git a/web/components/la-editor/la-editor.tsx b/web/components/la-editor/la-editor.tsx new file mode 100644 index 00000000..f55f4162 --- /dev/null +++ b/web/components/la-editor/la-editor.tsx @@ -0,0 +1,146 @@ +"use client" + +import * as React from "react" +import { EditorContent, useEditor } from "@tiptap/react" +import { Editor, Content } from "@tiptap/core" +import { useThrottleFn } from "react-use" +import { BubbleMenu } from "./components/bubble-menu" +import { createExtensions } from "./extensions" +import "./styles/index.css" +import { cn } from "@/lib/utils" +import { getOutput } from "./lib/utils" + +export interface LAEditorProps extends Omit, "value"> { + initialContent?: any + output?: "html" | "json" | "text" + placeholder?: string + editorClassName?: string + onUpdate?: (content: Content) => void + onBlur?: (content: Content) => void + onNewBlock?: (content: Content) => void + value?: Content + throttleDelay?: number +} + +export interface LAEditorRef { + focus: () => void +} + +interface CustomEditor extends Editor { + previousBlockCount?: number +} + +export const LAEditor = React.forwardRef( + ( + { + initialContent, + value, + placeholder, + output = "html", + editorClassName, + className, + onUpdate, + onBlur, + onNewBlock, + throttleDelay = 1000, + ...props + }, + ref + ) => { + const [content, setContent] = React.useState(value) + const throttledContent = useThrottleFn(defaultContent => defaultContent, throttleDelay, [content]) + const [lastThrottledContent, setLastThrottledContent] = React.useState(throttledContent) + + const handleUpdate = React.useCallback( + (editor: Editor) => { + const newContent = getOutput(editor, output) + setContent(newContent) + + const customEditor = editor as CustomEditor + const json = customEditor.getJSON() + + if (json.content && Array.isArray(json.content)) { + const currentBlockCount = json.content.length + + if ( + typeof customEditor.previousBlockCount === "number" && + currentBlockCount > customEditor.previousBlockCount + ) { + requestAnimationFrame(() => { + onNewBlock?.(newContent) + }) + } + + customEditor.previousBlockCount = currentBlockCount + } + }, + [output, onNewBlock] + ) + + const editor = useEditor({ + autofocus: false, + extensions: createExtensions({ placeholder }), + editorProps: { + attributes: { + autocomplete: "off", + autocorrect: "off", + autocapitalize: "off", + class: editorClassName || "" + } + }, + onCreate: ({ editor }) => { + if (editor.isEmpty && value) { + editor.commands.setContent(value) + } + }, + onUpdate: ({ editor }) => handleUpdate(editor), + onBlur: ({ editor }) => { + requestAnimationFrame(() => { + onBlur?.(getOutput(editor, output)) + }) + } + }) + + React.useEffect(() => { + if (editor && initialContent) { + // https://github.com/ueberdosis/tiptap/issues/3764 + setTimeout(() => { + editor.commands.setContent(initialContent) + }) + } + }, [editor, initialContent]) + + React.useEffect(() => { + if (lastThrottledContent !== throttledContent) { + setLastThrottledContent(throttledContent) + + requestAnimationFrame(() => { + onUpdate?.(throttledContent!) + }) + } + }, [throttledContent, lastThrottledContent, onUpdate]) + + React.useImperativeHandle( + ref, + () => ({ + focus: () => editor?.commands.focus() + }), + [editor] + ) + + if (!editor) { + return null + } + + return ( +
+ + +
+ ) + } +) + +LAEditor.displayName = "LAEditor" + +export default LAEditor diff --git a/web/components/la-editor/lib/utils/index.ts b/web/components/la-editor/lib/utils/index.ts new file mode 100644 index 00000000..885f4fa4 --- /dev/null +++ b/web/components/la-editor/lib/utils/index.ts @@ -0,0 +1,14 @@ +import { Editor } from "@tiptap/core" +import { LAEditorProps } from "../../la-editor" + +export function getOutput(editor: Editor, output: LAEditorProps["output"]) { + if (output === "html") return editor.getHTML() + if (output === "json") return editor.getJSON() + if (output === "text") return editor.getText() + return "" +} + +export * from "./keyboard" +export * from "./platform" +export * from "./isCustomNodeSelected" +export * from "./isTextSelected" diff --git a/web/components/la-editor/lib/utils/isCustomNodeSelected.ts b/web/components/la-editor/lib/utils/isCustomNodeSelected.ts new file mode 100644 index 00000000..7209049f --- /dev/null +++ b/web/components/la-editor/lib/utils/isCustomNodeSelected.ts @@ -0,0 +1,28 @@ +import { Editor } from "@tiptap/react" +import { Link } from "@/components/la-editor/extensions/link" +import { HorizontalRule } from "@/components/la-editor/extensions/horizontal-rule" + +export const isTableGripSelected = (node: HTMLElement) => { + let container = node + + while (container && !["TD", "TH"].includes(container.tagName)) { + container = container.parentElement! + } + + const gripColumn = container && container.querySelector && container.querySelector("a.grip-column.selected") + const gripRow = container && container.querySelector && container.querySelector("a.grip-row.selected") + + if (gripColumn || gripRow) { + return true + } + + return false +} + +export const isCustomNodeSelected = (editor: Editor, node: HTMLElement) => { + const customNodes = [HorizontalRule.name, Link.name] + + return customNodes.some(type => editor.isActive(type)) || isTableGripSelected(node) +} + +export default isCustomNodeSelected diff --git a/web/components/la-editor/lib/utils/isTextSelected.ts b/web/components/la-editor/lib/utils/isTextSelected.ts new file mode 100644 index 00000000..d4b17955 --- /dev/null +++ b/web/components/la-editor/lib/utils/isTextSelected.ts @@ -0,0 +1,25 @@ +import { isTextSelection } from "@tiptap/core" +import { Editor } from "@tiptap/react" + +export const isTextSelected = ({ editor }: { editor: Editor }) => { + const { + state: { + doc, + selection, + selection: { empty, from, to } + } + } = editor + + // Sometime check for `empty` is not enough. + // Doubleclick an empty paragraph returns a node size of 2. + // So we check also for an empty text size. + const isEmptyTextBlock = !doc.textBetween(from, to).length && isTextSelection(selection) + + if (empty || isEmptyTextBlock || !editor.isEditable) { + return false + } + + return true +} + +export default isTextSelected diff --git a/web/components/la-editor/lib/utils/keyboard.ts b/web/components/la-editor/lib/utils/keyboard.ts new file mode 100644 index 00000000..09739fc2 --- /dev/null +++ b/web/components/la-editor/lib/utils/keyboard.ts @@ -0,0 +1,25 @@ +import { isMacOS } from "./platform" + +export const getShortcutKey = (key: string) => { + const lowercaseKey = key.toLowerCase() + const macOS = isMacOS() + + switch (lowercaseKey) { + case "mod": + return macOS ? "⌘" : "Ctrl" + case "alt": + return macOS ? "⌥" : "Alt" + case "shift": + return macOS ? "⇧" : "Shift" + default: + return key + } +} + +export const getShortcutKeys = (keys: string | string[], separator: string = "") => { + const keyArray = Array.isArray(keys) ? keys : keys.split(/\s+/) + const shortcutKeys = keyArray.map(getShortcutKey) + return shortcutKeys.join(separator) +} + +export default { getShortcutKey, getShortcutKeys } diff --git a/web/components/la-editor/lib/utils/platform.ts b/web/components/la-editor/lib/utils/platform.ts new file mode 100644 index 00000000..2dffa98a --- /dev/null +++ b/web/components/la-editor/lib/utils/platform.ts @@ -0,0 +1,46 @@ +export interface NavigatorWithUserAgentData extends Navigator { + userAgentData?: { + brands: { brand: string; version: string }[] + mobile: boolean + platform: string + getHighEntropyValues: (hints: string[]) => Promise<{ + platform: string + platformVersion: string + uaFullVersion: string + }> + } +} + +let isMac: boolean | undefined + +const getPlatform = () => { + const nav = navigator as NavigatorWithUserAgentData + if (nav.userAgentData) { + if (nav.userAgentData.platform) { + return nav.userAgentData.platform + } + + nav.userAgentData + .getHighEntropyValues(["platform"]) + .then(highEntropyValues => { + if (highEntropyValues.platform) { + return highEntropyValues.platform + } + }) + .catch(() => { + return navigator.platform || "" + }) + } + + return navigator.platform || "" +} + +export const isMacOS = () => { + if (isMac === undefined) { + isMac = getPlatform().toLowerCase().includes("mac") + } + + return isMac +} + +export default isMacOS diff --git a/web/components/la-editor/styles/index.css b/web/components/la-editor/styles/index.css new file mode 100644 index 00000000..451af6bd --- /dev/null +++ b/web/components/la-editor/styles/index.css @@ -0,0 +1,140 @@ +:root { + --la-font-size-regular: 0.9375rem; + + --la-code-background: rgba(8, 43, 120, 0.047); + --la-code-color: rgb(212, 212, 212); + --la-secondary: rgb(157, 157, 159); + --la-pre-background: rgb(236, 236, 236); + --la-pre-border: rgb(224, 224, 224); + --la-pre-color: rgb(47, 47, 49); + --la-hr: rgb(220, 220, 220); + --la-drag-handle-hover: rgb(92, 92, 94); + + --hljs-string: rgb(170, 67, 15); + --hljs-title: rgb(176, 136, 54); + --hljs-comment: rgb(153, 153, 153); + --hljs-keyword: rgb(12, 94, 177); + --hljs-attr: rgb(58, 146, 188); + --hljs-literal: rgb(200, 43, 15); + --hljs-name: rgb(37, 151, 146); + --hljs-selector-tag: rgb(200, 80, 15); + --hljs-number: rgb(61, 160, 103); +} + +.dark .ProseMirror { + --la-code-background: rgba(255, 255, 255, 0.075); + --la-code-color: rgb(44, 46, 51); + --la-secondary: rgb(89, 90, 92); + --la-pre-background: rgb(8, 8, 8); + --la-pre-border: rgb(35, 37, 42); + --la-pre-color: rgb(227, 228, 230); + --la-hr: rgb(38, 40, 45); + --la-drag-handle-hover: rgb(150, 151, 153); + + --hljs-string: rgb(218, 147, 107); + --hljs-title: rgb(241, 213, 157); + --hljs-comment: rgb(170, 170, 170); + --hljs-keyword: rgb(102, 153, 204); + --hljs-attr: rgb(144, 202, 232); + --hljs-literal: rgb(242, 119, 122); + --hljs-name: rgb(95, 192, 160); + --hljs-selector-tag: rgb(232, 199, 133); + --hljs-number: rgb(182, 231, 182); +} + +.la-editor .ProseMirror { + @apply flex max-w-full flex-1 cursor-text flex-col; + @apply z-0 outline-0; +} + +.la-editor .ProseMirror > div.editor { + @apply block flex-1 whitespace-pre-wrap; +} + +.la-editor .ProseMirror .block-node:not(:last-child), +.la-editor .ProseMirror .list-node:not(:last-child), +.la-editor .ProseMirror .text-node:not(:last-child) { + @apply mb-2.5; +} + +.la-editor .ProseMirror ol, +.la-editor .ProseMirror ul { + @apply pl-6; +} + +.la-editor .ProseMirror blockquote, +.la-editor .ProseMirror dl, +.la-editor .ProseMirror ol, +.la-editor .ProseMirror p, +.la-editor .ProseMirror pre, +.la-editor .ProseMirror ul { + @apply m-0; +} + +.la-editor .ProseMirror li { + @apply leading-7; +} + +.la-editor .ProseMirror p { + @apply break-words; +} + +.la-editor .ProseMirror li .text-node:has(+ .list-node), +.la-editor .ProseMirror li > .list-node, +.la-editor .ProseMirror li > .text-node, +.la-editor .ProseMirror li p { + @apply mb-0; +} + +.la-editor .ProseMirror blockquote { + @apply relative pl-3.5; +} + +.la-editor .ProseMirror blockquote::before, +.la-editor .ProseMirror blockquote.is-empty::before { + @apply bg-accent absolute bottom-0 left-0 top-0 h-full w-1 rounded-sm content-['']; +} + +.la-editor .ProseMirror hr { + @apply my-3 h-0.5 w-full border-none bg-[var(--la-hr)]; +} + +.la-editor .ProseMirror-focused hr.ProseMirror-selectednode { + @apply outline-muted-foreground rounded-full outline outline-2 outline-offset-1; +} + +.la-editor .ProseMirror .ProseMirror-gapcursor { + @apply pointer-events-none absolute hidden; +} + +.la-editor .ProseMirror .ProseMirror-hideselection { + @apply caret-transparent; +} + +.la-editor .ProseMirror.resize-cursor { + @apply cursor-col-resize; +} + +.la-editor .ProseMirror .selection { + @apply inline-block; +} + +.la-editor .ProseMirror .selection, +.la-editor .ProseMirror *::selection, +::selection { + @apply bg-primary/40; +} + +/* Override native selection when custom selection is present */ +.la-editor .ProseMirror .selection::selection { + background: transparent; +} + +[data-theme="slash-command"] { + width: 1000vw; +} + +@import "./partials/code.css"; +@import "./partials/placeholder.css"; +@import "./partials/lists.css"; +@import "./partials/typography.css"; diff --git a/web/components/la-editor/styles/partials/code.css b/web/components/la-editor/styles/partials/code.css new file mode 100644 index 00000000..62d349c7 --- /dev/null +++ b/web/components/la-editor/styles/partials/code.css @@ -0,0 +1,86 @@ +.la-editor .ProseMirror code.inline { + @apply rounded border border-[var(--la-code-color)] bg-[var(--la-code-background)] px-1 py-0.5 text-sm; +} + +.la-editor .ProseMirror pre { + @apply relative overflow-auto rounded border font-mono text-sm; + @apply border-[var(--la-pre-border)] bg-[var(--la-pre-background)] text-[var(--la-pre-color)]; + @apply hyphens-none whitespace-pre text-left; +} + +.la-editor .ProseMirror code { + @apply break-words leading-[1.7em]; +} + +.la-editor .ProseMirror pre code { + @apply block overflow-x-auto p-3.5; +} + +.la-editor .ProseMirror pre { + .hljs-keyword, + .hljs-operator, + .hljs-function, + .hljs-built_in, + .hljs-builtin-name { + color: var(--hljs-keyword); + } + + .hljs-attr, + .hljs-symbol, + .hljs-property, + .hljs-attribute, + .hljs-variable, + .hljs-template-variable, + .hljs-params { + color: var(--hljs-attr); + } + + .hljs-name, + .hljs-regexp, + .hljs-link, + .hljs-type, + .hljs-addition { + color: var(--hljs-name); + } + + .hljs-string, + .hljs-bullet { + color: var(--hljs-string); + } + + .hljs-title, + .hljs-subst, + .hljs-section { + color: var(--hljs-title); + } + + .hljs-literal, + .hljs-type, + .hljs-deletion { + color: var(--hljs-literal); + } + + .hljs-selector-tag, + .hljs-selector-id, + .hljs-selector-class { + color: var(--hljs-selector-tag); + } + + .hljs-number { + color: var(--hljs-number); + } + + .hljs-comment, + .hljs-meta, + .hljs-quote { + color: var(--hljs-comment); + } + + .hljs-emphasis { + @apply italic; + } + + .hljs-strong { + @apply font-bold; + } +} diff --git a/web/components/la-editor/styles/partials/lists.css b/web/components/la-editor/styles/partials/lists.css new file mode 100644 index 00000000..5daa99bf --- /dev/null +++ b/web/components/la-editor/styles/partials/lists.css @@ -0,0 +1,82 @@ +.la-editor div.tiptap p { + @apply text-[var(--la-font-size-regular)]; +} + +.la-editor .ProseMirror ol { + @apply list-decimal; +} + +.la-editor .ProseMirror ol ol { + list-style: lower-alpha; +} + +.la-editor .ProseMirror ol ol ol { + list-style: lower-roman; +} + +.la-editor .ProseMirror ul { + list-style: disc; +} + +.la-editor .ProseMirror ul ul { + list-style: circle; +} + +.la-editor .ProseMirror ul ul ul { + list-style: square; +} + +.la-editor .ProseMirror ul[data-type="taskList"] { + @apply list-none pl-1; +} + +.la-editor .ProseMirror ul[data-type="taskList"] p { + @apply m-0; +} + +.la-editor .ProseMirror ul[data-type="taskList"] li > label { + @apply mr-2 mt-0.5 flex-none select-none; +} + +.la-editor .ProseMirror li[data-type="taskItem"] { + @apply flex flex-row items-start; +} + +.la-editor .ProseMirror li[data-type="taskItem"] .taskItem-checkbox-container { + @apply relative pr-2; +} + +.la-editor .ProseMirror .taskItem-drag-handle { + @apply absolute -left-5 top-1.5 h-[18px] w-[18px] cursor-move pl-0.5 text-[var(--la-secondary)] opacity-0; +} + +.la-editor + .ProseMirror + li[data-type="taskItem"]:hover:not(:has(li:hover)) + > .taskItem-checkbox-container + > .taskItem-drag-handle { + @apply opacity-100; +} + +.la-editor .ProseMirror .taskItem-drag-handle:hover { + @apply text-[var(--la-drag-handle-hover)]; +} + +.la-editor .ProseMirror .taskItem-checkbox { + fill-opacity: 0; + @apply h-3.5 w-3.5 flex-shrink-0 cursor-pointer select-none appearance-none rounded border border-solid border-[var(--la-secondary)] bg-transparent bg-[1px_2px] p-0.5 align-middle transition-colors duration-75 ease-out; +} + +.la-editor .ProseMirror .taskItem-checkbox:checked { + @apply border-primary bg-primary bg-no-repeat; + background-image: url("data:image/svg+xml;utf8,%3Csvg%20width=%2210%22%20height=%229%22%20viewBox=%220%200%2010%208%22%20xmlns=%22http://www.w3.org/2000/svg%22%20fill=%22%23fbfbfb%22%3E%3Cpath%20d=%22M3.46975%205.70757L1.88358%204.1225C1.65832%203.8974%201.29423%203.8974%201.06897%204.1225C0.843675%204.34765%200.843675%204.7116%201.06897%204.93674L3.0648%206.93117C3.29006%207.15628%203.65414%207.15628%203.8794%206.93117L8.93103%201.88306C9.15633%201.65792%209.15633%201.29397%208.93103%201.06883C8.70578%200.843736%208.34172%200.843724%208.11646%201.06879C8.11645%201.0688%208.11643%201.06882%208.11642%201.06883L3.46975%205.70757Z%22%20stroke-width=%220.2%22%20/%3E%3C/svg%3E"); +} + +.la-editor .ProseMirror .taskItem-content { + @apply min-w-0 flex-1; +} + +.la-editor .ProseMirror li[data-checked="true"] .taskItem-content > :not([data-type="taskList"]), +.la-editor .ProseMirror li[data-checked="true"] .taskItem-content .taskItem-checkbox { + @apply opacity-75; +} diff --git a/web/components/la-editor/styles/partials/placeholder.css b/web/components/la-editor/styles/partials/placeholder.css new file mode 100644 index 00000000..30fb78b8 --- /dev/null +++ b/web/components/la-editor/styles/partials/placeholder.css @@ -0,0 +1,12 @@ +.la-editor .ProseMirror .is-empty::before { + @apply pointer-events-none float-left h-0 w-full text-[var(--la-secondary)]; +} + +.la-editor .ProseMirror.ProseMirror-focused > p.has-focus.is-empty::before { + content: "Type / for commands..."; +} + +.la-editor .ProseMirror > p.is-editor-empty::before { + content: attr(data-placeholder); + @apply pointer-events-none float-left h-0 text-[var(--la-secondary)]; +} diff --git a/web/components/la-editor/styles/partials/typography.css b/web/components/la-editor/styles/partials/typography.css new file mode 100644 index 00000000..2e9cffd9 --- /dev/null +++ b/web/components/la-editor/styles/partials/typography.css @@ -0,0 +1,27 @@ +.la-editor .ProseMirror .heading-node { + @apply relative font-semibold; +} + +.la-editor .ProseMirror .heading-node:first-child { + @apply mt-0; +} + +.la-editor .ProseMirror h1 { + @apply mb-4 mt-[46px] text-[1.375rem] leading-7 tracking-[-0.004375rem]; +} + +.la-editor .ProseMirror h2 { + @apply mb-3.5 mt-8 text-[1.1875rem] leading-7 tracking-[0.003125rem]; +} + +.la-editor .ProseMirror h3 { + @apply mb-3 mt-6 text-[1.0625rem] leading-6 tracking-[0.00625rem]; +} + +.la-editor .ProseMirror a.link { + @apply text-primary cursor-pointer; +} + +.la-editor .ProseMirror a.link:hover { + @apply underline; +} diff --git a/web/components/la-editor/types.ts b/web/components/la-editor/types.ts new file mode 100644 index 00000000..adcf7646 --- /dev/null +++ b/web/components/la-editor/types.ts @@ -0,0 +1,20 @@ +import React from "react" +import { Editor as CoreEditor } from "@tiptap/core" +import { Editor } from "@tiptap/react" +import { EditorState } from "@tiptap/pm/state" +import { EditorView } from "@tiptap/pm/view" + +export interface MenuProps { + editor: Editor + appendTo?: React.RefObject + shouldHide?: boolean +} + +export interface ShouldShowProps { + editor?: CoreEditor + view: EditorView + state?: EditorState + oldState?: EditorState + from?: number + to?: number +} diff --git a/web/components/routes/globalTopic/globalTopic.tsx b/web/components/routes/globalTopic/globalTopic.tsx new file mode 100644 index 00000000..f0a021c4 --- /dev/null +++ b/web/components/routes/globalTopic/globalTopic.tsx @@ -0,0 +1,158 @@ +"use client" +import React, { useState } from "react" +import { ContentHeader } from "@/components/custom/content-header" +import { PiLinkSimple } from "react-icons/pi" +import { Bookmark, GraduationCap, Check } from "lucide-react" + +interface LinkProps { + title: string + url: string +} + +const links = [ + { title: "JavaScript", url: "https://justjavascript.com" }, + { title: "TypeScript", url: "https://www.typescriptlang.org/" }, + { title: "React", url: "https://reactjs.org/" } +] + +const LinkItem: React.FC = ({ title, url }) => ( +
+) +interface ButtonProps { + children: React.ReactNode + onClick: () => void + className?: string + color?: string + icon?: React.ReactNode + fullWidth?: boolean +} + +const Button: React.FC = ({ children, onClick, className = "", color = "", icon, fullWidth = false }) => { + return ( + + ) +} + +export default function GlobalTopic({ topic }: { topic: string }) { + const [showOptions, setShowOptions] = useState(false) + const [selectedOption, setSelectedOption] = useState(null) + const [activeTab, setActiveTab] = useState("Guide") + + const decodedTopic = decodeURIComponent(topic) + + const learningOptions = [ + { text: "To Learn", icon: }, + { text: "Learning", icon: }, + { text: "Learned", icon: } + ] + + const learningStatusColor = (option: string) => { + switch (option) { + case "To Learn": + return "text-white/70" + case "Learning": + return "text-[#D29752]" + case "Learned": + return "text-[#708F51]" + default: + return "text-white/70" + } + } + + const selectedStatus = (option: string) => { + setSelectedOption(option) + setShowOptions(false) + } + + return ( +
+ +
+

{decodedTopic}

+
+
+ + +
+
+
+ +
+ + {showOptions && ( +
+ {learningOptions.map(option => ( + + ))} +
+ )} +
+
+
+

Intro

+ {links.map((link, index) => ( + + ))} +
+
+

Other

+ {links.map((link, index) => ( + + ))} +
+
+
+ ) +} diff --git a/web/components/routes/link/form/manage.tsx b/web/components/routes/link/form/manage.tsx new file mode 100644 index 00000000..1dc01fb9 --- /dev/null +++ b/web/components/routes/link/form/manage.tsx @@ -0,0 +1,438 @@ +"use client" + +import React, { useState, useEffect, useRef } from "react" +import { useForm } from "react-hook-form" +import { zodResolver } from "@hookform/resolvers/zod" +import { useDebounce } from "react-use" +import { toast } from "sonner" +import Image from "next/image" +import { z } from "zod" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Textarea } from "@/components/ui/textarea" +import { Form, FormField, FormItem, FormLabel, FormControl } from "@/components/ui/form" +import { BoxIcon, PlusIcon, Trash2Icon, PieChartIcon, Bookmark, GraduationCap, Check } from "lucide-react" +import { cn, ensureUrlProtocol, generateUniqueSlug, isUrl as LibIsUrl } from "@/lib/utils" +import { useAccount, useCoState } from "@/lib/providers/jazz-provider" +import { LinkMetadata, PersonalLink } from "@/lib/schema/personal-link" +import { createLinkSchema } from "./schema" +import { TopicSelector } from "./partial/topic-section" +import { useAtom } from "jotai" +import { linkEditIdAtom, linkShowCreateAtom } from "@/store/link" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuTrigger +} from "@/components/ui/dropdown-menu" +import { useKey } from "react-use" + +export type LinkFormValues = z.infer + +const DEFAULT_FORM_VALUES: Partial = { + title: "", + description: "", + topic: "", + isLink: false, + meta: null +} + +const LinkManage: React.FC = () => { + const [showCreate, setShowCreate] = useAtom(linkShowCreateAtom) + const [, setEditId] = useAtom(linkEditIdAtom) + const formRef = useRef(null) + const buttonRef = useRef(null) + + const toggleForm = (event: React.MouseEvent) => { + event.stopPropagation() + setShowCreate(prev => !prev) + } + + useEffect(() => { + if (!showCreate) { + formRef.current?.reset() + setEditId(null) + } + }, [showCreate, setEditId]) + + useEffect(() => { + const handleOutsideClick = (event: MouseEvent) => { + if ( + formRef.current && + !formRef.current.contains(event.target as Node) && + buttonRef.current && + !buttonRef.current.contains(event.target as Node) + ) { + setShowCreate(false) + } + } + + if (showCreate) { + document.addEventListener("mousedown", handleOutsideClick) + } + + return () => { + document.removeEventListener("mousedown", handleOutsideClick) + } + }, [showCreate, setShowCreate]) + + useKey("Escape", () => { + setShowCreate(false) + }) + + return ( + <> + {showCreate && ( +
+ setShowCreate(false)} onCancel={() => setShowCreate(false)} /> +
+ )} + + + ) +} + +const CreateButton = React.forwardRef< + HTMLButtonElement, + { + onClick: (event: React.MouseEvent) => void + isOpen: boolean + } +>(({ onClick, isOpen }, ref) => ( + +)) + +CreateButton.displayName = "CreateButton" + +interface LinkFormProps extends React.ComponentPropsWithoutRef<"form"> { + onSuccess?: () => void + onCancel?: () => void + personalLink?: PersonalLink +} + +const LinkForm = React.forwardRef(({ onSuccess, onCancel, personalLink }, ref) => { + const selectedLink = useCoState(PersonalLink, personalLink?.id) + const [isFetching, setIsFetching] = useState(false) + const { me } = useAccount() + const form = useForm({ + resolver: zodResolver(createLinkSchema), + defaultValues: DEFAULT_FORM_VALUES + }) + + const title = form.watch("title") + const [originalLink, setOriginalLink] = useState("") + const [linkEntered, setLinkEntered] = useState(false) + const [debouncedText, setDebouncedText] = useState("") + useDebounce(() => setDebouncedText(title), 300, [title]) + + const [showStatusOptions, setShowStatusOptions] = useState(false) + const [selectedStatus, setSelectedStatus] = useState(null) + + const statusOptions = [ + { + text: "To Learn", + icon: , + color: "text-white/70" + }, + { + text: "Learning", + icon: , + color: "text-[#D29752]" + }, + { text: "Learned", icon: , color: "text-[#708F51]" } + ] + + const statusSelect = (status: string) => { + setSelectedStatus(status === selectedStatus ? null : status) + setShowStatusOptions(false) + } + + useEffect(() => { + if (selectedLink) { + form.setValue("title", selectedLink.title) + form.setValue("description", selectedLink.description ?? "") + form.setValue("isLink", selectedLink.isLink) + form.setValue("meta", selectedLink.meta) + } + }, [selectedLink, form]) + + useEffect(() => { + const fetchMetadata = async (url: string) => { + setIsFetching(true) + try { + const res = await fetch(`/api/metadata?url=${encodeURIComponent(url)}`, { cache: "no-store" }) + if (!res.ok) throw new Error("Failed to fetch metadata") + const data = await res.json() + form.setValue("isLink", true) + form.setValue("meta", data) + form.setValue("title", data.title) + form.setValue("description", data.description) + setOriginalLink(url) + } catch (err) { + form.setValue("isLink", false) + form.setValue("meta", null) + form.setValue("title", debouncedText) + form.setValue("description", "") + setOriginalLink("") + } finally { + setIsFetching(false) + } + } + + const lowerText = debouncedText.toLowerCase() + if (linkEntered && LibIsUrl(lowerText)) { + fetchMetadata(ensureUrlProtocol(lowerText)) + } + }, [debouncedText, form, linkEntered]) + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter" && LibIsUrl(e.currentTarget.value.toLowerCase())) { + e.preventDefault() + setLinkEntered(true) + } + } + + const onSubmit = (values: LinkFormValues) => { + if (isFetching) return + + try { + let linkMetadata: LinkMetadata | undefined + + const personalLinks = me.root?.personalLinks?.toJSON() || [] + const slug = generateUniqueSlug(personalLinks, values.title) + + if (values.isLink && values.meta) { + linkMetadata = LinkMetadata.create(values.meta, { owner: me._owner }) + } + + if (selectedLink) { + selectedLink.title = values.title + selectedLink.slug = slug + selectedLink.description = values.description ?? "" + selectedLink.isLink = values.isLink + + if (selectedLink.meta) { + Object.assign(selectedLink.meta, values.meta) + } + + // toast.success("Todo updated") + } else { + const newPersonalLink = PersonalLink.create( + { + title: values.title, + slug, + description: values.description, + sequence: me.root?.personalLinks?.length || 1, + completed: false, + isLink: values.isLink, + meta: linkMetadata + // topic: values.topic + }, + { owner: me._owner } + ) + + me.root?.personalLinks?.push(newPersonalLink) + } + + form.reset(DEFAULT_FORM_VALUES) + onSuccess?.() + } catch (error) { + console.error("Failed to create/update link", error) + toast.error(personalLink ? "Failed to update link" : "Failed to create link") + } + } + + const handleCancel: () => void = () => { + form.reset(DEFAULT_FORM_VALUES) + onCancel?.() + } + + return ( +
+
+
+ +
+
+
+
+ + + ( + + Text + + + + + )} + /> + + {linkEntered + ? originalLink + : LibIsUrl(form.watch("title").toLowerCase()) + ? 'Press "Enter" to confirm URL' + : ""} + +
+ +
+ + + {/* */} + + + Actions + + + Delete + + + +
+ + {showStatusOptions && ( +
+ {statusOptions.map(option => ( + + ))} +
+ )} +
+
+
+ +
+ ( + + Description + +