feat: add updatelog modal

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei 2024-10-31 22:06:32 +08:00
parent 89f11fc1da
commit 84228bc5c8
No known key found for this signature in database
GPG Key ID: 0F62D33977F021F7
16 changed files with 211 additions and 48 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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">

View File

@ -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()

View File

@ -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}

View File

@ -0,0 +1 @@
export { default as AppUpgradeProvider } from "../provider"

View File

@ -0,0 +1,3 @@
import { lazy } from "react"
export const AppUpgradeProvider = lazy(() => import("../provider"))

View File

@ -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>
)

View File

@ -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>

13
changelog/next.md Normal file
View File

@ -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.

View File

@ -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

View File

@ -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"

View File

@ -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}

View File

@ -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

View File

@ -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`)