chore: use prettier to format code (#306)
This commit is contained in:
parent
501e2f44c1
commit
26d01fd835
|
|
@ -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? -->
|
||||
|
|
|
|||
|
|
@ -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: |
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
src/hono.ts
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
export default {
|
||||
semi: false,
|
||||
singleQuote: false,
|
||||
printWidth: 100,
|
||||
tabWidth: 2,
|
||||
trailingComma: "all",
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
2085
CHANGELOG.md
2085
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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.",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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": [
|
||||
|
|
|
|||
17231
pnpm-lock.yaml
17231
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -1,6 +1,6 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
"tailwindcss": {},
|
||||
tailwindcss: {},
|
||||
"tailwindcss/nesting": {},
|
||||
|
||||
...(process.env.NODE_ENV === "production" ? { cssnano: {} } : {}),
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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}` : "",
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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" },
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
},
|
||||
})
|
||||
}),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }>()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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" },
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -47,6 +47,4 @@ viewAtom.onMount = () => {
|
|||
setSidebarActiveView(view)
|
||||
}
|
||||
}
|
||||
export const [, , useFeedColumnShow, , , setFeedColumnShow] = createAtomHooks(
|
||||
atom(true),
|
||||
)
|
||||
export const [, , useFeedColumnShow, , , setFeedColumnShow] = createAtomHooks(atom(true))
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
import {
|
||||
useLayoutEffect,
|
||||
useState,
|
||||
} from "react"
|
||||
import { useLayoutEffect, useState } from "react"
|
||||
|
||||
const NOT_RESOLVED = Symbol("NOT_RESOLVED")
|
||||
export const ExPromise = <T,>({
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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}</>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -19,5 +19,4 @@ export const ErrorFallbackMap = {
|
|||
[ErrorComponentType.FeedNotFound]: FeedNotFoundErrorFallback,
|
||||
}
|
||||
|
||||
export const getErrorFallback = (type: ErrorComponentType) =>
|
||||
ErrorFallbackMap[type]
|
||||
export const getErrorFallback = (type: ErrorComponentType) => ErrorFallbackMap[type]
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
))
|
||||
|
|
|
|||
|
|
@ -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)}>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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<{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
))
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
},
|
||||
) => {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,4 @@
|
|||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipPortal,
|
||||
TooltipTrigger,
|
||||
} from "../tooltip"
|
||||
import { Tooltip, TooltipContent, TooltipPortal, TooltipTrigger } from "../tooltip"
|
||||
|
||||
export interface LinkProps {
|
||||
href: string
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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, {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { createContext } from "react"
|
||||
|
||||
export const MarkdownRenderContainerRefContext =
|
||||
createContext<HTMLElement | null>(null)
|
||||
export const MarkdownRenderContainerRefContext = createContext<HTMLElement | null>(null)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}, [])
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in New Issue