mirror of
https://github.com/linsa-io/linsa.git
synced 2026-04-23 08:48:37 +02:00
chore(start): update to alpha and fix editor image removed (#187)
* chore(start): update to alpha and fix editor image removed * chore: fix image editor
This commit is contained in:
@@ -5,15 +5,9 @@ import {
|
||||
ScrollRestoration,
|
||||
createRootRouteWithContext,
|
||||
} from "@tanstack/react-router"
|
||||
import {
|
||||
Body,
|
||||
createServerFn,
|
||||
Head,
|
||||
Html,
|
||||
Meta,
|
||||
Scripts,
|
||||
} from "@tanstack/start"
|
||||
import { createServerFn, Meta, Scripts } from "@tanstack/start"
|
||||
import * as React from "react"
|
||||
import { getWebRequest } from "vinxi/http"
|
||||
import { DefaultCatchBoundary } from "~/components/DefaultCatchBoundary.js"
|
||||
import { NotFound } from "~/components/NotFound.js"
|
||||
import { seo } from "~/lib/utils/seo"
|
||||
@@ -28,8 +22,8 @@ export const TanStackRouterDevtools =
|
||||
})),
|
||||
)
|
||||
|
||||
export const fetchClerkAuth = createServerFn("GET", async (_, ctx) => {
|
||||
const auth = await getAuth(ctx.request)
|
||||
export const fetchClerkAuth = createServerFn().handler(async () => {
|
||||
const auth = await getAuth(getWebRequest())
|
||||
|
||||
return auth
|
||||
})
|
||||
@@ -37,44 +31,49 @@ export const fetchClerkAuth = createServerFn("GET", async (_, ctx) => {
|
||||
export const Route = createRootRouteWithContext<{
|
||||
auth?: ReturnType<typeof getAuth> | null
|
||||
}>()({
|
||||
meta: () => [
|
||||
{
|
||||
charSet: "utf-8",
|
||||
},
|
||||
{
|
||||
name: "viewport",
|
||||
content: "width=device-width, initial-scale=1",
|
||||
},
|
||||
...seo({
|
||||
title: "Learn Anything",
|
||||
description:
|
||||
"Discover and learn about any topic with Learn-Anything. Our free, comprehensive platform connects you to the best resources for every subject. Start learning today!",
|
||||
keywords:
|
||||
"learn anything, online learning, free education, educational resources, self-study, knowledge discovery, topic exploration, skill development, lifelong learning",
|
||||
}),
|
||||
],
|
||||
links: () => [
|
||||
{ rel: "stylesheet", href: appCss },
|
||||
{
|
||||
rel: "apple-touch-icon",
|
||||
sizes: "180x180",
|
||||
href: "/apple-touch-icon.png",
|
||||
},
|
||||
{
|
||||
rel: "icon",
|
||||
type: "image/png",
|
||||
sizes: "32x32",
|
||||
href: "/favicon-32x32.png",
|
||||
},
|
||||
{
|
||||
rel: "icon",
|
||||
type: "image/png",
|
||||
sizes: "16x16",
|
||||
href: "/favicon-16x16.png",
|
||||
},
|
||||
{ rel: "manifest", href: "/site.webmanifest", color: "#fffff" },
|
||||
{ rel: "icon", href: "/favicon.ico" },
|
||||
],
|
||||
head() {
|
||||
return {
|
||||
meta: [
|
||||
{
|
||||
charSet: "utf-8",
|
||||
},
|
||||
{
|
||||
name: "viewport",
|
||||
content: "width=device-width, initial-scale=1",
|
||||
},
|
||||
...seo({
|
||||
title: "Learn Anything",
|
||||
description:
|
||||
"Discover and learn about any topic with Learn-Anything. Our free, comprehensive platform connects you to the best resources for every subject. Start learning today!",
|
||||
keywords:
|
||||
"learn anything, online learning, free education, educational resources, self-study, knowledge discovery, topic exploration, skill development, lifelong learning",
|
||||
}),
|
||||
],
|
||||
links: [
|
||||
{ rel: "stylesheet", href: appCss },
|
||||
{
|
||||
rel: "apple-touch-icon",
|
||||
sizes: "180x180",
|
||||
href: "/apple-touch-icon.png",
|
||||
},
|
||||
{
|
||||
rel: "icon",
|
||||
type: "image/png",
|
||||
sizes: "32x32",
|
||||
href: "/favicon-32x32.png",
|
||||
},
|
||||
{
|
||||
rel: "icon",
|
||||
type: "image/png",
|
||||
sizes: "16x16",
|
||||
href: "/favicon-16x16.png",
|
||||
},
|
||||
{ rel: "manifest", href: "/site.webmanifest", color: "#fffff" },
|
||||
{ rel: "icon", href: "/favicon.ico" },
|
||||
],
|
||||
}
|
||||
},
|
||||
|
||||
beforeLoad: async (ctx) => {
|
||||
try {
|
||||
// Handle explicit null auth (logged out state)
|
||||
@@ -121,20 +120,17 @@ function RootComponent() {
|
||||
|
||||
function RootDocument({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<Html>
|
||||
<Head>
|
||||
<html lang="en" suppressHydrationWarning={true}>
|
||||
<head>
|
||||
<Meta />
|
||||
</Head>
|
||||
<Body>
|
||||
</head>
|
||||
<body>
|
||||
{children}
|
||||
|
||||
<React.Suspense>
|
||||
<TanStackRouterDevtools position="bottom-right" />
|
||||
</React.Suspense>
|
||||
|
||||
<ScrollRestoration />
|
||||
<TanStackRouterDevtools position="bottom-right" />
|
||||
<Scripts />
|
||||
</Body>
|
||||
</Html>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { createFileRoute, Outlet, redirect } from "@tanstack/react-router"
|
||||
|
||||
export const Route = createFileRoute("/_layout/(auth)/_auth")({
|
||||
beforeLoad({ context }) {
|
||||
if (context.auth?.userId) {
|
||||
beforeLoad: async ({ context }) => {
|
||||
const auth = await context.auth
|
||||
if (auth?.userId) {
|
||||
throw redirect({ to: "/links", replace: true })
|
||||
}
|
||||
},
|
||||
|
||||
@@ -228,7 +228,7 @@ export const LinkItem = React.forwardRef<HTMLDivElement, LinkItemProps>(
|
||||
<Link
|
||||
to={ensureUrlProtocol(link.url)}
|
||||
target="_blank"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onClick={(e: React.MouseEvent) => e.stopPropagation()}
|
||||
className="text-xs text-muted-foreground hover:text-primary"
|
||||
>
|
||||
<span className="line-clamp-1">{link.url}</span>
|
||||
|
||||
@@ -191,7 +191,9 @@ export const LinkItem = React.forwardRef<HTMLDivElement, LinkItemProps>(
|
||||
<Link
|
||||
to={ensureUrlProtocol(personalLink.url)}
|
||||
target="_blank"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onClick={(e: React.MouseEvent<HTMLAnchorElement>) =>
|
||||
e.stopPropagation()
|
||||
}
|
||||
className="mr-1 truncate text-xs hover:text-primary"
|
||||
>
|
||||
{personalLink.url}
|
||||
|
||||
@@ -44,70 +44,72 @@ export const globalLinkFormExceptionRefsAtom = atom<
|
||||
React.RefObject<HTMLElement>[]
|
||||
>([])
|
||||
|
||||
export const getMetadata = createServerFn("GET", async (url: string) => {
|
||||
if (!url) {
|
||||
return new Response('Missing "url" query parameter', {
|
||||
status: 400,
|
||||
})
|
||||
}
|
||||
|
||||
const result = urlSchema.safeParse(decodeURIComponent(url))
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(
|
||||
result.error.issues.map((issue) => issue.message).join(", "),
|
||||
)
|
||||
}
|
||||
|
||||
url = ensureUrlProtocol(decodeURIComponent(url))
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
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",
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`)
|
||||
export const getMetadata = createServerFn()
|
||||
.validator((url: string) => url)
|
||||
.handler(async ({ data: url }) => {
|
||||
if (!url) {
|
||||
return new Response('Missing "url" query parameter', {
|
||||
status: 400,
|
||||
})
|
||||
}
|
||||
|
||||
const data = await response.text()
|
||||
const $ = cheerio.load(data)
|
||||
const result = urlSchema.safeParse(decodeURIComponent(url))
|
||||
|
||||
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,
|
||||
icon:
|
||||
$('link[rel="icon"]').attr("href") ||
|
||||
$('link[rel="shortcut icon"]').attr("href") ||
|
||||
DEFAULT_VALUES.FAVICON,
|
||||
url: url,
|
||||
if (!result.success) {
|
||||
throw new Error(
|
||||
result.error.issues.map((issue) => issue.message).join(", "),
|
||||
)
|
||||
}
|
||||
|
||||
if (metadata.icon && !metadata.icon.startsWith("http")) {
|
||||
metadata.icon = new URL(metadata.icon, url).toString()
|
||||
}
|
||||
url = ensureUrlProtocol(decodeURIComponent(url))
|
||||
|
||||
return metadata
|
||||
} catch (error) {
|
||||
console.error("Error fetching metadata:", error)
|
||||
const defaultMetadata: Metadata = {
|
||||
title: DEFAULT_VALUES.TITLE,
|
||||
description: DEFAULT_VALUES.DESCRIPTION,
|
||||
icon: DEFAULT_VALUES.FAVICON,
|
||||
url: url,
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
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",
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`)
|
||||
}
|
||||
|
||||
const data = await response.text()
|
||||
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,
|
||||
icon:
|
||||
$('link[rel="icon"]').attr("href") ||
|
||||
$('link[rel="shortcut icon"]').attr("href") ||
|
||||
DEFAULT_VALUES.FAVICON,
|
||||
url: url,
|
||||
}
|
||||
|
||||
if (metadata.icon && !metadata.icon.startsWith("http")) {
|
||||
metadata.icon = new URL(metadata.icon, url).toString()
|
||||
}
|
||||
|
||||
return metadata
|
||||
} catch (error) {
|
||||
console.error("Error fetching metadata:", error)
|
||||
const defaultMetadata: Metadata = {
|
||||
title: DEFAULT_VALUES.TITLE,
|
||||
description: DEFAULT_VALUES.DESCRIPTION,
|
||||
icon: DEFAULT_VALUES.FAVICON,
|
||||
url: url,
|
||||
}
|
||||
return defaultMetadata
|
||||
}
|
||||
return defaultMetadata
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
interface LinkFormProps extends React.ComponentPropsWithoutRef<"form"> {
|
||||
onClose?: () => void
|
||||
@@ -196,7 +198,7 @@ export const LinkForm: React.FC<LinkFormProps> = ({
|
||||
const fetchMetadata = async (url: string) => {
|
||||
setIsFetching(true)
|
||||
try {
|
||||
const data = await getMetadata(encodeURIComponent(url))
|
||||
const data = await getMetadata({ data: encodeURIComponent(url) })
|
||||
setUrlFetched(data.url)
|
||||
form.setValue("url", data.url, {
|
||||
shouldValidate: true,
|
||||
|
||||
@@ -67,7 +67,7 @@ export const Route = createFileRoute("/_layout/_pages/_protected/tasks/")({
|
||||
// throw new Error("Unauthorized")
|
||||
// }
|
||||
|
||||
// const flag = await getFeatureFlag({ name: "TASK" })
|
||||
// const flag = await getFeatureFlag({ data: "TASK" })
|
||||
// const canAccess = context.user?.emailAddresses.some((email) =>
|
||||
// flag?.emails.includes(email.emailAddress),
|
||||
// )
|
||||
|
||||
@@ -49,7 +49,7 @@ const SearchItem: React.FC<SearchItemProps> = ({
|
||||
<div className="group flex items-center justify-between">
|
||||
<Link
|
||||
to={href}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onClick={(e: React.MouseEvent) => e.stopPropagation()}
|
||||
className="text-sm font-medium hover:text-primary hover:opacity-70"
|
||||
>
|
||||
{title}
|
||||
@@ -57,7 +57,7 @@ const SearchItem: React.FC<SearchItemProps> = ({
|
||||
{subtitle && (
|
||||
<Link
|
||||
to={href}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onClick={(e: React.MouseEvent) => e.stopPropagation()}
|
||||
className="ml-2 truncate text-xs text-muted-foreground hover:underline"
|
||||
>
|
||||
{subtitle}
|
||||
|
||||
Reference in New Issue
Block a user