chore: use prettier to format code (#306)

This commit is contained in:
Innei 2024-09-09 15:04:57 +08:00 committed by GitHub
parent 501e2f44c1
commit 26d01fd835
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
336 changed files with 15722 additions and 13266 deletions

View File

@ -17,7 +17,6 @@ Before submitting the PR, please make sure you do the following:
### Linked Issues
### Additional context
<!-- e.g. is there anything you'd like reviewers to focus on? -->
<!-- e.g. is there anything you'd like reviewers to focus on? -->

View File

@ -1,13 +1,13 @@
name: Setup Version
description: 'Setup Version'
description: "Setup Version"
outputs:
APP_VERSION:
description: 'App Version'
description: "App Version"
value: ${{ steps.version.outputs.APP_VERSION }}
runs:
using: 'composite'
using: "composite"
steps:
- name: 'Write Version'
- name: "Write Version"
id: version
shell: bash
run: |

1
.prettierignore Normal file
View File

@ -0,0 +1 @@
src/hono.ts

7
.prettierrc.mjs Normal file
View File

@ -0,0 +1,7 @@
export default {
semi: false,
singleQuote: false,
printWidth: 100,
tabWidth: 2,
trailingComma: "all",
}

View File

@ -12,7 +12,6 @@
// ["tw\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
["tw`([^`]*)`", "([^`]*)"]
],
// If you do not want to autofix some rules on save
@ -37,9 +36,7 @@
{ "rule": "prefer-const", "severity": "off" },
{ "rule": "unused-imports/no-unused-imports", "severity": "off" }
],
"cSpell.words": [
"rsshub"
],
"cSpell.words": ["rsshub"],
"editor.foldingImportsByDefault": true,
"commentTranslate.hover.enabled": false,
"typescript.tsdk": "node_modules/typescript/lib"

File diff suppressed because it is too large Load Diff

View File

@ -18,7 +18,6 @@ If you have access, you are welcome to use the following methods to download and
- You can get the installation packages for each platform from the [Release page](https://github.com/RSSNext/Follow/releases).
- If you are using Arch linux, you can install package [follow-appimage](https://aur.archlinux.org/packages/follow-appimage) that maintained by [timochan](https://github.com/ttimochan)
## Contributing
If you are eligible to use Follow, you are welcome to join the open source community to build together.

View File

@ -74,9 +74,7 @@ export default defineConfig({
DEBUG: process.env.DEBUG === "true",
ELECTRON: "true",
RELEASE_CHANNEL: JSON.stringify(
(prerelease(pkg.version)?.[0] as string) || "stable",
),
RELEASE_CHANNEL: JSON.stringify((prerelease(pkg.version)?.[0] as string) || "stable"),
},
},
})

View File

@ -3,12 +3,7 @@ import { defineConfig } from "eslint-config-hyoban"
export default defineConfig(
{
formatting: {
quotes: "double",
arrowParens: true,
braceStyle: "1tbs",
lineBreak: "after",
},
formatting: false,
lessOpinionated: true,
ignores: ["src/renderer/src/hono.ts", "src/hono.ts", "resources/**"],
preferESM: false,
@ -16,9 +11,7 @@ export default defineConfig(
{
settings: {
tailwindcss: {
whitelist: [
"center",
],
whitelist: ["center"],
},
},
rules: {
@ -30,7 +23,9 @@ export default defineConfig(
"error",
{
name: "location",
message: "Since you don't use the same router instance in electron and browser, you can't use the global location to get the route info. \n\n" + "You can use `useLocaltion` or `getReadonlyRoute` to get the route info.",
message:
"Since you don't use the same router instance in electron and browser, you can't use the global location to get the route info. \n\n" +
"You can use `useLocaltion` or `getReadonlyRoute` to get the route info.",
},
],
},

View File

@ -28,20 +28,9 @@ const ymlMapsMap = {
const keepModules = new Set(["font-list", "vscode-languagedetection"])
const keepLanguages = new Set(["en", "en_GB", "zh_CN", "zh-CN", "en-GB"])
// remove folders & files not to be included in the app
async function cleanSources(
buildPath,
electronVersion,
platform,
arch,
callback,
) {
async function cleanSources(buildPath, electronVersion, platform, arch, callback) {
// folders & files to be included in the app
const appItems = new Set([
"dist",
"node_modules",
"package.json",
"resources",
])
const appItems = new Set(["dist", "node_modules", "package.json", "resources"])
if (platform === "darwin") {
const frameworkResourcePath = resolve(
@ -60,9 +49,7 @@ async function cleanSources(
await Promise.all([
...(await readdir(buildPath).then((items) =>
items
.filter((item) => !appItems.has(item))
.map((item) => rimraf(path.join(buildPath, item))),
items.filter((item) => !appItems.has(item)).map((item) => rimraf(path.join(buildPath, item))),
)),
...(await readdir(path.join(buildPath, "node_modules")).then((items) =>
items
@ -74,9 +61,7 @@ async function cleanSources(
callback()
}
const ignorePattern = new RegExp(
`^/node_modules/(?!${[...keepModules].join("|")})`,
)
const ignorePattern = new RegExp(`^/node_modules/(?!${[...keepModules].join("|")})`)
const config: ForgeConfig = {
packagerConfig: {
@ -98,18 +83,18 @@ const config: ForgeConfig = {
...(process.env.APPLE_ID &&
process.env.APPLE_PASSWORD &&
process.env.APPLE_TEAM_ID && {
osxSign: {
optionsForFile: () => ({
entitlements: "build/entitlements.mac.plist",
}),
keychain: process.env.KEYCHAIN_PATH,
},
osxNotarize: {
appleId: process.env.APPLE_ID!,
appleIdPassword: process.env.APPLE_PASSWORD!,
teamId: process.env.APPLE_TEAM_ID!,
},
}),
osxSign: {
optionsForFile: () => ({
entitlements: "build/entitlements.mac.plist",
}),
keychain: process.env.KEYCHAIN_PATH,
},
osxNotarize: {
appleId: process.env.APPLE_ID!,
appleIdPassword: process.env.APPLE_PASSWORD!,
teamId: process.env.APPLE_TEAM_ID!,
},
}),
},
rebuildConfig: {},
makers: [
@ -204,10 +189,7 @@ const config: ForgeConfig = {
try {
const fileData = fs.readFileSync(newArtifact)
const hash = crypto
.createHash("sha512")
.update(fileData)
.digest("base64")
const hash = crypto.createHash("sha512").update(fileData).digest("base64")
const { size } = fs.statSync(newArtifact)
yml.files.push({

View File

@ -22,6 +22,7 @@
"dev": "electron-vite dev --outDir=dist",
"dev:debug": "export DEBUG=true && vite --debug",
"dev:web": "vite",
"format": "prettier --write .",
"lint": "eslint",
"lint:fix": "eslint --fix",
"polyfill-optimize": "pnpx nolyfill install",
@ -177,6 +178,7 @@
"nbump": "2.0.4",
"postcss": "8.4.41",
"postcss-js": "4.0.1",
"prettier": "3.3.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"rimraf": "6.0.1",
@ -208,7 +210,10 @@
"pre-commit": "pnpm exec lint-staged"
},
"lint-staged": {
"*": "eslint --fix"
"*": "eslint --fix",
"*.{js,jsx,ts,tsx,json}": [
"prettier --ignore-path ./.gitignore --write "
]
},
"bump": {
"before": [

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
module.exports = {
plugins: {
"tailwindcss": {},
tailwindcss: {},
"tailwindcss/nesting": {},
...(process.env.NODE_ENV === "production" ? { cssnano: {} } : {}),

View File

@ -1,10 +1,7 @@
import { createEnv } from "@t3-oss/env-core"
import { z } from "zod"
const isDev =
"process" in globalThis ?
process.env.NODE_ENV === "development" :
import.meta.env.DEV
const isDev = "process" in globalThis ? process.env.NODE_ENV === "development" : import.meta.env.DEV
export const env = createEnv({
clientPrefix: "VITE_",
client: {

View File

@ -6,9 +6,9 @@ import * as semver from "semver"
export const mode = process.env.NODE_ENV
export const isDev = mode === "development"
export const channel = isDev ?
"development" :
(semver.prerelease(app.getVersion())?.[0] as string) || "stable"
export const channel = isDev
? "development"
: (semver.prerelease(app.getVersion())?.[0] as string) || "stable"
const { platform } = process
export const isMacOS = platform === "darwin"

View File

@ -84,12 +84,8 @@ function bootsharp() {
if (process.env.NODE_ENV === "development") {
import("electron-devtools-installer").then(
({
default: installExtension,
REDUX_DEVTOOLS,
REACT_DEVELOPER_TOOLS,
}) => {
[REDUX_DEVTOOLS, REACT_DEVELOPER_TOOLS].forEach((extension) => {
({ default: installExtension, REDUX_DEVTOOLS, REACT_DEVELOPER_TOOLS }) => {
;[REDUX_DEVTOOLS, REACT_DEVELOPER_TOOLS].forEach((extension) => {
installExtension(extension, {
loadExtensionOptions: { allowFileAccess: true },
})
@ -113,8 +109,7 @@ function bootsharp() {
if (urlObj.hostname === "auth" || urlObj.pathname === "//auth") {
const token = urlObj.searchParams.get("token")
const apiURL =
process.env["VITE_API_URL"] || import.meta.env.VITE_API_URL
const apiURL = process.env["VITE_API_URL"] || import.meta.env.VITE_API_URL
if (token && apiURL) {
setAuthSessionToken(token)
mainWindow.webContents.session.cookies.set({

View File

@ -31,10 +31,7 @@ export const initializeApp = () => {
registerIpcMain(router)
app.setPath(
"appData",
path.join(app.getPath("appData"), isDev ? appFolder.dev : appFolder.prod),
)
app.setPath("appData", path.join(app.getPath("appData"), isDev ? appFolder.dev : appFolder.prod))
if (app.dock) {
app.dock.setIcon(getIconPath())

View File

@ -30,16 +30,13 @@ export const apiFetch = ofetch.create({
})
export const apiClient = hc<AppType>("", {
fetch: async (input, options = {}) => apiFetch(input.toString(), options),
headers() {
const authSessionToken = getAuthSessionToken()
return {
"X-App-Version": PKG.version,
"X-App-Dev": process.env.NODE_ENV === "development" ? "1" : "0",
"Cookie": authSessionToken ?
`authjs.session-token=${authSessionToken}` :
"",
Cookie: authSessionToken ? `authjs.session-token=${authSessionToken}` : "",
}
},
})

View File

@ -2,9 +2,7 @@ import { app } from "electron"
import { isWindows } from "../env"
export const setDockCount = (
input: number,
) => {
export const setDockCount = (input: number) => {
// TODO use Electron Overlay API
if (isWindows) return
if (app.dock) {

View File

@ -23,9 +23,8 @@ export async function readability(url: string) {
document.querySelectorAll("a").forEach((a) => {
a.href = replaceRelativeAddress(baseUrl, a.href)
});
(["img", "audio", "video"] as const).forEach((tag) => {
})
;(["img", "audio", "video"] as const).forEach((tag) => {
document.querySelectorAll(tag).forEach((img) => {
img.src = img.src && replaceRelativeAddress(baseUrl, img.src)
})

View File

@ -3,10 +3,7 @@ import { resolve } from "node:path"
import { app } from "electron"
import { JSONFileSyncPreset } from "lowdb/node"
export const db = JSONFileSyncPreset(
resolve(app.getPath("userData"), "db.json"),
{},
)
export const db = JSONFileSyncPreset(resolve(app.getPath("userData"), "db.json"), {})
export const store = {
get: (key: string) => db.data[key],

View File

@ -10,8 +10,8 @@ import { createSettingWindow, createWindow, getMainWindow } from "./window"
export const registerAppMenu = () => {
const menus: Array<MenuItemConstructorOptions | MenuItem> = [
...(isMacOS ?
([
...(isMacOS
? ([
{
label: name,
submenu: [
@ -37,8 +37,8 @@ export const registerAppMenu = () => {
{ role: "quit" },
],
},
] as MenuItemConstructorOptions[]) :
[]),
] as MenuItemConstructorOptions[])
: []),
{
role: "fileMenu",
@ -90,8 +90,8 @@ export const registerAppMenu = () => {
dispatchEventOnWindow(window, "OpenSearch")
},
},
...((isMacOS ?
[
...((isMacOS
? [
{ role: "pasteAndMatchStyle" },
{ role: "delete" },
{ role: "selectAll" },
@ -100,8 +100,8 @@ export const registerAppMenu = () => {
label: "Speech",
submenu: [{ role: "startSpeaking" }, { role: "stopSpeaking" }],
},
] :
[
]
: [
{ role: "delete" },
{ type: "separator" },
{ role: "selectAll" },

View File

@ -13,7 +13,7 @@ import { t } from "./_instance"
export const appRoute = {
saveToEagle: t.procedure
.input<{ url: string, mediaUrls: string[] }>()
.input<{ url: string; mediaUrls: string[] }>()
.action(async ({ input }) => {
try {
const res = await fetch("http://localhost:41595/api/item/addFromURLs", {
@ -42,9 +42,7 @@ export const appRoute = {
.action(async ({ input }) => {
const mainWindow = getMainWindow()
if (!mainWindow) return
const handlers = getRendererHandlers<RendererHandlers>(
mainWindow.webContents,
)
const handlers = getRendererHandlers<RendererHandlers>(mainWindow.webContents)
handlers.invalidateQuery.send(input)
}),
@ -52,9 +50,7 @@ export const appRoute = {
.input<{ action: "close" | "minimize" | "maximum" }>()
.action(async ({ input, context }) => {
if (context.sender.getType() === "window") {
const window: BrowserWindow | null = (
context.sender as Sender
).getOwnerBrowserWindow()
const window: BrowserWindow | null = (context.sender as Sender).getOwnerBrowserWindow()
if (!window) return
switch (input.action) {
@ -84,12 +80,15 @@ export const appRoute = {
window.setResizable(true)
window.setMovable(true)
window.setBounds({
width: stored.size[0],
height: stored.size[1],
x: stored.position[0],
y: stored.position[1],
}, true)
window.setBounds(
{
width: stored.size[0],
height: stored.size[1],
x: stored.position[0],
y: stored.position[1],
},
true,
)
delete window[storeKey]
} else {
@ -103,12 +102,15 @@ export const appRoute = {
// Maually Resize
const { workArea } = display
window.setBounds({
x: workArea.x,
y: workArea.y,
width: workArea.width,
height: workArea.height,
}, true)
window.setBounds(
{
x: workArea.x,
y: workArea.y,
width: workArea.width,
height: workArea.height,
},
true,
)
window.setResizable(false)
window.setMovable(false)
@ -128,29 +130,25 @@ export const appRoute = {
}
}
}),
getWindowIsMaximized: t.procedure
.input<void>()
.action(async ({ context }) => {
const window: BrowserWindow | null = (
context.sender as Sender
).getOwnerBrowserWindow()
getWindowIsMaximized: t.procedure.input<void>().action(async ({ context }) => {
const window: BrowserWindow | null = (context.sender as Sender).getOwnerBrowserWindow()
if (isWindows11 && window) {
const size = screen.getDisplayMatching(window.getBounds()).workAreaSize
if (isWindows11 && window) {
const size = screen.getDisplayMatching(window.getBounds()).workAreaSize
const windowSize = window.getSize()
const windowPosition = window.getPosition()
const isMaximized =
windowSize[0] === size.width &&
windowSize[1] === size.height &&
windowPosition[0] === 0 &&
windowPosition[1] === 0
const windowSize = window.getSize()
const windowPosition = window.getPosition()
const isMaximized =
windowSize[0] === size.width &&
windowSize[1] === size.height &&
windowPosition[0] === 0 &&
windowPosition[1] === 0
return !!isMaximized
}
return !!isMaximized
}
return window?.isMaximized()
}),
return window?.isMaximized()
}),
quitAndInstall: t.procedure.action(async () => {
quitAndInstall()
}),
@ -171,8 +169,7 @@ export const appRoute = {
.action(async ({ input, context }) => {
const { sender: webContents } = context
const { promise, resolve } =
Promise.withResolvers<Electron.Result | null>()
const { promise, resolve } = Promise.withResolvers<Electron.Result | null>()
let requestId = -1
webContents.once("found-in-page", (_, result) => {
@ -181,36 +178,28 @@ export const appRoute = {
requestId = webContents.findInPage(input.text, input.options)
return promise
}),
clearSearch: t.procedure.action(
async ({ context: { sender: webContents } }) => {
webContents.stopFindInPage("keepSelection")
},
),
clearSearch: t.procedure.action(async ({ context: { sender: webContents } }) => {
webContents.stopFindInPage("keepSelection")
}),
download: t.procedure
.input<string>()
.action(async ({ input, context: { sender } }) => {
const result = await dialog.showSaveDialog({
defaultPath: input.split("/").pop(),
})
if (result.canceled) return
// return result.filePath;
await downloadFile(input, result.filePath).catch((err) => {
const senderWindow = (sender as Sender).getOwnerBrowserWindow()
if (!senderWindow) return
callGlobalContextMethod(senderWindow, "toast.error", [
"Download failed!",
])
throw err
})
download: t.procedure.input<string>().action(async ({ input, context: { sender } }) => {
const result = await dialog.showSaveDialog({
defaultPath: input.split("/").pop(),
})
if (result.canceled) return
// return result.filePath;
await downloadFile(input, result.filePath).catch((err) => {
const senderWindow = (sender as Sender).getOwnerBrowserWindow()
if (!senderWindow) return
callGlobalContextMethod(senderWindow, "toast.success", [
"Download success!",
])
}),
callGlobalContextMethod(senderWindow, "toast.error", ["Download failed!"])
throw err
})
const senderWindow = (sender as Sender).getOwnerBrowserWindow()
if (!senderWindow) return
callGlobalContextMethod(senderWindow, "toast.success", ["Download success!"])
}),
}
interface Sender extends Electron.WebContents {
getOwnerBrowserWindow: () => Electron.BrowserWindow | null

View File

@ -2,7 +2,7 @@ import { t } from "./_instance"
export const debugRoute = {
inspectElement: t.procedure
.input<{ x: number, y: number }>()
.input<{ x: number; y: number }>()
.action(async ({ input, context }) => {
context.sender.inspectElement(input.x, input.y)
}),

View File

@ -14,9 +14,7 @@ const pollingMap = {
export const dockRoute = {
pollingUpdateUnreadCount: t.procedure.action(pollingUpdateUnreadCount),
cancelPollingUpdateUnreadCount: t.procedure.action(
cancelPollingUpdateUnreadCount,
),
cancelPollingUpdateUnreadCount: t.procedure.action(cancelPollingUpdateUnreadCount),
updateUnreadCount: t.procedure.action(async () => {
await updateUnreadCount()
@ -36,7 +34,9 @@ export async function pollingUpdateUnreadCount() {
pollingMap.unread = true
while (pollingMap.unread) {
await sleep(UNREAD_BACKGROUND_POLLING_INTERVAL)
if (pollingMap.unread) { await updateUnreadCount() }
if (pollingMap.unread) {
await updateUnreadCount()
}
}
}

View File

@ -35,14 +35,10 @@ export const menuRoute = {
click() {
context.sender.send(
"menu-click",
parentIndex !== undefined ?
`${parentIndex}-${index}` :
`${index}`,
parentIndex !== undefined ? `${parentIndex}-${index}` : `${index}`,
)
},
submenu: item.submenu ?
transformMenuItems(item.submenu, index) :
undefined,
submenu: item.submenu ? transformMenuItems(item.submenu, index) : undefined,
} as MenuItemConstructorOptions
})
}
@ -71,17 +67,15 @@ export const menuRoute = {
return result.response === 0
}),
showShareMenu: t.procedure
.input<string>()
.action(async ({ input, context }) => {
const menu = new ShareMenu({
urls: [input],
})
showShareMenu: t.procedure.input<string>().action(async ({ input, context }) => {
const menu = new ShareMenu({
urls: [input],
})
menu.popup({
callback: () => {
context.sender.send("menu-closed")
},
})
}),
menu.popup({
callback: () => {
context.sender.send("menu-closed")
},
})
}),
}

View File

@ -12,18 +12,16 @@ const require = createRequire(import.meta.url)
const tts = new MsEdgeTTS()
export const readerRoute = {
readability: t.procedure
.input<{ url: string }>()
.action(async ({ input }) => {
const { url } = input
readability: t.procedure.input<{ url: string }>().action(async ({ input }) => {
const { url } = input
if (!url) {
return null
}
const result = await readability(url)
if (!url) {
return null
}
const result = await readability(url)
return result
}),
return result
}),
tts: t.procedure
.input<{
@ -46,17 +44,14 @@ export const readerRoute = {
}
}),
getVoices: t.procedure
.action(async () => {
const voices = await tts.getVoices()
return voices
}),
getVoices: t.procedure.action(async () => {
const voices = await tts.getVoices()
return voices
}),
setVoice: t.procedure
.input<string>()
.action(async ({ input }) => {
await tts.setMetadata(input, OUTPUT_FORMAT.WEBM_24KHZ_16BIT_MONO_OPUS)
}),
setVoice: t.procedure.input<string>().action(async ({ input }) => {
await tts.setMetadata(input, OUTPUT_FORMAT.WEBM_24KHZ_16BIT_MONO_OPUS)
}),
detectCodeStringLanguage: t.procedure
.input<{ codeString: string }>()

View File

@ -12,13 +12,11 @@ export const settingRoute = {
getLoginItemSettings: t.procedure
.input<void>()
.action(async () => await app.getLoginItemSettings()),
setLoginItemSettings: t.procedure
.input<boolean>()
.action(async ({ input }) => {
app.setLoginItemSettings({
openAtLogin: input,
})
}),
setLoginItemSettings: t.procedure.input<boolean>().action(async ({ input }) => {
app.setLoginItemSettings({
openAtLogin: input,
})
}),
openSettingWindow: t.procedure.action(async () => createSettingWindow()),
getSystemFonts: t.procedure.action(
async (): Promise<string[]> =>
@ -28,18 +26,16 @@ export const settingRoute = {
require("font-list")
.getFonts()
.then((fonts) => {
resolve(fonts.map((font) => font.replaceAll("\"", "")))
resolve(fonts.map((font) => font.replaceAll('"', "")))
})
}),
),
getAppearance: t.procedure.action(async () => nativeTheme.themeSource),
setAppearance: t.procedure
.input<"light" | "dark" | "system">()
.action(async ({ input }) => {
nativeTheme.themeSource = input
setAppearance: t.procedure.input<"light" | "dark" | "system">().action(async ({ input }) => {
nativeTheme.themeSource = input
store.set("appearance", input)
}),
store.set("appearance", input)
}),
setDockBadge: t.procedure.input<number>().action(async ({ input }) => {
setDockCount(input)
}),

View File

@ -11,18 +11,11 @@ import type {
} from "builder-util-runtime"
import { HttpError, newError, parseXml } from "builder-util-runtime"
import { app } from "electron"
import type {
AppUpdater,
ResolvedUpdateFileInfo,
UpdateInfo,
} from "electron-updater"
import type { AppUpdater, ResolvedUpdateFileInfo, UpdateInfo } from "electron-updater"
import { CancellationToken } from "electron-updater"
import { BaseGitHubProvider } from "electron-updater/out/providers/GitHubProvider"
import type { ProviderRuntimeOptions } from "electron-updater/out/providers/Provider"
import {
parseUpdateInfo,
resolveFiles,
} from "electron-updater/out/providers/Provider"
import { parseUpdateInfo, resolveFiles } from "electron-updater/out/providers/Provider"
import * as semver from "semver"
import { isWindows } from "../env"
@ -72,18 +65,12 @@ export class CustomGitHubProvider extends BaseGitHubProvider<GithubUpdateInfo> {
)
if (!feedXml) {
throw new Error(
`Cannot find feed in the remote server (${this.baseUrl.href})`,
)
throw new Error(`Cannot find feed in the remote server (${this.baseUrl.href})`)
}
const feed = parseXml(feedXml)
// noinspection TypeScriptValidateJSTypes
let latestRelease = feed.element(
"entry",
false,
`No published versions on GitHub`,
)
let latestRelease = feed.element("entry", false, `No published versions on GitHub`)
let tag: string | null = null
try {
const currentChannel =
@ -99,15 +86,10 @@ export class CustomGitHubProvider extends BaseGitHubProvider<GithubUpdateInfo> {
)
}
const releaseTag = await this.getLatestTagByRelease(
currentChannel,
cancellationToken,
)
const releaseTag = await this.getLatestTagByRelease(currentChannel, cancellationToken)
for (const element of feed.getElements("entry")) {
// noinspection TypeScriptValidateJSTypes
const hrefElement = hrefRegExp.exec(
element.element("link").attribute("href"),
)
const hrefElement = hrefRegExp.exec(element.element("link").attribute("href"))
// If this is null then something is wrong and skip this release
if (hrefElement === null) continue
@ -116,8 +98,7 @@ export class CustomGitHubProvider extends BaseGitHubProvider<GithubUpdateInfo> {
const hrefTag = hrefElement[1]
// Get Channel from this release's tag
// If it is null, we believe it is stable version
const hrefChannel =
(semver.prerelease(hrefTag)?.[0] as string) || "stable"
const hrefChannel = (semver.prerelease(hrefTag)?.[0] as string) || "stable"
let isNextPreRelease = false
if (releaseTag) {
@ -134,18 +115,13 @@ export class CustomGitHubProvider extends BaseGitHubProvider<GithubUpdateInfo> {
}
} catch (e: any) {
throw newError(
`Cannot parse releases feed: ${
e.stack || e.message
},\nXML:\n${feedXml}`,
`Cannot parse releases feed: ${e.stack || e.message},\nXML:\n${feedXml}`,
"ERR_UPDATER_INVALID_RELEASE_FEED",
)
}
if (tag === null || tag === undefined) {
throw newError(
`No published versions on GitHub`,
"ERR_UPDATER_NO_PUBLISHED_VERSIONS",
)
throw newError(`No published versions on GitHub`, "ERR_UPDATER_NO_PUBLISHED_VERSIONS")
}
let rawData: string | null = null
@ -174,11 +150,9 @@ export class CustomGitHubProvider extends BaseGitHubProvider<GithubUpdateInfo> {
}
try {
const channel = this.updater.allowPrerelease ?
this.getCustomChannelName(
String(semver.prerelease(tag)?.[0] || "latest"),
) :
this.getDefaultChannelName()
const channel = this.updater.allowPrerelease
? this.getCustomChannelName(String(semver.prerelease(tag)?.[0] || "latest"))
: this.getDefaultChannelName()
rawData = await fetchData(channel)
} catch (e: any) {
if (this.updater.allowPrerelease) {
@ -227,7 +201,7 @@ export class CustomGitHubProvider extends BaseGitHubProvider<GithubUpdateInfo> {
const releasesStr = await this.httpRequest(
newUrlFromBase(`/repos${this.basePath}`, this.baseApiUrl),
{
"accept": "Accept: application/vnd.github+json",
accept: "Accept: application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
},
cancellationToken,
@ -244,8 +218,7 @@ export class CustomGitHubProvider extends BaseGitHubProvider<GithubUpdateInfo> {
}
const releaseTag = release.tag_name
const releaseChannel =
(semver.prerelease(releaseTag)?.[0] as string) || "stable"
const releaseChannel = (semver.prerelease(releaseTag)?.[0] as string) || "stable"
if (releaseChannel === currentChannel) {
return release.tag_name
}
@ -264,15 +237,14 @@ export class CustomGitHubProvider extends BaseGitHubProvider<GithubUpdateInfo> {
const isSquirrel = isSquirrelBuild()
// @ts-expect-error we should be able to modify the object
filteredUpdateInfo.files = updateInfo.files.filter((file) =>
isSquirrel ?
!file.url.includes("nsis.exe") :
file.url.includes("nsis.exe"),
isSquirrel ? !file.url.includes("nsis.exe") : file.url.includes("nsis.exe"),
)
}
// still replace space to - due to backward compatibility
return resolveFiles(filteredUpdateInfo, this.baseUrl, (p) =>
this.getBaseDownloadPath(filteredUpdateInfo.tag, p.replaceAll(" ", "-")))
this.getBaseDownloadPath(filteredUpdateInfo.tag, p.replaceAll(" ", "-")),
)
}
private getBaseDownloadPath(tag: string, fileName: string): string {
@ -306,9 +278,7 @@ export function computeReleaseNotes(
const releaseNotes: Array<ReleaseNoteInfo> = []
for (const release of feed.getElements("entry")) {
// noinspection TypeScriptValidateJSTypes
const versionRelease = /\/tag\/v?([^/]+)$/.exec(
release.element("link").attribute("href"),
)?.[1]
const versionRelease = /\/tag\/v?([^/]+)$/.exec(release.element("link").attribute("href"))?.[1]
if (versionRelease && semver.lt(currentVersion, versionRelease)) {
releaseNotes.push({
version: versionRelease,
@ -321,11 +291,7 @@ export function computeReleaseNotes(
// addRandomQueryToAvoidCaching is false by default because in most cases URL already contains version number,
// so, it makes sense only for Generic Provider for channel files
function newUrlFromBase(
pathname: string,
baseUrl: URL,
addRandomQueryToAvoidCaching = false,
): URL {
function newUrlFromBase(pathname: string, baseUrl: URL, addRandomQueryToAvoidCaching = false): URL {
const result = new URL(pathname, baseUrl)
// search is not propagated (search is an empty string if not specified)
const { search } = baseUrl

View File

@ -76,17 +76,16 @@ export const registerUpdater = async () => {
autoUpdater.autoInstallOnAppQuit = true
autoUpdater.autoRunAppAfterInstall = true
const feedUrl: Exclude<Parameters<typeof autoUpdater.setFeedURL>[0], string> =
{
channel,
// hack for custom provider
provider: "custom" as "github",
repo: "follow",
owner: "RSSNext",
releaseType: channel === "stable" ? "release" : "prerelease",
// @ts-expect-error hack for custom provider
updateProvider: CustomGitHubProvider,
}
const feedUrl: Exclude<Parameters<typeof autoUpdater.setFeedURL>[0], string> = {
channel,
// hack for custom provider
provider: "custom" as "github",
repo: "follow",
owner: "RSSNext",
releaseType: channel === "stable" ? "release" : "prerelease",
// @ts-expect-error hack for custom provider
updateProvider: CustomGitHubProvider,
}
logger.debug("auto-updater feed config", {
...feedUrl,
@ -119,9 +118,7 @@ export const registerUpdater = async () => {
const mainWindow = getMainWindow()
if (!mainWindow) return
const handlers = getRendererHandlers<RendererHandlers>(
mainWindow.webContents,
)
const handlers = getRendererHandlers<RendererHandlers>(mainWindow.webContents)
trackEvent("update_ready_to_install", {
version: e.version,

View File

@ -11,10 +11,7 @@ import { isDev, isMacOS, isWindows11 } from "./env"
import { getIconPath } from "./helper"
import { store } from "./lib/store"
import { logger } from "./logger"
import {
cancelPollingUpdateUnreadCount,
pollingUpdateUnreadCount,
} from "./tipc/dock"
import { cancelPollingUpdateUnreadCount, pollingUpdateUnreadCount } from "./tipc/dock"
const windows = {
settingWindow: null as BrowserWindow | null,
@ -69,7 +66,6 @@ export function createWindow(
backgroundMaterial: isWindows11 ? "mica" : undefined,
frame: true,
maximizable: !isWindows11,
} as Electron.BrowserWindowConstructorOptions)
break
}
@ -115,13 +111,9 @@ export function createWindow(
// HMR for renderer base on electron-vite cli.
// Load the remote URL for development or the local html file for production.
if (is.dev && process.env["ELECTRON_RENDERER_URL"]) {
window.loadURL(
process.env["ELECTRON_RENDERER_URL"] + (options?.extraPath || ""),
)
window.loadURL(process.env["ELECTRON_RENDERER_URL"] + (options?.extraPath || ""))
logger.log(
process.env["ELECTRON_RENDERER_URL"] + (options?.extraPath || ""),
)
logger.log(process.env["ELECTRON_RENDERER_URL"] + (options?.extraPath || ""))
} else {
const openPath = path.resolve(__dirname, "../renderer/index.html")
window.loadFile(openPath, {
@ -132,31 +124,24 @@ export function createWindow(
})
}
window.webContents.session.webRequest.onBeforeSendHeaders(
(details, callback) => {
const trueUrl =
process.env["VITE_IMGPROXY_URL"] &&
details.url.startsWith(process.env["VITE_IMGPROXY_URL"]) ?
decodeURIComponent(
window.webContents.session.webRequest.onBeforeSendHeaders((details, callback) => {
const trueUrl =
process.env["VITE_IMGPROXY_URL"] && details.url.startsWith(process.env["VITE_IMGPROXY_URL"])
? decodeURIComponent(
details.url.replace(
new RegExp(
`^${process.env["VITE_IMGPROXY_URL"]}/unsafe/\\d+x\\d+/`,
),
new RegExp(`^${process.env["VITE_IMGPROXY_URL"]}/unsafe/\\d+x\\d+/`),
"",
),
) :
details.url
const refererMatch = imageRefererMatches.find((item) =>
item.url.test(trueUrl),
)
callback({
requestHeaders: {
...details.requestHeaders,
Referer: refererMatch?.referer || trueUrl,
},
})
},
)
)
: details.url
const refererMatch = imageRefererMatches.find((item) => item.url.test(trueUrl))
callback({
requestHeaders: {
...details.requestHeaders,
Referer: refererMatch?.referer || trueUrl,
},
})
})
const selectionMenu = Menu.buildFromTemplate([
{ role: "copy", accelerator: "CmdOrCtrl+C" },

View File

@ -13,9 +13,7 @@ export const isWindows11 = isWindows && os.version().startsWith("Windows 11")
// Custom APIs for renderer
const api = {
canWindowBlur:
process.platform === "darwin" ||
(process.platform === "win32" && isWindows11),
canWindowBlur: process.platform === "darwin" || (process.platform === "win32" && isWindows11),
}
// Use `contextBridge` APIs to expose Electron APIs to

View File

@ -90,10 +90,7 @@ const AppSkeleton = () => {
}}
/>
<div className="relative size-full grow bg-theme-background">
<div
className="absolute inset-y-0 w-px bg-border"
style={{ left: entryColWidth }}
/>
<div className="absolute inset-y-0 w-px bg-border" style={{ left: entryColWidth }} />
</div>
</div>
)

View File

@ -1,10 +1,6 @@
import { createAtomHooks } from "@renderer/lib/jotai"
import { atom } from "jotai"
export const [, , useAppIsReady, , appIsReady, setAppIsReady] = createAtomHooks(
atom(false),
)
export const [, , useAppIsReady, , appIsReady, setAppIsReady] = createAtomHooks(atom(false))
export const [, , useAppSearchOpen, , , setAppSearchOpen] = createAtomHooks(
atom(false),
)
export const [, , useAppSearchOpen, , , setAppSearchOpen] = createAtomHooks(atom(false))

View File

@ -21,10 +21,7 @@ export const [
getCornerPlayerAtomValue,
setCornerPlayerAtomValue,
] = createAtomHooks<CornerPlayerAtomValue>(
atomWithStorage(
getStorageNS("corner-player"),
cornerPlayerInitialValue,
undefined,
{ getOnInit: true },
),
atomWithStorage(getStorageNS("corner-player"), cornerPlayerInitialValue, undefined, {
getOnInit: true,
}),
)

View File

@ -1,11 +1,5 @@
import { createAtomHooks } from "@renderer/lib/jotai"
import { atom } from "jotai"
export const [
,
,
useMainContainerElement,
,
getMainContainerElement,
setMainContainerElement,
] = createAtomHooks(atom<HTMLElement | null>(null))
export const [, , useMainContainerElement, , getMainContainerElement, setMainContainerElement] =
createAtomHooks(atom<HTMLElement | null>(null))

View File

@ -6,23 +6,18 @@ import { useCallback } from "react"
import { viewportAtom } from "../viewport"
export const useViewport = <T>(
selector: (value: ExtractAtomValue<typeof viewportAtom>) => T,
): T =>
useAtomValue(
selectAtom(
viewportAtom,
useCallback((atomValue) => selector(atomValue), []),
),
)
export const useIsMobile = () =>
useViewport(
useCallback((v: ExtractAtomValue<typeof viewportAtom>) => isMobile(v), []),
export const useViewport = <T>(selector: (value: ExtractAtomValue<typeof viewportAtom>) => T): T =>
useAtomValue(
selectAtom(
viewportAtom,
useCallback((atomValue) => selector(atomValue), []),
),
)
const isMobile = (v: ExtractAtomValue<typeof viewportAtom>) =>
(v.sm || v.md || !v.sm) && !v.lg
export const useIsMobile = () =>
useViewport(useCallback((v: ExtractAtomValue<typeof viewportAtom>) => isMobile(v), []))
const isMobile = (v: ExtractAtomValue<typeof viewportAtom>) => (v.sm || v.md || !v.sm) && !v.lg
export const currentIsMobile = () => {
const v = jotaiStore.get(viewportAtom)
return isMobile(v)

View File

@ -6,10 +6,9 @@ export enum NetworkStatus {
OFFLINE,
}
export const [, , useNetworkStatus, , getNetworkStatus, setNetworkStatus] =
createAtomHooks(
atom(navigator.onLine ? NetworkStatus.ONLINE : NetworkStatus.OFFLINE),
)
export const [, , useNetworkStatus, , getNetworkStatus, setNetworkStatus] = createAtomHooks(
atom(navigator.onLine ? NetworkStatus.ONLINE : NetworkStatus.OFFLINE),
)
export const [, , useApiStatus, , getApiStatus, setApiStatus] = createAtomHooks(
atom(NetworkStatus.ONLINE),
@ -22,9 +21,7 @@ export const subscribeNetworkStatus = () => {
window.addEventListener("online", handleOnline)
window.addEventListener("offline", handleOffline)
setNetworkStatus(
navigator.onLine ? NetworkStatus.ONLINE : NetworkStatus.OFFLINE,
)
setNetworkStatus(navigator.onLine ? NetworkStatus.ONLINE : NetworkStatus.OFFLINE)
return () => {
window.removeEventListener("online", handleOnline)

View File

@ -46,14 +46,9 @@ export const [
setAudioPlayerAtomValue,
useAudioPlayerAtomSelector,
] = createAtomHooks<PlayerAtomValue>(
atomWithStorage(
getStorageNS("player"),
playerInitialValue,
patchedLocalStorage,
{
getOnInit: true,
},
),
atomWithStorage(getStorageNS("player"), playerInitialValue, patchedLocalStorage, {
getOnInit: true,
}),
)
export const AudioPlayer = {
@ -64,9 +59,7 @@ export const AudioPlayer = {
get() {
return getAudioPlayerAtomValue()
},
mount(
v: Omit<PlayerAtomValue, "show" | "status" | "playedSeconds" | "duration">,
) {
mount(v: Omit<PlayerAtomValue, "show" | "status" | "playedSeconds" | "duration">) {
const curV = getAudioPlayerAtomValue()
if (!v.src || (curV.src === v.src && curV.status === "playing")) {
return
@ -98,14 +91,17 @@ export const AudioPlayer = {
}
const currentActionId = this.__currentActionId
return this.audio.play().then(() => {
if (currentActionId !== this.__currentActionId) return
setAudioPlayerAtomValue({
...getAudioPlayerAtomValue(),
status: "playing",
duration: this.audio.duration === Infinity ? 0 : this.audio.duration,
return this.audio
.play()
.then(() => {
if (currentActionId !== this.__currentActionId) return
setAudioPlayerAtomValue({
...getAudioPlayerAtomValue(),
status: "playing",
duration: this.audio.duration === Infinity ? 0 : this.audio.duration,
})
})
}).catch(noop)
.catch(noop)
},
teardown() {
this.currentTimeTimer && clearInterval(this.currentTimeTimer)

View File

@ -16,8 +16,8 @@ type Readability = {
const mergeObjectSetter =
<T>(setter: (prev: T) => void, getter: () => T) =>
(value: Partial<T>) =>
setter({ ...getter(), ...value })
(value: Partial<T>) =>
setter({ ...getter(), ...value })
export const [
,
@ -48,24 +48,17 @@ export const [
__setReadabilityStatus,
useReadabilityStatusSelector,
] = createAtomHooks(atom<Record<string, ReadabilityStatus>>({}))
export const setReadabilityStatus = mergeObjectSetter(
__setReadabilityStatus,
getReadabilityStatus,
)
export const setReadabilityStatus = mergeObjectSetter(__setReadabilityStatus, getReadabilityStatus)
export const useEntryIsInReadability = (entryId?: string) =>
useReadabilityStatusSelector(
(map) =>
entryId ? (map[entryId] ? isInReadability(map[entryId]) : false) : false,
(map) => (entryId ? (map[entryId] ? isInReadability(map[entryId]) : false) : false),
[entryId],
)
export const useEntryInReadabilityStatus = (entryId?: string) =>
useReadabilityStatusSelector(
(map) =>
entryId ?
map[entryId] || ReadabilityStatus.INITIAL :
ReadabilityStatus.INITIAL,
(map) => (entryId ? map[entryId] || ReadabilityStatus.INITIAL : ReadabilityStatus.INITIAL),
[entryId],
)

View File

@ -29,10 +29,7 @@ const noop = []
export const useReadonlyRouteSelector = <T>(
selector: (route: RouteAtom) => T,
deps: any[] = noop,
): T =>
useAtomValue(
useMemo(() => selectAtom(routeAtom, (route) => selector(route)), deps),
)
): T => useAtomValue(useMemo(() => selectAtom(routeAtom, (route) => selector(route)), deps))
// Vite HMR will create new router instance, but RouterProvider always stable
@ -40,7 +37,4 @@ const [, , , , navigate, setNavigate] = createAtomHooks(
atom<{ fn: NavigateFunction | null }>({ fn() {} }),
)
const getStableRouterNavigate = () => navigate().fn
export {
getStableRouterNavigate,
setNavigate,
}
export { getStableRouterNavigate, setNavigate }

View File

@ -35,11 +35,8 @@ export const {
settingAtom: __generalSettingAtom,
} = createSettingAtom("general", createDefaultSettings)
export const subscribeShouldUseIndexedDB = (
callback: (value: boolean) => void,
) =>
jotaiStore.sub(__generalSettingAtom, () =>
callback(getGeneralSettings().dataPersist))
export const subscribeShouldUseIndexedDB = (callback: (value: boolean) => void) =>
jotaiStore.sub(__generalSettingAtom, () => callback(getGeneralSettings().dataPersist))
export const generalServerSyncWhiteListKeys: (keyof GeneralSettings)[] = [
"appLaunchOnStartup",

View File

@ -22,17 +22,11 @@ export const createSettingAtom = <T extends object>(
settingKey: string,
createDefaultSettings: () => T,
) => {
const atom = atomWithStorage(
getStorageNS(settingKey),
createDefaultSettings(),
undefined,
{
getOnInit: true,
},
)
const atom = atomWithStorage(getStorageNS(settingKey), createDefaultSettings(), undefined, {
getOnInit: true,
})
const [, , useSettingValue, , getSettings, setSettings] =
createAtomHooks(atom)
const [, , useSettingValue, , getSettings, setSettings] = createAtomHooks(atom)
const initializeDefaultSettings = () => {
const currentSettings = getSettings()
@ -42,17 +36,16 @@ export const createSettingAtom = <T extends object>(
setSettings(newSettings)
}
const useSettingKey = <T extends keyof ReturnType<typeof getSettings>>(
key: T,
) => useAtomValue(useMemo(() => selectAtom(atom, (s) => s[key]), [key]))
const useSettingKey = <T extends keyof ReturnType<typeof getSettings>>(key: T) =>
useAtomValue(useMemo(() => selectAtom(atom, (s) => s[key]), [key]))
const useSettingSelector = <
T extends keyof ReturnType<typeof getSettings>,
S extends ReturnType<typeof getSettings>,
R = S[T],
>(
selector: (s: S) => R,
): R => {
selector: (s: S) => R,
): R => {
const stableSelector = useRefValue(selector)
return useAtomValue(
@ -114,22 +107,16 @@ export const createSettingAtom = <T extends object>(
}
export const createDefineSettingItem =
<T>(
_getSetting: () => T,
setSetting: (key: any, value: Partial<T>) => void,
) =>
<T>(_getSetting: () => T, setSetting: (key: any, value: Partial<T>) => void) =>
<K extends keyof T>(
key: K,
options: {
label: string
description?: string | JSX.Element
onChange?: (value: T[K]) => void
hide?: boolean
} & Omit<
SettingItem<any>,
"onChange" | "description" | "label" | "hide" | "key"
>,
): any => {
key: K,
options: {
label: string
description?: string | JSX.Element
onChange?: (value: T[K]) => void
hide?: boolean
} & Omit<SettingItem<any>, "onChange" | "description" | "label" | "hide" | "key">,
): any => {
const { label, description, onChange, hide, ...rest } = options
return {

View File

@ -47,6 +47,4 @@ viewAtom.onMount = () => {
setSidebarActiveView(view)
}
}
export const [, , useFeedColumnShow, , , setFeedColumnShow] = createAtomHooks(
atom(true),
)
export const [, , useFeedColumnShow, , , setFeedColumnShow] = createAtomHooks(atom(true))

View File

@ -2,12 +2,8 @@ import { createAtomHooks } from "@renderer/lib/jotai"
import { getStorageNS } from "@renderer/lib/ns"
import { atomWithStorage } from "jotai/utils"
export const [, , useUpdaterStatus, , , setUpdaterStatus] =
createAtomHooks(atomWithStorage(
getStorageNS("updater"),
false,
undefined,
{
getOnInit: true,
},
))
export const [, , useUpdaterStatus, , , setUpdaterStatus] = createAtomHooks(
atomWithStorage(getStorageNS("updater"), false, undefined, {
getOnInit: true,
}),
)

View File

@ -2,15 +2,7 @@ import type { User } from "@auth/core/types"
import { createAtomHooks } from "@renderer/lib/jotai"
import { atom } from "jotai"
export const [, , useWhoami, , whoami, setWhoami] = createAtomHooks(
atom<Nullable<User>>(null),
)
export const [, , useWhoami, , whoami, setWhoami] = createAtomHooks(atom<Nullable<User>>(null))
export const [
,
,
useLoginModalShow,
useSetLoginModalShow,
getLoginModalShow,
setLoginModalShow,
] = createAtomHooks(atom<boolean>(false))
export const [, , useLoginModalShow, useSetLoginModalShow, getLoginModalShow, setLoginModalShow] =
createAtomHooks(atom<boolean>(false))

View File

@ -4,28 +4,28 @@ export const viewportAtom = atom({
/**
* 640px
*/
"sm": false,
sm: false,
/**
* 768px
*/
"md": false,
md: false,
/**
* 1024px
*/
"lg": false,
lg: false,
/**
* 1280px
*/
"xl": false,
xl: false,
/**
* 1536px
*/
"2xl": false,
"h": 0,
"w": 0,
h: 0,
w: 0,
})

View File

@ -7,18 +7,9 @@ function Versions(): ReactNode {
if (!versions) return null
return (
<ul className="versions">
<li className="electron-version">
Electron v
{versions.electron}
</li>
<li className="chrome-version">
Chromium v
{versions.chrome}
</li>
<li className="node-version">
Node v
{versions.node}
</li>
<li className="electron-version">Electron v{versions.electron}</li>
<li className="chrome-version">Chromium v{versions.chrome}</li>
<li className="node-version">Node v{versions.node}</li>
</ul>
)
}

View File

@ -31,19 +31,12 @@ export const AppErrorBoundary: FC<
)
}
return (
<AppErrorBoundaryItem errorType={errorType}>
{children}
</AppErrorBoundaryItem>
)
return <AppErrorBoundaryItem errorType={errorType}>{children}</AppErrorBoundaryItem>
}
type ErrorFallbackProps = Parameters<FallbackRender>["0"]
export type AppErrorFallbackProps = ErrorFallbackProps & {}
const AppErrorBoundaryItem: FC<AppErrorBoundaryProps> = ({
errorType,
children,
}) => {
const AppErrorBoundaryItem: FC<AppErrorBoundaryProps> = ({ errorType, children }) => {
const fallbackRender = useCallback(
(fallbackProps: ErrorFallbackProps) =>
createElement(getErrorFallback(errorType), fallbackProps),

View File

@ -3,11 +3,7 @@ import { getNewIssueUrl } from "@renderer/lib/issues"
import { clearLocalPersistStoreData } from "@renderer/store/utils/clear"
import { captureException } from "@sentry/react"
import { useEffect, useRef } from "react"
import {
isRouteErrorResponse,
useNavigate,
useRouteError,
} from "react-router-dom"
import { isRouteErrorResponse, useNavigate, useRouteError } from "react-router-dom"
import { toast } from "sonner"
import { Button } from "../ui/button"
@ -16,18 +12,15 @@ import { PoweredByFooter } from "./PoweredByFooter"
export function ErrorElement() {
const error = useRouteError()
const navigate = useNavigate()
const message = isRouteErrorResponse(error) ?
`${error.status} ${error.statusText}` :
error instanceof Error ?
error.message :
JSON.stringify(error)
const message = isRouteErrorResponse(error)
? `${error.status} ${error.statusText}`
: error instanceof Error
? error.message
: JSON.stringify(error)
const stack = error instanceof Error ? error.stack : null
useEffect(() => {
console.error(
"Error handled by React Router default ErrorBoundary:",
error,
)
console.error("Error handled by React Router default ErrorBoundary:", error)
captureException(error)
}, [error])
@ -50,9 +43,7 @@ export function ErrorElement() {
<div className="drag-region fixed inset-x-0 top-0 h-12" />
<div className="center flex flex-col">
<i className="i-mgc-bug-cute-re size-12 text-red-400" />
<h2 className="mb-4 mt-12 text-2xl">
Sorry, {APP_NAME} has encountered an error
</h2>
<h2 className="mb-4 mt-12 text-2xl">Sorry, {APP_NAME} has encountered an error</h2>
</div>
<h3 className="text-xl">{message}</h3>
{import.meta.env.DEV && stack ? (
@ -62,10 +53,8 @@ export function ErrorElement() {
) : null}
<p className="my-8">
{APP_NAME}
{" "}
has a temporary problem, click the button below to try
reloading the app or another solution?
{APP_NAME} has a temporary problem, click the button below to try reloading the app or
another solution?
</p>
<div className="center gap-4">
@ -109,17 +98,7 @@ export const FallbackIssue = ({
className="ml-2 cursor-pointer text-theme-accent-500 duration-200 hover:text-accent"
href={getNewIssueUrl({
title: `Error: ${message}`,
body: [
"### Error",
"",
message,
"",
"### Stack",
"",
"```",
stack,
"```",
].join("\n"),
body: ["### Error", "", message, "", "### Stack", "", "```", stack, "```"].join("\n"),
label: "bug",
})}
target="_blank"

View File

@ -1,7 +1,4 @@
import {
useLayoutEffect,
useState,
} from "react"
import { useLayoutEffect, useState } from "react"
const NOT_RESOLVED = Symbol("NOT_RESOLVED")
export const ExPromise = <T,>({

View File

@ -1,7 +1,6 @@
import type { FC, ReactNode } from "react"
import { Fragment } from "react"
export const SafeFragment: FC<{ children: ReactNode }> = ({
children,
..._rest
}) => <Fragment>{children}</Fragment>
export const SafeFragment: FC<{ children: ReactNode }> = ({ children, ..._rest }) => (
<Fragment>{children}</Fragment>
)

View File

@ -6,9 +6,7 @@ type ImpressionProps = {
onTrack?: () => any
properties?: Record<string, any>
}
export const ImpressionView: Component<
{ shouldTrack?: boolean } & ImpressionProps
> = (props) => {
export const ImpressionView: Component<{ shouldTrack?: boolean } & ImpressionProps> = (props) => {
const { shouldTrack = true, ...rest } = props
if (!shouldTrack) {
return <>{props.children}</>

View File

@ -1,15 +1,11 @@
import type { FC, ReactNode } from "react"
import {
createElement,
useEffect,
useState,
} from "react"
import { createElement, useEffect, useState } from "react"
import { LoadingCircle } from "../ui/loading"
export const LoadRemixAsyncComponent: FC<{
loader: () => Promise<any>
Header: FC<{ loader: () => any, [key: string]: any }>
Header: FC<{ loader: () => any; [key: string]: any }>
}> = ({ loader, Header }) => {
const [loading, setLoading] = useState(true)

View File

@ -47,15 +47,11 @@ export const NotFound = () => {
You have come to a desert of knowledge where there is nothing.
</p>
<p>
Current path:
{" "}
<code>{location.pathname}</code>
Current path: <code>{location.pathname}</code>
</p>
<p>
<Button onClick={() => navigate("/")}>
Back to Home
</Button>
<Button onClick={() => navigate("/")}>Back to Home</Button>
</p>
</main>

View File

@ -5,10 +5,7 @@ import { Logo } from "../icons/logo"
export const PoweredByFooter: Component = ({ className }) => (
<footer className={cn("center mt-12 flex gap-2", className)}>
Powered by
{" "}
<Logo className="size-5" />
{" "}
Powered by <Logo className="size-5" />{" "}
<a
href={pkg.homepage}
className="cursor-pointer font-bold text-accent no-underline"

View File

@ -18,11 +18,8 @@ const ShadowDOMContext = createContext(false)
const MemoedDangerousHTMLStyle: FC<
{
children: string
} & React.DetailedHTMLProps<
React.StyleHTMLAttributes<HTMLStyleElement>,
HTMLStyleElement
> &
Record<string, unknown>
} & React.DetailedHTMLProps<React.StyleHTMLAttributes<HTMLStyleElement>, HTMLStyleElement> &
Record<string, unknown>
> = memo(({ children, ...rest }) => (
<style
{...rest}
@ -35,10 +32,7 @@ const MemoedDangerousHTMLStyle: FC<
/>
))
const weakMapElementKey = new WeakMap<
HTMLStyleElement | HTMLLinkElement,
string
>()
const weakMapElementKey = new WeakMap<HTMLStyleElement | HTMLLinkElement, string>()
const cloneStylesElement = (_mutationRecord?: MutationRecord) => {
const $styles = document.head.querySelectorAll("style").values()
const reactNodes = [] as ReactNode[]
@ -85,8 +79,7 @@ export const ShadowDOM: FC<PropsWithChildren<React.HTMLProps<HTMLElement>>> & {
} = (props) => {
const { ...rest } = props
const [stylesElements, setStylesElements] =
useState<ReactNode[]>(cloneStylesElement)
const [stylesElements, setStylesElements] = useState<ReactNode[]>(cloneStylesElement)
useLayoutEffect(() => {
const mutationObserver = new MutationObserver((e) => {
@ -139,12 +132,9 @@ function getLinkedStaticStyleSheets() {
.querySelectorAll("link[rel=stylesheet]")
.values() as unknown as HTMLLinkElement[]
const styleSheetMap = new WeakMap<
Element | ProcessingInstruction,
CSSStyleSheet
>()
const styleSheetMap = new WeakMap<Element | ProcessingInstruction, CSSStyleSheet>()
const cssArray = [] as { cssText: string, ref: HTMLLinkElement }[]
const cssArray = [] as { cssText: string; ref: HTMLLinkElement }[]
for (const sheet of document.styleSheets) {
if (!sheet.href) continue

View File

@ -36,8 +36,7 @@ export const FeedFoundCanBeFollowErrorFallback: FC<AppErrorFallbackProps> = ({
<div className="center gap-1 opacity-80">
<i className="i-mgc-information-cute-re" />
We found this feed in our database, you can follow it or go back to
homepage.
We found this feed in our database, you can follow it or go back to homepage.
</div>
<div className="center mt-12 gap-4">
<Button

View File

@ -6,10 +6,7 @@ import { Logo } from "../icons/logo"
import { Button } from "../ui/button"
import { CustomSafeError, useResetErrorWhenRouteChange } from "./helper"
export const FeedNotFoundErrorFallback: FC<AppErrorFallbackProps> = ({
resetError,
error,
}) => {
export const FeedNotFoundErrorFallback: FC<AppErrorFallbackProps> = ({ resetError, error }) => {
if (!(error instanceof FeedNotFound)) {
throw error
}

View File

@ -31,18 +31,15 @@ export const ModalErrorFallback: FC<AppErrorFallbackProps> = (props) => {
) : null}
<p className="my-8">
The App has a temporary problem, click the button below to try
reloading the app or another solution?
The App has a temporary problem, click the button below to try reloading the app or
another solution?
</p>
<div className="center gap-4">
<Button onClick={() => modal.dismiss()} variant="outline">
Close Modal
</Button>
<Button
onClick={() => window.location.reload()}
variant="outline"
>
<Button onClick={() => window.location.reload()} variant="outline">
Reload
</Button>
</div>

View File

@ -23,8 +23,8 @@ export const PageErrorFallback: FC<AppErrorFallbackProps> = (props) => {
) : null}
<p className="my-8">
The App has a temporary problem, click the button below to try
reloading the app or another solution?
The App has a temporary problem, click the button below to try reloading the app or
another solution?
</p>
<div className="center gap-4">
@ -32,10 +32,7 @@ export const PageErrorFallback: FC<AppErrorFallbackProps> = (props) => {
Retry
</Button>
<Button
onClick={() => window.location.reload()}
variant="outline"
>
<Button onClick={() => window.location.reload()} variant="outline">
Reload
</Button>
</div>

View File

@ -1,9 +1,7 @@
import { useEffect, useRef } from "react"
import { useLocation } from "react-router-dom"
export const parseError = (
error: unknown,
): { message?: string, stack?: string } => {
export const parseError = (error: unknown): { message?: string; stack?: string } => {
if (error instanceof Error) {
return {
message: error.message,
@ -28,7 +26,9 @@ export const useResetErrorWhenRouteChange = (resetError: () => void) => {
const currentPathnameRef = useRef(location.pathname)
const onceRef = useRef(false)
useEffect(() => {
if (onceRef.current) { return }
if (onceRef.current) {
return
}
if (currentPathnameRef.current !== location.pathname) {
resetError()
onceRef.current = true

View File

@ -19,5 +19,4 @@ export const ErrorFallbackMap = {
[ErrorComponentType.FeedNotFound]: FeedNotFoundErrorFallback,
}
export const getErrorFallback = (type: ErrorComponentType) =>
ErrorFallbackMap[type]
export const getErrorFallback = (type: ErrorComponentType) => ErrorFallbackMap[type]

View File

@ -17,7 +17,7 @@ const getFeedIconSrc = ({
src?: string
siteUrl?: string
fallback?: boolean
proxy?: { height: number, width: number }
proxy?: { height: number; width: number }
} = {}) => {
// src ? getProxyUrl(src) : "";
if (src) {
@ -91,8 +91,7 @@ export function FeedIcon({
}
const colors = useMemo(
() =>
getColorScheme(stringToHue(feed?.title || feed?.url || siteUrl!), true),
() => getColorScheme(stringToHue(feed?.title || feed?.url || siteUrl!), true),
[feed?.title, feed?.url, siteUrl],
)
let ImageElement: ReactNode
@ -111,11 +110,7 @@ export function FeedIcon({
finalSrc = src
ImageElement = (
<PlatformIcon
url={siteUrl}
style={sizeStyle}
className={cn("center mr-2", className)}
>
<PlatformIcon url={siteUrl} style={sizeStyle} className={cn("center mr-2", className)}>
<img style={sizeStyle} />
</PlatformIcon>
)
@ -128,11 +123,7 @@ export function FeedIcon({
height: size * 2,
})
ImageElement = (
<PlatformIcon
url={image}
style={sizeStyle}
className={cn("center mr-2", className)}
>
<PlatformIcon url={image} style={sizeStyle} className={cn("center mr-2", className)}>
<img className={cn("mr-2", className)} style={sizeStyle} />
</PlatformIcon>
)
@ -166,9 +157,7 @@ export function FeedIcon({
break
}
default: {
ImageElement = (
<i className="i-mgc-link-cute-re mr-2 shrink-0" style={sizeStyle} />
)
ImageElement = <i className="i-mgc-link-cute-re mr-2 shrink-0" style={sizeStyle} />
break
}
}
@ -215,19 +204,11 @@ export function FeedIcon({
// Else
return (
<Avatar className="shrink-0">
<AvatarImage
className="duration-200 animate-in fade-in-0"
asChild
src={finalSrc}
>
<AvatarImage className="duration-200 animate-in fade-in-0" asChild src={finalSrc}>
{ImageElement}
</AvatarImage>
<AvatarFallback>
<div
className={cn("mr-2", className)}
style={sizeStyle}
data-placeholder={finalSrc}
/>
<div className={cn("mr-2", className)} style={sizeStyle} data-placeholder={finalSrc} />
</AvatarFallback>
</Avatar>
)

View File

@ -2,10 +2,7 @@ import { whoami } from "@renderer/atoms/user"
import { cn } from "@renderer/lib/utils"
import { usePresentUserProfileModal } from "@renderer/modules/profile/hooks"
import type { MotionValue } from "framer-motion"
import {
useMotionValueEvent,
useScroll,
} from "framer-motion"
import { useMotionValueEvent, useScroll } from "framer-motion"
import type { FC } from "react"
import { cloneElement, useState } from "react"
import { Link } from "react-router-dom"
@ -76,14 +73,9 @@ const HoverableLink: FC<{
<Link
to={href}
target={href.startsWith("http") ? "_blank" : undefined}
className={cn(
"group center flex gap-3 duration-200 hover:text-accent",
className,
)}
className={cn("group center flex gap-3 duration-200 hover:text-accent", className)}
>
<span>
{cloneElement(icon, { className: `size-3 ${icon.props.className}` })}
</span>
<span>{cloneElement(icon, { className: `size-3 ${icon.props.className}` })}</span>
<span className="inline-flex h-[1.5em] flex-col overflow-hidden leading-[1.5em]">
<span className="inline-flex flex-col gap-2 duration-200 group-hover:translate-y-[calc(-50%-0.25em)]">
<span>{label}</span>

View File

@ -2,6 +2,17 @@ import type { SVGProps } from "react"
export function PhCloudX(props: SVGProps<SVGSVGElement>) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 256 256" {...props}><path fill="currentColor" d="M160 40a88.09 88.09 0 0 0-78.71 48.67A64 64 0 1 0 72 216h88a88 88 0 0 0 0-176m0 160H72a48 48 0 0 1 0-96c1.1 0 2.2 0 3.29.11A88 88 0 0 0 72 128a8 8 0 0 0 16 0a72 72 0 1 1 72 72m29.66-82.34L171.31 136l18.35 18.34a8 8 0 0 1-11.32 11.32L160 147.31l-18.34 18.35a8 8 0 0 1-11.32-11.32L148.69 136l-18.35-18.34a8 8 0 0 1 11.32-11.32L160 124.69l18.34-18.35a8 8 0 0 1 11.32 11.32" /></svg>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 256 256"
{...props}
>
<path
fill="currentColor"
d="M160 40a88.09 88.09 0 0 0-78.71 48.67A64 64 0 1 0 72 216h88a88 88 0 0 0 0-176m0 160H72a48 48 0 0 1 0-96c1.1 0 2.2 0 3.29.11A88 88 0 0 0 72 128a8 8 0 0 0 16 0a72 72 0 1 1 72 72m29.66-82.34L171.31 136l18.35 18.34a8 8 0 0 1-11.32 11.32L160 147.31l-18.34 18.35a8 8 0 0 1-11.32-11.32L148.69 136l-18.35-18.34a8 8 0 0 1 11.32-11.32L160 124.69l18.34-18.35a8 8 0 0 1 11.32 11.32"
/>
</svg>
)
}

View File

@ -1,10 +1,5 @@
export const FollowIcon: Component = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 344 330"
fill="none"
{...props}
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 344 330" fill="none" {...props}>
<g fill="currentColor" clipPath="url(#a)">
<path d="M297.639 0H88.19C62.862 0 42.33 20.514 42.33 45.82c0 25.305 20.532 45.819 45.86 45.819H297.64c25.328 0 45.861-20.514 45.861-45.82C343.5 20.514 322.967 0 297.639 0ZM185.96 119.13H46.361C21.033 119.13.5 139.644.5 164.95c0 25.305 20.533 45.819 45.861 45.819H185.96c25.328 0 45.861-20.514 45.861-45.819 0-25.306-20.533-45.82-45.861-45.82ZM199.466 284.181c0-25.306-20.533-45.82-45.861-45.82-25.328 0-45.861 20.514-45.861 45.82 0 25.305 20.533 45.819 45.861 45.819 25.328 0 45.861-20.514 45.861-45.819Z" />
</g>

View File

@ -1,50 +1,29 @@
import { forwardRef } from "react"
export const Logo = forwardRef<SVGSVGElement, React.SVGProps<SVGSVGElement>>(
(props, ref) => (
<svg
{...props}
ref={ref}
viewBox="0 0 145 145"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<mask
id="mask0_54_296"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="145"
height="145"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M144.491 45.4541C144.491 43.75 144.492 42.0456 144.481 40.3413C144.473 38.9056 144.456 37.4702 144.417 36.0352C144.333 32.9075 144.148 29.753 143.592 26.6601C143.028 23.5227 142.107 20.6027 140.655 17.752C139.228 14.9501 137.364 12.3862 135.139 10.1633C132.915 7.94053 130.351 6.07747 127.548 4.65117C124.694 3.19908 121.771 2.27818 118.63 1.71424C115.537 1.15884 112.381 0.974699 109.254 0.890204C107.818 0.851354 106.382 0.834978 104.945 0.826093C103.24 0.81564 101.535 0.816511 99.8296 0.816511L80.0305 0.745605H65.2222L45.7734 0.816511C44.0649 0.816511 42.3564 0.81564 40.6479 0.826093C39.2085 0.834978 37.7698 0.851354 36.331 0.890204C33.196 0.974699 30.0335 1.15902 26.933 1.71511C23.7879 2.27887 20.8604 3.19943 18.0029 4.65047C15.194 6.07694 12.6239 7.94018 10.3953 10.1633C8.1671 12.386 6.29934 14.9494 4.86956 17.7508C3.41382 20.6028 2.49083 23.5246 1.92533 26.6638C1.36854 29.7554 1.18405 32.9089 1.09921 36.0352C1.06053 37.4704 1.04381 38.9057 1.03509 40.3413C1.02464 42.0458 0.937012 44.163 0.937012 45.8674L0.937534 65.0468L0.937012 80.0136L1.02551 99.6005C1.02551 101.307 1.02482 103.014 1.03509 104.72C1.04381 106.158 1.06053 107.595 1.09938 109.032C1.18405 112.164 1.36889 115.323 1.9262 118.42C2.49153 121.562 3.41434 124.486 4.86886 127.34C6.29882 130.146 8.16692 132.713 10.3953 134.939C12.6237 137.165 15.1932 139.031 18.0015 140.459C20.8607 141.913 23.7896 142.835 26.9366 143.4C30.0357 143.956 33.1972 144.141 36.331 144.225C37.7698 144.264 39.2087 144.281 40.648 144.289C42.3566 144.3 44.0649 144.299 45.7734 144.299L65.398 144.3H80.243L99.8296 144.299C101.535 144.299 103.24 144.3 104.945 144.289C106.382 144.281 107.818 144.264 109.254 144.225C112.383 144.141 115.539 143.956 118.634 143.399C121.773 142.835 124.694 141.913 127.547 140.46C130.35 139.031 132.915 137.166 135.139 134.939C137.363 132.714 139.227 130.147 140.654 127.341C142.107 124.486 143.028 121.56 143.593 118.416C144.148 115.321 144.333 112.163 144.417 109.032C144.456 107.595 144.473 106.158 144.481 104.72C144.492 103.014 144.491 101.307 144.491 99.6005C144.491 99.6005 144.49 80.3594 144.49 80.0136V65.0311C144.49 64.7757 144.491 45.4541 144.491 45.4541Z"
fill="white"
/>
</mask>
<g mask="url(#mask0_54_296)">
<rect
x="0.414551"
y="-6.74585"
width="149.477"
height="158.537"
fill="#FF5C00"
/>
<path
d="M103.609 36.6948H53.5823C47.5327 36.6948 42.6284 41.5946 42.6284 47.6388C42.6284 53.683 47.5327 58.5828 53.5823 58.5828H103.609C109.659 58.5828 114.563 53.683 114.563 47.6388C114.563 41.5946 109.659 36.6948 103.609 36.6948Z"
fill="white"
/>
<path
d="M76.9348 65.1489H43.5916C37.5419 65.1489 32.6377 70.0487 32.6377 76.0929C32.6377 82.1371 37.5419 87.0369 43.5916 87.0369H76.9348C82.9845 87.0369 87.8888 82.1371 87.8888 76.0929C87.8888 70.0487 82.9845 65.1489 76.9348 65.1489Z"
fill="white"
/>
<path
d="M80.1608 104.571C80.1608 98.5272 75.2565 93.6274 69.2068 93.6274C63.1572 93.6274 58.2529 98.5272 58.2529 104.571C58.2529 110.616 63.1572 115.515 69.2068 115.515C75.2565 115.515 80.1608 110.616 80.1608 104.571Z"
fill="white"
/>
</g>
</svg>
),
)
export const Logo = forwardRef<SVGSVGElement, React.SVGProps<SVGSVGElement>>((props, ref) => (
<svg {...props} ref={ref} viewBox="0 0 145 145" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_54_296" maskUnits="userSpaceOnUse" x="0" y="0" width="145" height="145">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M144.491 45.4541C144.491 43.75 144.492 42.0456 144.481 40.3413C144.473 38.9056 144.456 37.4702 144.417 36.0352C144.333 32.9075 144.148 29.753 143.592 26.6601C143.028 23.5227 142.107 20.6027 140.655 17.752C139.228 14.9501 137.364 12.3862 135.139 10.1633C132.915 7.94053 130.351 6.07747 127.548 4.65117C124.694 3.19908 121.771 2.27818 118.63 1.71424C115.537 1.15884 112.381 0.974699 109.254 0.890204C107.818 0.851354 106.382 0.834978 104.945 0.826093C103.24 0.81564 101.535 0.816511 99.8296 0.816511L80.0305 0.745605H65.2222L45.7734 0.816511C44.0649 0.816511 42.3564 0.81564 40.6479 0.826093C39.2085 0.834978 37.7698 0.851354 36.331 0.890204C33.196 0.974699 30.0335 1.15902 26.933 1.71511C23.7879 2.27887 20.8604 3.19943 18.0029 4.65047C15.194 6.07694 12.6239 7.94018 10.3953 10.1633C8.1671 12.386 6.29934 14.9494 4.86956 17.7508C3.41382 20.6028 2.49083 23.5246 1.92533 26.6638C1.36854 29.7554 1.18405 32.9089 1.09921 36.0352C1.06053 37.4704 1.04381 38.9057 1.03509 40.3413C1.02464 42.0458 0.937012 44.163 0.937012 45.8674L0.937534 65.0468L0.937012 80.0136L1.02551 99.6005C1.02551 101.307 1.02482 103.014 1.03509 104.72C1.04381 106.158 1.06053 107.595 1.09938 109.032C1.18405 112.164 1.36889 115.323 1.9262 118.42C2.49153 121.562 3.41434 124.486 4.86886 127.34C6.29882 130.146 8.16692 132.713 10.3953 134.939C12.6237 137.165 15.1932 139.031 18.0015 140.459C20.8607 141.913 23.7896 142.835 26.9366 143.4C30.0357 143.956 33.1972 144.141 36.331 144.225C37.7698 144.264 39.2087 144.281 40.648 144.289C42.3566 144.3 44.0649 144.299 45.7734 144.299L65.398 144.3H80.243L99.8296 144.299C101.535 144.299 103.24 144.3 104.945 144.289C106.382 144.281 107.818 144.264 109.254 144.225C112.383 144.141 115.539 143.956 118.634 143.399C121.773 142.835 124.694 141.913 127.547 140.46C130.35 139.031 132.915 137.166 135.139 134.939C137.363 132.714 139.227 130.147 140.654 127.341C142.107 124.486 143.028 121.56 143.593 118.416C144.148 115.321 144.333 112.163 144.417 109.032C144.456 107.595 144.473 106.158 144.481 104.72C144.492 103.014 144.491 101.307 144.491 99.6005C144.491 99.6005 144.49 80.3594 144.49 80.0136V65.0311C144.49 64.7757 144.491 45.4541 144.491 45.4541Z"
fill="white"
/>
</mask>
<g mask="url(#mask0_54_296)">
<rect x="0.414551" y="-6.74585" width="149.477" height="158.537" fill="#FF5C00" />
<path
d="M103.609 36.6948H53.5823C47.5327 36.6948 42.6284 41.5946 42.6284 47.6388C42.6284 53.683 47.5327 58.5828 53.5823 58.5828H103.609C109.659 58.5828 114.563 53.683 114.563 47.6388C114.563 41.5946 109.659 36.6948 103.609 36.6948Z"
fill="white"
/>
<path
d="M76.9348 65.1489H43.5916C37.5419 65.1489 32.6377 70.0487 32.6377 76.0929C32.6377 82.1371 37.5419 87.0369 43.5916 87.0369H76.9348C82.9845 87.0369 87.8888 82.1371 87.8888 76.0929C87.8888 70.0487 82.9845 65.1489 76.9348 65.1489Z"
fill="white"
/>
<path
d="M80.1608 104.571C80.1608 98.5272 75.2565 93.6274 69.2068 93.6274C63.1572 93.6274 58.2529 98.5272 58.2529 104.571C58.2529 110.616 63.1572 115.515 69.2068 115.515C75.2565 115.515 80.1608 110.616 80.1608 104.571Z"
fill="white"
/>
</g>
</svg>
))

View File

@ -23,8 +23,7 @@ export type Suggestion = {
name: string
value: string
}
export interface AutocompleteProps
extends React.InputHTMLAttributes<HTMLInputElement> {
export interface AutocompleteProps extends React.InputHTMLAttributes<HTMLInputElement> {
suggestions: Suggestion[]
renderSuggestion?: (suggestion: Suggestion) => any
@ -59,11 +58,8 @@ export const Autocomplete = forwardRef<HTMLInputElement, AutocompleteProps>(
},
forwardedRef,
) => {
const [filterableSuggestions, setFilterableSuggestions] =
useState(suggestions)
const [inputValue, setInputValue] = useState(
inputProps.value || inputProps.defaultValue || "",
)
const [filterableSuggestions, setFilterableSuggestions] = useState(suggestions)
const [inputValue, setInputValue] = useState(inputProps.value || inputProps.defaultValue || "")
const doFilter = useEventCallback(() => {
const fuse = new Fuse(suggestions, {
@ -101,15 +97,16 @@ export const Autocomplete = forwardRef<HTMLInputElement, AutocompleteProps>(
setIsOpen(false)
})
const handleInputKeyDown: React.KeyboardEventHandler<HTMLInputElement> =
useEventCallback((e) => {
const handleInputKeyDown: React.KeyboardEventHandler<HTMLInputElement> = useEventCallback(
(e) => {
if (e.key === "Enter") {
e.preventDefault()
onConfirm?.((e.target as HTMLInputElement).value)
setIsOpen(false)
}
inputProps.onKeyDown?.(e)
})
},
)
const inputRef = useRef<HTMLInputElement>(null)
useImperativeHandle(forwardedRef, () => inputRef.current!)
@ -122,18 +119,14 @@ export const Autocomplete = forwardRef<HTMLInputElement, AutocompleteProps>(
if (!$input) return
const handleKeyDown = (e: KeyboardEvent) => {
const currentActiveIndex = currentActiveIndexRef.current
const filterableSuggestionsLength =
filterableSuggestionsRef.current.length
const filterableSuggestionsLength = filterableSuggestionsRef.current.length
const filterableSuggestions = filterableSuggestionsRef.current
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
e.preventDefault()
const nextIndex =
currentActiveIndex +
(e.key === "ArrowDown" ? 1 : e.key === "ArrowUp" ? -1 : 0)
setCurrentActiveIndex(
Math.max(0, Math.min(nextIndex, filterableSuggestionsLength - 1)),
)
currentActiveIndex + (e.key === "ArrowDown" ? 1 : e.key === "ArrowUp" ? -1 : 0)
setCurrentActiveIndex(Math.max(0, Math.min(nextIndex, filterableSuggestionsLength - 1)))
} else if (
(e.key === "Enter" || e.key === "Tab") &&
currentActiveIndex >= 0 &&
@ -220,19 +213,16 @@ export const Autocomplete = forwardRef<HTMLInputElement, AutocompleteProps>(
}, [])}
style={merge(
{},
portal && inputRect ?
{
portal && inputRect
? {
width: `${inputRect.width}px`,
left: `${inputRect.x}px`,
top: `${inputRect.y + inputRect.height}px`,
bottom: dropdownPos === "up" ? 0 : undefined,
transform:
dropdownPos === "up" ?
`translateY(-${dropdownHeight}px)` :
undefined,
transform: dropdownPos === "up" ? `translateY(-${dropdownHeight}px)` : undefined,
maxHeight,
} :
{},
}
: {},
)}
>
<ul
@ -258,8 +248,7 @@ export const Autocomplete = forwardRef<HTMLInputElement, AutocompleteProps>(
className={cn(
"cursor-default px-4 py-1.5 text-sm",
currentActiveIndex === index &&
"bg-theme-item-hover dark:bg-neutral-800",
currentActiveIndex === index && "bg-theme-item-hover dark:bg-neutral-800",
)}
key={suggestion.value}
onMouseDown={handleClick}
@ -272,11 +261,10 @@ export const Autocomplete = forwardRef<HTMLInputElement, AutocompleteProps>(
</ul>
</div>
)
const handleFocus: React.FocusEventHandler<HTMLInputElement> =
useEventCallback((e) => {
setIsOpen(true)
inputProps.onFocus?.(e)
})
const handleFocus: React.FocusEventHandler<HTMLInputElement> = useEventCallback((e) => {
setIsOpen(true)
inputProps.onFocus?.(e)
})
return (
<div className={cn("pointer-events-auto relative", wrapperClassName)}>

View File

@ -35,8 +35,7 @@ export const AutoResizeHeight: React.FC<AnimateChangeInHeightProps> = ({
const style = getComputedStyle(target)
const marginHeight =
Number.parseFloat(style.marginTop) +
Number.parseFloat(style.marginBottom)
Number.parseFloat(style.marginTop) + Number.parseFloat(style.marginBottom)
// add margin top
setHeight(observedHeight + marginHeight)
})
@ -55,12 +54,12 @@ export const AutoResizeHeight: React.FC<AnimateChangeInHeightProps> = ({
initial={false}
animate={{ height }}
transition={
spring ?
{
spring
? {
...softSpringPreset,
duration,
} :
{
}
: {
duration,
}
}

View File

@ -8,10 +8,7 @@ const Avatar = React.forwardRef<
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn(
"relative flex size-10 shrink-0 overflow-hidden rounded-full",
className,
)}
className={cn("relative flex size-10 shrink-0 overflow-hidden rounded-full", className)}
{...props}
/>
))
@ -35,10 +32,7 @@ const AvatarFallback = React.forwardRef<
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
"flex size-full items-center justify-center rounded-full bg-muted",
className,
)}
className={cn("flex size-full items-center justify-center rounded-full bg-muted", className)}
{...props}
/>
))

View File

@ -22,8 +22,8 @@ const Win32Material: Props = ({ className, children, ...rest }) => (
{children}
</div>
)
export const WindowUnderBlur: Props = SYSTEM_CAN_UNDER_BLUR_WINDOW ?
(props) => {
export const WindowUnderBlur: Props = SYSTEM_CAN_UNDER_BLUR_WINDOW
? (props) => {
const opaqueSidebar = useUISettingKey("opaqueSidebar")
if (opaqueSidebar) {
return <Noop {...props} />
@ -43,5 +43,5 @@ export const WindowUnderBlur: Props = SYSTEM_CAN_UNDER_BLUR_WINDOW ?
return <Noop {...props} />
}
}
} :
Noop
}
: Noop

View File

@ -9,12 +9,7 @@ import type { OptionsOrDependencyArray } from "react-hotkeys-hook/dist/types"
import { KbdCombined } from "../kbd/Kbd"
import { LoadingCircle } from "../loading"
import {
Tooltip,
TooltipContent,
TooltipPortal,
TooltipTrigger,
} from "../tooltip"
import { Tooltip, TooltipContent, TooltipPortal, TooltipTrigger } from "../tooltip"
import { styledButtonVariant } from "./variants"
export interface BaseButtonProps {
@ -56,12 +51,7 @@ export const ActionButton = React.forwardRef<
return (
<>
{shortcut && (
<HotKeyTrigger
shortcut={shortcut}
fn={() => buttonRef.current?.click()}
/>
)}
{shortcut && <HotKeyTrigger shortcut={shortcut} fn={() => buttonRef.current?.click()} />}
<Tooltip disableHoverableContent>
<TooltipTrigger asChild>
<button
@ -80,26 +70,21 @@ export const ActionButton = React.forwardRef<
disabled={disabled}
{...rest}
>
{typeof icon === "function" ?
React.createElement(icon, {
className: "size-4 grayscale text-current",
}) :
icon}
{typeof icon === "function"
? React.createElement(icon, {
className: "size-4 grayscale text-current",
})
: icon}
{children}
</button>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent
className="flex items-center gap-1"
side={tooltipSide ?? "bottom"}
>
<TooltipContent className="flex items-center gap-1" side={tooltipSide ?? "bottom"}>
{tooltip}
{!!shortcut && (
<div className="ml-1">
<KbdCombined className="text-foreground/80">
{shortcut}
</KbdCombined>
<KbdCombined className="text-foreground/80">{shortcut}</KbdCombined>
</div>
)}
</TooltipContent>
@ -125,21 +110,20 @@ const HotKeyTrigger = ({
})
return null
}
export const MotionButtonBase = React.forwardRef<
HTMLButtonElement,
HTMLMotionProps<"button">
>(({ children, ...rest }, ref) => (
<m.button
layout="size"
initial
whileFocus={{ scale: 1.02 }}
whileTap={{ scale: 0.95 }}
{...rest}
ref={ref}
>
{children}
</m.button>
))
export const MotionButtonBase = React.forwardRef<HTMLButtonElement, HTMLMotionProps<"button">>(
({ children, ...rest }, ref) => (
<m.button
layout="size"
initial
whileFocus={{ scale: 1.02 }}
whileTap={{ scale: 0.95 }}
{...rest}
ref={ref}
>
{children}
</m.button>
),
)
MotionButtonBase.displayName = "MotionButtonBase"
@ -147,64 +131,55 @@ export const Button = React.forwardRef<
HTMLButtonElement,
React.PropsWithChildren<
Omit<HTMLMotionProps<"button">, "children"> &
BaseButtonProps &
VariantProps<typeof styledButtonVariant> & {
buttonClassName?: string
}
BaseButtonProps &
VariantProps<typeof styledButtonVariant> & {
buttonClassName?: string
}
>
>(
(
{ className, buttonClassName, isLoading, variant, status, ...props },
ref,
) => {
const handleClick: React.MouseEventHandler<HTMLButtonElement> =
React.useCallback(
(e) => {
if (isLoading || props.disabled) {
e.preventDefault()
return
}
>(({ className, buttonClassName, isLoading, variant, status, ...props }, ref) => {
const handleClick: React.MouseEventHandler<HTMLButtonElement> = React.useCallback(
(e) => {
if (isLoading || props.disabled) {
e.preventDefault()
return
}
props.onClick?.(e)
},
[isLoading, props],
)
return (
<MotionButtonBase
ref={ref}
className={cn(
styledButtonVariant({
variant,
status: isLoading || props.disabled ? "disabled" : undefined,
}),
className,
buttonClassName,
props.onClick?.(e)
},
[isLoading, props],
)
return (
<MotionButtonBase
ref={ref}
className={cn(
styledButtonVariant({
variant,
status: isLoading || props.disabled ? "disabled" : undefined,
}),
className,
buttonClassName,
)}
{...props}
onClick={handleClick}
>
<m.span className="center">
{isLoading && (
<m.span className="center">
<LoadingCircle size="small" className="center mr-2" />
</m.span>
)}
{...props}
onClick={handleClick}
>
<m.span className="center">
{isLoading && (
<m.span className="center">
<LoadingCircle size="small" className="center mr-2" />
</m.span>
)}
<m.span className={cn("center", className)}>{props.children}</m.span>
</m.span>
</MotionButtonBase>
)
},
)
<m.span className={cn("center", className)}>{props.children}</m.span>
</m.span>
</MotionButtonBase>
)
})
export const IconButton = React.forwardRef<
HTMLButtonElement,
React.DetailedHTMLProps<
React.ButtonHTMLAttributes<HTMLButtonElement>,
HTMLButtonElement
> &
React.PropsWithChildren<{
icon: React.JSX.Element
}>
React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement> &
React.PropsWithChildren<{
icon: React.JSX.Element
}>
>((props, ref) => {
const { icon, ...rest } = props
return (
@ -233,9 +208,7 @@ export const IconButton = React.forwardRef<
),
})}
</span>
<span className="duration-200 group-hover:opacity-0">
{props.children}
</span>
<span className="duration-200 group-hover:opacity-0">{props.children}</span>
</button>
)
})

View File

@ -57,10 +57,7 @@ export const styledButtonVariant = cva(
"hover:contrast-[1.10] active:contrast-125",
"focus:text-accent focus:outline-none",
),
ghost: cn(
"px-1.5 font-semibold",
"hover:bg-theme-button-hover",
),
ghost: cn("px-1.5 font-semibold", "hover:bg-theme-button-hover"),
},
},

View File

@ -1,78 +1,55 @@
import { cn } from "@renderer/lib/utils"
import * as React from "react"
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-lg border bg-card text-card-foreground",
className,
)}
{...props}
/>
))
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("rounded-lg border bg-card text-card-foreground", className)}
{...props}
/>
),
)
Card.displayName = "Card"
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
))
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} />
),
)
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn(
"text-2xl font-semibold leading-none tracking-tight",
className,
)}
{...props}
/>
))
const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn("text-2xl font-semibold leading-none tracking-tight", className)}
{...props}
/>
),
)
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
<p ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
),
)
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
))
const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn("flex items-center p-6 pt-0", className)} {...props} />
),
)
CardFooter.displayName = "CardFooter"
export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle }

View File

@ -8,17 +8,17 @@ const Checkbox = React.forwardRef<
onCheckedChange?: (checked: boolean) => void
}
>(({ className, onCheckedChange, ...props }, ref) => (
<input
type="checkbox"
ref={ref}
className={cn("checkbox", className)}
{...props}
onChange={(event) => {
onCheckedChange?.(event.target.checked)
props.onChange?.(event)
}}
/>
))
<input
type="checkbox"
ref={ref}
className={cn("checkbox", className)}
{...props}
onChange={(event) => {
onCheckedChange?.(event.target.checked)
props.onChange?.(event)
}}
/>
))
Checkbox.displayName = CheckboxPrimitive.Root.displayName
export { Checkbox }

View File

@ -48,17 +48,9 @@ export const CopyButton: Component<{
>
<AnimatePresence mode="wait">
{copied ? (
<m.i
key="copied"
className="i-mgc-check-filled size-4"
{...copyIconVariants}
/>
<m.i key="copied" className="i-mgc-check-filled size-4" {...copyIconVariants} />
) : (
<m.i
key="copy"
className="i-mgc-copy-2-cute-re size-4"
{...copyIconVariants}
/>
<m.i key="copy" className="i-mgc-copy-2-cute-re size-4" {...copyIconVariants} />
)}
</AnimatePresence>
</MotionButtonBase>

View File

@ -1,18 +1,10 @@
import {
useUISettingKey,
useUISettingSelector,
} from "@renderer/atoms/settings/ui"
import { useUISettingKey, useUISettingSelector } from "@renderer/atoms/settings/ui"
import { isElectronBuild } from "@renderer/constants"
import { tipcClient } from "@renderer/lib/client"
import { cn } from "@renderer/lib/utils"
import { useIsomorphicLayoutEffect } from "foxact/use-isomorphic-layout-effect"
import type { FC } from "react"
import {
useInsertionEffect,
useMemo,
useRef,
useState,
} from "react"
import { useInsertionEffect, useMemo, useRef, useState } from "react"
import type {
BundledLanguage,
BundledTheme,
@ -20,10 +12,7 @@ import type {
DynamicImportThemeRegistration,
HighlighterCore,
} from "shiki"
import {
createHighlighterCoreSync,
createJavaScriptRegexEngine,
} from "shiki/core"
import { createHighlighterCoreSync, createJavaScriptRegexEngine } from "shiki/core"
import { CopyButton } from "../copy-button"
import { shikiTransformers } from "./shared"
@ -48,12 +37,8 @@ export interface ShikiProps {
theme?: string
}
let langModule: Record<
BundledLanguage,
DynamicImportLanguageRegistration
> | null = null
let themeModule: Record<BundledTheme, DynamicImportThemeRegistration> | null =
null
let langModule: Record<BundledLanguage, DynamicImportLanguageRegistration> | null = null
let themeModule: Record<BundledTheme, DynamicImportThemeRegistration> | null = null
let bundledLanguagesKeysSet: Set<string> | null = null
export const ShikiHighLighter: FC<ShikiProps> = (props) => {
@ -75,9 +60,7 @@ const ShikiHighLighterRender: FC<
}
> = (props) => {
const { code, language, className, theme: overrideTheme, shiki } = props
const [currentLanguage, setCurrentLanguage] = useState(
language || "plaintext",
)
const [currentLanguage, setCurrentLanguage] = useState(language || "plaintext")
const guessCodeLanguage = useUISettingKey("guessCodeLanguage")
useInsertionEffect(() => {
@ -96,16 +79,14 @@ const ShikiHighLighterRender: FC<
}
function guessLanguage() {
tipcClient
?.detectCodeStringLanguage({ codeString: code })
.then((result) => {
for (const item of result) {
if (bundledLanguagesKeysSet?.has(item.languageId)) {
setCurrentLanguage(item.languageId)
break
}
tipcClient?.detectCodeStringLanguage({ codeString: code }).then((result) => {
for (const item of result) {
if (bundledLanguagesKeysSet?.has(item.languageId)) {
setCurrentLanguage(item.languageId)
break
}
})
}
})
}
}, [guessCodeLanguage])
@ -114,9 +95,7 @@ const ShikiHighLighterRender: FC<
const [loaded, setLoaded] = useState(false)
const codeTheme = useUISettingSelector(
(s) => overrideTheme || s.codeHighlightTheme,
)
const codeTheme = useUISettingSelector((s) => overrideTheme || s.codeHighlightTheme)
useIsomorphicLayoutEffect(() => {
let isMounted = true
setLoaded(false)
@ -138,14 +117,14 @@ const ShikiHighLighterRender: FC<
if (!currentLanguage || !codeTheme) return
const [{ bundledLanguages }, { bundledThemes }] =
langModule && themeModule ?
[
langModule && themeModule
? [
{
bundledLanguages: langModule,
},
{ bundledThemes: themeModule },
] :
await Promise.all([import("shiki/langs"), import("shiki/themes")])
]
: await Promise.all([import("shiki/langs"), import("shiki/themes")])
langModule = bundledLanguages
themeModule = bundledThemes
@ -194,9 +173,7 @@ const ShikiHighLighterRender: FC<
</pre>
)
}
return (
<ShikiCode {...props} language={currentLanguage} codeTheme={codeTheme} />
)
return <ShikiCode {...props} language={currentLanguage} codeTheme={codeTheme} />
}
const ShikiCode: FC<
@ -237,10 +214,7 @@ const ShikiCode: FC<
className,
)}
>
<div
dangerouslySetInnerHTML={{ __html: rendered }}
data-language={language}
/>
<div dangerouslySetInnerHTML={{ __html: rendered }} data-language={language} />
<CopyButton
value={code}
className="absolute right-1 top-1 opacity-0 duration-200 group-hover:opacity-100"

View File

@ -36,13 +36,7 @@ export const Collapse: Component<CollapseProps> = (props) => {
React.useEffect(() => {
setIsOpened(currentId === id)
}, [currentId, id])
return (
<CollapseControlled
isOpened={isOpened}
onOpenChange={setIsOpened}
{...props}
/>
)
return <CollapseControlled isOpened={isOpened} onOpenChange={setIsOpened} {...props} />
}
export const CollapseControlled: Component<
@ -63,17 +57,12 @@ export const CollapseControlled: Component<
{!props.hideArrow && (
<div className="inline-flex shrink-0 items-center text-gray-400">
<i
className={cn(
"i-mingcute-down-line duration-200",
props.isOpened ? "rotate-180" : "",
)}
className={cn("i-mingcute-down-line duration-200", props.isOpened ? "rotate-180" : "")}
/>
</div>
)}
</div>
<CollapseContent isOpened={props.isOpened}>
{props.children}
</CollapseContent>
<CollapseContent isOpened={props.isOpened}>{props.children}</CollapseContent>
</div>
)
export const CollapseContent: Component<{

View File

@ -15,5 +15,4 @@ export const [
____CollapseGroupItemStateContext,
] = createContextState<Record<string, boolean>>({})
export const useCollapseId = () => useContext(____CollapseStateContext)
export const useCollapseGroupItem = () =>
useContext(____CollapseGroupItemStateContext)
export const useCollapseGroupItem = () => useContext(____CollapseGroupItemStateContext)

View File

@ -112,8 +112,7 @@ const ContextMenuCheckboxItem = React.forwardRef<
{children}
</ContextMenuPrimitive.CheckboxItem>
))
ContextMenuCheckboxItem.displayName =
ContextMenuPrimitive.CheckboxItem.displayName
ContextMenuCheckboxItem.displayName = ContextMenuPrimitive.CheckboxItem.displayName
const ContextMenuLabel = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Label>,
@ -123,11 +122,7 @@ const ContextMenuLabel = React.forwardRef<
>(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold text-foreground",
inset && "pl-8",
className,
)}
className={cn("px-2 py-1.5 text-sm font-semibold text-foreground", inset && "pl-8", className)}
{...props}
/>
))

View File

@ -3,12 +3,7 @@ import dayjs from "dayjs"
import type { FC } from "react"
import { useEffect, useRef, useState } from "react"
import {
Tooltip,
TooltipContent,
TooltipPortal,
TooltipTrigger,
} from "../tooltip"
import { Tooltip, TooltipContent, TooltipPortal, TooltipTrigger } from "../tooltip"
const formatTemplateString = "lll"
const formatTime = (
@ -16,15 +11,10 @@ const formatTime = (
relativeBeforeDay?: number,
template = formatTemplateString,
) => {
if (
relativeBeforeDay &&
Math.abs(dayjs(date).diff(new Date(), "d")) > relativeBeforeDay
) {
if (relativeBeforeDay && Math.abs(dayjs(date).diff(new Date(), "d")) > relativeBeforeDay) {
return dayjs(date).format(template)
}
return dayjs
.duration(dayjs(date).diff(dayjs(), "minute"), "minute")
.humanize()
return dayjs.duration(dayjs(date).diff(dayjs(), "minute"), "minute").humanize()
}
const getUpdateInterval = (date: string | Date, relativeBeforeDay?: number) => {
@ -53,10 +43,7 @@ export const RelativeTime: FC<{
displayAbsoluteTimeAfterDay?: number
dateFormatTemplate?: string
}> = (props) => {
const {
displayAbsoluteTimeAfterDay = 29,
dateFormatTemplate = formatTemplateString,
} = props
const { displayAbsoluteTimeAfterDay = 29, dateFormatTemplate = formatTemplateString } = props
const [relative, setRelative] = useState<string>(() =>
formatTime(props.date, displayAbsoluteTimeAfterDay, dateFormatTemplate),
)
@ -65,13 +52,8 @@ export const RelativeTime: FC<{
useEffect(() => {
const updateRelativeTime = () => {
setRelative(
formatTime(props.date, displayAbsoluteTimeAfterDay, dateFormatTemplate),
)
const updateInterval = getUpdateInterval(
props.date,
displayAbsoluteTimeAfterDay,
)
setRelative(formatTime(props.date, displayAbsoluteTimeAfterDay, dateFormatTemplate))
const updateInterval = getUpdateInterval(props.date, displayAbsoluteTimeAfterDay)
if (updateInterval !== null) {
timerRef.current = setTimeout(updateRelativeTime, updateInterval)
@ -92,9 +74,7 @@ export const RelativeTime: FC<{
return (
<Tooltip>
{/* https://github.com/radix-ui/primitives/issues/2248#issuecomment-2147056904 */}
<TooltipTrigger onFocusCapture={stopPropagation}>
{relative}
</TooltipTrigger>
<TooltipTrigger onFocusCapture={stopPropagation}>{relative}</TooltipTrigger>
<TooltipPortal>
<TooltipContent>{formated}</TooltipContent>
@ -109,11 +89,7 @@ export const RelativeDay = ({ date }: { date: Date }) => {
// Remove the time part for comparison
const nowDate = new Date(now.getFullYear(), now.getMonth(), now.getDate())
const inputDate = new Date(
date.getFullYear(),
date.getMonth(),
date.getDate(),
)
const inputDate = new Date(date.getFullYear(), date.getMonth(), date.getDate())
const diffTime = nowDate.getTime() - inputDate.getTime()
const diffDays = diffTime / (1000 * 3600 * 24)
@ -131,9 +107,7 @@ export const RelativeDay = ({ date }: { date: Date }) => {
}
}
const timerRef = useRef<any>(null)
const [dateString, setDateString] = useState<string>(() =>
formatDateString(date),
)
const [dateString, setDateString] = useState<string>(() => formatDateString(date))
useEffect(() => {
const updateInterval = getUpdateInterval(date, 3)
@ -155,9 +129,7 @@ export const RelativeDay = ({ date }: { date: Date }) => {
}
return (
<Tooltip>
<TooltipTrigger onFocusCapture={stopPropagation}>
{dateString}
</TooltipTrigger>
<TooltipTrigger onFocusCapture={stopPropagation}>{dateString}</TooltipTrigger>
<TooltipPortal>
<TooltipContent>{formated}</TooltipContent>
</TooltipPortal>

View File

@ -1,16 +1,13 @@
import { cn } from "@renderer/lib/utils"
import type { DetailedHTMLProps, FC, HTMLAttributes } from "react"
export const Divider: FC<
DetailedHTMLProps<HTMLAttributes<HTMLHRElement>, HTMLHRElement>
> = (props) => {
export const Divider: FC<DetailedHTMLProps<HTMLAttributes<HTMLHRElement>, HTMLHRElement>> = (
props,
) => {
const { className, ...rest } = props
return (
<hr
className={cn(
"my-4 h-[0.5px] border-0 bg-black !bg-opacity-30 dark:bg-white",
className,
)}
className={cn("my-4 h-[0.5px] border-0 bg-black !bg-opacity-30 dark:bg-white", className)}
{...rest}
/>
)

View File

@ -2,10 +2,7 @@ import { cn } from "@renderer/lib/utils"
import * as React from "react"
export const PanelSplitter = (
props: React.DetailedHTMLProps<
React.HTMLAttributes<HTMLDivElement>,
HTMLDivElement
> & {
props: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> & {
isDragging?: boolean
},
) => {

View File

@ -33,8 +33,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
<i className="i-mingcute-down-line ml-auto size-4" />
</DropdownMenuPrimitive.SubTrigger>
))
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName
DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
@ -49,8 +48,7 @@ const DropdownMenuSubContent = React.forwardRef<
{...props}
/>
))
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName
DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
@ -89,9 +87,7 @@ const DropdownMenuItem = React.forwardRef<
{...props}
>
{props.icon && (
<span className="mr-1.5 inline-flex size-4 items-center justify-center">
{props.icon}
</span>
<span className="mr-1.5 inline-flex size-4 items-center justify-center">{props.icon}</span>
)}
{props.children}
{/* Justify Fill */}
@ -121,8 +117,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
{children}
</DropdownMenuPrimitive.CheckboxItem>
))
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName
DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
@ -132,11 +127,7 @@ const DropdownMenuLabel = React.forwardRef<
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className,
)}
className={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
{...props}
/>
))
@ -154,14 +145,8 @@ const DropdownMenuSeparator = React.forwardRef<
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => (
<span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props}
/>
const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => (
<span className={cn("ml-auto text-xs tracking-widest opacity-60", className)} {...props} />
)
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"

View File

@ -15,9 +15,7 @@ type FormFieldContextValue<
name: TName
}
const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue,
)
const FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue)
const FormField = <
TFieldValues extends FieldValues = FieldValues,
@ -57,22 +55,19 @@ type FormItemContextValue = {
id: string
}
const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue,
const FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue)
const FormItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => {
const id = React.useId()
return (
<FormItemContext.Provider value={React.useMemo(() => ({ id }), [id])}>
<div ref={ref} className={cn("space-y-2", className)} {...props} />
</FormItemContext.Provider>
)
},
)
const FormItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const id = React.useId()
return (
<FormItemContext.Provider value={React.useMemo(() => ({ id }), [id])}>
<div ref={ref} className={cn("space-y-2", className)} {...props} />
</FormItemContext.Provider>
)
})
FormItem.displayName = "FormItem"
const FormLabel = React.forwardRef<
@ -96,18 +91,13 @@ const FormControl = React.forwardRef<
React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } =
useFormField()
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={
!error ?
`${formDescriptionId}` :
`${formDescriptionId} ${formMessageId}`
}
aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
aria-invalid={!!error}
{...props}
/>

View File

@ -8,10 +8,7 @@ import { forwardRef } from "react"
// @see https://foxact.skk.moe/use-composition-input
export const Input = forwardRef<
HTMLInputElement,
Omit<
DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>,
"ref"
>
Omit<DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, "ref">
>(({ className, ...props }, ref) => {
const inputProps = useInputComposition(props)
return (
@ -25,9 +22,7 @@ export const Input = forwardRef<
"focus:!bg-accent/5",
"border border-border",
"dark:text-zinc-200 dark:placeholder:text-zinc-500",
props.type === "password" ?
"font-mono placeholder:font-sans" :
"font-sans",
props.type === "password" ? "font-mono placeholder:font-sans" : "font-sans",
"w-full",
className,
)}

View File

@ -5,113 +5,106 @@ import { stopPropagation } from "@renderer/lib/dom"
import { cn } from "@renderer/lib/utils"
import clsx from "clsx"
import { useMotionValue } from "framer-motion"
import type {
DetailedHTMLProps,
PropsWithChildren,
TextareaHTMLAttributes,
} from "react"
import type { DetailedHTMLProps, PropsWithChildren, TextareaHTMLAttributes } from "react"
import { forwardRef, useCallback, useState } from "react"
const roundedMap = {
"sm": "rounded-sm",
"md": "rounded-md",
"lg": "rounded-lg",
"xl": "rounded-xl",
sm: "rounded-sm",
md: "rounded-md",
lg: "rounded-lg",
xl: "rounded-xl",
"2xl": "rounded-2xl",
"3xl": "rounded-3xl",
"default": "rounded",
default: "rounded",
}
export const TextArea = forwardRef<
HTMLTextAreaElement,
DetailedHTMLProps<
TextareaHTMLAttributes<HTMLTextAreaElement>,
HTMLTextAreaElement
> &
PropsWithChildren<{
wrapperClassName?: string
onCmdEnter?: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void
rounded?: "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "default"
bordered?: boolean
}>
DetailedHTMLProps<TextareaHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement> &
PropsWithChildren<{
wrapperClassName?: string
onCmdEnter?: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void
rounded?: "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "default"
bordered?: boolean
}>
>((props, ref) => {
const {
const {
className,
wrapperClassName,
children,
rounded = "xl",
bordered = true,
onCmdEnter,
...rest
} = props
const mouseX = useMotionValue(0)
const mouseY = useMotionValue(0)
const handleMouseMove = useCallback(
({ clientX, clientY, currentTarget }: React.MouseEvent) => {
const bounds = currentTarget.getBoundingClientRect()
mouseX.set(clientX - bounds.left)
mouseY.set(clientY - bounds.top)
},
[mouseX, mouseY],
)
const inputProps = useInputComposition<HTMLTextAreaElement>(props)
const [isFocus, setIsFocus] = useState(false)
return (
<div
className={cn(
"group relative h-full border ring-0 ring-accent/20 duration-200 [--spotlight-color:oklch(var(--a)_/_0.12)]",
roundedMap[rounded],
"border-transparent",
isFocus && "border-accent/80 ring-2",
"dark:text-zinc-200 dark:placeholder:text-zinc-500",
wrapperClassName,
)}
onMouseMove={handleMouseMove}
>
{bordered && (
<div
className={clsx(
"pointer-events-none absolute inset-0 z-0 border border-border",
roundedMap[rounded],
)}
aria-hidden="true"
/>
)}
<textarea
ref={ref}
className={cn(
"size-full resize-none bg-transparent",
"overflow-auto px-3 py-4",
"!outline-none",
"text-neutral-900/80 dark:text-slate-100/80",
"focus:!bg-accent/5",
roundedMap[rounded],
className,
wrapperClassName,
children,
rounded = "xl",
bordered = true,
onCmdEnter,
...rest
} = props
const mouseX = useMotionValue(0)
const mouseY = useMotionValue(0)
const handleMouseMove = useCallback(
({ clientX, clientY, currentTarget }: React.MouseEvent) => {
const bounds = currentTarget.getBoundingClientRect()
mouseX.set(clientX - bounds.left)
mouseY.set(clientY - bounds.top)
},
[mouseX, mouseY],
)
)}
{...rest}
onFocus={(e) => {
setIsFocus(true)
rest.onFocus?.(e)
}}
onBlur={(e) => {
setIsFocus(false)
rest.onBlur?.(e)
}}
onContextMenu={stopPropagation}
{...inputProps}
onKeyDown={(e) => {
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
onCmdEnter?.(e)
}
rest.onKeyDown?.(e)
inputProps.onKeyDown?.(e)
}}
/>
const inputProps = useInputComposition<HTMLTextAreaElement>(props)
const [isFocus, setIsFocus] = useState(false)
return (
<div
className={cn(
"group relative h-full border ring-0 ring-accent/20 duration-200 [--spotlight-color:oklch(var(--a)_/_0.12)]",
roundedMap[rounded],
"border-transparent",
isFocus && "border-accent/80 ring-2",
"dark:text-zinc-200 dark:placeholder:text-zinc-500",
wrapperClassName,
)}
onMouseMove={handleMouseMove}
>
{bordered && (
<div
className={clsx(
"pointer-events-none absolute inset-0 z-0 border border-border",
roundedMap[rounded],
)}
aria-hidden="true"
/>
)}
<textarea
ref={ref}
className={cn(
"size-full resize-none bg-transparent",
"overflow-auto px-3 py-4",
"!outline-none",
"text-neutral-900/80 dark:text-slate-100/80",
"focus:!bg-accent/5",
roundedMap[rounded],
className,
)}
{...rest}
onFocus={(e) => {
setIsFocus(true)
rest.onFocus?.(e)
}}
onBlur={(e) => {
setIsFocus(false)
rest.onBlur?.(e)
}}
onContextMenu={stopPropagation}
{...inputProps}
onKeyDown={(e) => {
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
onCmdEnter?.(e)
}
rest.onKeyDown?.(e)
inputProps.onKeyDown?.(e)
}}
/>
{children}
</div>
)
})
{children}
</div>
)
})
TextArea.displayName = "TextArea"

View File

@ -70,59 +70,57 @@ export const KbdCombined: FC<{
</div>
)
}
export const Kbd: FC<{ children: string, className?: string }> = memo(
({ children, className }) => {
let specialKeys = (SpecialKeys as any)[os] as Record<string, string>
specialKeys = { ...SharedKeys, ...specialKeys }
export const Kbd: FC<{ children: string; className?: string }> = memo(({ children, className }) => {
let specialKeys = (SpecialKeys as any)[os] as Record<string, string>
specialKeys = { ...SharedKeys, ...specialKeys }
const [isKeyPressed, setIsKeyPressed] = React.useState(false)
React.useEffect(() => {
const handler = () => {
setIsKeyPressed(isHotkeyPressed(children.toLowerCase()))
}
document.addEventListener("keydown", handler)
document.addEventListener("keyup", handler)
const [isKeyPressed, setIsKeyPressed] = React.useState(false)
React.useEffect(() => {
const handler = () => {
setIsKeyPressed(isHotkeyPressed(children.toLowerCase()))
}
document.addEventListener("keydown", handler)
document.addEventListener("keyup", handler)
return () => {
document.removeEventListener("keydown", handler)
document.removeEventListener("keyup", handler)
}
}, [children])
return () => {
document.removeEventListener("keydown", handler)
document.removeEventListener("keyup", handler)
}
}, [children])
return (
<kbd
className={cn(
"kbd h-4 space-x-1 font-mono text-[0.7rem]",
return (
<kbd
className={cn(
"kbd h-4 space-x-1 font-mono text-[0.7rem]",
isKeyPressed ? "" : "border-b-2",
className,
)}
>
{children.split("+").map((key_) => {
let key: string = key_.toLowerCase()
for (const [k, v] of Object.entries(specialKeys)) {
key = key.replace(k, v)
isKeyPressed ? "" : "border-b-2",
className,
)}
>
{children.split("+").map((key_) => {
let key: string = key_.toLowerCase()
for (const [k, v] of Object.entries(specialKeys)) {
key = key.replace(k, v)
}
switch (key) {
case SharedKeys.space: {
return <i className="i-mingcute-space-line" key={key} />
}
switch (key) {
case SharedKeys.space: {
return <i className="i-mingcute-space-line" key={key} />
}
case SharedKeys.backspace: {
return <i className="i-mingcute-delete-back-line" key={key} />
}
default: {
return (
<span className="capitalize" key={key}>
{key}
</span>
)
}
case SharedKeys.backspace: {
return <i className="i-mingcute-delete-back-line" key={key} />
}
})}
</kbd>
)
},
)
default: {
return (
<span className="capitalize" key={key}>
{key}
</span>
)
}
}
})}
</kbd>
)
})

View File

@ -10,14 +10,9 @@ const labelVariants = cva(
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
<LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />
))
Label.displayName = LabelPrimitive.Root.displayName

View File

@ -1,9 +1,4 @@
import {
Tooltip,
TooltipContent,
TooltipPortal,
TooltipTrigger,
} from "../tooltip"
import { Tooltip, TooltipContent, TooltipPortal, TooltipTrigger } from "../tooltip"
export interface LinkProps {
href: string

View File

@ -39,9 +39,7 @@ export const ListItemHoverOverlay = ({
const mClassName = clsx(
"absolute z-[-1]",
"bg-zinc-200/80 dark:bg-neutral-800",
views[view].wideMode ?
"inset-x-0 inset-y-1 rounded-xl" :
"-inset-x-2 inset-y-0",
views[view].wideMode ? "inset-x-0 inset-y-1 rounded-xl" : "-inset-x-2 inset-y-0",
className,
)
const motionConfig = {

View File

@ -10,10 +10,7 @@ const sizeMap = {
medium: 24,
large: 30,
}
export const LoadingCircle: Component<LoadingCircleProps> = ({
className,
size,
}) => (
export const LoadingCircle: Component<LoadingCircleProps> = ({ className, size }) => (
<div
className={className}
style={{
@ -95,10 +92,7 @@ export const LoadingWithIcon: Component<
/>
</span>
<span
className={cn(
"absolute bottom-0 right-0",
"animate-pulse duration-700",
)}
className={cn("absolute bottom-0 right-0", "animate-pulse duration-700")}
style={smallIconStyle}
>
{cloneElement(icon, {

View File

@ -42,18 +42,11 @@ export const HTML = <A extends keyof JSX.IntrinsicElements = "div">(
accessory?: React.ReactNode
noMedia?: boolean
} & JSX.IntrinsicElements[A] &
Partial<{
renderInlineStyle: boolean
}>,
Partial<{
renderInlineStyle: boolean
}>,
) => {
const {
children,
renderInlineStyle,
as = "div",
accessory,
noMedia,
...rest
} = props
const { children, renderInlineStyle, as = "div", accessory, noMedia, ...rest } = props
const [remarkOptions, setRemarkOptions] = useState({
renderInlineStyle,
noMedia,
@ -62,10 +55,7 @@ export const HTML = <A extends keyof JSX.IntrinsicElements = "div">(
useEffect(() => {
setRemarkOptions((options) => {
if (
renderInlineStyle === options.renderInlineStyle ||
noMedia === options.noMedia
) {
if (renderInlineStyle === options.renderInlineStyle || noMedia === options.noMedia) {
return options
}
@ -89,9 +79,7 @@ export const HTML = <A extends keyof JSX.IntrinsicElements = "div">(
return (
<MarkdownRenderContainerRefContext.Provider value={refElement}>
{createElement(as, { ...rest, ref: setRefElement }, markdownElement)}
{accessory && (
<Fragment key={shouldForceReMountKey}>{accessory}</Fragment>
)}
{accessory && <Fragment key={shouldForceReMountKey}>{accessory}</Fragment>}
</MarkdownRenderContainerRefContext.Provider>
)
}

View File

@ -44,8 +44,7 @@ export const Toc: Component = ({ className }) => {
const $headings = useMemo(
() =>
(markdownElement?.querySelectorAll("h1, h2, h3, h4, h5, h6") ||
[]) as HTMLHeadingElement[],
(markdownElement?.querySelectorAll("h1, h2, h3, h4, h5, h6") || []) as HTMLHeadingElement[],
[markdownElement],
)
@ -72,12 +71,12 @@ export const Toc: Component = ({ className }) => {
const rootDepth = useMemo(
() =>
toc?.length ?
(toc.reduce(
toc?.length
? (toc.reduce(
(d: number, cur) => Math.min(d, cur.depth),
toc[0]?.depth || 0,
) as any as number) :
0,
) as any as number)
: 0,
[toc],
)
@ -91,8 +90,7 @@ export const Toc: Component = ({ className }) => {
(i: number, $el: HTMLElement | null, _anchorId: string) => {
if ($el) {
const handle = () => {
scrollDelayHandlerRef.current &&
clearTimeout(scrollDelayHandlerRef.current)
scrollDelayHandlerRef.current && clearTimeout(scrollDelayHandlerRef.current)
springScrollToElement($el, -100, scrollContainerElement!).then(() => {
throttleCallerRef.current?.cancel()
scrollDelayHandlerRef.current = setTimeout(() => {
@ -115,8 +113,7 @@ export const Toc: Component = ({ className }) => {
const $heading = $headings[i]
const headingTop =
Number.parseInt($heading.dataset["containerTop"] || "0") ||
getElementTop($heading)
Number.parseInt($heading.dataset["containerTop"] || "0") || getElementTop($heading)
if (!$heading.dataset) {
// @ts-expect-error
$heading.dataset["containerTop"] = headingTop.toString()
@ -154,8 +151,7 @@ export const Toc: Component = ({ className }) => {
const { y } = getWrappedElPos()
const top = scrollContainerElement.scrollTop + y
const winHeight = getViewport().h
const deltaHeight =
top >= winHeight ? winHeight : (top / winHeight) * winHeight
const deltaHeight = top >= winHeight ? winHeight : (top / winHeight) * winHeight
const actualTop = Math.floor(Math.max(0, top - y + deltaHeight)) || 0
@ -194,27 +190,16 @@ export const Toc: Component = ({ className }) => {
scrollContainerElement.removeEventListener("scroll", handler)
handler.cancel()
}
}, [
getWrappedElPos,
scrollContainerElement,
titleBetweenPositionTopRangeMap,
])
}, [getWrappedElPos, scrollContainerElement, titleBetweenPositionTopRangeMap])
const [hoverShow, setHoverShow] = useState(false)
if (toc.length === 0) return null
return (
<div className="flex grow flex-col scroll-smooth px-2 scrollbar-none">
<HoverCard.Root
openDelay={100}
open={hoverShow}
onOpenChange={setHoverShow}
>
<HoverCard.Root openDelay={100} open={hoverShow} onOpenChange={setHoverShow}>
<HoverCard.Trigger asChild>
<ul
ref={setTreeRef}
className={cn("group overflow-auto scrollbar-none", className)}
>
<ul ref={setTreeRef} className={cn("group overflow-auto scrollbar-none", className)}>
{toc.map((heading, index) => (
<MemoedItem
heading={heading}
@ -222,9 +207,7 @@ export const Toc: Component = ({ className }) => {
rootDepth={rootDepth}
onClick={handleScrollTo}
isScrollOut={index < currentScrollRange[0]}
range={
index === currentScrollRange[0] ? currentScrollRange[1] : 0
}
range={index === currentScrollRange[0] ? currentScrollRange[1] : 0}
/>
))}
</ul>
@ -245,10 +228,7 @@ export const Toc: Component = ({ className }) => {
)}
>
{toc.map((heading, index) => (
<li
key={heading.anchorId}
className="flex h-[24px] w-full items-center"
>
<li key={heading.anchorId} className="flex h-[24px] w-full items-center">
<button
className={cn(
"group flex w-full cursor-pointer justify-between",
@ -256,20 +236,14 @@ export const Toc: Component = ({ className }) => {
)}
type="button"
onClick={() => {
handleScrollTo(
index,
heading.$heading,
heading.anchorId,
)
handleScrollTo(index, heading.$heading, heading.anchorId)
}}
>
<span className="duration-200 group-hover:text-accent/80">
{heading.title}
</span>
<span className="ml-4 text-[8px] opacity-50">
H{heading.depth}
</span>
<span className="ml-4 text-[8px] opacity-50">H{heading.depth}</span>
</button>
</li>
))}
@ -310,8 +284,7 @@ const MemoedItem = memo<TocItemProps>((props) => {
const itemTop = itemOffsetTop - scrollTop
const itemBottom = itemTop + itemHeight
if (itemTop < 0 || itemBottom > containerHeight) {
$container.scrollTop =
itemOffsetTop - containerHeight / 2 + itemHeight / 2
$container.scrollTop = itemOffsetTop - containerHeight / 2 + itemHeight / 2
}
}, [active])

View File

@ -57,7 +57,7 @@ export const TocItem: FC<TocItemProps> = memo((props) => {
"dark:bg-zinc-800/80 dark:hover:!bg-zinc-600 dark:group-hover:bg-zinc-600/50",
isScrollOut && "dark:bg-zinc-700",
!!range &&
"!bg-zinc-400/50 data-[active=true]:group-hover:!bg-zinc-500 dark:!bg-zinc-600",
"!bg-zinc-400/50 data-[active=true]:group-hover:!bg-zinc-500 dark:!bg-zinc-600",
"overflow-hidden",
)}
>

View File

@ -1,4 +1,3 @@
import { createContext } from "react"
export const MarkdownRenderContainerRefContext =
createContext<HTMLElement | null>(null)
export const MarkdownRenderContainerRefContext = createContext<HTMLElement | null>(null)

View File

@ -1,7 +1,7 @@
import { captureException } from "@sentry/react"
import { useEffect } from "react"
export const BlockError = (props: { error: any, message: string }) => {
export const BlockError = (props: { error: any; message: string }) => {
useEffect(() => {
captureException(props.error)
}, [])

View File

@ -7,51 +7,43 @@ import { MarkdownRenderContainerRefContext } from "../context"
export const createHeadingRenderer =
(level: number) =>
(
props: React.DetailedHTMLProps<
React.HTMLAttributes<HTMLHeadingElement>,
HTMLHeadingElement
>,
) => {
const rid = useId()
(
props: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>,
) => {
const rid = useId()
const As = `h${level}` as any
const { node, ...rest } = props as any
const As = `h${level}` as any
const { node, ...rest } = props as any
const scroller = useScrollViewElement()
const renderContainer = useContext(MarkdownRenderContainerRefContext)
const ref = useRef<HTMLHeadingElement>(null)
const scroller = useScrollViewElement()
const renderContainer = useContext(MarkdownRenderContainerRefContext)
const ref = useRef<HTMLHeadingElement>(null)
return (
<As
ref={ref}
{...rest}
data-rid={rid}
className={cn(rest.className, "group relative")}
return (
<As ref={ref} {...rest} data-rid={rid} className={cn(rest.className, "group relative")}>
{rest.children}
<span
className={cn(
"cursor-pointer select-none text-accent opacity-0 transition-opacity duration-200 group-hover:opacity-100",
"relative ml-2",
"@2xl:absolute @2xl:left-[-1.5em] @2xl:top-0 @2xl:opacity-0",
)}
aria-hidden
onClick={() => {
if (!renderContainer) return
springScrollToElement(
renderContainer.querySelector(`[data-rid="${rid}"]`)!,
-100,
scroller!,
)
}}
>
{rest.children}
<span
className={cn(
"cursor-pointer select-none text-accent opacity-0 transition-opacity duration-200 group-hover:opacity-100",
"relative ml-2",
"@2xl:absolute @2xl:left-[-1.5em] @2xl:top-0 @2xl:opacity-0",
)}
aria-hidden
onClick={() => {
if (!renderContainer) return
springScrollToElement(
renderContainer.querySelector(`[data-rid="${rid}"]`)!,
-100,
scroller!,
)
}}
>
<i className="i-mingcute-hashtag-line invisible" />
<span className="center absolute inset-0">
<i className="i-mingcute-hashtag-line" />
</span>
<i className="i-mingcute-hashtag-line invisible" />
<span className="center absolute inset-0">
<i className="i-mingcute-hashtag-line" />
</span>
</As>
)
}
</span>
</As>
)
}

View File

@ -4,12 +4,7 @@ import { useFeedByIdSelector } from "@renderer/store/feed"
import { useMemo } from "react"
import type { LinkProps } from "../../link"
import {
Tooltip,
TooltipContent,
TooltipPortal,
TooltipTrigger,
} from "../../tooltip"
import { Tooltip, TooltipContent, TooltipPortal, TooltipTrigger } from "../../tooltip"
import { ensureAndRenderTimeStamp } from "../utils"
const safeUrl = (url: string, baseUrl: string) => {

Some files were not shown because too many files have changed in this diff Show More