chore: Release v0.0.1-alpha.21 (#913)
This commit is contained in:
commit
f690e6e260
|
|
@ -21,6 +21,7 @@ env:
|
|||
VITE_IMGPROXY_URL: ${{ vars.VITE_IMGPROXY_URL }}
|
||||
VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }}
|
||||
VITE_POSTHOG_KEY: ${{ vars.VITE_POSTHOG_KEY }}
|
||||
VITE_FIREBASE_CONFIG: ${{ vars.VITE_FIREBASE_CONFIG }}
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
|
||||
jobs:
|
||||
|
|
@ -150,7 +151,7 @@ jobs:
|
|||
out/make/**/*.AppImage
|
||||
out/make/**/*.yml
|
||||
|
||||
- run: npx @innei/changelogithub
|
||||
- run: npx changelogithub
|
||||
if: github.ref_type == 'tag' || github.event.inputs.tag_version != ''
|
||||
continue-on-error: true
|
||||
env:
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ env:
|
|||
VITE_IMGPROXY_URL: ${{ vars.VITE_IMGPROXY_URL }}
|
||||
VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }}
|
||||
VITE_POSTHOG_KEY: ${{ vars.VITE_POSTHOG_KEY }}
|
||||
VITE_FIREBASE_CONFIG: ${{ vars.VITE_FIREBASE_CONFIG }}
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
|
||||
jobs:
|
||||
|
|
|
|||
10
CHANGELOG.md
10
CHANGELOG.md
|
|
@ -1,6 +1,6 @@
|
|||
# CHANGELOG
|
||||
|
||||
## [0.0.1-alpha.20](https://github.com/RSSNext/follow/compare/v0.0.1-alpha.19...v0.0.1-alpha.20) (2024-10-12)
|
||||
## [0.0.1-alpha.21](https://github.com/RSSNext/follow/compare/v0.0.1-alpha.19...v0.0.1-alpha.21) (2024-10-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
* accept import opml ([39ecc82](https://github.com/RSSNext/follow/commit/39ecc82a8ceecd77139ffccb31a33904082a3d1b))
|
||||
* **achievement:** loading button style ([96bb514](https://github.com/RSSNext/follow/commit/96bb51425427864bc4e77b75808271371839c57a))
|
||||
* add app version on posthog ([9933463](https://github.com/RSSNext/follow/commit/99334639e28b2a2a17b9c3fdd64f0d6d37bdd181))
|
||||
* add aria-describedby to modal content ([bb463a3](https://github.com/RSSNext/follow/commit/bb463a311fe020d079ed021489239a9141246a48))
|
||||
* add bg when context menu trigger, fix [#389](https://github.com/RSSNext/follow/issues/389) ([53f3185](https://github.com/RSSNext/follow/commit/53f3185e7955d1a72912905d8565c5efb0a0bda0))
|
||||
* add copy image in electron, fix [#317](https://github.com/RSSNext/follow/issues/317) ([466e0b7](https://github.com/RSSNext/follow/commit/466e0b78ccc5df66ab11a1e71a800e70c9b34312))
|
||||
* add db index ([a888a78](https://github.com/RSSNext/follow/commit/a888a789e10ecd8b3fab713e859bfe9f56bfe0ad))
|
||||
|
|
@ -246,6 +247,7 @@
|
|||
* improve proxy URI handling ([#810](https://github.com/RSSNext/follow/issues/810)) ([e7e930d](https://github.com/RSSNext/follow/commit/e7e930d1771e19d909433021d6757eabb44de73f))
|
||||
* inbox data refreshment ([a3a7c82](https://github.com/RSSNext/follow/commit/a3a7c82ea6308756a800514d9df528815ba77781))
|
||||
* incorrect tooltip in read history ([#385](https://github.com/RSSNext/follow/issues/385)) ([cabe210](https://github.com/RSSNext/follow/commit/cabe210aaf8f4a1133fa653532874fa86d02c252))
|
||||
* init store after app init ([728ea84](https://github.com/RSSNext/follow/commit/728ea84fa6e9faff1990184285390a1d86eb2c56))
|
||||
* inline table style ([5837bd4](https://github.com/RSSNext/follow/commit/5837bd4089b59855d0b583aed5d0e97fce04e094))
|
||||
* input box table style ([e0890d5](https://github.com/RSSNext/follow/commit/e0890d5b388facd1f9c16ac706a32f0aa2efdef5))
|
||||
* Input issues fixed [#535](https://github.com/RSSNext/follow/issues/535),[#536](https://github.com/RSSNext/follow/issues/536) ([3d48637](https://github.com/RSSNext/follow/commit/3d486379b91dff13bfbcc90532efd3e186405a13))
|
||||
|
|
@ -254,6 +256,7 @@
|
|||
* invitation page error display area ([5915357](https://github.com/RSSNext/follow/commit/59153573d8a5d368adfe41b0f5cebc4d6d44a9f3))
|
||||
* **invitation:** reset app data then logout ([5933cea](https://github.com/RSSNext/follow/commit/5933ceab63de0a7a46e85d0b841acbf893d8fcec))
|
||||
* **item:** center content if no desc ([18e26a4](https://github.com/RSSNext/follow/commit/18e26a41c3cf80316bc97c44a1239e6ec45c5db9))
|
||||
* json parse for env VITE_FIREBASE_CONFIG ([b2916bd](https://github.com/RSSNext/follow/commit/b2916bd8b81d02e1f5b673ed34c7f0f181d641c2))
|
||||
* kbd cls and set home scope in shortcuts guideline ([d9999a3](https://github.com/RSSNext/follow/commit/d9999a3fc6cfeae0f3b45b6390cf79d9135566b7))
|
||||
* lang/*.json ([#526](https://github.com/RSSNext/follow/issues/526)) ([be3b2e7](https://github.com/RSSNext/follow/commit/be3b2e7503c1061a1e312394bb0c3556e39ec062))
|
||||
* language setting syncing ([fa1dc5d](https://github.com/RSSNext/follow/commit/fa1dc5df35a2225bb22320a8fc049d3c0b75fa5b))
|
||||
|
|
@ -507,6 +510,7 @@
|
|||
* user modal list padding ([992ea15](https://github.com/RSSNext/follow/commit/992ea1594af02e3815adae2fe99de4f205543239))
|
||||
* user profile can not scroll by scrollbar ([eac1965](https://github.com/RSSNext/follow/commit/eac1965cfc3d2bc86e11f256237322f15892b897))
|
||||
* userActions in feed store ([8da4fb7](https://github.com/RSSNext/follow/commit/8da4fb71d9cc14832bc985cf8c1bd6f04dbe4e4c))
|
||||
* **vercel:** filter subpath ([fc38072](https://github.com/RSSNext/follow/commit/fc38072320f8225e6ae60f4e25719eaeb9ff5e6a))
|
||||
* video preview ([e38820c](https://github.com/RSSNext/follow/commit/e38820c1aa2294f45e05d331bf4bce0e4d8efb44))
|
||||
* view icon color in dark mode ([991bff7](https://github.com/RSSNext/follow/commit/991bff7660e93b872f1398c89c0fe4316f4327d9))
|
||||
* view source content in picture view ([16938f6](https://github.com/RSSNext/follow/commit/16938f60786f8496152361405c0fcff4635f7d59))
|
||||
|
|
@ -522,6 +526,7 @@
|
|||
* windows maximize will lost frame and background material ([2bd0e78](https://github.com/RSSNext/follow/commit/2bd0e78e4f859d9c98f53f748c9871a41348db1c))
|
||||
* windows multi-display ([7490cd1](https://github.com/RSSNext/follow/commit/7490cd12d0e42a7f41931acd9d77bf64be39a26d))
|
||||
* windows sep, fixes [#741](https://github.com/RSSNext/follow/issues/741) ([9b1de77](https://github.com/RSSNext/follow/commit/9b1de775d2157606c30c4ece3287a42e1e9463a3))
|
||||
* windows web mark all read ui ([#908](https://github.com/RSSNext/follow/issues/908)) ([45b4bd8](https://github.com/RSSNext/follow/commit/45b4bd8085377e8178b6804c0718726ecc15145b))
|
||||
* **windows:** dont remove locale ([7305eba](https://github.com/RSSNext/follow/commit/7305eba3fee218330818d96ca155f833aa99d631))
|
||||
* **windows:** skip remove locle ([5df82d0](https://github.com/RSSNext/follow/commit/5df82d0c0f98bebb6e78a694a6ceda3644eb36ef))
|
||||
* withdraw availableBalance ([167d82a](https://github.com/RSSNext/follow/commit/167d82adc061078102fa9984d29f94735d052ce9))
|
||||
|
|
@ -541,6 +546,7 @@
|
|||
* add biz code i18n for fr and ru ([#521](https://github.com/RSSNext/follow/issues/521)) ([0dc4b86](https://github.com/RSSNext/follow/commit/0dc4b866d7eff2d32df2158902d6d1fbdea41f27))
|
||||
* add biz code i18n for zh-cn ([#503](https://github.com/RSSNext/follow/issues/503)) ([d35e011](https://github.com/RSSNext/follow/commit/d35e0119953a154d18273c1d155f14d03c9a6ce8))
|
||||
* add biz user info on sentry tracker ([e3e52ab](https://github.com/RSSNext/follow/commit/e3e52abedeb5507ca71d663faa7ef050b661cf0a))
|
||||
* add bundleId to PushReceiver ([f196493](https://github.com/RSSNext/follow/commit/f196493b68a84489de93ac0c3944b039c79f25c0))
|
||||
* add copy title to context menu of feed entry ([#801](https://github.com/RSSNext/follow/issues/801)) ([1f57ab2](https://github.com/RSSNext/follow/commit/1f57ab2ac76ef04aff37814b4c7e961ad33b6e11))
|
||||
* add discover back to top fab ([a97e60c](https://github.com/RSSNext/follow/commit/a97e60cd005ca7ebb5aed56655b94975ebf1ccb7))
|
||||
* add divider when sticky for date item ([755e292](https://github.com/RSSNext/follow/commit/755e292af05c3adeae8943cc5c4c05af11d30d16))
|
||||
|
|
@ -705,6 +711,7 @@
|
|||
* new power page ([c3630f8](https://github.com/RSSNext/follow/commit/c3630f85bd221ecf875b20337e5c6a452c613362))
|
||||
* no media available tip in picture item ([1d4fca4](https://github.com/RSSNext/follow/commit/1d4fca4e7d0e7c6f02f1ac775f36079ef5ce1129))
|
||||
* no media available tip in picture item ([58d9b3b](https://github.com/RSSNext/follow/commit/58d9b3b1b09f52def911521c08d9835f31221401))
|
||||
* notifications logger ([97041ab](https://github.com/RSSNext/follow/commit/97041abab5f0846249343de07f592ba6fc421870))
|
||||
* only closing window can trigger query invalidation ([a8e48f4](https://github.com/RSSNext/follow/commit/a8e48f493127104035e9537e0461c4a40e2dc339))
|
||||
* only show has media entry item in picture view ([f7dedf2](https://github.com/RSSNext/follow/commit/f7dedf2b27f2597a49e9381af5581b6d0550421b))
|
||||
* optimize 404 page ([f51b1e2](https://github.com/RSSNext/follow/commit/f51b1e2de9e8d8c3bdea9db79476c17cd7247a4b))
|
||||
|
|
@ -763,6 +770,7 @@
|
|||
* support pageup/pagedown to scroll up/down ([6e0c3bc](https://github.com/RSSNext/follow/commit/6e0c3bc5e3e2437a51fa991940b4e4496ed2bb75))
|
||||
* support setting proxy for app ([#452](https://github.com/RSSNext/follow/issues/452)) ([cfd5275](https://github.com/RSSNext/follow/commit/cfd527545ade50a529698bdd57ec16d0f3ccee98))
|
||||
* support shortcut to toggle wide mode ([b7a054f](https://github.com/RSSNext/follow/commit/b7a054f3132079693f0d93a65eb236c95920bcb8))
|
||||
* support specific import for web and electron ([9a84a29](https://github.com/RSSNext/follow/commit/9a84a293967d5d415e86041c7a586846519def5f))
|
||||
* support toc parser ([fb4ca52](https://github.com/RSSNext/follow/commit/fb4ca522440aced4d2aa18fda2a9d5c43e32d0d1))
|
||||
* sync indicator when disable ([945ddda](https://github.com/RSSNext/follow/commit/945dddae2534f07e5289e307c36de1f8334636bc))
|
||||
* tiny styles ([78bcc55](https://github.com/RSSNext/follow/commit/78bcc5562baa12cece51da6564b77c6f37a1596c))
|
||||
|
|
|
|||
|
|
@ -12,16 +12,13 @@ import { getIconPath } from "./helper"
|
|||
import { t } from "./lib/i18n"
|
||||
import { store } from "./lib/store"
|
||||
import { updateNotificationsToken } from "./lib/user"
|
||||
import { logger } from "./logger"
|
||||
import { registerAppMenu } from "./menu"
|
||||
import type { RendererHandlers } from "./renderer-handlers"
|
||||
import { initializeSentry } from "./sentry"
|
||||
import { router } from "./tipc"
|
||||
import { createMainWindow, getMainWindow } from "./window"
|
||||
|
||||
const appFolder = {
|
||||
prod: "Follow",
|
||||
dev: "Follow (dev)",
|
||||
}
|
||||
if (process.argv.length === 3 && process.argv[2].startsWith("follow-dev:")) {
|
||||
process.env.NODE_ENV = "development"
|
||||
}
|
||||
|
|
@ -31,7 +28,8 @@ const isDev = process.env.NODE_ENV === "development"
|
|||
* Mandatory and fast initializers for the app
|
||||
*/
|
||||
export function initializeAppStage0() {
|
||||
app.setPath("appData", path.join(app.getPath("appData"), isDev ? appFolder.dev : appFolder.prod))
|
||||
if (isDev) app.setPath("appData", path.join(app.getPath("appData"), "Follow (dev)"))
|
||||
initializeSentry()
|
||||
}
|
||||
export const initializeAppStage1 = () => {
|
||||
if (process.defaultApp) {
|
||||
|
|
@ -44,8 +42,6 @@ export const initializeAppStage1 = () => {
|
|||
app.setAsDefaultProtocolClient(APP_PROTOCOL)
|
||||
}
|
||||
|
||||
initializeSentry()
|
||||
|
||||
registerIpcMain(router)
|
||||
|
||||
if (app.dock) {
|
||||
|
|
@ -149,17 +145,28 @@ const registerPushNotifications = async () => {
|
|||
updateNotificationsToken()
|
||||
|
||||
const instance = new PushReceiver({
|
||||
debug: isDev,
|
||||
firebase: env.VITE_FIREBASE_CONFIG,
|
||||
debug: true,
|
||||
firebase: JSON.parse(env.VITE_FIREBASE_CONFIG),
|
||||
persistentIds: persistentIds || [],
|
||||
credentials,
|
||||
credentials: credentials || null,
|
||||
bundleId: "is.follow",
|
||||
chromeId: "is.follow",
|
||||
})
|
||||
logger.info(
|
||||
`PushReceiver initialized with credentials ${JSON.stringify(credentials)} and firebase config ${env.VITE_FIREBASE_CONFIG}`,
|
||||
)
|
||||
|
||||
instance.onReady(() => {
|
||||
logger.info("PushReceiver ready")
|
||||
})
|
||||
|
||||
instance.onCredentialsChanged(({ newCredentials }) => {
|
||||
logger.info(`PushReceiver credentials changed to ${newCredentials?.fcm?.token}`)
|
||||
updateNotificationsToken(newCredentials)
|
||||
})
|
||||
|
||||
instance.onNotification((notification) => {
|
||||
logger.info(`PushReceiver received notification: ${JSON.stringify(notification.message.data)}`)
|
||||
const data = notification.message.data as MessagingData
|
||||
switch (data.type) {
|
||||
case "new-entry": {
|
||||
|
|
@ -191,5 +198,11 @@ const registerPushNotifications = async () => {
|
|||
store.set(persistentIdsKey, instance.persistentIds)
|
||||
})
|
||||
|
||||
await instance.connect()
|
||||
try {
|
||||
await instance.connect()
|
||||
} catch (error) {
|
||||
logger.error(`PushReceiver error: ${error instanceof Error ? error.stack : error}`)
|
||||
}
|
||||
|
||||
logger.info("PushReceiver connected")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,15 +3,26 @@ import { resolve } from "node:path"
|
|||
import { app } from "electron"
|
||||
import { JSONFileSyncPreset } from "lowdb/node"
|
||||
|
||||
const db = JSONFileSyncPreset(resolve(app.getPath("userData"), "db.json"), {}) as {
|
||||
let db: {
|
||||
data: Record<string, unknown>
|
||||
write: () => void
|
||||
read: () => void
|
||||
}
|
||||
|
||||
const createOrGetDb = () => {
|
||||
if (!db) {
|
||||
db = JSONFileSyncPreset(resolve(app.getPath("userData"), "db.json"), {}) as typeof db
|
||||
}
|
||||
return db
|
||||
}
|
||||
export const store = {
|
||||
get: (key: string) => db.data[key] as any,
|
||||
get: (key: string) => {
|
||||
const db = createOrGetDb()
|
||||
|
||||
return db.data[key] as any
|
||||
},
|
||||
set: (key: string, value: any) => {
|
||||
const db = createOrGetDb()
|
||||
db.data[key] = value
|
||||
db.write()
|
||||
},
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ const ContextMenuSubContent = React.forwardRef<
|
|||
<ContextMenuPrimitive.SubContent
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"z-[1001] min-w-32 overflow-hidden rounded-md border bg-theme-modal-background-opaque p-1 text-theme-foreground/90 shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:shadow-zinc-800/60",
|
||||
"min-w-32 overflow-hidden rounded-md border bg-theme-modal-background-opaque p-1 text-theme-foreground/90 shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:shadow-zinc-800/60",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -6,9 +6,7 @@ import { jotaiStore } from "~/lib/jotai"
|
|||
import { cn } from "~/lib/utils"
|
||||
|
||||
import { modalStackAtom } from "./atom"
|
||||
import { MODAL_STACK_Z_INDEX } from "./constants"
|
||||
import { ModalInternal } from "./modal"
|
||||
import { ModalOverlay } from "./overlay"
|
||||
import type { ModalProps } from "./types"
|
||||
|
||||
export interface DeclarativeModalProps extends Omit<ModalProps, "content"> {
|
||||
|
|
@ -40,12 +38,9 @@ const DeclarativeModalImpl: FC<DeclarativeModalProps> = ({
|
|||
return (
|
||||
<AnimatePresence>
|
||||
{open && (
|
||||
<>
|
||||
<ModalInternal isTop onClose={onOpenChange} index={index} item={item}>
|
||||
{children}
|
||||
</ModalInternal>
|
||||
<ModalOverlay zIndex={MODAL_STACK_Z_INDEX - 1 + index} />
|
||||
</>
|
||||
<ModalInternal isTop onClose={onOpenChange} index={index} item={item}>
|
||||
{children}
|
||||
</ModalInternal>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -34,11 +34,12 @@ import { Divider } from "../../divider"
|
|||
import { RootPortalProvider } from "../../portal/provider"
|
||||
import { EllipsisHorizontalTextWithTooltip } from "../../typography"
|
||||
import { modalStackAtom } from "./atom"
|
||||
import { MODAL_STACK_Z_INDEX, modalMontionConfig } from "./constants"
|
||||
import { modalMontionConfig } from "./constants"
|
||||
import type { CurrentModalContentProps, ModalActionsInternal } from "./context"
|
||||
import { CurrentModalContext } from "./context"
|
||||
import { useResizeableModal } from "./hooks"
|
||||
import type { ModalProps } from "./types"
|
||||
import { ModalOverlay } from "./overlay"
|
||||
import type { ModalOverlayOptions, ModalProps } from "./types"
|
||||
|
||||
const DragBar = isElectronBuild ? (
|
||||
<span className="drag-region fixed left-0 right-36 top-0 h-8" />
|
||||
|
|
@ -51,9 +52,10 @@ export const ModalInternal = memo(
|
|||
index: number
|
||||
|
||||
isTop: boolean
|
||||
overlayOptions?: ModalOverlayOptions
|
||||
onClose?: (open: boolean) => void
|
||||
} & PropsWithChildren
|
||||
>(function Modal({ item, index, onClose: onPropsClose, children, isTop }, ref) {
|
||||
>(function Modal({ item, overlayOptions, onClose: onPropsClose, children, isTop }, ref) {
|
||||
const {
|
||||
CustomModalComponent,
|
||||
modalClassName,
|
||||
|
|
@ -96,8 +98,8 @@ export const ModalInternal = memo(
|
|||
)
|
||||
|
||||
const opaque = useUISettingKey("modalOpaque")
|
||||
const modalSettingOverlay = useUISettingKey("modalOverlay")
|
||||
|
||||
const zIndexStyle = useMemo(() => ({ zIndex: MODAL_STACK_Z_INDEX + index + 1 }), [index])
|
||||
const dismiss = useCallback(
|
||||
(e: SyntheticEvent) => {
|
||||
e.stopPropagation()
|
||||
|
|
@ -224,10 +226,7 @@ export const ModalInternal = memo(
|
|||
}
|
||||
}, [switchHotkeyScope])
|
||||
|
||||
const modalStyle = useMemo(
|
||||
() => ({ ...zIndexStyle, ...resizeableStyle }),
|
||||
[resizeableStyle, zIndexStyle],
|
||||
)
|
||||
const modalStyle = resizeableStyle
|
||||
const isSelectingRef = useRef(false)
|
||||
const handleSelectStart = useCallback(() => {
|
||||
isSelectingRef.current = true
|
||||
|
|
@ -269,17 +268,27 @@ export const ModalInternal = memo(
|
|||
}, [])
|
||||
|
||||
useImperativeHandle(ref, () => modalElementRef.current!)
|
||||
|
||||
const Overlay = (
|
||||
<ModalOverlay
|
||||
blur={overlayOptions?.blur}
|
||||
className={cn(overlayOptions?.className, {
|
||||
hidden: item.overlay ? false : !modalSettingOverlay,
|
||||
})}
|
||||
/>
|
||||
)
|
||||
if (CustomModalComponent) {
|
||||
return (
|
||||
<Wrapper>
|
||||
<Dialog.Root open onOpenChange={onClose} modal={modal}>
|
||||
<Dialog.Portal>
|
||||
{Overlay}
|
||||
<Dialog.DialogTitle className="sr-only">{title}</Dialog.DialogTitle>
|
||||
<Dialog.Content asChild onOpenAutoFocus={openAutoFocus}>
|
||||
<Dialog.Content asChild aria-describedby={undefined} onOpenAutoFocus={openAutoFocus}>
|
||||
<div
|
||||
ref={edgeElementRef}
|
||||
className={cn(
|
||||
"no-drag-region fixed z-20",
|
||||
"no-drag-region fixed",
|
||||
modal ? "inset-0 overflow-auto" : "left-0 top-0",
|
||||
currentIsClosing ? "!pointer-events-none" : "!pointer-events-auto",
|
||||
modalContainerClassName,
|
||||
|
|
@ -287,7 +296,6 @@ export const ModalInternal = memo(
|
|||
onPointerUp={handleDetectSelectEnd}
|
||||
onClick={handleClickOutsideToDismiss}
|
||||
onFocus={stopPropagation}
|
||||
style={zIndexStyle}
|
||||
>
|
||||
{DragBar}
|
||||
<div
|
||||
|
|
@ -315,12 +323,12 @@ export const ModalInternal = memo(
|
|||
<Wrapper>
|
||||
<Dialog.Root modal={modal} open onOpenChange={onClose}>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Content asChild onOpenAutoFocus={openAutoFocus}>
|
||||
{Overlay}
|
||||
<Dialog.Content asChild aria-describedby={undefined} onOpenAutoFocus={openAutoFocus}>
|
||||
<div
|
||||
ref={edgeElementRef}
|
||||
style={zIndexStyle}
|
||||
className={cn(
|
||||
"fixed z-20 flex",
|
||||
"fixed flex",
|
||||
modal ? "inset-0 overflow-auto" : "left-0 top-0",
|
||||
currentIsClosing && "!pointer-events-none",
|
||||
modalContainerClassName,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import * as Dialog from "@radix-ui/react-dialog"
|
||||
import type { ForwardedRef } from "react"
|
||||
import { forwardRef } from "react"
|
||||
|
||||
import { m } from "~/components/common/Motion"
|
||||
import { cn } from "~/lib/utils"
|
||||
|
||||
import { RootPortal } from "../../portal"
|
||||
import { softSpringPreset } from "../../constants/spring"
|
||||
|
||||
export const ModalOverlay = forwardRef(
|
||||
(
|
||||
|
|
@ -19,20 +20,22 @@ export const ModalOverlay = forwardRef(
|
|||
},
|
||||
ref: ForwardedRef<HTMLDivElement>,
|
||||
) => (
|
||||
<RootPortal>
|
||||
<Dialog.Overlay asChild>
|
||||
<m.div
|
||||
ref={ref}
|
||||
id="modal-overlay"
|
||||
className={cn(
|
||||
"!pointer-events-none fixed inset-0 z-[11] rounded-[var(--fo-window-radius)] bg-zinc-50/80 dark:bg-neutral-900/80",
|
||||
// NOTE: pointer-events-none is required, if remove this, when modal is closing, you can not click element behind the modal
|
||||
"!pointer-events-none fixed inset-0 rounded-[var(--fo-window-radius)] bg-zinc-50/80 dark:bg-neutral-900/80",
|
||||
blur && "backdrop-blur-sm",
|
||||
className,
|
||||
)}
|
||||
transition={softSpringPreset}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
style={{ zIndex }}
|
||||
/>
|
||||
</RootPortal>
|
||||
</Dialog.Overlay>
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,12 +2,8 @@ import { AnimatePresence } from "framer-motion"
|
|||
import { useAtomValue } from "jotai"
|
||||
import type { FC, PropsWithChildren } from "react"
|
||||
|
||||
import { useUISettingKey } from "~/atoms/settings/ui"
|
||||
|
||||
import { modalStackAtom } from "./atom"
|
||||
import { MODAL_STACK_Z_INDEX } from "./constants"
|
||||
import { ModalInternal } from "./modal"
|
||||
import { ModalOverlay } from "./overlay"
|
||||
|
||||
export const ModalStackProvider: FC<PropsWithChildren> = ({ children }) => (
|
||||
<>
|
||||
|
|
@ -19,11 +15,6 @@ export const ModalStackProvider: FC<PropsWithChildren> = ({ children }) => (
|
|||
const ModalStack = () => {
|
||||
const stack = useAtomValue(modalStackAtom)
|
||||
|
||||
const modalSettingOverlay = useUISettingKey("modalOverlay")
|
||||
|
||||
const forceOverlay = stack.some((item) => item.overlay)
|
||||
const allForceHideOverlay = stack.every((item) => item.overlay === false)
|
||||
|
||||
const topModalIndex = stack.findLastIndex((item) => item.modal)
|
||||
const overlayIndex = stack.findLastIndex((item) => item.overlay || item.modal)
|
||||
const overlayOptions = stack[overlayIndex]?.overlayOptions
|
||||
|
|
@ -35,15 +26,9 @@ const ModalStack = () => {
|
|||
item={item}
|
||||
index={index * 2}
|
||||
isTop={index === topModalIndex * 2}
|
||||
overlayOptions={overlayOptions}
|
||||
/>
|
||||
))}
|
||||
{stack.length > 0 && (modalSettingOverlay || forceOverlay) && !allForceHideOverlay && (
|
||||
<ModalOverlay
|
||||
zIndex={MODAL_STACK_Z_INDEX + overlayIndex * 2 - 2}
|
||||
blur={overlayOptions?.blur}
|
||||
className={overlayOptions?.className}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ const SelectContent = React.forwardRef<
|
|||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"shadow-perfect relative z-[1000] max-h-96 min-w-32 overflow-hidden rounded-md border bg-popover text-popover-foreground",
|
||||
"shadow-perfect relative max-h-96 min-w-32 overflow-hidden rounded-md border bg-popover text-popover-foreground",
|
||||
position === "popper" && [
|
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { useOnClickOutside } from "usehooks-ts"
|
|||
import { ActionButton, Button, IconButton } from "~/components/ui/button"
|
||||
import { Kbd, KbdCombined } from "~/components/ui/kbd/Kbd"
|
||||
import { RootPortal } from "~/components/ui/portal"
|
||||
import { ElECTRON_CUSTOM_TITLEBAR_HEIGHT, HotKeyScopeMap } from "~/constants"
|
||||
import { ElECTRON_CUSTOM_TITLEBAR_HEIGHT, HotKeyScopeMap, isElectronBuild } from "~/constants"
|
||||
import { shortcuts } from "~/constants/shortcuts"
|
||||
import { useI18n } from "~/hooks/common"
|
||||
import { cn, getOS } from "~/lib/utils"
|
||||
|
|
@ -51,7 +51,9 @@ export const MarkAllReadWithOverlay = forwardRef<
|
|||
<m.div
|
||||
ref={setPopoverRef}
|
||||
initial={{ y: -70 }}
|
||||
animate={{ y: getOS() === "Windows" ? -ElECTRON_CUSTOM_TITLEBAR_HEIGHT : 0 }}
|
||||
animate={{
|
||||
y: isElectronBuild && getOS() === "Windows" ? -ElECTRON_CUSTOM_TITLEBAR_HEIGHT : 0,
|
||||
}}
|
||||
exit={{ y: -70 }}
|
||||
transition={{ type: "spring", damping: 20, stiffness: 300 }}
|
||||
className="shadow-modal absolute z-50 bg-theme-modal-background-opaque shadow"
|
||||
|
|
|
|||
|
|
@ -14,12 +14,12 @@ export const EntryPlaceholderLogo = () => {
|
|||
<div
|
||||
onContextMenu={stopPropagation}
|
||||
className={cn(
|
||||
"flex w-full min-w-0 flex-col items-center justify-center gap-1 text-balance px-12 pb-6 text-center text-lg font-medium text-zinc-400 duration-500",
|
||||
"flex w-full min-w-0 flex-col items-center justify-center gap-1 px-12 pb-6 text-center text-lg font-medium text-zinc-400 duration-500",
|
||||
!logoShow && "translate-y-[-50px] opacity-0",
|
||||
)}
|
||||
>
|
||||
<Logo className="size-16 opacity-40 grayscale" />
|
||||
<span className="max-w-[60ch]">{title}</span>
|
||||
<div className="line-clamp-3 w-[60ch] max-w-full">{title}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react
|
|||
import { useDebounceCallback, useEventCallback } from "usehooks-ts"
|
||||
|
||||
import { softSpringPreset } from "~/components/ui/constants/spring"
|
||||
import { RootPortal } from "~/components/ui/portal"
|
||||
import { useInputComposition, useRefValue } from "~/hooks/common"
|
||||
import { tipcClient } from "~/lib/client"
|
||||
import { nextFrame } from "~/lib/dom"
|
||||
|
|
@ -117,7 +118,7 @@ const CmdFImpl: FC<{
|
|||
e.preventDefault()
|
||||
nativeSearch(value)
|
||||
}}
|
||||
className="center shadow-perfect fixed right-8 top-12 z-[1000] size-9 w-64 gap-2 rounded-2xl border bg-zinc-50/90 pl-3 pr-2 backdrop-blur duration-200 focus-within:border-accent dark:bg-neutral-800/80"
|
||||
className="center shadow-perfect fixed right-8 top-12 size-9 w-64 gap-2 rounded-2xl border bg-zinc-50/90 pl-3 pr-2 backdrop-blur duration-200 focus-within:border-accent dark:bg-neutral-800/80"
|
||||
>
|
||||
<div className="relative h-full grow">
|
||||
<input
|
||||
|
|
@ -238,22 +239,24 @@ export const CmdF = () => {
|
|||
setShow(true)
|
||||
})
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{show && (
|
||||
<m.div
|
||||
className="relative z-[1000]"
|
||||
initial={{ opacity: 0.8, y: -150 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -150 }}
|
||||
transition={softSpringPreset}
|
||||
>
|
||||
<CmdFImpl
|
||||
onClose={() => {
|
||||
setShow(false)
|
||||
}}
|
||||
/>
|
||||
</m.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<RootPortal>
|
||||
<AnimatePresence>
|
||||
{show && (
|
||||
<m.div
|
||||
className="fixed top-0 w-full"
|
||||
initial={{ opacity: 0.8, y: -150 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -150 }}
|
||||
transition={softSpringPreset}
|
||||
>
|
||||
<CmdFImpl
|
||||
onClose={() => {
|
||||
setShow(false)
|
||||
}}
|
||||
/>
|
||||
</m.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</RootPortal>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ export const TransactionsSection: Component = ({ className }) => {
|
|||
</Tabs>
|
||||
<div className={cn("w-fit min-w-0 grow overflow-x-auto", className)}>
|
||||
<Table className="w-full table-fixed">
|
||||
<TableHeader className="sticky top-0 z-10 bg-theme-background">
|
||||
<TableHeader className="sticky top-0 bg-theme-background">
|
||||
<TableRow className="[&_*]:!pl-0 [&_*]:!font-semibold">
|
||||
<TableHead>{t("wallet.transactions.type")}</TableHead>
|
||||
<TableHead>{t("wallet.transactions.amount")}</TableHead>
|
||||
|
|
|
|||
|
|
@ -398,7 +398,7 @@ const SubscriptionItem: FC<{
|
|||
!isLoose && "flex items-center",
|
||||
)}
|
||||
>
|
||||
<div className="truncate font-medium leading-none">{subscription.feeds?.title}</div>
|
||||
<div className="truncate font-medium leading-tight">{subscription.feeds?.title}</div>
|
||||
{isLoose && (
|
||||
<div className="mt-1 line-clamp-1 text-xs text-zinc-500">
|
||||
{subscription.feeds?.description}
|
||||
|
|
|
|||
|
|
@ -123,10 +123,6 @@ export function Component() {
|
|||
</AppErrorBoundary>
|
||||
</main>
|
||||
|
||||
<SearchCmdK />
|
||||
<CmdNTrigger />
|
||||
{ELECTRON && <CmdF />}
|
||||
|
||||
{isAuthFail && !user && (
|
||||
<RootPortal>
|
||||
<DeclarativeModal
|
||||
|
|
@ -141,6 +137,10 @@ export function Component() {
|
|||
</DeclarativeModal>
|
||||
</RootPortal>
|
||||
)}
|
||||
|
||||
<SearchCmdK />
|
||||
<CmdNTrigger />
|
||||
{ELECTRON && <CmdF />}
|
||||
</RootContainer>
|
||||
)
|
||||
}
|
||||
|
|
@ -155,7 +155,7 @@ const RootContainer = forwardRef<HTMLDivElement, PropsWithChildren>(({ children
|
|||
"--fo-feed-col-w": `${feedColWidth}px`,
|
||||
} as any
|
||||
}
|
||||
className="flex h-screen overflow-hidden"
|
||||
className="relative z-0 flex h-screen overflow-hidden"
|
||||
onContextMenu={preventDefault}
|
||||
>
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export const AppLayoutGridContainerProvider: FC<PropsWithChildren> = ({ children
|
|||
|
||||
return (
|
||||
<AppLayoutGridContainerWidthContext.Provider value={width}>
|
||||
<div ref={ref} className="contents">
|
||||
<div ref={ref} className="relative z-0 contents">
|
||||
{children}
|
||||
</div>
|
||||
</AppLayoutGridContainerWidthContext.Provider>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
export { ContextMenuProvider as LazyContextMenuProvider } from "../context-menu-provider"
|
||||
export { ExtensionExposeProvider as LazyExtensionExposeProvider } from "../extension-expose-provider"
|
||||
export { LottieRenderContainer as LazyLottieRenderContainer } from "~/components/ui/lottie-container"
|
||||
export { ModalStackProvider as LazyModalStackProvider } from "~/components/ui/modal"
|
||||
export { FeatureFlagDebugger as LazyFeatureFlagDebugger } from "~/modules/ab/providers"
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
import { lazy } from "react"
|
||||
|
||||
const LazyLottieRenderContainer = lazy(() =>
|
||||
import("../../components/ui/lottie-container").then((res) => ({
|
||||
default: res.LottieRenderContainer,
|
||||
})),
|
||||
)
|
||||
const LazyContextMenuProvider = lazy(() =>
|
||||
import("./../context-menu-provider").then((res) => ({
|
||||
default: res.ContextMenuProvider,
|
||||
})),
|
||||
)
|
||||
const LazyModalStackProvider = lazy(() =>
|
||||
import("../../components/ui/modal/stacked/provider").then((res) => ({
|
||||
default: res.ModalStackProvider,
|
||||
})),
|
||||
)
|
||||
|
||||
const LazyExtensionExposeProvider = lazy(() =>
|
||||
import("./../extension-expose-provider").then((res) => ({
|
||||
default: res.ExtensionExposeProvider,
|
||||
})),
|
||||
)
|
||||
const LazyFeatureFlagDebugger = lazy(() =>
|
||||
import("../../modules/ab/providers").then((res) => ({
|
||||
default: res.FeatureFlagDebugger,
|
||||
})),
|
||||
)
|
||||
export {
|
||||
LazyContextMenuProvider,
|
||||
LazyExtensionExposeProvider,
|
||||
LazyFeatureFlagDebugger,
|
||||
LazyLottieRenderContainer,
|
||||
LazyModalStackProvider,
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client
|
|||
import { LazyMotion, MotionConfig } from "framer-motion"
|
||||
import { Provider } from "jotai"
|
||||
import type { FC, PropsWithChildren } from "react"
|
||||
import { lazy, Suspense } from "react"
|
||||
import { Suspense } from "react"
|
||||
import { HotkeysProvider } from "react-hotkeys-hook"
|
||||
|
||||
import { Toaster } from "~/components/ui/sonner"
|
||||
|
|
@ -15,36 +15,18 @@ import { persistConfig, queryClient } from "~/lib/query-client"
|
|||
import { EventProvider } from "./event-provider"
|
||||
import { I18nProvider } from "./i18n-provider"
|
||||
import { InvalidateQueryProvider } from "./invalidate-query-provider"
|
||||
import {
|
||||
LazyContextMenuProvider,
|
||||
LazyExtensionExposeProvider,
|
||||
LazyFeatureFlagDebugger,
|
||||
LazyLottieRenderContainer,
|
||||
LazyModalStackProvider,
|
||||
// specific import should add `index` postfix
|
||||
} from "./lazy/index"
|
||||
import { SettingSync } from "./setting-sync"
|
||||
import { StableRouterProvider } from "./stable-router-provider"
|
||||
import { UserProvider } from "./user-provider"
|
||||
|
||||
const LazyLottieRenderContainer = lazy(() =>
|
||||
import("../components/ui/lottie-container").then((res) => ({
|
||||
default: res.LottieRenderContainer,
|
||||
})),
|
||||
)
|
||||
const LazyContextMenuProvider = lazy(() =>
|
||||
import("./context-menu-provider").then((res) => ({
|
||||
default: res.ContextMenuProvider,
|
||||
})),
|
||||
)
|
||||
const LazyModalStackProvider = lazy(() =>
|
||||
import("../components/ui/modal/stacked/provider").then((res) => ({
|
||||
default: res.ModalStackProvider,
|
||||
})),
|
||||
)
|
||||
|
||||
const LazyExtensionExposeProvider = lazy(() =>
|
||||
import("./extension-expose-provider").then((res) => ({
|
||||
default: res.ExtensionExposeProvider,
|
||||
})),
|
||||
)
|
||||
const LazyFeatureFlagDebugger = lazy(() =>
|
||||
import("../modules/ab/providers").then((res) => ({
|
||||
default: res.FeatureFlagDebugger,
|
||||
})),
|
||||
)
|
||||
const loadFeatures = () => import("../framer-lazy-feature").then((res) => res.default)
|
||||
export const RootProviders: FC<PropsWithChildren> = ({ children }) => (
|
||||
<LazyMotion features={loadFeatures} strict key="framer">
|
||||
|
|
@ -62,6 +44,13 @@ export const RootProviders: FC<PropsWithChildren> = ({ children }) => (
|
|||
<EventProvider />
|
||||
|
||||
<UserProvider />
|
||||
|
||||
<StableRouterProvider />
|
||||
<SettingSync />
|
||||
|
||||
{import.meta.env.DEV && <Devtools />}
|
||||
{children}
|
||||
|
||||
<Suspense>
|
||||
<LazyExtensionExposeProvider />
|
||||
<LazyModalStackProvider />
|
||||
|
|
@ -69,12 +58,6 @@ export const RootProviders: FC<PropsWithChildren> = ({ children }) => (
|
|||
<LazyLottieRenderContainer />
|
||||
<LazyFeatureFlagDebugger />
|
||||
</Suspense>
|
||||
|
||||
<StableRouterProvider />
|
||||
<SettingSync />
|
||||
|
||||
{import.meta.env.DEV && <Devtools />}
|
||||
{children}
|
||||
<Toaster />
|
||||
</I18nProvider>
|
||||
</Provider>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
import { IN_ELECTRON } from "@follow/shared/constants"
|
||||
import { wrapCreateBrowserRouter } from "@sentry/react"
|
||||
import { createBrowserRouter, createHashRouter } from "react-router-dom"
|
||||
|
||||
import { ErrorElement } from "./components/common/ErrorElement"
|
||||
import { NotFound } from "./components/common/NotFound"
|
||||
import { buildGlobRoutes } from "./lib/route-builder"
|
||||
|
||||
const globTree = import.meta.glob("./pages/**/*.tsx")
|
||||
const tree = buildGlobRoutes(globTree)
|
||||
|
||||
let routerCreator =
|
||||
IN_ELECTRON || globalThis["__DEBUG_PROXY__"] ? createHashRouter : createBrowserRouter
|
||||
if (window.SENTRY_RELEASE) {
|
||||
routerCreator = wrapCreateBrowserRouter(routerCreator)
|
||||
}
|
||||
|
||||
export const router = routerCreator([
|
||||
{
|
||||
path: "/",
|
||||
lazy: () => import("./App"),
|
||||
children: tree,
|
||||
errorElement: <ErrorElement />,
|
||||
},
|
||||
{
|
||||
path: "*",
|
||||
element: <NotFound />,
|
||||
},
|
||||
])
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
export default {
|
||||
tagFilter: (tag: string) => tag.startsWith("v") && !tag.includes("nightly"),
|
||||
dry: true,
|
||||
dry: !process.env.CI,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { resolve } from "node:path"
|
|||
import { defineConfig } from "electron-vite"
|
||||
|
||||
import { viteRenderBaseConfig } from "./configs/vite.render.config"
|
||||
import { createPlatformSpecificImportPlugin } from "./plugins/vite/specific-import"
|
||||
|
||||
export default defineConfig({
|
||||
main: {
|
||||
|
|
@ -41,6 +42,8 @@ export default defineConfig({
|
|||
renderer: {
|
||||
...viteRenderBaseConfig,
|
||||
|
||||
plugins: [...viteRenderBaseConfig.plugins, createPlatformSpecificImportPlugin(true)],
|
||||
|
||||
root: "apps/renderer",
|
||||
build: {
|
||||
outDir: "dist/renderer",
|
||||
|
|
|
|||
|
|
@ -154,6 +154,7 @@ const config: ForgeConfig = {
|
|||
new MakerSquirrel({
|
||||
name: "Follow",
|
||||
setupIcon: "resources/icon.ico",
|
||||
iconUrl: "https://app.follow.is/favicon.ico",
|
||||
}),
|
||||
new MakerAppImage({
|
||||
options: {
|
||||
|
|
|
|||
|
|
@ -54,9 +54,9 @@
|
|||
"entry_actions.copied_notify": "{{which}}已複製到剪貼板",
|
||||
"entry_actions.copy_link": "複製連結",
|
||||
"entry_actions.copy_title": "複製標題",
|
||||
"entry_actions.failed_to_save_to_eagle": "無法保存至 Eagle。",
|
||||
"entry_actions.failed_to_save_to_instapaper": "無法保存至 Instapaper。",
|
||||
"entry_actions.failed_to_save_to_readwise": "無法保存至 Readwise。",
|
||||
"entry_actions.failed_to_save_to_eagle": "無法保存至 Eagle",
|
||||
"entry_actions.failed_to_save_to_instapaper": "無法保存至 Instapaper",
|
||||
"entry_actions.failed_to_save_to_readwise": "無法保存至 Readwise",
|
||||
"entry_actions.mark_as_read": "標記為已讀",
|
||||
"entry_actions.mark_as_unread": "標記為未讀",
|
||||
"entry_actions.open_in_browser": "在{{which}}中開啟",
|
||||
|
|
@ -64,17 +64,17 @@
|
|||
"entry_actions.save_media_to_eagle": "保存媒體至 Eagle",
|
||||
"entry_actions.save_to_instapaper": "保存至 Instapaper",
|
||||
"entry_actions.save_to_readwise": "保存至 Readwise",
|
||||
"entry_actions.saved_to_eagle": "已保存至 Eagle。",
|
||||
"entry_actions.saved_to_instapaper": "已保存至 Instapaper。",
|
||||
"entry_actions.saved_to_readwise": "已保存至 Readwise。",
|
||||
"entry_actions.saved_to_eagle": "已保存至 Eagle",
|
||||
"entry_actions.saved_to_instapaper": "已保存至 Instapaper",
|
||||
"entry_actions.saved_to_readwise": "已保存至 Readwise",
|
||||
"entry_actions.share": "分享",
|
||||
"entry_actions.star": "收藏",
|
||||
"entry_actions.starred": "已收藏。",
|
||||
"entry_actions.starred": "已收藏",
|
||||
"entry_actions.tip": "贊助",
|
||||
"entry_actions.unstar": "取消收藏",
|
||||
"entry_actions.unstarred": "已取消收藏。",
|
||||
"entry_actions.unstarred": "已取消收藏",
|
||||
"entry_actions.view_source_content": "查看原始內容",
|
||||
"entry_column.filtered_content_tip": "你已隱藏篩選內容。",
|
||||
"entry_column.filtered_content_tip": "你已隱藏篩選內容",
|
||||
"entry_column.filtered_content_tip_2": "除了以上顯示的條目,還有一些已篩選的內容。",
|
||||
"entry_column.refreshing": "刷新新條目中...",
|
||||
"entry_content.ai_summary": "AI 摘要",
|
||||
|
|
@ -85,12 +85,12 @@
|
|||
"entry_content.readability_notice": "此內容由 Readability 提供。如果你發現排版異常,請到來源網站查看原始內容。",
|
||||
"entry_content.render_error": "渲染錯誤:",
|
||||
"entry_content.report_issue": "報告問題",
|
||||
"entry_content.support_amount": "{{amount}} 人支持了此訂閱源的創作者。",
|
||||
"entry_content.support_amount": "{{amount}} 人支持了此訂閱源的創作者",
|
||||
"entry_content.support_creator": "支持創作者",
|
||||
"entry_content.web_app_notice": "網頁應用可能不支持此內容類型。你可以下載桌面應用程式。",
|
||||
"entry_list.zero_unread": "全部已讀",
|
||||
"entry_list_header.daily_report": "每日報告",
|
||||
"entry_list_header.hide_no_image_items": "隱藏沒有圖片的條目。",
|
||||
"entry_list_header.hide_no_image_items": "隱藏沒有圖片的條目",
|
||||
"entry_list_header.items": "項目",
|
||||
"entry_list_header.new_entries_available": "有新條目可用",
|
||||
"entry_list_header.refetch": "重新抓取",
|
||||
|
|
@ -105,12 +105,12 @@
|
|||
"entry_list_header.unread": "未讀",
|
||||
"feed_claim_modal.choose_verification_method": "有三種驗證方法可選,你可以選擇其中一種進行驗證。",
|
||||
"feed_claim_modal.claim_button": "認領",
|
||||
"feed_claim_modal.content_instructions": "複製以下內容並貼到你最新的 RSS 訂閱源中。",
|
||||
"feed_claim_modal.content_instructions": "複製以下內容並貼到你最新的 RSS 訂閱源中",
|
||||
"feed_claim_modal.description_current": "當前描述:",
|
||||
"feed_claim_modal.description_instructions": "複製以下內容並貼到你的 RSS 訂閱源中的 <code /> 欄位。",
|
||||
"feed_claim_modal.description_instructions": "複製以下內容並貼到你的 RSS 訂閱源中的 <code /> 欄位",
|
||||
"feed_claim_modal.failed_to_load": "無法加載認領信息",
|
||||
"feed_claim_modal.rss_format_choice": "RSS 生成器通常有兩種格式可選。請根據需要複製以下的 XML 和 JSON 格式。",
|
||||
"feed_claim_modal.rss_instructions": "複製以下代碼並貼到你的 RSS 生成器中。",
|
||||
"feed_claim_modal.rss_instructions": "複製以下代碼並貼到你的 RSS 生成器中",
|
||||
"feed_claim_modal.rss_json_format": "JSON 格式",
|
||||
"feed_claim_modal.rss_xml_format": "XML 格式",
|
||||
"feed_claim_modal.rsshub_notice": "此訂閱源由 RSSHub 提供,緩存時間為 1 小時。發布內容後,請等待最多 1 小時更改才能生效。",
|
||||
|
|
@ -122,23 +122,23 @@
|
|||
"feed_form.add_follow": "新增關注",
|
||||
"feed_form.category": "分類",
|
||||
"feed_form.category_description": "默認情況下,你的關注會按網站分組。",
|
||||
"feed_form.error_fetching_feed": "獲取訂閱源時出錯。",
|
||||
"feed_form.error_fetching_feed": "獲取訂閱源時出錯",
|
||||
"feed_form.fee": "跟隨費用",
|
||||
"feed_form.fee_description": "要跟隨此清單,您必須向清單創建者支付費用。",
|
||||
"feed_form.feed_not_found": "未找到相關資訊流。",
|
||||
"feed_form.feed_not_found": "未找到相關資訊流",
|
||||
"feed_form.feedback": "反饋",
|
||||
"feed_form.follow": "關注",
|
||||
"feed_form.follow_with_fee": "跟隨需要 {{fee}} Power",
|
||||
"feed_form.followed": "🎉 已關注。",
|
||||
"feed_form.followed": "🎉 已關注",
|
||||
"feed_form.private_follow": "私人關注",
|
||||
"feed_form.private_follow_description": "此關注是否在你的個人資料頁面上公開可見。",
|
||||
"feed_form.private_follow_description": "此關注是否在你的個人資料頁面上公開可見",
|
||||
"feed_form.retry": "重試",
|
||||
"feed_form.title": "標題",
|
||||
"feed_form.title_description": "此資訊流的自定義標題。留空則使用默認標題。",
|
||||
"feed_form.unfollow": "取消關注",
|
||||
"feed_form.update": "更新",
|
||||
"feed_form.update_follow": "更新關注",
|
||||
"feed_form.updated": "🎉 已更新。",
|
||||
"feed_form.updated": "🎉 已更新",
|
||||
"feed_form.view": "視圖",
|
||||
"feed_item.claimed_by_owner": "訂閲源所有者",
|
||||
"feed_item.claimed_by_unknown": "未知所有者",
|
||||
|
|
@ -152,7 +152,7 @@
|
|||
"feed_view_type.pictures": "圖片",
|
||||
"feed_view_type.social_media": "社交媒體",
|
||||
"feed_view_type.videos": "視頻",
|
||||
"mark_all_read_button.auto_confirm_info": "將在 3 秒後自動確認。",
|
||||
"mark_all_read_button.auto_confirm_info": "將在 3 秒後自動確認",
|
||||
"mark_all_read_button.confirm": "確認",
|
||||
"mark_all_read_button.confirm_mark_all": "確定將 <which /> 標記為已讀?",
|
||||
"mark_all_read_button.confirm_mark_all_info": "確定將所有標記為已讀?",
|
||||
|
|
@ -163,7 +163,7 @@
|
|||
"notify.unSupportWidth": "{{app_name}} 目前尚未支援流動裝置",
|
||||
"notify.unSupportWidth_1": "您的裝置寬度為 <b>{{width}}</b>,低於支援的最小寬度 {{minWidth}}",
|
||||
"notify.unSupportWidth_2": "請切換至桌面應用程式繼續使用 {{app_name}} <br /> 下載連結:<url />",
|
||||
"notify.unfollow_feed": "已取消關注 <FeedItem />。",
|
||||
"notify.unfollow_feed": "已取消關注 <FeedItem />",
|
||||
"notify.update_info": "{{app_name}} 已準備好更新!",
|
||||
"notify.update_info_1": "點擊以重新啟動",
|
||||
"player.back_10s": "倒退 10 秒",
|
||||
|
|
@ -181,7 +181,7 @@
|
|||
"player.volume": "音量",
|
||||
"resize.tooltip.double_click_to_collapse": "<b>雙擊</b>以摺疊",
|
||||
"resize.tooltip.drag_to_resize": "<b>拖曳</b>以調整大小",
|
||||
"search.empty.no_results": "未找到結果。",
|
||||
"search.empty.no_results": "未找到結果",
|
||||
"search.group.entries": "條目",
|
||||
"search.group.feeds": "訂閱源",
|
||||
"search.options.all": "所有",
|
||||
|
|
@ -239,7 +239,7 @@
|
|||
"tip_modal.feed_owner": "訂閱源擁有者",
|
||||
"tip_modal.low_balance": "你的餘額不足以覆蓋此贊助。請調整金額。",
|
||||
"tip_modal.no_wallet": "你尚未擁有錢包。請創建一個錢包以進行贊助。",
|
||||
"tip_modal.tip_amount_sent": "已經發送給作者。",
|
||||
"tip_modal.tip_amount_sent": "已經發送給作者",
|
||||
"tip_modal.tip_now": "立刻贊助",
|
||||
"tip_modal.tip_sent": "贊助成功!感謝你的支持。",
|
||||
"tip_modal.tip_support": "⭐ 贊助以顯示你的支持!",
|
||||
|
|
@ -284,6 +284,7 @@
|
|||
"words.search": "搜尋",
|
||||
"words.starred": "收藏",
|
||||
"words.title": "標題",
|
||||
"words.transform": "轉換",
|
||||
"words.trending": "熱門",
|
||||
"words.undo": "撤銷",
|
||||
"words.unread": "未讀",
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
"words.back": "返回",
|
||||
"words.copy": "複製",
|
||||
"words.create": "創建",
|
||||
"words.delete": "刪除",
|
||||
"words.edit": "編輯",
|
||||
"words.entry": "條目",
|
||||
"words.id": "ID",
|
||||
|
|
|
|||
|
|
@ -89,6 +89,9 @@
|
|||
"general.app": "應用程式",
|
||||
"general.data_persist.description": "本地保存數據以啟用離線存取和本地搜尋。",
|
||||
"general.data_persist.label": "離線使用時保留數據",
|
||||
"general.export.button": "匯出",
|
||||
"general.export.description": "匯出你的訂閱到 OPML 文件",
|
||||
"general.export.label": "匯出訂閱",
|
||||
"general.group_by_date.description": "按日期分組條目。",
|
||||
"general.group_by_date.label": "按日期分組",
|
||||
"general.language": "語言",
|
||||
|
|
@ -159,6 +162,8 @@
|
|||
"lists.create": "建立新清單",
|
||||
"lists.created.error": "建立清單失敗。",
|
||||
"lists.created.success": "成功建立清單!",
|
||||
"lists.delete.error": "刪除清單失敗。",
|
||||
"lists.delete.success": "成功刪除清單!",
|
||||
"lists.description": "描述",
|
||||
"lists.earnings": "收益",
|
||||
"lists.edit.error": "編輯清單失敗。",
|
||||
|
|
@ -184,7 +189,7 @@
|
|||
"lists.submit": "提交",
|
||||
"lists.subscriptions": "訂閱",
|
||||
"lists.title": "標題",
|
||||
"lists.view": "查看",
|
||||
"lists.view": "視圖",
|
||||
"profile.avatar.label": "頭像",
|
||||
"profile.handle.description": "你的唯一識別符。",
|
||||
"profile.handle.label": "識別符",
|
||||
|
|
@ -226,6 +231,7 @@
|
|||
"wallet.transactions.to": "發送至",
|
||||
"wallet.transactions.tx": "交易",
|
||||
"wallet.transactions.type": "類型",
|
||||
"wallet.transactions.types.all": "全部",
|
||||
"wallet.transactions.types.burn": "銷燬",
|
||||
"wallet.transactions.types.mint": "鑄造",
|
||||
"wallet.transactions.types.purchase": "購買",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "Follow",
|
||||
"type": "module",
|
||||
"version": "0.0.1-alpha.20",
|
||||
"version": "0.0.1-alpha.21",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@9.11.0",
|
||||
"description": "Next generation information browser",
|
||||
|
|
|
|||
|
|
@ -11,27 +11,7 @@ export const env = createEnv({
|
|||
VITE_SENTRY_DSN: z.string().optional(),
|
||||
VITE_POSTHOG_KEY: z.string().optional(),
|
||||
VITE_INBOXES_EMAIL: z.string().default("@follow.re"),
|
||||
VITE_FIREBASE_CONFIG: z
|
||||
.string()
|
||||
.transform((content) => {
|
||||
try {
|
||||
return JSON.parse(content)
|
||||
} catch {
|
||||
return z.NEVER
|
||||
}
|
||||
})
|
||||
.pipe(
|
||||
z.object({
|
||||
apiKey: z.string(),
|
||||
authDomain: z.string(),
|
||||
projectId: z.string(),
|
||||
storageBucket: z.string(),
|
||||
messagingSenderId: z.string(),
|
||||
appId: z.string(),
|
||||
measurementId: z.string(),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
VITE_FIREBASE_CONFIG: z.string().optional(),
|
||||
},
|
||||
|
||||
emptyStringAsUndefined: true,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import type { Plugin, UserConfig } from "vite"
|
||||
|
||||
export function createDependencyChunksPlugin(dependencies: string[] | string[][]): Plugin {
|
||||
export function createDependencyChunksPlugin(dependencies: string[][]): Plugin {
|
||||
return {
|
||||
name: "dependency-chunks",
|
||||
config(config: UserConfig) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
import type { Plugin } from "vite"
|
||||
|
||||
export function createPlatformSpecificImportPlugin(isElectron = false): Plugin {
|
||||
return {
|
||||
name: "platform-specific-import",
|
||||
enforce: "pre",
|
||||
async resolveId(source, importer) {
|
||||
if (!importer) {
|
||||
return null
|
||||
}
|
||||
|
||||
const allowExts = [".js", ".jsx", ".ts", ".tsx"]
|
||||
|
||||
if (!allowExts.some((ext) => importer.endsWith(ext))) return null
|
||||
|
||||
if (importer.includes("node_modules")) return null
|
||||
const [path, query] = source.split("?")
|
||||
|
||||
if (path.startsWith(".") || path.startsWith("/")) {
|
||||
const priorities = isElectron
|
||||
? [".electron.ts", ".electron.tsx", ".electron.js", ".electron.jsx"]
|
||||
: [".web.ts", ".web.tsx", ".web.js", ".web.jsx"]
|
||||
|
||||
for (const ext of priorities) {
|
||||
const resolvedPath = await this.resolve(
|
||||
`${path}${ext}${query ? `?${query}` : ""}`,
|
||||
importer,
|
||||
{
|
||||
skipSelf: true,
|
||||
},
|
||||
)
|
||||
|
||||
if (resolvedPath) {
|
||||
return resolvedPath.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ interface ImportMetaEnv {
|
|||
VITE_IMGPROXY_URL: string
|
||||
VITE_SENTRY_DSN: string
|
||||
VITE_POSTHOG_KEY: string
|
||||
VITE_FIREBASE_CONFIG: string
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { viteRenderBaseConfig } from "./configs/vite.render.config"
|
|||
import type { env as EnvType } from "./packages/shared/src/env"
|
||||
import { createDependencyChunksPlugin } from "./plugins/vite/deps"
|
||||
import { htmlInjectPlugin } from "./plugins/vite/html-inject"
|
||||
import { createPlatformSpecificImportPlugin } from "./plugins/vite/specific-import"
|
||||
|
||||
const __dirname = fileURLToPath(new URL(".", import.meta.url))
|
||||
const isCI = process.env.CI === "true" || process.env.CI === "1"
|
||||
|
|
@ -75,7 +76,8 @@ export default ({ mode }) => {
|
|||
devPrint(),
|
||||
createDependencyChunksPlugin([
|
||||
// React framework
|
||||
["react", "react-dom", "react-router-dom", "react-error-boundary", "react-dom/server"],
|
||||
["react", "react-dom"],
|
||||
["react-error-boundary", "react-dom/server", "react-router-dom"],
|
||||
// Data Statement
|
||||
["zustand", "jotai", "use-context-selector", "immer", "dexie"],
|
||||
// Remark
|
||||
|
|
@ -131,7 +133,6 @@ export default ({ mode }) => {
|
|||
"@tanstack/react-query-persist-client",
|
||||
"@tanstack/query-sync-storage-persister",
|
||||
],
|
||||
["blurhash", "react-blurhash"],
|
||||
["tldts"],
|
||||
["shiki", "@shikijs/transformers"],
|
||||
["@sentry/react", "posthog-js"],
|
||||
|
|
@ -139,6 +140,8 @@ export default ({ mode }) => {
|
|||
|
||||
["swiper"],
|
||||
]),
|
||||
|
||||
createPlatformSpecificImportPlugin(false),
|
||||
],
|
||||
|
||||
define: {
|
||||
|
|
|
|||
Loading…
Reference in New Issue