feat: add updatelog modal
Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
parent
89f11fc1da
commit
84228bc5c8
|
|
@ -85,11 +85,7 @@ export const ShadowDOM: FC<
|
|||
const dark = useIsDark()
|
||||
|
||||
const reduceMotion = useReduceMotion()
|
||||
const [customCSS, uiFont, usePointerCursor] = useUISettingKeys([
|
||||
"customCSS",
|
||||
"uiFontFamily",
|
||||
"usePointerCursor",
|
||||
])
|
||||
const [uiFont, usePointerCursor] = useUISettingKeys(["uiFontFamily", "usePointerCursor"])
|
||||
|
||||
return (
|
||||
<root.div {...rest}>
|
||||
|
|
@ -108,7 +104,6 @@ export const ShadowDOM: FC<
|
|||
className="font-theme"
|
||||
>
|
||||
{injectHostStyles ? stylesElements : null}
|
||||
<MemoedDangerousHTMLStyle>{customCSS}</MemoedDangerousHTMLStyle>
|
||||
{props.children}
|
||||
</div>
|
||||
</ShadowDOMContext.Provider>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ const InPeekModal = createContext(false)
|
|||
export const useInPeekModal = () => useContext(InPeekModal)
|
||||
export const PeekModal = (
|
||||
props: PropsWithChildren<{
|
||||
to: string
|
||||
to?: string
|
||||
}>,
|
||||
) => {
|
||||
const { dismissAll } = useModalStack()
|
||||
|
|
@ -40,19 +40,21 @@ export const PeekModal = (
|
|||
}}
|
||||
className="fixed right-3 flex items-center gap-4 safe-inset-top-2"
|
||||
>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Link
|
||||
className="center flex size-8 cursor-button rounded-full bg-theme-background p-1 shadow-sm ring-1 ring-zinc-200 dark:ring-neutral-800"
|
||||
to={to}
|
||||
onClick={dismissAll}
|
||||
>
|
||||
<i className="i-mgc-fullscreen-2-cute-re text-lg" />
|
||||
<span className="sr-only">Go to this link</span>
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{t("words.expand")}</TooltipContent>
|
||||
</Tooltip>
|
||||
{!!to && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Link
|
||||
className="center flex size-8 cursor-button rounded-full bg-theme-background p-1 shadow-sm ring-1 ring-zinc-200 dark:ring-neutral-800"
|
||||
to={to}
|
||||
onClick={dismissAll}
|
||||
>
|
||||
<i className="i-mgc-fullscreen-2-cute-re text-lg" />
|
||||
<span className="sr-only">Go to this link</span>
|
||||
</Link>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{t("words.expand")}</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
<FixedModalCloseButton onClick={dismiss} />
|
||||
</m.div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@ declare const APP_VERSION: string
|
|||
declare const APP_NAME: string
|
||||
declare const RELEASE_CHANNEL: string
|
||||
declare const I18N_COMPLETENESS_MAP: Record<string, number>
|
||||
declare const CHANGELOG_CONTENT: string
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { Button } from "@follow/components/ui/button/index.js"
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
|
|
@ -7,15 +8,53 @@ import {
|
|||
import { env } from "@follow/shared/env"
|
||||
|
||||
import { useUserRole } from "~/atoms/user"
|
||||
import { useModalStack } from "~/components/ui/modal/stacked/hooks"
|
||||
import { isDev } from "~/constants"
|
||||
|
||||
import { DebugRegistry } from "../debug/registry"
|
||||
|
||||
export const EnvironmentIndicator = () => {
|
||||
const role = useUserRole()
|
||||
const { present } = useModalStack()
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div className="fixed bottom-0 right-0 z-[99999] rounded-tl bg-accent px-1 py-0.5 text-xs text-white">
|
||||
{role}:{import.meta.env.MODE}
|
||||
</div>
|
||||
<button
|
||||
tabIndex={-1}
|
||||
aria-hidden
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (!isDev) return
|
||||
|
||||
const actionMap = DebugRegistry.getAll()
|
||||
|
||||
present({
|
||||
title: "Debug Actions",
|
||||
content: () => {
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
{Object.entries(actionMap).map(([key, action]) => {
|
||||
return (
|
||||
<div key={key} className="flex items-center gap-2">
|
||||
<span>{key}</span>
|
||||
<Button variant="outline" type="button" onClick={() => action()}>
|
||||
<i className="i-mgc-play-cute-fi size-3" />
|
||||
<span className="ml-1">Run</span>
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
}}
|
||||
>
|
||||
<div className="center fixed bottom-0 right-0 z-[99999] flex rounded-tl bg-accent px-1 py-0.5 text-xs text-white">
|
||||
{role}:{isDev && <i className="i-mgc-bug-cute-re size-3" />}
|
||||
{import.meta.env.MODE}
|
||||
</div>
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipPortal>
|
||||
<TooltipContent className="max-w-max break-all" side="top">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
class Registry {
|
||||
private actions: Record<string, () => void> = {}
|
||||
|
||||
add(key: string, action: () => void) {
|
||||
this.actions[key] = action
|
||||
|
||||
return () => {
|
||||
delete this.actions[key]
|
||||
}
|
||||
}
|
||||
|
||||
remove(key: string) {
|
||||
delete this.actions[key]
|
||||
}
|
||||
|
||||
getAll() {
|
||||
return this.actions
|
||||
}
|
||||
|
||||
get(key: string) {
|
||||
return this.actions[key]
|
||||
}
|
||||
}
|
||||
|
||||
export const DebugRegistry = new Registry()
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import { MemoedDangerousHTMLStyle } from "@follow/components/common/MemoedDangerousHTMLStyle.js"
|
||||
import { AutoResizeHeight } from "@follow/components/ui/auto-resize-height/index.jsx"
|
||||
import { LoadingWithIcon } from "@follow/components/ui/loading/index.jsx"
|
||||
import { RootPortal } from "@follow/components/ui/portal/index.jsx"
|
||||
|
|
@ -159,7 +160,7 @@ export const EntryContentRender: Component<{
|
|||
),
|
||||
[entry?.entries.media, data?.entries.media],
|
||||
)
|
||||
|
||||
const customCSS = useUISettingKey("customCSS")
|
||||
if (!entry) return null
|
||||
|
||||
const content = entry?.entries.content ?? data?.entries.content
|
||||
|
|
@ -237,6 +238,9 @@ export const EntryContentRender: Component<{
|
|||
<ErrorBoundary fallback={RenderError}>
|
||||
{!isInReadabilityMode ? (
|
||||
<ShadowDOM injectHostStyles={!isInbox}>
|
||||
{!!customCSS && (
|
||||
<MemoedDangerousHTMLStyle>{customCSS}</MemoedDangerousHTMLStyle>
|
||||
)}
|
||||
<EntryContentHTMLRenderer
|
||||
view={view}
|
||||
feedId={feed?.id}
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
export { default as AppUpgradeProvider } from "../provider"
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import { lazy } from "react"
|
||||
|
||||
export const AppUpgradeProvider = lazy(() => import("../provider"))
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
import { useOnce } from "@follow/hooks"
|
||||
import { repository } from "@pkg"
|
||||
import type { FC } from "react"
|
||||
import { toast } from "sonner"
|
||||
|
||||
import { Markdown } from "~/components/ui/markdown/Markdown"
|
||||
import { PeekModal } from "~/components/ui/modal/inspire/PeekModal"
|
||||
import { useModalStack } from "~/components/ui/modal/stacked/hooks"
|
||||
import { Paper } from "~/components/ui/paper"
|
||||
import { isDev } from "~/constants"
|
||||
import { DebugRegistry } from "~/modules/debug/registry"
|
||||
|
||||
const AppUpgradeProvider: FC = () => {
|
||||
const { present } = useModalStack()
|
||||
useOnce(() => {
|
||||
const toaster = () => {
|
||||
toast.success(
|
||||
<div>
|
||||
App is upgraded to{" "}
|
||||
<a href={`${repository.url}/releases/tag/${APP_VERSION}`}>{APP_VERSION}</a>, enjoy the new
|
||||
features! 🎉
|
||||
</div>,
|
||||
{
|
||||
duration: 10e8,
|
||||
action: CHANGELOG_CONTENT
|
||||
? {
|
||||
label: "What's new?",
|
||||
onClick: () => {
|
||||
present({
|
||||
clickOutsideToDismiss: true,
|
||||
title: "What's new?",
|
||||
autoFocus: false,
|
||||
modalClassName:
|
||||
"relative mx-auto mt-[10vh] scrollbar-none max-w-full overflow-auto px-2 lg:max-w-[65rem] lg:p-0",
|
||||
|
||||
CustomModalComponent: ({ children }) => {
|
||||
return <PeekModal>{children}</PeekModal>
|
||||
},
|
||||
content: Changelog,
|
||||
overlay: true,
|
||||
})
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
)
|
||||
}
|
||||
if (window.__app_is_upgraded__) {
|
||||
setTimeout(toaster)
|
||||
}
|
||||
|
||||
isDev && DebugRegistry.add("simulate_app_upgraded_toast", toaster)
|
||||
})
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export default AppUpgradeProvider
|
||||
|
||||
const Changelog = () => (
|
||||
<Paper>
|
||||
<Markdown className="mt-8">{CHANGELOG_CONTENT}</Markdown>
|
||||
</Paper>
|
||||
)
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
import { useViewport } from "@follow/components/hooks/useViewport.js"
|
||||
import { PanelSplitter } from "@follow/components/ui/divider/index.js"
|
||||
import { RootPortal } from "@follow/components/ui/portal/index.jsx"
|
||||
import { useOnce } from "@follow/hooks"
|
||||
import { IN_ELECTRON } from "@follow/shared/constants"
|
||||
import { preventDefault } from "@follow/utils/dom"
|
||||
import { cn } from "@follow/utils/utils"
|
||||
|
|
@ -15,7 +14,6 @@ import { useHotkeys } from "react-hotkeys-hook"
|
|||
import { Trans, useTranslation } from "react-i18next"
|
||||
import { useResizable } from "react-resizable-layout"
|
||||
import { Outlet } from "react-router-dom"
|
||||
import { toast } from "sonner"
|
||||
|
||||
import { setMainContainerElement } from "~/atoms/dom"
|
||||
import { getUISettings, setUISetting, useUISettingKey } from "~/atoms/settings/ui"
|
||||
|
|
@ -46,6 +44,7 @@ import { useShortcutsModal } from "~/modules/modal/shortcuts"
|
|||
import { CmdF } from "~/modules/panel/cmdf"
|
||||
import { SearchCmdK } from "~/modules/panel/cmdk"
|
||||
import { CmdNTrigger } from "~/modules/panel/cmdn"
|
||||
import { AppUpgradeProvider } from "~/modules/upgrade/lazy/index"
|
||||
import { AppLayoutGridContainerProvider } from "~/providers/app-grid-layout-container-provider"
|
||||
import { settings } from "~/queries/settings"
|
||||
|
||||
|
|
@ -87,22 +86,6 @@ const errorTypes = [
|
|||
ErrorComponentType.FeedNotFound,
|
||||
] as ErrorComponentType[]
|
||||
|
||||
const useAppUpgraded = () => {
|
||||
useOnce(() => {
|
||||
if (window.__app_is_upgraded__) {
|
||||
setTimeout(() => {
|
||||
toast.success(
|
||||
<div>
|
||||
App is upgraded to{" "}
|
||||
<a href={`${repository.url}/releases/tag/${APP_VERSION}`}>{APP_VERSION}</a>, enjoy the
|
||||
new features! 🎉
|
||||
</div>,
|
||||
)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const supportMinWidth = 1024
|
||||
export function Component() {
|
||||
const isAuthFail = useLoginModalShow()
|
||||
|
|
@ -111,7 +94,6 @@ export function Component() {
|
|||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useDailyTask()
|
||||
useAppUpgraded()
|
||||
|
||||
const isNotSupportWidth = useViewport((v) => v.w < supportMinWidth && v.w !== 0) && !IN_ELECTRON
|
||||
|
||||
|
|
@ -126,6 +108,7 @@ export function Component() {
|
|||
<RootContainer ref={containerRef}>
|
||||
{!import.meta.env.PROD && <EnvironmentIndicator />}
|
||||
|
||||
<AppUpgradeProvider />
|
||||
<AppLayoutGridContainerProvider>
|
||||
<FeedResponsiveResizerContainer containerRef={containerRef}>
|
||||
<FeedColumn>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
# NEXT_VERSION Changelog
|
||||
|
||||
## Features
|
||||
|
||||
- Custom CSS is now supported, so you can add any CSS style and apply it to the Entry content view.
|
||||
- New Zen mode has been added, so you can now read the full text in full screen without interruption, and we've optimized the user experience of the ToC component in full screen mode as well as added a new brief timeline on the left side.
|
||||
- Support for hiding extra badges around the feed, such as Claim or Boost, which you may not want to see.
|
||||
|
||||
## Improvements
|
||||
|
||||
- Fixed the issue that Entry list cannot be loaded offline.
|
||||
- Optimized the performance experience of some scenarios.
|
||||
- The UI of some components has been fine-tuned to look more natural.
|
||||
|
|
@ -14,10 +14,20 @@ import { twMacro } from "../plugins/vite/tw-macro"
|
|||
import i18nCompleteness from "../plugins/vite/utils/i18n-completeness"
|
||||
import { getGitHash } from "../scripts/lib"
|
||||
|
||||
const pkgDir = dirname(fileURLToPath(import.meta.url))
|
||||
const pkg = JSON.parse(readFileSync(resolve(pkgDir, "../package.json"), "utf8"))
|
||||
const pkgDir = resolve(dirname(fileURLToPath(import.meta.url)), "..")
|
||||
const pkg = JSON.parse(readFileSync(resolve(pkgDir, "./package.json"), "utf8"))
|
||||
const isCI = process.env.CI === "true" || process.env.CI === "1"
|
||||
|
||||
const getChangelogFileContent = () => {
|
||||
const { version } = pkg
|
||||
const isDev = process.env.NODE_ENV === "development"
|
||||
try {
|
||||
return readFileSync(resolve(pkgDir, "./changelog", `${isDev ? "next" : version}.md`), "utf8")
|
||||
} catch {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
const changelogFile = getChangelogFileContent()
|
||||
export const viteRenderBaseConfig = {
|
||||
resolve: {
|
||||
alias: {
|
||||
|
|
@ -73,5 +83,6 @@ export const viteRenderBaseConfig = {
|
|||
DEBUG: process.env.DEBUG === "true",
|
||||
|
||||
I18N_COMPLETENESS_MAP: JSON.stringify({ ...i18nCompleteness, en: 100 }),
|
||||
CHANGELOG_CONTENT: JSON.stringify(changelogFile),
|
||||
},
|
||||
} satisfies UserConfig
|
||||
|
|
|
|||
|
|
@ -131,7 +131,8 @@
|
|||
},
|
||||
"bump": {
|
||||
"before": [
|
||||
"git pull --rebase"
|
||||
"git pull --rebase",
|
||||
"tsx scripts/apply-changelog.ts ${NEW_VERSION}"
|
||||
],
|
||||
"after": [
|
||||
"gh pr create --title 'chore: Release v${NEW_VERSION}' --body 'v${NEW_VERSION}' --base main --head dev"
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ export const Toaster = ({ ...props }: ToasterProps) => (
|
|||
className: tw`pointer-events-auto`,
|
||||
classNames: {
|
||||
content: "min-w-0",
|
||||
icon: tw`self-start translate-y-[2px]`,
|
||||
},
|
||||
}}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -3607,7 +3607,7 @@ packages:
|
|||
'@radix-ui/react-slot@1.1.0':
|
||||
resolution: {integrity: sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==}
|
||||
peerDependencies:
|
||||
'@types/react': npm:types-react@19.0.0-rc.1
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
|
|
@ -20345,7 +20345,7 @@ snapshots:
|
|||
terser@5.35.0:
|
||||
dependencies:
|
||||
'@jridgewell/source-map': 0.3.6
|
||||
acorn: 8.13.0
|
||||
acorn: 8.14.0
|
||||
commander: 2.20.3
|
||||
source-map-support: 0.5.21
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
import { readFileSync, renameSync, writeFileSync } from "node:fs"
|
||||
import { dirname, join, resolve } from "node:path"
|
||||
import { fileURLToPath } from "node:url"
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const changelogDir = join(__dirname, "..", "changelog")
|
||||
|
||||
const nextFile = join(changelogDir, "next.md")
|
||||
|
||||
const new_version = process.argv[2]
|
||||
|
||||
const nextContent = readFileSync(nextFile, "utf-8")
|
||||
writeFileSync(nextFile, nextContent.replaceAll("NEXT_VERSION", new_version))
|
||||
|
||||
// Rename the next.md to the new version
|
||||
renameSync(nextFile, join(changelogDir, `${new_version}.md`))
|
||||
// Replace the NEXT_VERSION in the next.md file with the new version
|
||||
|
||||
// Create the new next.md file
|
||||
writeFileSync(resolve(changelogDir, "next.md"), `# NEXT_VERSION Changelog`)
|
||||
Loading…
Reference in New Issue