feat: support platform icon and adjust breakpoint of gird

Signed-off-by: Innei <i@innei.in>
This commit is contained in:
Innei 2024-07-30 15:47:16 +08:00
parent b75c58b83c
commit dd74a99bcb
No known key found for this signature in database
GPG Key ID: 0F62D33977F021F7
9 changed files with 262 additions and 18 deletions

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none"><path fill="#fff" fill-opacity=".01" d="M24 0v24H0V0z"/><path fill="#10161F" fill-rule="evenodd" d="M12 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2M6.987 14.789a.75.75 0 0 0-.474 1.423c.183.058.345.328.487.566.07.115.134.223.195.296.227.273.537.567.985.757.31.132.658.2 1.046.202l-.005.457a1 1 0 0 0 2 .02l.029-2.745v-.01c0-.215.09-.408.236-.546.604-.57.296-1.561-.493-1.719-1.74-.347-2.66-1.446-2.66-2.402 0-.447.181-.896.556-1.305.264-.287.337-.702.189-1.063a1.402 1.402 0 0 1-.091-.457c.209.098.424.223.621.376.256.198.605.261.913.166.45-.138.95-.217 1.479-.217.529 0 1.028.079 1.477.217.309.095.657.031.913-.166.197-.153.412-.278.62-.376-.008.171-.037.329-.09.456-.148.362-.074.776.19 1.063.375.41.556.859.556 1.306 0 .956-.92 2.055-2.659 2.402-.789.158-1.097 1.15-.493 1.719a.745.745 0 0 1 .236.542l-.029 2.739a1 1 0 1 0 2 .02l.029-2.745v-.01c0-.239-.03-.47-.088-.691 1.69-.714 3.004-2.131 3.004-3.976 0-.82-.272-1.572-.72-2.207.091-.471.076-.924.034-1.27-.046-.377-.121-.797-.329-1.123a1.054 1.054 0 0 0-.414-.377c-.458-.23-1.027-.054-1.482.101a5.77 5.77 0 0 0-1.17.553A7.119 7.119 0 0 0 12 6.588c-.549 0-1.082.062-1.588.177a5.771 5.771 0 0 0-1.17-.553c-.454-.155-1.024-.33-1.481-.101-.17.085-.313.217-.414.377-.208.326-.283.747-.33 1.124-.041.347-.057.8.034 1.271a3.813 3.813 0 0 0-.718 2.205c0 1.845 1.315 3.262 3.005 3.976-.057.219-.088.448-.088.685l-.008.784c-.22 0-.37-.038-.476-.083a1.106 1.106 0 0 1-.419-.336c-.096-.116-.182-.238-.268-.361-.064-.093-.129-.185-.198-.274-.179-.232-.457-.545-.894-.69" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none"><path fill="#fff" fill-opacity=".01" d="M24 0v24H0V0z"/><path fill="#10161F" fill-rule="evenodd" d="M12 4.25c-2.214 0-4.571.151-6.248.287a3.552 3.552 0 0 0-3.266 3.198C2.366 8.97 2.25 10.537 2.25 12c0 1.463.116 3.03.236 4.265a3.552 3.552 0 0 0 3.266 3.198c1.677.136 4.034.287 6.248.287 2.214 0 4.571-.151 6.248-.287a3.552 3.552 0 0 0 3.267-3.198c.12-1.234.235-2.802.235-4.265 0-1.463-.116-3.03-.235-4.265a3.552 3.552 0 0 0-3.267-3.198C16.571 4.401 14.214 4.25 12 4.25m-1.802 4.875c.482-.278.957-.023 1.908.486a31.144 31.144 0 0 1 1.694.978c.911.566 1.367.85 1.367 1.406 0 .557-.456.839-1.368 1.404a31.942 31.942 0 0 1-1.697.98c-.947.508-1.421.763-1.904.485-.482-.278-.499-.816-.533-1.892a31.058 31.058 0 0 1 0-1.951c.034-1.078.051-1.618.533-1.896" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 850 B

View File

@ -1,6 +1,8 @@
import { cn } from "@renderer/lib/utils"
import { parse } from "tldts"
import { PlatformIcon } from "./ui/platform-icon"
export function SiteIcon({
url,
className,
@ -19,27 +21,36 @@ export function SiteIcon({
try {
host = new URL(url).host
const pureDomain = parse(host).domainWithoutSuffix
fallback = `https://avatar.vercel.sh/${pureDomain}.svg?text=${pureDomain?.slice(0, 2).toUpperCase()}`
fallback = `https://avatar.vercel.sh/${pureDomain}.svg?text=${pureDomain
?.slice(0, 2)
.toUpperCase()}`
src = `https://unavatar.follow.is/${host}?fallback=${fallback}`
} catch {
const pureDomain = parse(url).domainWithoutSuffix
src = `https://avatar.vercel.sh/${pureDomain}.svg?text=${pureDomain?.slice(0, 2).toUpperCase()}`
src = `https://avatar.vercel.sh/${pureDomain}.svg?text=${pureDomain
?.slice(0, 2)
.toUpperCase()}`
}
return (
<img
src={src}
className={cn("mr-2 size-5 shrink-0 rounded-sm", className)}
loading="lazy"
onError={(e) => {
if (fallback && e.currentTarget.src !== fallback) {
e.currentTarget.src = fallback
} else {
// Empty svg
e.currentTarget.src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3C/svg%3E"
}
}}
<PlatformIcon
url={url}
style={style}
/>
className={cn("mr-2 size-5 shrink-0 rounded-sm", className)}
>
<img
src={src}
loading="lazy"
onError={(e) => {
if (fallback && e.currentTarget.src !== fallback) {
e.currentTarget.src = fallback
} else {
// Empty svg
e.currentTarget.src =
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3C/svg%3E"
}
}}
/>
</PlatformIcon>
)
}

View File

@ -0,0 +1,39 @@
import { Slot } from "@radix-ui/react-slot"
import { cn } from "@renderer/lib/utils"
import type { FC } from "react"
import { getSupportedPlatformIconName } from "./utils"
export const PlatformIcon: FC<{
url: string
children: React.JSX.Element
className?: string
style?: React.CSSProperties
}> = ({ className, url, children, style, ...rest }) => {
const iconName = getSupportedPlatformIconName(url)
if (!iconName) {
return (
<Slot className={className} style={style} {...rest}>
{children}
</Slot>
)
}
return (
<i
className={cn(`${IconMap[iconName]} ${className}`)}
style={{
...style,
maskImage: "var(--svg)",
}}
/>
)
}
const IconMap = {
github: tw`i-mgc-github-2-cute-fi text-black dark:text-white`,
twitter: tw`i-mgc-twitter-cute-fi text-[#55acee]`,
x: tw`i-mgc-social-x-cute-li`,
youtube: tw`i-mgc-youtube-cute-fi text-[#ff0000]`,
}

View File

@ -0,0 +1,38 @@
import {
isGithubUrl,
isTwitterUrl,
isXUrl,
isYoutubeUrl,
} from "@renderer/lib/link-parser"
export const getSupportedPlatformIconName = (url: string) => {
const safeUrl = parseSafeUrl(url)
if (!safeUrl) {
return false
}
switch (true) {
case isGithubUrl(safeUrl): {
return "github"
}
case isTwitterUrl(safeUrl): {
return "twitter"
}
case isXUrl(safeUrl): {
return "x"
}
case isYoutubeUrl(safeUrl): {
return "youtube"
}
}
return false
}
const parseSafeUrl = (url: string) => {
try {
return new URL(url)
} catch {
return null
}
}

View File

@ -0,0 +1,153 @@
export const getTweetId = (url: URL) => url.pathname.split("/").pop()!
const GITHUB_HOST = "github.com"
export const isGithubRepoUrl = (url: URL) =>
url.hostname === GITHUB_HOST &&
url.pathname.startsWith("/") &&
url.pathname.split("/").length === 3
export const isGithubPrUrl = (url: URL) =>
url.hostname === GITHUB_HOST && url.pathname.includes("/pull/")
export const isYoutubeUrl = (url: URL) =>
url.hostname === "www.youtube.com" && url.pathname.startsWith("/watch")
export const isGistUrl = (url: URL) => url.hostname === "gist.github.com"
export const isGithubCommitUrl = (url: URL) => {
const [_, , , type] = url.pathname.split("/")
return url.hostname === GITHUB_HOST && type === "commit"
}
export const isGithubProfileUrl = (url: URL) =>
url.hostname === GITHUB_HOST && url.pathname.split("/").length === 2
export const isGithubFilePreviewUrl = (url: URL) => {
// https://github.com/Innei/sprightly/blob/14234594f44956e6f56f1f92952ce82db37ef4df/src/socket/handler.ts
const [_, , , type] = url.pathname.split("/")
return url.hostname === GITHUB_HOST && type === "blob"
}
export const isTweetUrl = (url: URL) =>
isTwitterUrl(url) && url.pathname.startsWith("/")
export const isTwitterProfileUrl = (url: URL) =>
isTwitterUrl(url) && url.pathname.split("/").length === 2
export const isGithubUrl = (url: URL) => url.hostname === GITHUB_HOST
export const isTwitterUrl = (url: URL) => url.hostname === "twitter.com"
export const isXUrl = (url: URL) => url.hostname === "x.com"
export const isXOrTwitterUrl = (url: URL) => isXUrl(url) || isTwitterUrl(url)
export const isTelegramUrl = (url: URL) => url.hostname === "t.me"
export const isCodesandboxUrl = (url: URL) =>
// https://codesandbox.io/s/framer-motion-layoutroot-prop-forked-p39g96
url.hostname === "codesandbox.io" && url.pathname.split("/").length === 3
export const isBilibiliUrl = (url: URL) =>
url.hostname.includes("bilibili.com")
export const isBilibiliVideoUrl = (url: URL) =>
isBilibiliUrl(url) && url.pathname.startsWith("/video/BV")
export const isZhihuUrl = (url: URL) => url.hostname === "www.zhihu.com"
export const isZhihuProfileUrl = (url: URL) =>
isZhihuUrl(url) && url.pathname.startsWith("/people/")
export const isWikipediaUrl = (url: URL) =>
url.hostname.includes("wikipedia.org")
export const isTMDBUrl = (url: URL) => url.hostname.includes("themoviedb.org")
export const isNpmUrl = (url: URL) => url.hostname.includes("npmjs.com")
export const isMozillaUrl = (url: URL) => url.hostname.includes("mozilla.org")
export const parseSelfArticleUrl = (url: URL) => {
const [_, type, ...rest] = url.pathname.split("/")
switch (type) {
case "posts": {
return {
type,
slug: rest.join("/"),
}
}
case "notes": {
return {
type,
nid: +rest[0],
}
}
}
}
export const parseGithubRepoUrl = (url: URL) => {
const [_, owner, repo] = url.pathname.split("/")
return {
owner,
repo,
}
}
export const parseGithubGistUrl = (url: URL) => {
const [_, owner, id] = url.pathname.split("/")
return {
owner,
id,
}
}
export const parseGithubTypedUrl = (url: URL) => {
// https://github.com/Innei/sprightly/blob/14234594f44956e6f56f1f92952ce82db37ef4df/src/socket/handler.ts
// https://github.com/mx-space/core/commit/e1b4d881cf18e1cb66294d2620eada35937d9a12
const split = url.pathname.split("/")
const [_, owner, repo, type, id] = split
const afterTypeString = split.slice(4).join("/")
return {
owner,
repo,
type,
id,
afterTypeString,
}
}
export const parseZhihuProfileUrl = (url: URL) => {
const [_, type, id] = url.pathname.split("/")
return {
type,
id,
}
}
export const parseGithubPrUrl = (url: URL) => {
const [_, owner, repo, type, pr] = url.pathname.split("/")
return {
owner,
repo,
type,
pr,
}
}
export const parseTMDBUrl = (url: URL) => {
const [_, type, id] = url.pathname.split("/")
return {
type,
id,
}
}
export const parseBilibiliVideoUrl = (url: URL) => {
// https://www.bilibili.com/video/BV1tj42197hU
const [_, type, id] = url.pathname.split("/")
return {
type,
id,
}
}

View File

@ -30,7 +30,7 @@ export function GridItem({
{children}
<div
className={cn(
"relative flex-1 px-2 pb-3 pt-1 text-sm",
"relative flex-1 px-2 py-1 text-sm",
!asRead &&
"before:absolute before:-left-1 before:top-[13.5px] before:block before:size-2 before:rounded-full before:bg-theme-accent",
)}

View File

@ -171,7 +171,7 @@ export function EntryColumn() {
ref={scrollRef}
flex
rootClassName="h-full"
viewportClassName="[&>div]:grow flex pt-3"
viewportClassName="[&>div]:grow flex pt-3 @container"
>
{virtuosoOptions.totalCount === 0 ? (
entries.isLoading ?
@ -182,7 +182,7 @@ export function EntryColumn() {
) : view && views[view].gridMode ?
(
<VirtuosoGrid
listClassName="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 px-4"
listClassName={tw`grid grid-cols-1 @lg:grid-cols-2 @3xl:grid-cols-3 @6xl:grid-cols-4 @7xl:grid-cols-5 px-4 gap-1.5`}
{...virtuosoOptions}
ref={virtuosoRef}
/>

View File

@ -156,6 +156,7 @@ export const UserProfileModalContent: FC<{
</m.div>
</div>
<ScrollArea.ScrollArea
mask
ref={setScrollerRef}
rootClassName="mb-4 h-[400px] grow w-[70ch] max-w-full px-5"
viewportClassName="[&>div]:space-y-4"