switch to monorepo structure
This commit is contained in:
111
packages/web/src/app/-route-tree.gen.ts
Normal file
111
packages/web/src/app/-route-tree.gen.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
/* eslint-disable */
|
||||
|
||||
// @ts-nocheck
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
|
||||
// This file was automatically generated by TanStack Router.
|
||||
// You should NOT make any changes in this file as it will be overwritten.
|
||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||
|
||||
// Import Routes
|
||||
|
||||
import { Route as rootRoute } from "./__root"
|
||||
import { Route as BookmarksImport } from "./bookmarks"
|
||||
import { Route as IndexImport } from "./index"
|
||||
|
||||
// Create/Update Routes
|
||||
|
||||
const BookmarksRoute = BookmarksImport.update({
|
||||
id: "/bookmarks",
|
||||
path: "/bookmarks",
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
const IndexRoute = IndexImport.update({
|
||||
id: "/",
|
||||
path: "/",
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
// Populate the FileRoutesByPath interface
|
||||
|
||||
declare module "@tanstack/react-router" {
|
||||
interface FileRoutesByPath {
|
||||
"/": {
|
||||
id: "/"
|
||||
path: "/"
|
||||
fullPath: "/"
|
||||
preLoaderRoute: typeof IndexImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
"/bookmarks": {
|
||||
id: "/bookmarks"
|
||||
path: "/bookmarks"
|
||||
fullPath: "/bookmarks"
|
||||
preLoaderRoute: typeof BookmarksImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create and export the route tree
|
||||
|
||||
export interface FileRoutesByFullPath {
|
||||
"/": typeof IndexRoute
|
||||
"/bookmarks": typeof BookmarksRoute
|
||||
}
|
||||
|
||||
export interface FileRoutesByTo {
|
||||
"/": typeof IndexRoute
|
||||
"/bookmarks": typeof BookmarksRoute
|
||||
}
|
||||
|
||||
export interface FileRoutesById {
|
||||
__root__: typeof rootRoute
|
||||
"/": typeof IndexRoute
|
||||
"/bookmarks": typeof BookmarksRoute
|
||||
}
|
||||
|
||||
export interface FileRouteTypes {
|
||||
fileRoutesByFullPath: FileRoutesByFullPath
|
||||
fullPaths: "/" | "/bookmarks"
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
to: "/" | "/bookmarks"
|
||||
id: "__root__" | "/" | "/bookmarks"
|
||||
fileRoutesById: FileRoutesById
|
||||
}
|
||||
|
||||
export interface RootRouteChildren {
|
||||
IndexRoute: typeof IndexRoute
|
||||
BookmarksRoute: typeof BookmarksRoute
|
||||
}
|
||||
|
||||
const rootRouteChildren: RootRouteChildren = {
|
||||
IndexRoute: IndexRoute,
|
||||
BookmarksRoute: BookmarksRoute,
|
||||
}
|
||||
|
||||
export const routeTree = rootRoute
|
||||
._addFileChildren(rootRouteChildren)
|
||||
._addFileTypes<FileRouteTypes>()
|
||||
|
||||
/* ROUTE_MANIFEST_START
|
||||
{
|
||||
"routes": {
|
||||
"__root__": {
|
||||
"filePath": "__root.tsx",
|
||||
"children": [
|
||||
"/",
|
||||
"/bookmarks"
|
||||
]
|
||||
},
|
||||
"/": {
|
||||
"filePath": "index.tsx"
|
||||
},
|
||||
"/bookmarks": {
|
||||
"filePath": "bookmarks.tsx"
|
||||
}
|
||||
}
|
||||
}
|
||||
ROUTE_MANIFEST_END */
|
15
packages/web/src/app/__root.tsx
Normal file
15
packages/web/src/app/__root.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Outlet, createRootRoute } from "@tanstack/react-router"
|
||||
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"
|
||||
|
||||
function Root() {
|
||||
return (
|
||||
<>
|
||||
<Outlet />
|
||||
<TanStackRouterDevtools />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const Route = createRootRoute({
|
||||
component: Root,
|
||||
})
|
306
packages/web/src/app/bookmarks.tsx
Normal file
306
packages/web/src/app/bookmarks.tsx
Normal file
@@ -0,0 +1,306 @@
|
||||
import type { LinkBookmark } from "@markone/core/bookmark"
|
||||
import { createFileRoute } from "@tanstack/react-router"
|
||||
import clsx from "clsx"
|
||||
import { useEffect } from "react"
|
||||
import { create } from "zustand"
|
||||
import { Button } from "~/components/button"
|
||||
|
||||
const testBookmarks: LinkBookmark[] = [
|
||||
{
|
||||
kind: "link",
|
||||
id: "1",
|
||||
title: "Running a Docker container as a non-root user",
|
||||
url: "https://test.website.com/article/123",
|
||||
},
|
||||
{
|
||||
kind: "link",
|
||||
id: "2",
|
||||
title: "Running a Docker container as a non-root user",
|
||||
url: "https://test.website.com/article/123",
|
||||
},
|
||||
]
|
||||
|
||||
const LAYOUT_MODE = {
|
||||
popup: "popup",
|
||||
sideBySide: "side-by-side",
|
||||
} as const
|
||||
type LayoutMode = (typeof LAYOUT_MODE)[keyof typeof LAYOUT_MODE]
|
||||
|
||||
interface BookmarkPageState {
|
||||
bookmarks: LinkBookmark[]
|
||||
selectedBookmarkIndex: number
|
||||
isBookmarkItemExpanded: boolean
|
||||
isBookmarkPreviewOpened: boolean
|
||||
layoutMode: LayoutMode
|
||||
|
||||
setBookmarkItemExpanded: (isExpanded: boolean) => void
|
||||
setBookmarkPreviewOpened: (isOpened: boolean) => void
|
||||
setLayoutMode: (mode: LayoutMode) => void
|
||||
selectBookmarkAt: (index: number) => void
|
||||
}
|
||||
|
||||
const useBookmarkPageStore = create<BookmarkPageState>()((set, get) => ({
|
||||
bookmarks: testBookmarks,
|
||||
selectedBookmarkIndex: 0,
|
||||
isBookmarkItemExpanded: false,
|
||||
isBookmarkPreviewOpened: false,
|
||||
layoutMode: LAYOUT_MODE.popup,
|
||||
|
||||
setBookmarkItemExpanded(isExpanded: boolean) {
|
||||
set({ isBookmarkItemExpanded: isExpanded })
|
||||
},
|
||||
|
||||
setBookmarkPreviewOpened(isOpened: boolean) {
|
||||
set({ isBookmarkPreviewOpened: isOpened })
|
||||
},
|
||||
|
||||
setLayoutMode(mode: LayoutMode) {
|
||||
set({ layoutMode: mode })
|
||||
},
|
||||
|
||||
selectBookmarkAt(index: number) {
|
||||
const bookmarks = get().bookmarks
|
||||
if (index >= 0 && index < bookmarks.length) {
|
||||
set({ selectedBookmarkIndex: index })
|
||||
}
|
||||
},
|
||||
}))
|
||||
|
||||
function Page() {
|
||||
const setLayoutMode = useBookmarkPageStore((state) => state.setLayoutMode)
|
||||
|
||||
useEffect(() => {
|
||||
function onKeyDown(event: KeyboardEvent) {
|
||||
const state = useBookmarkPageStore.getState()
|
||||
|
||||
switch (event.key) {
|
||||
case "ArrowDown":
|
||||
state.selectBookmarkAt(state.selectedBookmarkIndex + 1)
|
||||
break
|
||||
case "ArrowUp":
|
||||
state.selectBookmarkAt(state.selectedBookmarkIndex - 1)
|
||||
break
|
||||
case "ArrowLeft":
|
||||
state.setBookmarkItemExpanded(false)
|
||||
break
|
||||
case "ArrowRight":
|
||||
state.setBookmarkItemExpanded(true)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("keydown", onKeyDown)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("keydown", onKeyDown)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
function mediaQueryListener(this: MediaQueryList) {
|
||||
if (this.matches) {
|
||||
setLayoutMode(LAYOUT_MODE.sideBySide)
|
||||
} else {
|
||||
setLayoutMode(LAYOUT_MODE.popup)
|
||||
}
|
||||
}
|
||||
|
||||
const q = window.matchMedia("(width >= 64rem)")
|
||||
q.addEventListener("change", mediaQueryListener)
|
||||
|
||||
mediaQueryListener.call(q)
|
||||
|
||||
return () => {
|
||||
q.removeEventListener("change", mediaQueryListener)
|
||||
}
|
||||
}, [setLayoutMode])
|
||||
|
||||
return (
|
||||
<div className="flex justify-center h-full">
|
||||
<Main>
|
||||
<div className="flex flex-col md:flex-row justify-center py-16 lg:py-32 ">
|
||||
<header className="mb-4 md:mb-0 md:mr-16 text-start">
|
||||
<h1 className="font-bold text-start">
|
||||
<span className="invisible md:hidden">> </span>
|
||||
YOUR BOOKMARKS
|
||||
</h1>
|
||||
</header>
|
||||
<div className="flex flex-col container max-w-2xl -mt-2">
|
||||
{testBookmarks.map((bookmark, i) => (
|
||||
<BookmarkListItem key={bookmark.id} index={i} bookmark={bookmark} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<BookmarkPreview />
|
||||
</Main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Main({ children }: React.PropsWithChildren) {
|
||||
const isPreviewOpened = useBookmarkPageStore((state) => state.isBookmarkPreviewOpened)
|
||||
const layoutMode = useBookmarkPageStore((state) => state.layoutMode)
|
||||
|
||||
return (
|
||||
<main
|
||||
className={clsx(
|
||||
"px-4 lg:px-8 2xl:px-0 grid flex justify-center relative w-full",
|
||||
isPreviewOpened && layoutMode === LAYOUT_MODE.sideBySide ? "grid-cols-2" : "grid-cols-1",
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
function BookmarkPreview() {
|
||||
const isVisible = useBookmarkPageStore((state) => state.isBookmarkPreviewOpened)
|
||||
const layoutMode = useBookmarkPageStore((state) => state.layoutMode)
|
||||
|
||||
if (!isVisible) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"h-screen flex justify-center items-center border-l border-stone-700 dark:border-stone-300 flex dark:bg-stone-900",
|
||||
{
|
||||
"absolute inset-0 border-l-0": layoutMode === LAYOUT_MODE.popup,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<p>Content here</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function BookmarkListItem({ bookmark, index }: { bookmark: LinkBookmark; index: number }) {
|
||||
const url = new URL(bookmark.url)
|
||||
const selectedBookmark = useBookmarkPageStore((state) => state.bookmarks[state.selectedBookmarkIndex])
|
||||
const isSelected = selectedBookmark.id === bookmark.id
|
||||
const isBookmarkItemExpanded = useBookmarkPageStore((state) => state.isBookmarkItemExpanded)
|
||||
const setBookmarkItemExpanded = useBookmarkPageStore((state) => state.setBookmarkItemExpanded)
|
||||
const selectBookmarkAt = useBookmarkPageStore((state) => state.selectBookmarkAt)
|
||||
const setBookmarkPreviewOpened = useBookmarkPageStore((state) => state.setBookmarkPreviewOpened)
|
||||
|
||||
function expandOrOpenPreview() {
|
||||
setBookmarkItemExpanded(true)
|
||||
if (useBookmarkPageStore.getState().layoutMode === LAYOUT_MODE.sideBySide) {
|
||||
console.log(useBookmarkPageStore.getState().layoutMode)
|
||||
setBookmarkPreviewOpened(true)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx("group flex flex-row justify-start py-2", {
|
||||
"bg-teal-600 text-stone-100": isBookmarkItemExpanded && isSelected,
|
||||
"text-teal-600": isSelected && !isBookmarkItemExpanded,
|
||||
})}
|
||||
onMouseEnter={() => {
|
||||
if (!isBookmarkItemExpanded) {
|
||||
selectBookmarkAt(index)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
disabled={!isSelected}
|
||||
className={clsx("select-none flex items-start font-bold hover:bg-teal-600 hover:text-stone-100", {
|
||||
invisible: !isSelected,
|
||||
})}
|
||||
onClick={() => {
|
||||
setBookmarkItemExpanded(!isBookmarkItemExpanded)
|
||||
setBookmarkPreviewOpened(false)
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Options for this bookmark</span>
|
||||
<span> </span>
|
||||
<span className={isBookmarkItemExpanded ? "rotate-90" : ""}>></span>
|
||||
<span> </span>
|
||||
</button>
|
||||
<div className="flex flex-col w-full">
|
||||
<button type="button" className="text-start font-bold" onClick={expandOrOpenPreview}>
|
||||
{bookmark.title}
|
||||
</button>
|
||||
<p className="opacity-80 text-sm">{url.host}</p>
|
||||
{isBookmarkItemExpanded && isSelected ? (
|
||||
<div className="flex flex-col space-y-1 md:flex-row md:space-y-0 md:space-x-2 items-end justify-between pt-2">
|
||||
<p className="text-sm">#dev #devops #devops #devops #devops #devops #devops</p>
|
||||
<div className="flex space-x-2">
|
||||
<OpenBookmarkPreviewButton />
|
||||
<Button className="text-sm">
|
||||
<span className="underline">E</span>dit
|
||||
</Button>
|
||||
<Button className="text-sm">
|
||||
<span className="underline">D</span>elete
|
||||
</Button>
|
||||
<span className="-ml-2"> </span>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function OpenBookmarkPreviewButton() {
|
||||
const isBookmarkPreviewOpened = useBookmarkPageStore((state) => state.isBookmarkPreviewOpened)
|
||||
const setBookmarkPreviewOpened = useBookmarkPageStore((state) => state.setBookmarkPreviewOpened)
|
||||
const setBookmarkItemExpanded = useBookmarkPageStore((state) => state.setBookmarkItemExpanded)
|
||||
|
||||
useEffect(() => {
|
||||
function onKeyDown(event: KeyboardEvent) {
|
||||
if (isBookmarkPreviewOpened && event.key === "c") {
|
||||
closePreview()
|
||||
} else if (!isBookmarkPreviewOpened && event.key === "o") {
|
||||
openPreview()
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("keydown", onKeyDown)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("keydown", onKeyDown)
|
||||
}
|
||||
}, [isBookmarkPreviewOpened])
|
||||
|
||||
function closePreview() {
|
||||
setBookmarkPreviewOpened(false)
|
||||
setBookmarkItemExpanded(false)
|
||||
}
|
||||
|
||||
function openPreview() {
|
||||
setBookmarkPreviewOpened(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
className="text-sm"
|
||||
onClick={() => {
|
||||
if (isBookmarkPreviewOpened) {
|
||||
closePreview()
|
||||
} else {
|
||||
openPreview()
|
||||
}
|
||||
}}
|
||||
>
|
||||
{isBookmarkPreviewOpened ? (
|
||||
<>
|
||||
<span className="underline">C</span>lose
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="underline">O</span>pen
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export const Route = createFileRoute("/bookmarks")({
|
||||
component: Page,
|
||||
})
|
75
packages/web/src/app/index.tsx
Normal file
75
packages/web/src/app/index.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import { DEMO_USER } from "@markone/core/user"
|
||||
import { createFileRoute, useNavigate } from "@tanstack/react-router"
|
||||
import { useLogin } from "~/auth"
|
||||
import { Link } from "~/components/link"
|
||||
|
||||
function Index() {
|
||||
return (
|
||||
<main className="p-48 flex flex-row items-start justify-center space-x-24">
|
||||
<div className="flex flex-col items-start">
|
||||
<h1 className="text-2xl">
|
||||
<span className="font-bold">MARKONE</span>
|
||||
<br />
|
||||
</h1>
|
||||
<p className="pb-4 text-2xl uppercase">BOOKMARK MANAGER</p>
|
||||
<div className="flex flex-col text-lg">
|
||||
<Link>LOGIN</Link>
|
||||
<Link>SIGN UP</Link>
|
||||
<DemoButton />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p>
|
||||
<strong>WHAT IS MARKONE?</strong>
|
||||
<br />
|
||||
MARKONE is a local-first, self-hostable bookmark manager.
|
||||
</p>
|
||||
<ul className="px-2 pt-2">
|
||||
<li>* Curate interesting websites you find online, and let MARKONE archive them.</li>
|
||||
<li>* Reference your saved bookmarks anytime, even when offline.</li>
|
||||
<li>* Share your collections to others with a permalink.</li>
|
||||
</ul>
|
||||
<br />
|
||||
<p>
|
||||
<strong>WHERE IS MARKONE?</strong>
|
||||
<br />
|
||||
MARKONE is available as a web app and a browser extension for now.
|
||||
</p>
|
||||
<br />
|
||||
<p>
|
||||
<strong>TECHNICAL STUFF</strong>
|
||||
<br />
|
||||
Source code, as well as hosting guide for MARKONE is <Link href="https://github.com/">available here</Link>.
|
||||
<br />
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
function DemoButton() {
|
||||
const loginMutation = useLogin()
|
||||
const navigate = useNavigate()
|
||||
|
||||
async function startDemo() {
|
||||
await loginMutation.mutateAsync({
|
||||
username: DEMO_USER.username,
|
||||
password: DEMO_USER.unhashedPassword,
|
||||
})
|
||||
navigate({ to: "/bookmarks" })
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={startDemo}
|
||||
className="underline active:bg-stone-700 dark:active:bg-stone-200 dark:active:text-stone-800 active:text-stone-200 text-left"
|
||||
>
|
||||
DEMO
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export const Route = createFileRoute("/")({
|
||||
component: Index,
|
||||
})
|
Reference in New Issue
Block a user