├── TODO.md ├── .prettierignore ├── src ├── components │ ├── ui │ │ ├── list │ │ │ ├── TimelineList.module.css │ │ │ └── TimelineList.tsx │ │ ├── avatar │ │ │ └── index.ts │ │ ├── banner │ │ │ └── index.ts │ │ ├── modal │ │ │ ├── index.ts │ │ │ └── stacked │ │ │ │ ├── index.ts │ │ │ │ ├── helper.tsx │ │ │ │ ├── types.tsx │ │ │ │ ├── overlay.tsx │ │ │ │ ├── constants.ts │ │ │ │ └── context.tsx │ │ ├── select │ │ │ └── index.ts │ │ ├── sheet │ │ │ ├── index.ts │ │ │ └── context.tsx │ │ ├── text │ │ │ └── index.ts │ │ ├── checkbox │ │ │ └── index.ts │ │ ├── collapse │ │ │ └── index.ts │ │ ├── divider │ │ │ └── index.ts │ │ ├── gallery │ │ │ ├── index.ts │ │ │ └── Gallery.module.css │ │ ├── image │ │ │ ├── index.ts │ │ │ └── ZoomedImage.module.css │ │ ├── link │ │ │ └── index.ts │ │ ├── masonry │ │ │ └── index.ts │ │ ├── skeleton │ │ │ ├── index.ts │ │ │ └── Skeleton.tsx │ │ ├── spinner │ │ │ ├── index.ts │ │ │ └── Spinner.tsx │ │ ├── tabs │ │ │ └── index.ts │ │ ├── code-editor │ │ │ ├── index.ts │ │ │ └── index.demo.tsx │ │ ├── dialog │ │ │ ├── index.ts │ │ │ └── DialogOverlay.tsx │ │ ├── excalidraw │ │ │ ├── index.ts │ │ │ └── ExcalidrawLoading.tsx │ │ ├── float-panel │ │ │ └── index.ts │ │ ├── viewport │ │ │ ├── index.ts │ │ │ ├── OnlyMobile.tsx │ │ │ └── OnlyDesktop.tsx │ │ ├── float-popover │ │ │ └── index.ts │ │ ├── number-transition │ │ │ └── index.ts │ │ ├── relative-time │ │ │ └── index.ts │ │ ├── theme-switcher │ │ │ └── index.ts │ │ ├── auto-completion │ │ │ └── index.ts │ │ ├── react-component-render │ │ │ └── index.ts │ │ ├── scroll-area │ │ │ └── index.ts │ │ ├── link-card │ │ │ ├── index.ts │ │ │ └── enums.tsx │ │ ├── fab │ │ │ ├── index.ts │ │ │ └── BackToTopFAB.tsx │ │ ├── markdown │ │ │ ├── index.ts │ │ │ ├── renderers │ │ │ │ ├── index.ts │ │ │ │ ├── tag.tsx │ │ │ │ ├── blockqoute.tsx │ │ │ │ ├── video.tsx │ │ │ │ └── index.module.css │ │ │ ├── utils │ │ │ │ ├── get-id.ts │ │ │ │ ├── redHighlight.tsx │ │ │ │ └── image.ts │ │ │ └── parsers │ │ │ │ ├── ins.tsx │ │ │ │ └── spoiler.tsx │ │ ├── rich-link │ │ │ ├── index.ts │ │ │ └── SocialSourceLink.tsx │ │ ├── code-highlighter │ │ │ ├── index.ts │ │ │ └── shiki │ │ │ │ └── utils.tsx │ │ ├── typography │ │ │ ├── index.ts │ │ │ └── index.demo.tsx │ │ ├── input │ │ │ └── index.ts │ │ ├── label │ │ │ ├── index.ts │ │ │ ├── ErrorLabelLine.tsx │ │ │ ├── LabelContext.tsx │ │ │ └── Label.tsx │ │ ├── button │ │ │ ├── index.ts │ │ │ ├── MotionButton.tsx │ │ │ └── RoundedIconButton.tsx │ │ ├── form │ │ │ ├── index.ts │ │ │ ├── FormContext.tsx │ │ │ └── types.ts │ │ ├── transition │ │ │ ├── FadeInOutTransitionView.tsx │ │ │ ├── ScaleTransitionView.tsx │ │ │ ├── LeftToRightTransitionView.tsx │ │ │ ├── RightToLeftTransitionView.tsx │ │ │ ├── index.ts │ │ │ ├── BottomToUpSoftScaleTransitionView.tsx │ │ │ ├── typings.ts │ │ │ └── BottomToUpTransitionView.tsx │ │ ├── portal │ │ │ ├── provider.tsx │ │ │ └── index.tsx │ │ ├── toast │ │ │ └── index.tsx │ │ ├── media │ │ │ └── VolumeSlider.tsx │ │ ├── katex │ │ │ └── index.tsx │ │ └── loading │ │ │ └── index.tsx │ ├── layout │ │ ├── footer │ │ │ ├── index.ts │ │ │ ├── OwnerName.tsx │ │ │ ├── Footer.tsx │ │ │ └── VercelPoweredBy.tsx │ │ ├── header │ │ │ ├── index.ts │ │ │ ├── internal │ │ │ │ ├── grid.module.css │ │ │ │ ├── BluredBackground.tsx │ │ │ │ ├── HeaderActionButton.tsx │ │ │ │ ├── HeaderDrawerButton.tsx │ │ │ │ ├── UserAuthFromIcon.tsx │ │ │ │ └── HeaderWithShadow.tsx │ │ │ └── hooks.ts │ │ ├── content │ │ │ └── Content.tsx │ │ ├── dashboard │ │ │ └── PageLoading.tsx │ │ ├── container │ │ │ ├── Wider.tsx │ │ │ ├── Normal.tsx │ │ │ └── Paper.tsx │ │ └── root │ │ │ └── Root.tsx │ ├── modules │ │ ├── dashboard │ │ │ ├── home │ │ │ │ └── index.ts │ │ │ ├── crossbell │ │ │ │ ├── index.ts │ │ │ │ └── XLogEnabled.tsx │ │ │ ├── ip │ │ │ │ └── index.ts │ │ │ ├── note-editing │ │ │ │ ├── index.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── sidebar │ │ │ │ │ └── DateInput.tsx │ │ │ │ └── NoteNid.tsx │ │ │ ├── post-editing │ │ │ │ ├── index.ts │ │ │ │ └── sidebar │ │ │ │ │ └── SummaryInput.tsx │ │ │ ├── writing │ │ │ │ ├── atoms │ │ │ │ │ └── index.ts │ │ │ │ ├── PresentComponentFab.tsx │ │ │ │ └── TitleInput.tsx │ │ │ ├── utils │ │ │ │ ├── context.ts │ │ │ │ └── helper.ts │ │ │ ├── comments │ │ │ │ ├── index.ts │ │ │ │ └── UrlRender.tsx │ │ │ └── layouts │ │ │ │ └── index.tsx │ │ ├── comment │ │ │ ├── CommentBox │ │ │ │ ├── index.ts │ │ │ │ ├── constants.ts │ │ │ │ └── AuthedInputSkeleton.tsx │ │ │ ├── types.ts │ │ │ ├── CommentRootLazy.tsx │ │ │ ├── CommentMarkdown.tsx │ │ │ ├── index.ts │ │ │ └── CommentProvider.tsx │ │ ├── say │ │ │ ├── index.ts │ │ │ └── Button.tsx │ │ ├── toc │ │ │ ├── index.ts │ │ │ └── TocAutoScroll.tsx │ │ ├── xlog │ │ │ ├── index.ts │ │ │ ├── utils.ts │ │ │ └── types.ts │ │ ├── subscribe │ │ │ ├── index.ts │ │ │ └── SubscribeTextButton.tsx │ │ ├── post │ │ │ ├── index.ts │ │ │ ├── PostPinIcon.tsx │ │ │ └── PostOutdate.tsx │ │ ├── peek │ │ │ └── PeekPortal.tsx │ │ ├── shared │ │ │ ├── NothingFound.tsx │ │ │ ├── BlockLoading.tsx │ │ │ ├── LoadMoreIndicator.tsx │ │ │ ├── EmojiPicker.tsx │ │ │ └── Tweet.tsx │ │ └── note │ │ │ ├── index.ts │ │ │ └── NoteTopicMarkdownRender.tsx │ ├── icons │ │ ├── thumbs-up.tsx │ │ ├── TiltedSendIcon.tsx │ │ ├── close.tsx │ │ ├── ic-baseline-menu-open.tsx │ │ ├── calendar.tsx │ │ ├── fa-hash.tsx │ │ ├── pen.tsx │ │ ├── platform │ │ │ ├── XIcon.tsx │ │ │ ├── MicrosoftIcon.tsx │ │ │ ├── NpmIcon.tsx │ │ │ ├── AppleIcon.tsx │ │ │ ├── SteamIcon.tsx │ │ │ ├── Twitter.tsx │ │ │ ├── BilibiliIcon.tsx │ │ │ ├── Telegram.tsx │ │ │ ├── BlueskyIcon.tsx │ │ │ └── FacebookIcon.tsx │ │ ├── mermaid.tsx │ │ ├── return.tsx │ │ ├── user-heart.tsx │ │ ├── coffee.tsx │ │ ├── Progress.tsx │ │ ├── tag.tsx │ │ ├── ParkOutlineTopicIcon.tsx │ │ ├── user-arrow-left.tsx │ │ ├── clock.tsx │ │ └── bookmark.tsx │ ├── common │ │ ├── NotSupport.tsx │ │ ├── QueryHydrate.tsx │ │ ├── AckRead.tsx │ │ ├── ProviderComposer.tsx │ │ ├── Global.ts │ │ ├── ScrollTop.tsx │ │ ├── ClientOnly.tsx │ │ ├── BizErrorPage.tsx │ │ ├── HydrationEndDetector.tsx │ │ └── PageHolder.tsx │ └── hoc │ │ └── with-no-ssr.tsx ├── lib │ ├── request.ts │ ├── ns.ts │ ├── noop.ts │ ├── error-factory.ts │ ├── env.ts │ ├── bangumi.ts │ ├── store.ts │ ├── mine-type.ts │ ├── query-client.server.ts │ ├── fonts.ts │ ├── github.ts │ ├── biz.ts │ ├── atom.ts │ ├── upload.ts │ ├── helper.ts │ ├── request.shared.ts │ ├── markdown.ts │ ├── attach-fetch.ts │ ├── authjs.ts │ └── helper.server.ts ├── queries │ ├── keys │ │ ├── index.ts │ │ └── comment.ts │ ├── definition │ │ ├── index.ts │ │ ├── page.ts │ │ ├── aggregation.ts │ │ └── comment.ts │ └── helper.ts ├── app │ ├── (app) │ │ ├── notes │ │ │ ├── loading.tsx │ │ │ ├── [id] │ │ │ │ ├── loading.tsx │ │ │ │ ├── Transition.tsx │ │ │ │ └── api.tsx │ │ │ ├── redirect.tsx │ │ │ └── layout.tsx │ │ ├── posts │ │ │ ├── loading.tsx │ │ │ └── (post-detail) │ │ │ │ ├── [category] │ │ │ │ ├── [slug] │ │ │ │ │ ├── loading.tsx │ │ │ │ │ └── api.tsx │ │ │ │ └── page.tsx │ │ │ │ ├── Container.tsx │ │ │ │ └── layout.tsx │ │ ├── not-found.tsx │ │ ├── says │ │ │ ├── loading.tsx │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── friends │ │ │ ├── loading.tsx │ │ │ └── layout.tsx │ │ ├── projects │ │ │ ├── loading.tsx │ │ │ ├── [id] │ │ │ │ └── layout.tsx │ │ │ └── layout.tsx │ │ ├── thinking │ │ │ ├── constants.ts │ │ │ ├── loading.tsx │ │ │ └── layout.tsx │ │ ├── timeline │ │ │ └── loading.tsx │ │ ├── (page-detail) │ │ │ ├── loading.tsx │ │ │ ├── [slug] │ │ │ │ └── loading.tsx │ │ │ ├── Container.tsx │ │ │ └── layout.tsx │ │ ├── categories │ │ │ └── [slug] │ │ │ │ ├── loading.tsx │ │ │ │ ├── api.tsx │ │ │ │ ├── query.ts │ │ │ │ └── layout.tsx │ │ ├── (note-topic) │ │ │ └── notes │ │ │ │ ├── topics │ │ │ │ ├── loading.tsx │ │ │ │ └── layout.tsx │ │ │ │ └── (topic-detail) │ │ │ │ └── topics │ │ │ │ └── [slug] │ │ │ │ ├── loading.tsx │ │ │ │ └── query.ts │ │ ├── (home) │ │ │ ├── query.ts │ │ │ └── components │ │ │ │ └── types.tsx │ │ ├── common │ │ │ └── deleted │ │ │ │ └── page.tsx │ │ └── error.tsx │ ├── init.ts │ ├── robots.ts │ ├── InitInClient.ts │ ├── layout.tsx │ ├── api │ │ ├── leetcode │ │ │ └── route.ts │ │ ├── music │ │ │ ├── tencent │ │ │ │ └── route.ts │ │ │ └── netease │ │ │ │ └── route.ts │ │ └── bilibili │ │ │ └── check_live │ │ │ └── types │ │ │ └── room.ts │ ├── not-found.tsx │ └── (dashboard) │ │ └── dashboard │ │ └── [[...catch_all]] │ │ └── page.tsx ├── constants │ ├── dom-id.ts │ ├── event.ts │ ├── tracker.ts │ ├── system.ts │ ├── env.ts │ ├── keys.ts │ └── language.ts ├── models │ ├── activity.ts │ └── session.ts ├── providers │ ├── root │ │ ├── framer-lazy-feature.ts │ │ ├── jotai-provider.tsx │ │ ├── debug-provider.tsx │ │ ├── app-feature-provider.tsx │ │ ├── sentry-provider.tsx │ │ └── auth-provider.tsx │ ├── post │ │ └── CurrentPostDataProvider.tsx │ └── page │ │ └── CurrentPageDataProvider.tsx ├── styles │ ├── print.css │ ├── index.css │ ├── animation.css │ ├── image-zoom.css │ └── sonner.css ├── socket │ ├── index.ts │ └── util.ts ├── events │ ├── index.ts │ ├── refetch.ts │ ├── publish.ts │ ├── write-edit.ts │ └── socket.ts ├── atoms │ ├── index.ts │ ├── hooks │ │ ├── index.ts │ │ ├── reader.ts │ │ └── owner.ts │ ├── css-media.ts │ ├── socket.ts │ ├── is-interactive.ts │ └── viewport.ts ├── hooks │ ├── common │ │ ├── useVideo.ts │ │ ├── use-is-dark.ts │ │ ├── use-callback.ts │ │ ├── use-force-update.ts │ │ ├── use-state-ref.ts │ │ ├── use-before-mounted.ts │ │ ├── use-is-mounted.ts │ │ ├── use-previous.ts │ │ ├── use-get-state.ts │ │ ├── use-is-unmounted.ts │ │ ├── use-ref-value.ts │ │ ├── use-safe-setState.ts │ │ ├── use-event-callback.ts │ │ ├── use-debounce-value.ts │ │ ├── use-sync-effect.ts │ │ ├── use-is-active.ts │ │ ├── use-is-client.ts │ │ ├── use-disclosure.ts │ │ ├── use-uncontrolled-input.ts │ │ └── use-single-double-click.ts │ ├── biz │ │ ├── use-ack-read-count.ts │ │ ├── use-refetch-data.ts │ │ └── use-save-confirm.ts │ └── shared │ │ └── use-read-percent.ts ├── routes │ ├── comments │ │ └── layout.tsx │ ├── notes │ │ ├── layout.tsx │ │ └── topics │ │ │ └── index.tsx │ └── posts │ │ ├── layout.tsx │ │ └── category │ │ └── index.tsx ├── types │ └── api.ts └── app.static.config.ts ├── storybook ├── src │ ├── vite-env.d.ts │ ├── markdown.css │ ├── routes │ │ └── root.tsx │ ├── index.css │ ├── components │ │ └── Markdown │ │ │ └── index.tsx │ └── main.tsx ├── config.ts ├── mock-packages │ ├── next_dynamic │ │ └── package.json │ ├── next_link │ │ ├── package.json │ │ └── index.js │ ├── next_image │ │ ├── package.json │ │ └── index.js │ ├── next-runtime-env │ │ ├── package.json │ │ └── index.js │ └── next_navigation │ │ ├── package.json │ │ └── index.js ├── TODO.md ├── postcss.config.cjs ├── tailwind.config.ts ├── tsconfig.node.json ├── index.html ├── .gitignore ├── typings │ └── index.ts ├── tsconfig.json └── package.json ├── public ├── favicon.ico ├── favicon-16x16.png ├── favicon-32x32.png ├── apple-touch-icon.png ├── android-chrome-192x192.png └── android-chrome-512x512.png ├── .prettierrc.mjs ├── .gitmodules ├── packages └── fetch │ ├── pnpm-lock.yaml │ ├── package.json │ └── tsconfig.json ├── postcss.config.cjs ├── .env.template ├── next-env.d.ts ├── NOTE.md ├── renovate.json ├── .npmrc ├── .dockerignore ├── docker-compose.yml ├── ecosystem.standalone.config.cjs ├── ecosystem.config.cjs ├── SAY.md ├── .vscode └── settings.json ├── ci-release-build.sh ├── standalone-bundle.sh ├── .gitignore ├── scripts ├── delete-ci-build-artifact.mjs └── download-latest-ci-build-artifact.sh ├── .github └── FUNDING.yml ├── taze.config.js ├── cssAsPlugin.js └── plugins └── tw-css-plugin.js /TODO.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | language.ts 2 | -------------------------------------------------------------------------------- /src/components/ui/list/TimelineList.module.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/lib/request.ts: -------------------------------------------------------------------------------- 1 | export * from '@shiro/fetch' 2 | -------------------------------------------------------------------------------- /src/queries/keys/index.ts: -------------------------------------------------------------------------------- 1 | export * from './comment' 2 | -------------------------------------------------------------------------------- /src/components/ui/avatar/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Avatar' 2 | -------------------------------------------------------------------------------- /src/components/ui/banner/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Banner' 2 | -------------------------------------------------------------------------------- /src/components/ui/modal/index.ts: -------------------------------------------------------------------------------- 1 | export * from './stacked' 2 | -------------------------------------------------------------------------------- /src/components/ui/select/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Select' 2 | -------------------------------------------------------------------------------- /src/components/ui/sheet/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Sheet' 2 | -------------------------------------------------------------------------------- /src/components/ui/text/index.ts: -------------------------------------------------------------------------------- 1 | export * from './FlexText' 2 | -------------------------------------------------------------------------------- /src/components/layout/footer/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Footer' 2 | -------------------------------------------------------------------------------- /src/components/layout/header/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Header' 2 | -------------------------------------------------------------------------------- /src/components/ui/checkbox/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CheckBox' 2 | -------------------------------------------------------------------------------- /src/components/ui/collapse/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Collapse' 2 | -------------------------------------------------------------------------------- /src/components/ui/divider/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Divider' 2 | -------------------------------------------------------------------------------- /src/components/ui/gallery/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Gallery' 2 | -------------------------------------------------------------------------------- /src/components/ui/image/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ZoomedImage' 2 | -------------------------------------------------------------------------------- /src/components/ui/link/index.ts: -------------------------------------------------------------------------------- 1 | export * from './MarkdownLink' 2 | -------------------------------------------------------------------------------- /src/components/ui/masonry/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Masonry' 2 | -------------------------------------------------------------------------------- /src/components/ui/skeleton/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Skeleton' 2 | -------------------------------------------------------------------------------- /src/components/ui/spinner/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Spinner' 2 | -------------------------------------------------------------------------------- /src/components/ui/tabs/index.ts: -------------------------------------------------------------------------------- 1 | export * as Tabs from './Tabs' 2 | -------------------------------------------------------------------------------- /src/components/ui/code-editor/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CodeEditor' 2 | -------------------------------------------------------------------------------- /src/components/ui/dialog/index.ts: -------------------------------------------------------------------------------- 1 | export * from './DialogOverlay' 2 | -------------------------------------------------------------------------------- /src/components/ui/excalidraw/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Excalidraw' 2 | -------------------------------------------------------------------------------- /src/components/ui/float-panel/index.ts: -------------------------------------------------------------------------------- 1 | export * from './FloatPanel' 2 | -------------------------------------------------------------------------------- /src/components/ui/viewport/index.ts: -------------------------------------------------------------------------------- 1 | export * from './OnlyDesktop' 2 | -------------------------------------------------------------------------------- /storybook/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/components/modules/dashboard/home/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Hitokoto' 2 | -------------------------------------------------------------------------------- /src/components/ui/float-popover/index.ts: -------------------------------------------------------------------------------- 1 | export * from './FloatPopover' 2 | -------------------------------------------------------------------------------- /src/components/ui/number-transition/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CountUp' 2 | -------------------------------------------------------------------------------- /src/components/ui/relative-time/index.ts: -------------------------------------------------------------------------------- 1 | export * from './RelativeTime' 2 | -------------------------------------------------------------------------------- /src/components/ui/theme-switcher/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ThemeSwitcher' 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Innei/Shiro/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/app/(app)/notes/loading.tsx: -------------------------------------------------------------------------------- 1 | export { default } from '../friends/loading' 2 | -------------------------------------------------------------------------------- /src/app/(app)/posts/loading.tsx: -------------------------------------------------------------------------------- 1 | export { default } from '../friends/loading' 2 | -------------------------------------------------------------------------------- /src/components/modules/dashboard/crossbell/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | -------------------------------------------------------------------------------- /src/components/modules/dashboard/ip/index.ts: -------------------------------------------------------------------------------- 1 | export * from './IpInfoPopover' 2 | -------------------------------------------------------------------------------- /src/components/ui/auto-completion/index.ts: -------------------------------------------------------------------------------- 1 | export * from './AutoCompletion' 2 | -------------------------------------------------------------------------------- /src/constants/dom-id.ts: -------------------------------------------------------------------------------- 1 | export const MAIN_MARKDOWN_ID = 'main-markdown-render' 2 | -------------------------------------------------------------------------------- /src/components/ui/react-component-render/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ComponentRender' 2 | -------------------------------------------------------------------------------- /src/components/ui/scroll-area/index.ts: -------------------------------------------------------------------------------- 1 | export * as ScrollArea from './ScrollArea' 2 | -------------------------------------------------------------------------------- /src/models/activity.ts: -------------------------------------------------------------------------------- 1 | export type { ActivityPresence } from '@mx-space/api-client' 2 | -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Innei/Shiro/HEAD/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Innei/Shiro/HEAD/public/favicon-32x32.png -------------------------------------------------------------------------------- /src/app/(app)/not-found.tsx: -------------------------------------------------------------------------------- 1 | export { NotFound404 as default } from '~/components/common/404' 2 | -------------------------------------------------------------------------------- /src/components/modules/comment/CommentBox/index.ts: -------------------------------------------------------------------------------- 1 | export { CommentBoxRoot } from './Root' 2 | -------------------------------------------------------------------------------- /src/components/ui/link-card/index.ts: -------------------------------------------------------------------------------- 1 | export * from './enums' 2 | export * from './LinkCard' 3 | -------------------------------------------------------------------------------- /src/providers/root/framer-lazy-feature.ts: -------------------------------------------------------------------------------- 1 | export { domMax as default } from 'motion/react' 2 | -------------------------------------------------------------------------------- /storybook/config.ts: -------------------------------------------------------------------------------- 1 | export const GLOB_PATH = '../../src/components/ui/*/index.demo.(tsx|mdx)' 2 | -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Innei/Shiro/HEAD/public/apple-touch-icon.png -------------------------------------------------------------------------------- /src/app/(app)/says/loading.tsx: -------------------------------------------------------------------------------- 1 | export { FullPageLoading as default } from '~/components/ui/loading' 2 | -------------------------------------------------------------------------------- /src/components/ui/fab/index.ts: -------------------------------------------------------------------------------- 1 | export * from './BackToTopFAB' 2 | export * from './FABContainer' 3 | -------------------------------------------------------------------------------- /src/components/ui/markdown/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Markdown' 2 | export * from 'markdown-to-jsx' 3 | -------------------------------------------------------------------------------- /src/constants/event.ts: -------------------------------------------------------------------------------- 1 | export const enum DOMCustomEvents { 2 | RefreshToc = 'refresh-toc', 3 | } 4 | -------------------------------------------------------------------------------- /src/app/(app)/friends/loading.tsx: -------------------------------------------------------------------------------- 1 | export { FullPageLoading as default } from '~/components/ui/loading' 2 | -------------------------------------------------------------------------------- /src/app/(app)/projects/loading.tsx: -------------------------------------------------------------------------------- 1 | export { FullPageLoading as default } from '~/components/ui/loading' 2 | -------------------------------------------------------------------------------- /src/app/(app)/thinking/constants.ts: -------------------------------------------------------------------------------- 1 | export const FETCH_SIZE = 10 2 | export const QUERY_KEY = ['recent'] 3 | -------------------------------------------------------------------------------- /src/app/(app)/thinking/loading.tsx: -------------------------------------------------------------------------------- 1 | export { FullPageLoading as default } from '~/components/ui/loading' 2 | -------------------------------------------------------------------------------- /src/app/(app)/timeline/loading.tsx: -------------------------------------------------------------------------------- 1 | export { FullPageLoading as default } from '~/components/ui/loading' 2 | -------------------------------------------------------------------------------- /src/components/ui/rich-link/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Favicon' 2 | export * from './SocialSourceLink' 3 | -------------------------------------------------------------------------------- /src/queries/keys/comment.ts: -------------------------------------------------------------------------------- 1 | export const buildCommentsQueryKey = (refId: string) => ['comments', refId] 2 | -------------------------------------------------------------------------------- /public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Innei/Shiro/HEAD/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Innei/Shiro/HEAD/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /src/app/(app)/(page-detail)/loading.tsx: -------------------------------------------------------------------------------- 1 | export { FullPageLoading as default } from '~/components/ui/loading' 2 | -------------------------------------------------------------------------------- /src/app/(app)/notes/[id]/loading.tsx: -------------------------------------------------------------------------------- 1 | export { FullPageLoading as default } from '~/components/ui/loading' 2 | -------------------------------------------------------------------------------- /src/lib/ns.ts: -------------------------------------------------------------------------------- 1 | const NAMESPACE = 'shiro' 2 | export const buildNSKey = (key: string) => `@${NAMESPACE}/${key}` 3 | -------------------------------------------------------------------------------- /src/styles/print.css: -------------------------------------------------------------------------------- 1 | @media print { 2 | [data-hide-print] { 3 | display: none !important; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /storybook/mock-packages/next_dynamic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next_d", 3 | "main": "./index.js" 4 | } 5 | -------------------------------------------------------------------------------- /storybook/mock-packages/next_link/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next_link", 3 | "main": "./index.js" 4 | } 5 | -------------------------------------------------------------------------------- /src/app/(app)/categories/[slug]/loading.tsx: -------------------------------------------------------------------------------- 1 | export { FullPageLoading as default } from '~/components/ui/loading' 2 | -------------------------------------------------------------------------------- /src/components/ui/code-highlighter/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CodeHighlighter' 2 | 3 | // export * from './Shiki' 4 | -------------------------------------------------------------------------------- /src/components/ui/typography/index.ts: -------------------------------------------------------------------------------- 1 | export * from './AdjustableText' 2 | export * from './EllipsisWithTooltip' 3 | -------------------------------------------------------------------------------- /storybook/mock-packages/next_image/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next_image", 3 | "main": "./index.js" 4 | } 5 | -------------------------------------------------------------------------------- /src/app/(app)/(page-detail)/[slug]/loading.tsx: -------------------------------------------------------------------------------- 1 | export { FullPageLoading as default } from '~/components/ui/loading' 2 | -------------------------------------------------------------------------------- /src/components/modules/dashboard/note-editing/index.ts: -------------------------------------------------------------------------------- 1 | export * from './data-provider' 2 | export * from './sidebar' 3 | -------------------------------------------------------------------------------- /src/lib/noop.ts: -------------------------------------------------------------------------------- 1 | export const noopArr = [] 2 | 3 | export const noopObj = {} 4 | 5 | export const Noop = () => null 6 | -------------------------------------------------------------------------------- /storybook/mock-packages/next-runtime-env/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-runtime-env", 3 | "main": "index.js" 4 | } -------------------------------------------------------------------------------- /.prettierrc.mjs: -------------------------------------------------------------------------------- 1 | import { factory } from '@innei/prettier' 2 | 3 | export default factory({ 4 | importSort: false, 5 | }) 6 | -------------------------------------------------------------------------------- /src/app/(app)/(note-topic)/notes/topics/loading.tsx: -------------------------------------------------------------------------------- 1 | export { FullPageLoading as default } from '~/components/ui/loading' 2 | -------------------------------------------------------------------------------- /src/components/ui/input/index.ts: -------------------------------------------------------------------------------- 1 | export * from './AdvancedInput' 2 | export * from './Input' 3 | export * from './TextArea' 4 | -------------------------------------------------------------------------------- /src/socket/index.ts: -------------------------------------------------------------------------------- 1 | export type { TSocketClient } from './worker-client' 2 | export { socketWorker } from './worker-client' 3 | -------------------------------------------------------------------------------- /storybook/mock-packages/next_navigation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next_navigation", 3 | "main": "./index.js" 4 | } 5 | -------------------------------------------------------------------------------- /src/components/icons/thumbs-up.tsx: -------------------------------------------------------------------------------- 1 | export function ThumbsupIcon() { 2 | return 3 | } 4 | -------------------------------------------------------------------------------- /src/components/modules/say/index.ts: -------------------------------------------------------------------------------- 1 | export * from './hooks' 2 | export * from './SayMasonry' 3 | export * from './SayModalForm' 4 | -------------------------------------------------------------------------------- /src/components/modules/toc/index.ts: -------------------------------------------------------------------------------- 1 | export * from './TocAside' 2 | export * from './TocAutoScroll' 3 | export * from './TocItem' 4 | -------------------------------------------------------------------------------- /src/components/modules/xlog/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './XLogInfo' 3 | export * from './XLogSummary' 4 | -------------------------------------------------------------------------------- /src/components/modules/xlog/utils.ts: -------------------------------------------------------------------------------- 1 | export const getCidForBaseModel = (data: any) => { 2 | return data?.meta?.xLog?.cid 3 | } 4 | -------------------------------------------------------------------------------- /src/components/ui/label/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ErrorLabelLine' 2 | export * from './Label' 3 | export * from './LabelContext' 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "reporter-assets"] 2 | path = reporter-assets 3 | url = https://github.com/Innei/reporter-assets.git 4 | -------------------------------------------------------------------------------- /src/app/(app)/posts/(post-detail)/[category]/[slug]/loading.tsx: -------------------------------------------------------------------------------- 1 | export { FullPageLoading as default } from '~/components/ui/loading' 2 | -------------------------------------------------------------------------------- /src/components/modules/subscribe/index.ts: -------------------------------------------------------------------------------- 1 | export * from './hooks' 2 | export * from './SubscribeBell' 3 | export * from './SubscribeModal' 4 | -------------------------------------------------------------------------------- /src/components/ui/button/index.ts: -------------------------------------------------------------------------------- 1 | export * from './MotionButton' 2 | export * from './RoundedIconButton' 3 | export * from './StyledButton' 4 | -------------------------------------------------------------------------------- /src/events/index.ts: -------------------------------------------------------------------------------- 1 | export * from './publish' 2 | export * from './refetch' 3 | export * from './socket' 4 | export * from './write-edit' 5 | -------------------------------------------------------------------------------- /src/app/(app)/(note-topic)/notes/(topic-detail)/topics/[slug]/loading.tsx: -------------------------------------------------------------------------------- 1 | export { FullPageLoading as default } from '~/components/ui/loading' 2 | -------------------------------------------------------------------------------- /src/app/init.ts: -------------------------------------------------------------------------------- 1 | import 'dayjs/locale/zh-cn' 2 | 3 | import dayjs from 'dayjs' 4 | 5 | export const init = () => { 6 | dayjs.locale('zh-cn') 7 | } 8 | -------------------------------------------------------------------------------- /src/components/modules/dashboard/post-editing/index.ts: -------------------------------------------------------------------------------- 1 | export * from './data-provider' 2 | export * from './sidebar' 3 | export * from './SlugInput' 4 | -------------------------------------------------------------------------------- /src/components/ui/form/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Form' 2 | export * from './FormContext' 3 | export * from './FormInput' 4 | export * from './types' 5 | -------------------------------------------------------------------------------- /src/constants/tracker.ts: -------------------------------------------------------------------------------- 1 | export enum TrackerAction { 2 | Click = 'click', 3 | Interaction = 'interaction', 4 | Impression = 'impression', 5 | } 6 | -------------------------------------------------------------------------------- /storybook/TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | - [x] Support MDX 4 | - [ ] Edit code realtime 5 | - [ ] Support component metadata 6 | - [ ] Define component API usage -------------------------------------------------------------------------------- /storybook/mock-packages/next-runtime-env/index.js: -------------------------------------------------------------------------------- 1 | export const env = (key) => process.env[key] 2 | export const PublicEnvScript = (props) => props.children 3 | -------------------------------------------------------------------------------- /src/atoms/index.ts: -------------------------------------------------------------------------------- 1 | export * from './css-media' 2 | export * from './owner' 3 | export * from './socket' 4 | export * from './url' 5 | export * from './viewport' 6 | -------------------------------------------------------------------------------- /src/components/ui/markdown/renderers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './collapse' 2 | export * from './footnotes' 3 | export * from './paragraph' 4 | export * from './table' 5 | -------------------------------------------------------------------------------- /src/atoms/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './activity' 2 | export * from './owner' 3 | export * from './socket' 4 | export * from './url' 5 | export * from './viewport' 6 | -------------------------------------------------------------------------------- /src/components/modules/comment/types.ts: -------------------------------------------------------------------------------- 1 | export interface CommentBaseProps { 2 | refId: string 3 | 4 | afterSubmit?: () => void 5 | initialValue?: string 6 | } 7 | -------------------------------------------------------------------------------- /src/hooks/common/useVideo.ts: -------------------------------------------------------------------------------- 1 | import createHTMLMediaHook from './factory/createHTMLMediaHook' 2 | 3 | export const useVideo = createHTMLMediaHook('video') 4 | -------------------------------------------------------------------------------- /src/lib/error-factory.ts: -------------------------------------------------------------------------------- 1 | export class PreRenderError extends Error { 2 | constructor(message: string) { 3 | super(message) 4 | this.name = 'PreRenderError' 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/components/ui/modal/stacked/index.ts: -------------------------------------------------------------------------------- 1 | export * from './context' 2 | export * from './helper' 3 | export * from './modal' 4 | export * from './provider' 5 | export * from './types' 6 | -------------------------------------------------------------------------------- /packages/fetch/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: {} 10 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | 'postcss-import': {}, 4 | 'tailwindcss/nesting': {}, 5 | tailwindcss: {}, 6 | autoprefixer: {}, 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /src/lib/env.ts: -------------------------------------------------------------------------------- 1 | export const isClientSide = typeof window !== 'undefined' 2 | export const isServerSide = !isClientSide 3 | 4 | export const isDev = process.env.NODE_ENV === 'development' 5 | -------------------------------------------------------------------------------- /storybook/mock-packages/next_link/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export const Link = (props) => React.createElement('a', props, props.children) 4 | 5 | export default Link 6 | -------------------------------------------------------------------------------- /src/lib/bangumi.ts: -------------------------------------------------------------------------------- 1 | export const typeMap = { 2 | subject: 'subjects', 3 | character: 'characters', 4 | person: 'persons', 5 | } 6 | export const allowedBangumiTypes = Object.keys(typeMap) 7 | -------------------------------------------------------------------------------- /src/lib/store.ts: -------------------------------------------------------------------------------- 1 | import { setGlobalStore } from 'jojoo' 2 | import { createStore } from 'jotai' 3 | 4 | const store = createStore() 5 | setGlobalStore(store) 6 | export const jotaiStore = store 7 | -------------------------------------------------------------------------------- /storybook/mock-packages/next_image/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export const Image = (props) => 4 | React.createElement('img', props, props.children) 5 | 6 | export default Image 7 | -------------------------------------------------------------------------------- /storybook/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | 'tailwindcss/nesting': {}, 4 | tailwindcss: {}, 5 | autoprefixer: {}, 6 | 'postcss-import': {}, 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /src/atoms/css-media.ts: -------------------------------------------------------------------------------- 1 | import { createAtomHooks } from 'jojoo/react' 2 | import { atom } from 'jotai' 3 | 4 | export const [, , useIsPrintMode, , , setIsPrintMode] = createAtomHooks( 5 | atom(false), 6 | ) 7 | -------------------------------------------------------------------------------- /src/components/ui/markdown/utils/get-id.ts: -------------------------------------------------------------------------------- 1 | export const getFootNoteRefDomId = (id: string | number) => `footnote-ref-${id}` 2 | 3 | export const getFootNoteDomId = (id: string | number) => `footnote-${id}` 4 | -------------------------------------------------------------------------------- /src/routes/comments/layout.tsx: -------------------------------------------------------------------------------- 1 | export const config = { 2 | title: '评论', 3 | icon: , 4 | priority: 4, 5 | } 6 | 7 | export { Outlet as Component } from 'react-router-dom' 8 | -------------------------------------------------------------------------------- /src/components/icons/TiltedSendIcon.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx' 2 | 3 | export function TiltedSendIcon({ className }: { className?: string }) { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /src/components/ui/list/TimelineList.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx' 2 | 3 | export const TimelineList: Component = ({ children, className }) => { 4 | return
    {children}
5 | } 6 | -------------------------------------------------------------------------------- /src/components/modules/dashboard/writing/atoms/index.ts: -------------------------------------------------------------------------------- 1 | import { atomWithStorage } from 'jotai/utils' 2 | 3 | import { buildNSKey } from '~/lib/ns' 4 | 5 | export const syncToXlogAtom = atomWithStorage(buildNSKey('sync-to-xlog'), true) 6 | -------------------------------------------------------------------------------- /src/events/refetch.ts: -------------------------------------------------------------------------------- 1 | import { EmitKeyMap } from '~/constants/keys' 2 | 3 | export class RefetchEvent extends Event { 4 | static readonly type = EmitKeyMap.Refetch 5 | constructor() { 6 | super(EmitKeyMap.Refetch) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/app/(app)/projects/[id]/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { PropsWithChildren } from 'react' 2 | 3 | export const metadata = { 4 | title: '项目详情', 5 | } 6 | export default function Page(props: PropsWithChildren) { 7 | return props.children 8 | } 9 | -------------------------------------------------------------------------------- /src/components/layout/content/Content.tsx: -------------------------------------------------------------------------------- 1 | export const Content: Component = ({ children }) => { 2 | return ( 3 |
4 | {children} 5 |
6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /.env.template: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_API_URL=http://127.0.0.1:2333/api/v2 2 | NEXT_PUBLIC_GATEWAY_URL=http://127.0.0.1:2333/ 3 | NEXT_PUBLIC_API_URL=https://innei.ren/api/v2 4 | NEXT_PUBLIC_GATEWAY_URL=https://api.innei.ren 5 | 6 | TMDB_API_KEY= 7 | GH_TOKEN= 8 | -------------------------------------------------------------------------------- /packages/fetch/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@shiro/fetch", 3 | "exports": { 4 | ".": { 5 | "react-server": "./src/fetch.server.ts", 6 | "default": "./src/fetch.client.ts" 7 | } 8 | }, 9 | "devDependencies": {} 10 | } -------------------------------------------------------------------------------- /src/hooks/common/use-is-dark.ts: -------------------------------------------------------------------------------- 1 | import { useTheme } from 'next-themes' 2 | 3 | export const useIsDark = () => { 4 | const { theme, systemTheme } = useTheme() 5 | return theme === 'dark' || (theme === 'system' && systemTheme === 'dark') 6 | } 7 | -------------------------------------------------------------------------------- /storybook/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import config from '../tailwind.config' 2 | 3 | export default { 4 | ...config, 5 | content: [ 6 | './src/**/*.{ts,tsx}', 7 | './index.html', 8 | '../src/components/**/*.{ts,tsx}', 9 | ], 10 | } 11 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. 6 | -------------------------------------------------------------------------------- /src/hooks/common/use-callback.ts: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react' 2 | 3 | export const useTypeScriptHappyCallback: ( 4 | fn: (...args: Args) => R, 5 | deps: React.DependencyList, 6 | ) => (...args: Args) => R = useCallback 7 | -------------------------------------------------------------------------------- /NOTE.md: -------------------------------------------------------------------------------- 1 | ## How to setting page cache on Vercel Edge Network 2 | 3 | https://vercel.com/docs/edge-network/headers#s-maxage-example 4 | https://vercel.com/docs/edge-network/caching#how-to-cache-responses 5 | https://vercel.com/docs/projects/project-configuration#headers -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "local>Innei/renovate-config", 5 | "regexManagers:biomeVersions" 6 | ], 7 | "ignoreDeps": [ 8 | "jojoo", 9 | "next" 10 | ] 11 | } -------------------------------------------------------------------------------- /src/app/(app)/posts/(post-detail)/Container.tsx: -------------------------------------------------------------------------------- 1 | export const Container: Component = ({ children }) => { 2 | return ( 3 |
4 | {children} 5 |
6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /src/app/(app)/posts/(post-detail)/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { PropsWithChildren } from 'react' 2 | 3 | import { Container } from './Container' 4 | 5 | export default async ({ children }: PropsWithChildren) => { 6 | return {children} 7 | } 8 | -------------------------------------------------------------------------------- /src/providers/root/jotai-provider.tsx: -------------------------------------------------------------------------------- 1 | import { Provider } from 'jotai' 2 | 3 | import { jotaiStore } from '~/lib/store' 4 | 5 | export const JotaiStoreProvider: Component = ({ children }) => { 6 | return {children} 7 | } 8 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | # https://zenn.dev/haxibami/scraps/083718c1beec04 2 | strict-peer-dependencies=false 3 | 4 | registry=https://registry.npmjs.org 5 | 6 | public-hoist-pattern[]=mdast-util-to-markdown 7 | public-hoist-pattern[]=*eslint* 8 | public-hoist-pattern[]=*prettier* 9 | -------------------------------------------------------------------------------- /src/app/(app)/(page-detail)/Container.tsx: -------------------------------------------------------------------------------- 1 | export const Container: Component = ({ children }) => { 2 | return ( 3 |
4 | {children} 5 |
6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /src/app/(app)/(page-detail)/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { PropsWithChildren } from 'react' 2 | 3 | import { Container } from './Container' 4 | 5 | export default function Page(props: PropsWithChildren) { 6 | return {props.children} 7 | } 8 | -------------------------------------------------------------------------------- /src/components/modules/dashboard/utils/context.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react' 2 | 3 | import type { RouteItem } from '../../../../app/(dashboard)/dashboard/[[...catch_all]]/router' 4 | 5 | export const DashboardLayoutContext = createContext([]) 6 | -------------------------------------------------------------------------------- /src/constants/system.ts: -------------------------------------------------------------------------------- 1 | export const REQUEST_PATHNAME = 'request_pathname' 2 | export const REQUEST_QUERY = 'request_query' 3 | 4 | export const REQUEST_GEO = 'request_geo' 5 | export const REQUEST_IP = 'request_ip' 6 | 7 | export const REQUEST_HOST = 'request_host' 8 | -------------------------------------------------------------------------------- /src/hooks/common/use-force-update.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from 'react' 2 | 3 | export const useForceUpdate = () => { 4 | const [updated, forceUpdate] = useState(0) 5 | 6 | return [useCallback(() => forceUpdate((v) => ++v), []), updated] as const 7 | } 8 | -------------------------------------------------------------------------------- /src/hooks/common/use-state-ref.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react' 2 | 3 | export const useStateToRef = (state: T) => { 4 | const ref = useRef(state) 5 | useEffect(() => { 6 | ref.current = state 7 | }, [state]) 8 | return ref 9 | } 10 | -------------------------------------------------------------------------------- /src/lib/mine-type.ts: -------------------------------------------------------------------------------- 1 | const videoExts = new Set([ 2 | 'mp4', 3 | 'webm', 4 | 'ogg', 5 | 'avi', 6 | 'mov', 7 | 'flv', 8 | 'wmv', 9 | 'mkv', 10 | ]) 11 | 12 | export const isVideoExt = (ext: string) => { 13 | return videoExts.has(ext) 14 | } 15 | -------------------------------------------------------------------------------- /src/components/ui/excalidraw/ExcalidrawLoading.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | import { BlockLoading } from '~/components/modules/shared/BlockLoading' 4 | 5 | export const ExcalidrawLoading = () => { 6 | return Excalidraw Loading... 7 | } 8 | -------------------------------------------------------------------------------- /src/hooks/common/use-before-mounted.ts: -------------------------------------------------------------------------------- 1 | import { useRef } from 'react' 2 | 3 | export const useBeforeMounted = (fn: () => any) => { 4 | const effectOnce = useRef(false) 5 | 6 | if (!effectOnce.current) { 7 | effectOnce.current = true 8 | fn?.() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/components/layout/header/internal/grid.module.css: -------------------------------------------------------------------------------- 1 | .header--grid { 2 | grid-template-areas: 'left center right'; 3 | } 4 | 5 | .header--grid__logo { 6 | grid-area: center; 7 | } 8 | 9 | @screen lg { 10 | .header--grid__logo { 11 | grid-area: left; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/hooks/common/use-is-mounted.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | 3 | export const useIsMountedState = () => { 4 | const [mounted, setMounted] = useState(false) 5 | 6 | useEffect(() => { 7 | setMounted(true) 8 | }, []) 9 | return mounted 10 | } 11 | -------------------------------------------------------------------------------- /src/types/api.ts: -------------------------------------------------------------------------------- 1 | export type ArticleDataType = 2 | | { 3 | type: 'post' 4 | category: string 5 | slug: string 6 | } 7 | | { 8 | type: 'note' 9 | nid: number 10 | } 11 | | { 12 | type: 'page' 13 | slug: string 14 | } 15 | -------------------------------------------------------------------------------- /storybook/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /src/components/common/NotSupport.tsx: -------------------------------------------------------------------------------- 1 | export const NotSupport: Component<{ 2 | text?: string 3 | }> = ({ text }) => { 4 | return ( 5 |
6 | {text || '您当前所在地区暂不支持此功能'} 7 |
8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /src/components/modules/dashboard/utils/helper.ts: -------------------------------------------------------------------------------- 1 | export type DashboardRouteConfig = { 2 | title: string 3 | icon: React.ReactNode 4 | priority: number 5 | redirect?: string 6 | } 7 | export const defineRouteConfig = (config: DashboardRouteConfig) => { 8 | return config 9 | } 10 | -------------------------------------------------------------------------------- /src/components/ui/sheet/context.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from 'react' 2 | 3 | interface SheetContextValue { 4 | dismiss(): void 5 | } 6 | export const SheetContext = createContext(null!) 7 | 8 | export const useSheetContext = () => useContext(SheetContext) 9 | -------------------------------------------------------------------------------- /src/hooks/common/use-previous.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react' 2 | 3 | export const usePrevious = (value: T): (() => T | undefined) => { 4 | const ref = useRef() 5 | useEffect(() => { 6 | ref.current = value 7 | }, [value]) 8 | return () => ref.current 9 | } 10 | -------------------------------------------------------------------------------- /src/hooks/common/use-get-state.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useRef } from 'react' 2 | 3 | export const useGetState = (state: T): (() => T) => { 4 | const ref = useRef(state) 5 | 6 | useEffect(() => void (ref.current = state), [state]) 7 | return useCallback(() => ref.current, []) 8 | } 9 | -------------------------------------------------------------------------------- /src/components/layout/footer/OwnerName.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { useAggregationSelector } from '~/providers/root/aggregation-data-provider' 4 | 5 | export const OwnerName = () => { 6 | const ownerName = useAggregationSelector((state) => state.user.name) 7 | 8 | return ownerName 9 | } 10 | -------------------------------------------------------------------------------- /src/components/ui/transition/FadeInOutTransitionView.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { createTransitionView } from './factor' 4 | 5 | export const FadeInOutTransitionView = createTransitionView({ 6 | from: { 7 | opacity: 0.001, 8 | }, 9 | to: { 10 | opacity: 1, 11 | }, 12 | }) 13 | -------------------------------------------------------------------------------- /src/styles/index.css: -------------------------------------------------------------------------------- 1 | @import './tailwindcss.css'; 2 | @import './variables.css'; 3 | 4 | @import './scrollbar.css'; 5 | @import './print.css'; 6 | 7 | @import './image-zoom.css'; 8 | @import './sonner.css'; 9 | 10 | @import './theme.css'; 11 | @import './webfont.css'; 12 | @import './mask.css'; 13 | -------------------------------------------------------------------------------- /src/components/common/QueryHydrate.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import type { HydrationBoundaryProps } from '@tanstack/react-query' 4 | import { HydrationBoundary as RQHydrate } from '@tanstack/react-query' 5 | 6 | export function QueryHydrate(props: HydrationBoundaryProps) { 7 | return 8 | } 9 | -------------------------------------------------------------------------------- /src/app/robots.ts: -------------------------------------------------------------------------------- 1 | import type { MetadataRoute } from 'next' 2 | 3 | export default function robots(): MetadataRoute.Robots { 4 | return { 5 | rules: [ 6 | { 7 | userAgent: '*', 8 | disallow: ['/login/', '/preview/', '/dashboard', '/_next'], 9 | }, 10 | ], 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/components/ui/markdown/renderers/tag.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { Tag } from '../../tag/Tag' 3 | 4 | export const MTag: Component = ({ children }) => { 5 | const text = children?.[0] 6 | if (typeof text !== 'string') return null 7 | return 8 | } 9 | -------------------------------------------------------------------------------- /src/components/ui/transition/ScaleTransitionView.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { createTransitionView } from './factor' 4 | 5 | export const ScaleTransitionView = createTransitionView({ 6 | from: { 7 | scale: 0.001, 8 | opacity: 0.001, 9 | }, 10 | to: { 11 | scale: 1, 12 | opacity: 1, 13 | }, 14 | }) 15 | -------------------------------------------------------------------------------- /src/app/InitInClient.ts: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { cheatVueDevtools } from 'bypass-vue-devtools' 4 | import { useEffect } from 'react' 5 | 6 | import { init } from './init' 7 | 8 | init() 9 | 10 | export const InitInClient = () => { 11 | useEffect(() => { 12 | cheatVueDevtools() 13 | }, []) 14 | return null 15 | } 16 | -------------------------------------------------------------------------------- /src/components/ui/transition/LeftToRightTransitionView.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { createTransitionView } from './factor' 4 | 5 | export const LeftToRightTransitionView = createTransitionView({ 6 | from: { 7 | x: -70, 8 | opacity: 0.001, 9 | }, 10 | to: { 11 | x: 0, 12 | opacity: 1, 13 | }, 14 | }) 15 | -------------------------------------------------------------------------------- /src/components/ui/transition/RightToLeftTransitionView.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { createTransitionView } from './factor' 4 | 5 | export const RightToLeftTransitionView = createTransitionView({ 6 | from: { 7 | x: 42, 8 | opacity: 0.001, 9 | }, 10 | to: { 11 | x: 0, 12 | opacity: 1, 13 | }, 14 | }) 15 | -------------------------------------------------------------------------------- /src/events/publish.ts: -------------------------------------------------------------------------------- 1 | import { EmitKeyMap } from '~/constants/keys' 2 | import type { NoteDto, PostDto } from '~/models/writing' 3 | 4 | export class PublishEvent extends Event { 5 | static readonly type = EmitKeyMap.Publish 6 | constructor(public readonly data: NoteDto | PostDto) { 7 | super(EmitKeyMap.Publish) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/components/common/AckRead.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import type { FC } from 'react' 4 | 5 | import { useAckReadCount } from '~/hooks/biz/use-ack-read-count' 6 | 7 | export const AckRead: FC<{ 8 | id: string 9 | type: 'post' | 'note' 10 | }> = (props) => { 11 | useAckReadCount(props.type, props.id) 12 | 13 | return null 14 | } 15 | -------------------------------------------------------------------------------- /src/hooks/common/use-is-unmounted.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react' 2 | 3 | export const useIsUnMounted = () => { 4 | const unmounted = useRef(false) 5 | useEffect(() => { 6 | unmounted.current = false 7 | return () => { 8 | unmounted.current = true 9 | } 10 | }, []) 11 | 12 | return unmounted 13 | } 14 | -------------------------------------------------------------------------------- /src/lib/query-client.server.ts: -------------------------------------------------------------------------------- 1 | import 'server-only' 2 | 3 | import { QueryClient } from '@tanstack/react-query' 4 | 5 | const sharedClient = new QueryClient({ 6 | defaultOptions: { 7 | queries: { 8 | staleTime: 1000 * 3, 9 | gcTime: 1000 * 30, 10 | }, 11 | }, 12 | }) 13 | export const getQueryClient = () => sharedClient 14 | -------------------------------------------------------------------------------- /src/components/modules/dashboard/comments/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CommentAction' 2 | export * from './CommentAuthorCell' 3 | export * from './CommentBatchActionGroup' 4 | export * from './CommentContentCell' 5 | export * from './CommentContext' 6 | export * from './CommentDesktopTable' 7 | export * from './CommentMobileList' 8 | export * from './ReplyModal' 9 | -------------------------------------------------------------------------------- /src/components/modules/post/index.ts: -------------------------------------------------------------------------------- 1 | export * from './PostActionAside' 2 | export * from './PostCopyright' 3 | export * from './PostItem' 4 | export * from './PostItemHoverOverlay' 5 | export * from './PostMetaBar' 6 | export * from './PostOutdate' 7 | export * from './PostPagination' 8 | export * from './PostPinIcon' 9 | export * from './PostRelated' 10 | -------------------------------------------------------------------------------- /src/routes/notes/layout.tsx: -------------------------------------------------------------------------------- 1 | import { defineRouteConfig } from '~/components/modules/dashboard/utils/helper' 2 | 3 | export { Outlet as Component } from 'react-router-dom' 4 | 5 | export const config = defineRouteConfig({ 6 | title: '日记', 7 | icon: , 8 | priority: 3, 9 | redirect: '/notes/list', 10 | }) 11 | -------------------------------------------------------------------------------- /src/routes/posts/layout.tsx: -------------------------------------------------------------------------------- 1 | import { defineRouteConfig } from '~/components/modules/dashboard/utils/helper' 2 | 3 | export { Outlet as Component } from 'react-router-dom' 4 | 5 | export const config = defineRouteConfig({ 6 | title: '文稿', 7 | icon: , 8 | priority: 2, 9 | redirect: '/posts/list', 10 | }) 11 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Dependency directories 2 | node_modules/ 3 | 4 | # next.js build output 5 | .next 6 | 7 | # typescript build output 8 | dist 9 | .env 10 | 11 | # pwa 12 | sw.js 13 | sw.js.* 14 | workbox-*.js 15 | workbox-*.js.* 16 | pages/debug.tsx 17 | /temp 18 | 19 | pages/dev 20 | 21 | patch/dist 22 | tmp 23 | out 24 | release.zip 25 | 26 | run 27 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | shiro: 5 | container_name: shiro 6 | image: innei/shiro:latest 7 | volumes: 8 | - ./.env:/app/.env 9 | - ./public:/app/public 10 | restart: always 11 | environment: 12 | - NEXT_SHARP_PATH=/usr/local/lib/node_modules/sharp 13 | ports: 14 | - 2323:2323 15 | -------------------------------------------------------------------------------- /src/components/modules/dashboard/comments/UrlRender.tsx: -------------------------------------------------------------------------------- 1 | export const CommentUrlRender = ({ 2 | url, 3 | author, 4 | }: { 5 | url: string | null | undefined 6 | author: string 7 | }) => 8 | url ? ( 9 | 10 | {author} 11 | 12 | ) : ( 13 | {author} 14 | ) 15 | -------------------------------------------------------------------------------- /src/events/write-edit.ts: -------------------------------------------------------------------------------- 1 | import { EmitKeyMap } from '~/constants/keys' 2 | import type { NoteDto, PostDto } from '~/models/writing' 3 | 4 | export class WriteEditEvent extends Event { 5 | static readonly type = EmitKeyMap.EditDataUpdate 6 | constructor(public readonly data: NoteDto | PostDto) { 7 | super(EmitKeyMap.EditDataUpdate) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/hooks/common/use-ref-value.ts: -------------------------------------------------------------------------------- 1 | import { useRef } from 'react' 2 | 3 | // @see https://react.dev/reference/react/useRef#avoiding-recreating-the-ref-contents 4 | export const useRefValue = (value: () => T): T => { 5 | const ref = useRef() 6 | 7 | if (!ref.current) { 8 | ref.current = value() 9 | } 10 | 11 | return ref.current! 12 | } 13 | -------------------------------------------------------------------------------- /src/components/modules/dashboard/note-editing/constants.ts: -------------------------------------------------------------------------------- 1 | export const MOOD_SET = [ 2 | '开心', 3 | '伤心', 4 | '决心', 5 | '坚定', 6 | '痛恨', 7 | '生气', 8 | '悲哀', 9 | '痛苦', 10 | '可怕', 11 | '不快', 12 | '可恶', 13 | '担心', 14 | '绝望', 15 | '焦虑', 16 | '激动', 17 | ] as const 18 | export const WEATHER_SET = ['晴', '多云', '雨', '阴', '雪', '雷雨'] as const 19 | -------------------------------------------------------------------------------- /src/components/layout/dashboard/PageLoading.tsx: -------------------------------------------------------------------------------- 1 | import { AbsoluteCenterSpinner } from '~/components/ui/spinner' 2 | 3 | interface Props { 4 | loadingText?: string 5 | } 6 | 7 | export const PageLoading: Component = (props) => ( 8 | 9 | {props.loadingText} 10 | 11 | ) 12 | -------------------------------------------------------------------------------- /src/components/modules/comment/CommentRootLazy.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { ErrorBoundary } from '~/components/common/ErrorBoundary' 4 | 5 | import { CommentAreaRoot } from './CommentRoot' 6 | 7 | export const CommentAreaRootLazy: typeof CommentAreaRoot = (props) => ( 8 | 9 | 10 | 11 | ) 12 | -------------------------------------------------------------------------------- /src/app/(app)/(home)/query.ts: -------------------------------------------------------------------------------- 1 | import type { AggregateTop } from '@mx-space/api-client' 2 | import { useQuery } from '@tanstack/react-query' 3 | 4 | export const queryKey = ['home'] 5 | 6 | export const useHomeQueryData = () => { 7 | return useQuery({ 8 | queryKey, 9 | queryFn: async () => null! as AggregateTop, 10 | enabled: false, 11 | }).data! 12 | } 13 | -------------------------------------------------------------------------------- /storybook/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | React Components 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/styles/animation.css: -------------------------------------------------------------------------------- 1 | @tailwind components; 2 | 3 | @layer components { 4 | .animate-scale-in { 5 | animation: scale-in 0.3s cubic-bezier(0.4, 0, 0.2, 1); 6 | } 7 | 8 | @keyframes scale-in { 9 | 0% { 10 | transform: scale(0.9); 11 | opacity: 0; 12 | } 13 | 100% { 14 | transform: scale(1); 15 | opacity: 1; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /storybook/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /src/components/ui/markdown/utils/redHighlight.tsx: -------------------------------------------------------------------------------- 1 | export function redHighlight(id: string) { 2 | const fnRefElement = document.getElementById(id) 3 | if (fnRefElement) { 4 | fnRefElement.style.color = '#ef4444' 5 | setTimeout(() => { 6 | fnRefElement.style.color = '' 7 | }, 5000) 8 | } else { 9 | console.error(`Element with id fnref:${id} not found.`) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/components/ui/modal/stacked/helper.tsx: -------------------------------------------------------------------------------- 1 | import type { Context, PropsWithChildren } from 'react' 2 | import { memo, useContext } from 'react' 3 | 4 | export const InjectContext = (context: Context) => { 5 | const ctxValue = useContext(context) 6 | return memo(({ children }: PropsWithChildren) => ( 7 | {children} 8 | )) 9 | } 10 | -------------------------------------------------------------------------------- /storybook/typings/index.ts: -------------------------------------------------------------------------------- 1 | import type * as React from 'react' 2 | 3 | export interface DocumentComponent extends React.FC { 4 | meta?: Partial<{ 5 | title?: string 6 | /** 7 | * @description Markdown support 8 | */ 9 | description?: string 10 | }> 11 | } 12 | 13 | export interface DocumentPageMeta { 14 | title: string 15 | description?: string 16 | } 17 | -------------------------------------------------------------------------------- /src/app/(app)/friends/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | import type { PropsWithChildren } from 'react' 3 | 4 | import { NormalContainer } from '~/components/layout/container/Normal' 5 | 6 | export const metadata: Metadata = { 7 | title: '朋友们', 8 | } 9 | export default async function (props: PropsWithChildren) { 10 | return {props.children} 11 | } 12 | -------------------------------------------------------------------------------- /src/app/(app)/projects/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | import type { PropsWithChildren } from 'react' 3 | 4 | import { WiderContainer } from '~/components/layout/container/Wider' 5 | 6 | export const metadata: Metadata = { 7 | title: '项目', 8 | } 9 | export default async function Layout(props: PropsWithChildren) { 10 | return {props.children} 11 | } 12 | -------------------------------------------------------------------------------- /src/app/(app)/says/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | import type { PropsWithChildren } from 'react' 3 | 4 | import { WiderContainer } from '~/components/layout/container/Wider' 5 | 6 | export const metadata: Metadata = { 7 | title: '一言', 8 | } 9 | export default async function Layout(props: PropsWithChildren) { 10 | return {props.children} 11 | } 12 | -------------------------------------------------------------------------------- /src/components/common/ProviderComposer.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import type { JSX } from 'react' 4 | import * as React from 'react' 5 | 6 | export const ProviderComposer: Component<{ 7 | contexts: JSX.Element[] 8 | }> = ({ contexts, children }) => { 9 | return contexts.reduceRight((kids: any, parent: any) => { 10 | return React.cloneElement(parent, { children: kids }) 11 | }, children) 12 | } 13 | -------------------------------------------------------------------------------- /src/app/(app)/thinking/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | import type { PropsWithChildren } from 'react' 3 | 4 | import { NormalContainer } from '~/components/layout/container/Normal' 5 | 6 | export const metadata: Metadata = { 7 | title: '思考', 8 | } 9 | export default async function Layout(props: PropsWithChildren) { 10 | return {props.children} 11 | } 12 | -------------------------------------------------------------------------------- /src/components/ui/label/ErrorLabelLine.tsx: -------------------------------------------------------------------------------- 1 | import { Label } from './Label' 2 | 3 | export const ErrorLabelLine = ({ 4 | errorMessage, 5 | id, 6 | }: { 7 | id: string 8 | errorMessage: string 9 | }) => { 10 | return ( 11 |
12 | 15 |
16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /src/components/ui/link-card/enums.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | export enum LinkCardSource { 4 | GHRepo = 'gh-repo', 5 | Self = 'self', 6 | MixSpace = 'mx-space', 7 | GHCommit = 'gh-commit', 8 | GHPr = 'gh-pr', 9 | TMDB = 'tmdb', 10 | Bangumi = 'bangumi', 11 | LEETCODE = 'leetcode', 12 | Arxiv = 'arxiv', 13 | QQMusicSong = 'qq-music-song', 14 | NeteaseMusicSong = 'netease-music-song', 15 | } 16 | -------------------------------------------------------------------------------- /src/atoms/socket.ts: -------------------------------------------------------------------------------- 1 | import { createAtomHooks } from 'jojoo/react' 2 | import { atom } from 'jotai' 3 | 4 | import { jotaiStore } from '~/lib/store' 5 | 6 | export const [, , useOnlineCount, , , setOnlineCount] = createAtomHooks(atom(0)) 7 | 8 | export const socketIsConnectAtom = atom(false) 9 | 10 | export const setSocketIsConnect = (value: boolean) => { 11 | jotaiStore.set(socketIsConnectAtom, value) 12 | } 13 | -------------------------------------------------------------------------------- /src/hooks/common/use-safe-setState.ts: -------------------------------------------------------------------------------- 1 | import type { Dispatch, MutableRefObject, SetStateAction } from 'react' 2 | 3 | export const useSafeSetState = ( 4 | setState: Dispatch>, 5 | unmountedRef: MutableRefObject, 6 | ) => { 7 | const setSafeState = (state: S) => { 8 | if (!unmountedRef.current) { 9 | setState(state) 10 | } 11 | } 12 | return setSafeState 13 | } 14 | -------------------------------------------------------------------------------- /src/models/session.ts: -------------------------------------------------------------------------------- 1 | import type { AuthSocialProviders } from '~/lib/authjs' 2 | 3 | export interface SessionReader { 4 | id: string 5 | name: string 6 | email: string 7 | image: string 8 | emailVerified: null 9 | isOwner: boolean 10 | scope: string 11 | tokenType: string 12 | providerAccountId: string 13 | provider: AuthSocialProviders 14 | type: string 15 | 16 | handle?: string 17 | } 18 | -------------------------------------------------------------------------------- /src/app/(app)/notes/[id]/Transition.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { createTransitionView } from '~/components/ui/transition/factor' 4 | 5 | export const Transition = createTransitionView({ 6 | from: { 7 | y: 80, 8 | opacity: 0.001, 9 | }, 10 | to: { 11 | y: 0, 12 | opacity: 1, 13 | }, 14 | preset: { 15 | type: 'spring', 16 | damping: 20, 17 | stiffness: 200, 18 | }, 19 | }) 20 | -------------------------------------------------------------------------------- /storybook/src/markdown.css: -------------------------------------------------------------------------------- 1 | .markdown-body { 2 | & > h1, 3 | & > h2, 4 | & > h3, 5 | & > h4, 6 | & > h5, 7 | & > h6 { 8 | font-weight: 400; 9 | line-height: 1.5; 10 | margin: 12px 0; 11 | padding: 0; 12 | } 13 | 14 | & > p { 15 | margin: 12px 0; 16 | } 17 | } 18 | 19 | .markdown-body h1 { 20 | font-size: 24px; 21 | } 22 | 23 | .markdown-body h2 { 24 | font-size: 20px; 25 | } 26 | -------------------------------------------------------------------------------- /src/atoms/is-interactive.ts: -------------------------------------------------------------------------------- 1 | import { atom, useAtomValue } from 'jotai' 2 | 3 | import { jotaiStore } from '~/lib/store' 4 | 5 | const isInteractiveAtom = atom(false) 6 | export const useIsInteractive = () => useAtomValue(isInteractiveAtom) 7 | 8 | export const getIsInteractive = () => jotaiStore.get(isInteractiveAtom) 9 | export const setIsInteractive = (value: boolean) => 10 | jotaiStore.set(isInteractiveAtom, value) 11 | -------------------------------------------------------------------------------- /src/components/modules/peek/PeekPortal.tsx: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect } from 'react' 2 | 3 | import { usePeek } from './usePeek' 4 | 5 | declare global { 6 | interface Window { 7 | peek: ReturnType 8 | } 9 | } 10 | 11 | export const PeekPortal = () => { 12 | const peek = usePeek() 13 | 14 | useLayoutEffect(() => { 15 | peek && (window.peek = peek) 16 | }, [peek]) 17 | return null 18 | } 19 | -------------------------------------------------------------------------------- /storybook/src/routes/root.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet, ScrollRestoration } from 'react-router-dom' 2 | 3 | import { Sidebar } from '../components/Sidebar' 4 | 5 | export const Root = () => { 6 | return ( 7 |
8 | 9 |
10 | 11 | 12 |
13 |
14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import '../styles/index.css' 2 | 3 | import type { PropsWithChildren } from 'react' 4 | 5 | import { init } from './init' 6 | import { InitInClient } from './InitInClient' 7 | 8 | init() 9 | export default async function RootLayout({ children }: PropsWithChildren) { 10 | return ( 11 | <> 12 | {children} 13 | 14 | {/* */} 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /src/components/ui/viewport/OnlyMobile.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { useIsMobile } from '~/atoms/hooks' 4 | import { useIsClient } from '~/hooks/common/use-is-client' 5 | 6 | export const OnlyMobile: Component = ({ children }) => { 7 | const isClient = useIsClient() 8 | 9 | const isMobile = useIsMobile() 10 | 11 | if (!isClient) return null 12 | 13 | if (!isMobile) return null 14 | 15 | return children 16 | } 17 | -------------------------------------------------------------------------------- /storybook/src/index.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;1,200;1,300;1,400;1,500;1,600;1,700&display=swap'); 2 | @import url('https://raw.githubusercontent.com/sindresorhus/github-markdown-css/main/github-markdown.css'); 3 | 4 | @tailwind base; 5 | @tailwind components; 6 | @tailwind utilities; 7 | 8 | html { 9 | font-family: system-ui !important; 10 | } 11 | -------------------------------------------------------------------------------- /src/atoms/viewport.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'jotai' 2 | 3 | export const viewportAtom = atom({ 4 | /** 5 | * 640px 6 | */ 7 | sm: false, 8 | 9 | /** 10 | * 768px 11 | */ 12 | md: false, 13 | 14 | /** 15 | * 1024px 16 | */ 17 | lg: false, 18 | 19 | /** 20 | * 1280px 21 | */ 22 | xl: false, 23 | 24 | /** 25 | * 1536px 26 | */ 27 | '2xl': false, 28 | 29 | h: 0, 30 | w: 0, 31 | }) 32 | -------------------------------------------------------------------------------- /src/components/ui/transition/index.ts: -------------------------------------------------------------------------------- 1 | export * from './BottomToUpSoftScaleTransitionView' 2 | export * from './BottomToUpTransitionView' 3 | export * from './FadeInOutTransitionView' 4 | export * from './IconSmoothTransition' 5 | export * from './LeftToRightTransitionView' 6 | export * from './RightToLeftTransitionView' 7 | export * from './ScaleTransitionView' 8 | export * from './TextUpTransitionView' 9 | export * from './typings' 10 | -------------------------------------------------------------------------------- /src/components/icons/close.tsx: -------------------------------------------------------------------------------- 1 | export const CloseIcon: Component = ({ className }) => ( 2 | 9 | 13 | 14 | ) 15 | -------------------------------------------------------------------------------- /ecosystem.standalone.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | apps: [ 3 | { 4 | name: 'shiro', 5 | script: 'server.js', 6 | autorestart: true, 7 | watch: false, 8 | max_memory_restart: '500M', 9 | env: { 10 | PORT: 2323, 11 | NODE_ENV: 'production', 12 | NEXT_SHARP_PATH: process.env.NEXT_SHARP_PATH, 13 | }, 14 | log_date_format: 'YYYY-MM-DD HH:mm:ss', 15 | }, 16 | ], 17 | } 18 | -------------------------------------------------------------------------------- /src/components/icons/ic-baseline-menu-open.tsx: -------------------------------------------------------------------------------- 1 | import type { SVGProps } from 'react' 2 | 3 | export function IcBaselineMenuOpen(props: SVGProps) { 4 | return ( 5 | 6 | 10 | 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /src/components/modules/shared/NothingFound.tsx: -------------------------------------------------------------------------------- 1 | import { EmptyIcon } from '~/components/icons/empty' 2 | import { NormalContainer } from '~/components/layout/container/Normal' 3 | 4 | export const NothingFound: Component = () => { 5 | return ( 6 | 7 | 8 |

这里空空如也

9 |

稍后再来看看吧!

10 |
11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /src/components/ui/transition/BottomToUpSoftScaleTransitionView.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { softSpringPreset } from '~/constants/spring' 4 | 5 | import { createTransitionView } from './factor' 6 | 7 | export const BottomToUpSoftScaleTransitionView = createTransitionView({ 8 | from: { opacity: 0.00001, scale: 0.96, y: 10 }, 9 | to: { 10 | y: 0, 11 | scale: 1, 12 | opacity: 1, 13 | }, 14 | preset: softSpringPreset, 15 | }) 16 | -------------------------------------------------------------------------------- /ecosystem.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | apps: [ 3 | { 4 | name: 'Shiro', 5 | script: 'npx next start -p 2323', 6 | instances: 1, 7 | autorestart: true, 8 | watch: false, 9 | max_memory_restart: '500M', 10 | env: { 11 | NODE_ENV: 'production', 12 | NEXT_SHARP_PATH: process.env.NEXT_SHARP_PATH, 13 | }, 14 | log_date_format: 'YYYY-MM-DD HH:mm:ss', 15 | }, 16 | ], 17 | } 18 | -------------------------------------------------------------------------------- /src/app/(app)/(note-topic)/notes/(topic-detail)/topics/[slug]/query.ts: -------------------------------------------------------------------------------- 1 | import { apiClient } from '~/lib/request' 2 | import { defineQuery } from '~/queries/helper' 3 | 4 | export const getTopicQuery = (topicSlug: string) => 5 | defineQuery({ 6 | queryKey: ['topic', topicSlug], 7 | queryFn: async ({ queryKey }) => { 8 | const [_, slug] = queryKey 9 | return (await apiClient.topic.getTopicBySlug(slug)).$serialized 10 | }, 11 | }) 12 | -------------------------------------------------------------------------------- /src/components/common/Global.ts: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { useIsomorphicLayoutEffect } from 'foxact/use-isomorphic-layout-effect' 4 | import * as React from 'react' 5 | import ReactDOM from 'react-dom' 6 | 7 | export const Global = () => { 8 | useIsomorphicLayoutEffect(() => { 9 | Object.assign(window, { 10 | React, 11 | ReactDOM, 12 | react: React, 13 | reactDom: ReactDOM, 14 | }) 15 | }, []) 16 | return null 17 | } 18 | -------------------------------------------------------------------------------- /src/components/modules/xlog/types.ts: -------------------------------------------------------------------------------- 1 | export interface XLogMeta { 2 | pageId?: string 3 | cid?: string 4 | relatedUrls?: string[] 5 | metadata?: { 6 | network: string 7 | proof: string 8 | raw?: Record 9 | owner?: string 10 | transactions?: string[] 11 | [key: string]: any 12 | } 13 | } 14 | 15 | declare module '@mx-space/api-client' { 16 | export interface PostMeta { 17 | xLog?: XLogMeta 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /storybook/src/components/Markdown/index.tsx: -------------------------------------------------------------------------------- 1 | import { marked } from 'marked' 2 | import { memo } from 'react' 3 | 4 | export const Markdown = memo((props: { value: string }) => { 5 | const { value } = props 6 | const html = marked(value) 7 | return ( 8 |
14 | ) 15 | }) 16 | Markdown.displayName = 'Markdown' 17 | -------------------------------------------------------------------------------- /src/components/ui/markdown/renderers/blockqoute.tsx: -------------------------------------------------------------------------------- 1 | import type { FC } from 'react' 2 | 3 | import { GitAlert } from './alert' 4 | 5 | export const MBlockQuote: FC<{ 6 | className?: string 7 | children: React.ReactNode 8 | alert?: string 9 | }> = ({ className, children, alert }) => { 10 | if (alert) { 11 | return 12 | } 13 | return
{children}
14 | } 15 | -------------------------------------------------------------------------------- /src/events/socket.ts: -------------------------------------------------------------------------------- 1 | import { EmitKeyMap } from '~/constants/keys' 2 | 3 | export class SocketConnectedEvent extends Event { 4 | static readonly type = EmitKeyMap.SocketConnected 5 | constructor() { 6 | super(EmitKeyMap.SocketConnected) 7 | } 8 | } 9 | 10 | export class SocketDisconnectedEvent extends Event { 11 | static readonly type = EmitKeyMap.SocketDisconnected 12 | constructor() { 13 | super(EmitKeyMap.SocketDisconnected) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/queries/definition/index.ts: -------------------------------------------------------------------------------- 1 | import { aggregation } from './aggregation' 2 | import { commentAdmin } from './comment' 3 | import { note, noteAdmin } from './note' 4 | import { page } from './page' 5 | import { post, postAdmin } from './post' 6 | 7 | export const queries = { 8 | aggregation, 9 | note, 10 | post, 11 | page, 12 | } 13 | 14 | export const adminQueries = { 15 | post: postAdmin, 16 | note: noteAdmin, 17 | comment: commentAdmin, 18 | } 19 | -------------------------------------------------------------------------------- /src/app/(app)/categories/[slug]/api.tsx: -------------------------------------------------------------------------------- 1 | import { cache } from 'react' 2 | 3 | import { attachServerFetch } from '~/lib/attach-fetch' 4 | import { apiClient } from '~/lib/request' 5 | import { requestErrorHandler } from '~/lib/request.server' 6 | 7 | export const getData = cache(async (params: { slug: string }) => { 8 | attachServerFetch() 9 | return await apiClient.category 10 | .getCategoryByIdOrSlug(params.slug) 11 | .catch(requestErrorHandler) 12 | }) 13 | -------------------------------------------------------------------------------- /src/components/layout/container/Wider.tsx: -------------------------------------------------------------------------------- 1 | import { clsxm } from '~/lib/helper' 2 | 3 | export const WiderContainer: Component = (props) => { 4 | const { children, className } = props 5 | 6 | return ( 7 |
14 | {children} 15 |
16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /src/hooks/common/use-event-callback.ts: -------------------------------------------------------------------------------- 1 | 'use client' 2 | import { useIsomorphicLayoutEffect } from 'foxact/use-isomorphic-layout-effect' 3 | import { useCallback, useRef } from 'react' 4 | 5 | export const useEventCallback = any>(fn: T) => { 6 | const ref = useRef(fn) 7 | useIsomorphicLayoutEffect(() => { 8 | ref.current = fn 9 | }, [fn]) 10 | 11 | return useCallback((...args: any[]) => ref.current(...args), []) as T 12 | } 13 | -------------------------------------------------------------------------------- /src/hooks/biz/use-ack-read-count.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | 3 | import { apiClient } from '~/lib/request' 4 | import { queryClient } from '~/providers/root/react-query-provider' 5 | 6 | export const useAckReadCount = (type: 'post' | 'note', id: string) => { 7 | useEffect(() => { 8 | queryClient.fetchQuery({ 9 | queryKey: ['ack-read-count', type, id], 10 | queryFn: async () => apiClient.ack.read(type, id), 11 | }) 12 | }, []) 13 | } 14 | -------------------------------------------------------------------------------- /src/queries/definition/page.ts: -------------------------------------------------------------------------------- 1 | import { apiClient } from '~/lib/request' 2 | 3 | import { defineQuery } from '../helper' 4 | 5 | export const page = { 6 | bySlug: (slug: string) => 7 | defineQuery({ 8 | queryKey: ['page', slug], 9 | 10 | queryFn: async ({ queryKey }) => { 11 | const [, slug] = queryKey 12 | 13 | const data = await apiClient.page.getBySlug(slug) 14 | 15 | return data.$serialized 16 | }, 17 | }), 18 | } 19 | -------------------------------------------------------------------------------- /SAY.md: -------------------------------------------------------------------------------- 1 | 2 | ## 动机 3 | 4 | Kami 的代码已经超过 3 年,实在变得难以维护,一堆屎一样的代码。自己看了都感到恶心。其次是太多的 Hydration Error 看着心烦,换个风格也挺好。 5 | 6 | 本次重写不仅在风格上的不同,在代码层面上也更加符合 React 哲学,使用了非常牛逼的 Jotai 作为状态管理,🍞老师果然说的没错,Jotai yyds。 7 | 8 | 几乎重写了 80% 的原代码,覆盖了几个主要页面。 9 | 10 | ## Status 11 | 12 | **WIP** 13 | 14 | ## 设计细节 15 | 16 | ### 本站依旧接入了 WebSocket 连接,通知和更新当前的活动。 17 | 18 | - 发布文章站内通知 19 | - 文章的实时修改,实时反映 20 | - 实时评论 WIP 21 | 22 | ### 头部 23 | 24 | - 4 种不同的状态 25 | 26 | ### FAB 27 | 28 | 对移动端进行了优化,调整了尺寸为了更好的点击。 -------------------------------------------------------------------------------- /src/app/(app)/(note-topic)/notes/topics/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | import type { PropsWithChildren } from 'react' 3 | 4 | import { NormalContainer } from '~/components/layout/container/Normal' 5 | 6 | export const dynamic = 'force-dynamic' 7 | export const metadata: Metadata = { 8 | title: '专栏', 9 | } 10 | 11 | export default async function Layout(props: PropsWithChildren) { 12 | return {props.children} 13 | } 14 | -------------------------------------------------------------------------------- /src/components/hoc/with-no-ssr.tsx: -------------------------------------------------------------------------------- 1 | import type { FC, PropsWithChildren } from 'react' 2 | 3 | import { useIsClientTransition } from '~/hooks/common/use-is-client' 4 | 5 | export const withNoSSR = ( 6 | Component: FC>, 7 | ): FC> => { 8 | return function NoSSRWrapper(props: PropsWithChildren

) { 9 | const isClient = useIsClientTransition() 10 | if (!isClient) return null 11 | return 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/icons/calendar.tsx: -------------------------------------------------------------------------------- 1 | import type { SVGProps } from 'react' 2 | import * as React from 'react' 3 | 4 | export function MdiCalendar(props: SVGProps) { 5 | return ( 6 | 7 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /src/app/(app)/categories/[slug]/query.ts: -------------------------------------------------------------------------------- 1 | import { apiClient } from '~/lib/request' 2 | import { defineQuery } from '~/queries/helper' 3 | 4 | export const getPageBySlugQuery = (slug: string) => 5 | defineQuery({ 6 | queryKey: ['category', slug], 7 | queryFn: async ({ queryKey }) => { 8 | const [, slug] = queryKey 9 | 10 | const data = await apiClient.category.getCategoryByIdOrSlug(slug) 11 | 12 | return { 13 | ...data, 14 | } 15 | }, 16 | }) 17 | -------------------------------------------------------------------------------- /src/components/modules/shared/BlockLoading.tsx: -------------------------------------------------------------------------------- 1 | import { clsxm } from '~/lib/helper' 2 | 3 | export const BlockLoading: Component<{ 4 | style?: React.CSSProperties 5 | }> = (props) => { 6 | return ( 7 |

14 | {props.children} 15 |
16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /src/hooks/common/use-debounce-value.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | 3 | export default function useDebounceValue(value: T, delay: number): T { 4 | const [debouncedValue, setDebouncedValue] = useState(value) 5 | 6 | useEffect(() => { 7 | const handler = setTimeout(() => { 8 | setDebouncedValue(value) 9 | }, delay) 10 | 11 | return () => { 12 | clearTimeout(handler) 13 | } 14 | }, [value, delay]) 15 | 16 | return debouncedValue 17 | } 18 | -------------------------------------------------------------------------------- /src/hooks/common/use-sync-effect.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react' 2 | 3 | type CleanupFn = () => void | undefined 4 | const noop = () => {} 5 | export const useSyncEffectOnce = (effect: (() => CleanupFn) | (() => void)) => { 6 | const ref = useRef(false) 7 | const cleanupRef = useRef<(() => void) | null>(null) 8 | useEffect(() => cleanupRef.current || noop, []) 9 | 10 | if (ref.current) return 11 | cleanupRef.current = effect() || null 12 | ref.current = true 13 | } 14 | -------------------------------------------------------------------------------- /src/constants/env.ts: -------------------------------------------------------------------------------- 1 | import { env } from 'next-runtime-env' 2 | 3 | import { isClientSide, isDev } from '~/lib/env' 4 | 5 | export const API_URL: string = (() => { 6 | if (isDev) return env('NEXT_PUBLIC_API_URL') || '' 7 | 8 | if (isClientSide && env('NEXT_PUBLIC_CLIENT_API_URL')) { 9 | return env('NEXT_PUBLIC_CLIENT_API_URL') || '' 10 | } 11 | 12 | return env('NEXT_PUBLIC_API_URL') || '/api/v2' 13 | })() as string 14 | export const GATEWAY_URL = env('NEXT_PUBLIC_GATEWAY_URL') || '' 15 | -------------------------------------------------------------------------------- /src/styles/image-zoom.css: -------------------------------------------------------------------------------- 1 | /* image-zoom */ 2 | .medium-zoom-overlay { 3 | z-index: 99; 4 | 5 | @apply !bg-zinc-50 dark:!bg-neutral-900; 6 | } 7 | 8 | /* .medium-zoom-overlay + .medium-zoom-image { 9 | } */ 10 | 11 | .medium-zoom-image { 12 | border-radius: 0.5rem; 13 | transition: border-radius 0.3s ease-in-out; 14 | } 15 | .medium-zoom-image.medium-zoom-image--opened { 16 | border-radius: 0; 17 | 18 | z-index: 100; 19 | opacity: 1; 20 | transition: all 0.5s ease-in-out; 21 | } 22 | -------------------------------------------------------------------------------- /src/components/common/ScrollTop.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { usePathname } from 'next/navigation' 4 | import { memo, useEffect } from 'react' 5 | 6 | import { isDev } from '~/lib/env' 7 | import { springScrollToTop } from '~/lib/scroller' 8 | 9 | export const ScrollTop = memo(() => { 10 | const pathname = usePathname() 11 | useEffect(() => { 12 | if (isDev) return 13 | springScrollToTop() 14 | }, [pathname]) 15 | return null 16 | }) 17 | 18 | ScrollTop.displayName = 'ScrollTop' 19 | -------------------------------------------------------------------------------- /src/hooks/common/use-is-active.ts: -------------------------------------------------------------------------------- 1 | import { useSyncExternalStore } from 'react' 2 | 3 | const subscribe = (cb: () => void) => { 4 | document.addEventListener('visibilitychange', cb) 5 | return () => { 6 | document.removeEventListener('visibilitychange', cb) 7 | } 8 | } 9 | 10 | const getSnapshot = () => document.visibilityState === 'visible' 11 | const getServerSnapshot = () => true 12 | export const usePageIsActive = () => 13 | useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) 14 | -------------------------------------------------------------------------------- /src/lib/fonts.ts: -------------------------------------------------------------------------------- 1 | import { Manrope, Noto_Serif_SC } from 'next/font/google' 2 | 3 | const sansFont = Manrope({ 4 | subsets: ['latin'], 5 | weight: ['300', '400', '500'], 6 | variable: '--font-sans', 7 | display: 'swap', 8 | }) 9 | 10 | const serifFont = Noto_Serif_SC({ 11 | subsets: ['latin'], 12 | weight: ['400'], 13 | variable: '--font-serif', 14 | display: 'swap', 15 | // adjustFontFallback: false, 16 | fallback: ['Noto Serif SC'], 17 | }) 18 | 19 | export { sansFont, serifFont } 20 | -------------------------------------------------------------------------------- /storybook/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "include": [ 4 | ".", 5 | "./src" 6 | ], 7 | "exclude": [ 8 | "dist", 9 | "build", 10 | "node_modules" 11 | ], 12 | "compilerOptions": { 13 | "baseUrl": ".", 14 | "paths": { 15 | "~": [ 16 | "../src" 17 | ], 18 | "~/*": [ 19 | "../src/*" 20 | ], 21 | }, 22 | "lib": [ 23 | "dom", 24 | "dom.iterable", 25 | "esnext" 26 | ] 27 | } 28 | } -------------------------------------------------------------------------------- /src/queries/helper.ts: -------------------------------------------------------------------------------- 1 | import type { FetchQueryOptions, QueryKey } from '@tanstack/react-query' 2 | 3 | export const defineQuery = < 4 | TQueryFnData = unknown, 5 | TError = unknown, 6 | TData = TQueryFnData, 7 | TQueryKey extends QueryKey = QueryKey, 8 | >( 9 | options: FetchQueryOptions, 10 | ): Omit< 11 | FetchQueryOptions, 12 | 'queryKey' 13 | > & { queryKey: string[] } => { 14 | return options as any 15 | } 16 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "typescript.enablePromptUseWorkspaceTsdk": true, 4 | "tailwindCSS.experimental.classRegex": [ 5 | [ 6 | "tv\\(([^)]*)\\)", 7 | "[\"'`]([^\"'`]*).*?[\"'`]", 8 | // match variants className 9 | ] 10 | ], 11 | // https://biomejs.dev/reference/vscode/#fix-on-save 12 | "editor.codeActionsOnSave": { 13 | "quickfix.biome": "explicit", 14 | "source.organizeImports.biome": "explicit" 15 | } 16 | } -------------------------------------------------------------------------------- /ci-release-build.sh: -------------------------------------------------------------------------------- 1 | #!env bash 2 | set -e 3 | CWD=$(pwd) 4 | 5 | npm run build 6 | cd .next 7 | pwd 8 | rm -rf cache 9 | cp -r ../public ./standalone/public 10 | 11 | cd ./standalone 12 | echo ';process.title = "Shiro (NextJS)"' >>server.js 13 | mv ../static/ ./.next/static 14 | 15 | cp $CWD/ecosystem.standalone.config.cjs ./ecosystem.config.js 16 | cp $CWD/.env.template .env 17 | 18 | cd .. 19 | 20 | mkdir -p $CWD/assets 21 | rm -rf $CWD/assets/release.zip 22 | zip --symlinks -r $CWD/assets/release.zip ./* 23 | -------------------------------------------------------------------------------- /src/components/modules/dashboard/note-editing/sidebar/DateInput.tsx: -------------------------------------------------------------------------------- 1 | import { SidebarDateInputField } from '../../writing/SidebarDateInputField' 2 | import { useNoteModelSingleFieldAtom } from '../data-provider' 3 | 4 | export const CustomCreatedInput = () => ( 5 | 6 | ) 7 | 8 | export const PublicAtInput = () => ( 9 | 13 | ) 14 | -------------------------------------------------------------------------------- /src/app/api/leetcode/route.ts: -------------------------------------------------------------------------------- 1 | import type { NextRequest } from 'next/server' 2 | import { NextResponse } from 'next/server' 3 | 4 | export const POST = async (req: NextRequest) => { 5 | const requestBody = await req.json() 6 | 7 | const response = await fetch('https://leetcode.cn/graphql/', { 8 | method: 'POST', 9 | headers: { 10 | 'Content-Type': 'application/json', 11 | }, 12 | body: JSON.stringify(requestBody), 13 | }) 14 | 15 | return NextResponse.json(await response.json()) 16 | } 17 | -------------------------------------------------------------------------------- /src/components/modules/comment/CommentBox/constants.ts: -------------------------------------------------------------------------------- 1 | import { sample } from '~/lib/lodash' 2 | 3 | const placeholderCopywrites = [ 4 | '在这里说点什么呢。', 5 | '小可爱,你想说点什么呢?', 6 | '或许此地可以留下足迹', 7 | '你的留言是我前进的动力!', 8 | '说点什么吧,我会好好听的。', 9 | '来一发评论,送你一个小星星!', 10 | '你的评论会让我更加努力哦!', 11 | '留下你的足迹,让我知道你来过。', 12 | '我在这里等你的留言呢!', 13 | '你的评论是我最大的动力!', 14 | '来一发评论,让我知道你的想法吧!', 15 | ] 16 | export const getRandomPlaceholder = () => sample(placeholderCopywrites) 17 | 18 | export const MAX_COMMENT_TEXT_LENGTH = 500 19 | -------------------------------------------------------------------------------- /src/components/ui/image/ZoomedImage.module.css: -------------------------------------------------------------------------------- 1 | .error, 2 | .loading { 3 | @apply opacity-0; 4 | } 5 | 6 | .loaded { 7 | animation: imageLoad 500ms ease-in-out forwards; 8 | } 9 | 10 | @keyframes imageLoad { 11 | 0% { 12 | mask: linear-gradient(90deg, #000 25%, #000000e6 50%, #00000000) 150% 0 / 13 | 400% no-repeat; 14 | opacity: 0.2; 15 | } 16 | 100% { 17 | mask: linear-gradient(90deg, #000 25%, #000000e6 50%, #00000000) 0 / 400% 18 | no-repeat; 19 | opacity: 1; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/constants/keys.ts: -------------------------------------------------------------------------------- 1 | export const enum EmitKeyMap { 2 | EditDataUpdate = 'editDataUpdate', 3 | 4 | Publish = 'Publish', 5 | Refetch = 'Refetch', 6 | 7 | SocketConnected = 'SocketConnected', 8 | SocketDisconnected = 'SocketDisconnected', 9 | } 10 | 11 | export const CacheKeyMap = { 12 | RootData: 'root-data', 13 | AggregateTop: 'aggregate-top', 14 | PostListWithPage: (current: number) => CacheKeyMap.PostList + current, 15 | PostList: 'post-list:', 16 | Post: (id: string) => `post-${id}`, 17 | } 18 | -------------------------------------------------------------------------------- /src/components/modules/dashboard/crossbell/XLogEnabled.tsx: -------------------------------------------------------------------------------- 1 | import { lazy, Suspense, useMemo } from 'react' 2 | 3 | export const XLogEnable = () => 4 | 'ethereum' in globalThis ? : null 5 | 6 | const XlogSwitchLazy = () => { 7 | const Component = useMemo( 8 | () => 9 | lazy(() => 10 | import('./XlogSwitch').then((mo) => ({ default: mo.XlogSwitch })), 11 | ), 12 | [], 13 | ) 14 | return ( 15 | 16 | 17 | 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /src/components/ui/modal/stacked/types.tsx: -------------------------------------------------------------------------------- 1 | import type { FC, PropsWithChildren, ReactNode } from 'react' 2 | 3 | import type { ModalContentPropsInternal } from './context' 4 | 5 | export interface ModalProps { 6 | title: ReactNode 7 | CustomModalComponent?: FC 8 | content: FC 9 | clickOutsideToDismiss?: boolean 10 | modalClassName?: string 11 | modalContainerClassName?: string 12 | 13 | max?: boolean 14 | 15 | wrapper?: FC 16 | 17 | overlay?: boolean 18 | } 19 | -------------------------------------------------------------------------------- /standalone-bundle.sh: -------------------------------------------------------------------------------- 1 | #!env bash 2 | set -e 3 | CWD=$(pwd) 4 | 5 | cd .next 6 | pwd 7 | rm -rf cache 8 | # cp ../next.config.mjs ./standalone/next.config.mjs 9 | cp -r ../public ./standalone/public 10 | 11 | cd ./standalone 12 | echo ';process.title = "Shiro (NextJS)"' >>server.js 13 | mv ../static/ ./.next/static 14 | 15 | cp $CWD/ecosystem.standalone.config.cjs ./ecosystem.config.js 16 | 17 | cd .. 18 | 19 | mkdir -p $CWD/assets 20 | rm -rf $CWD/assets/release.zip 21 | zip --symlinks -r $CWD/assets/release.zip ./* 22 | -------------------------------------------------------------------------------- /src/components/modules/note/index.ts: -------------------------------------------------------------------------------- 1 | export * from './NoteActionAside' 2 | export * from './NoteBanner' 3 | export * from './NoteBottomTopic' 4 | export * from './NoteFooterNavigation' 5 | export * from './NoteHideIfSecret' 6 | export * from './NoteLeftSidebar' 7 | export * from './NoteMainContainer' 8 | export * from './NoteMetaBar' 9 | export * from './NotePasswordForm' 10 | export * from './NoteTimeline' 11 | export * from './NoteTopicDetail' 12 | export * from './NoteTopicInfo' 13 | export * from './NoteTopicMarkdownRender' 14 | -------------------------------------------------------------------------------- /src/components/ui/portal/provider.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from 'react' 2 | 3 | import { isClientSide } from '~/lib/env' 4 | 5 | export const useRootPortal = () => { 6 | const ctx = useContext(RootPortalContext) 7 | if (!isClientSide) { 8 | return null 9 | } 10 | return ctx.to || document.body 11 | } 12 | 13 | const RootPortalContext = createContext<{ 14 | to?: HTMLElement | undefined 15 | }>({ 16 | to: undefined, 17 | }) 18 | 19 | export const RootPortalProvider = RootPortalContext.Provider 20 | -------------------------------------------------------------------------------- /src/components/icons/fa-hash.tsx: -------------------------------------------------------------------------------- 1 | import type { SVGProps } from 'react' 2 | 3 | export function FeHash(props: SVGProps) { 4 | return ( 5 | 6 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /src/components/ui/modal/stacked/overlay.tsx: -------------------------------------------------------------------------------- 1 | import { m } from 'motion/react' 2 | 3 | import { RootPortal } from '../../portal' 4 | 5 | export const ModalOverlay = ({ zIndex }: { zIndex?: number }) => ( 6 | 7 | 15 | 16 | ) 17 | -------------------------------------------------------------------------------- /src/components/ui/transition/typings.ts: -------------------------------------------------------------------------------- 1 | import type { HTMLMotionProps, m, TargetAndTransition } from 'motion/react' 2 | 3 | export interface BaseTransitionProps 4 | extends Omit, 'ref'> { 5 | duration?: number 6 | 7 | timeout?: { 8 | exit?: number 9 | enter?: number 10 | } 11 | 12 | delay?: number 13 | 14 | animation?: { 15 | enter?: TargetAndTransition['transition'] 16 | exit?: TargetAndTransition['transition'] 17 | } 18 | 19 | lcpOptimization?: boolean 20 | as?: keyof typeof m 21 | } 22 | -------------------------------------------------------------------------------- /storybook/mock-packages/next_navigation/index.js: -------------------------------------------------------------------------------- 1 | export const useRouter = () => { 2 | return { 3 | push(path) { 4 | location.pathname = path 5 | }, 6 | } 7 | } 8 | 9 | export const usePathname = () => location.pathname 10 | 11 | export const useSearchParams = () => { 12 | const params = new URLSearchParams(location.search) 13 | return { 14 | get(key) { 15 | return params.get(key) 16 | }, 17 | } 18 | } 19 | export const notFound = () => {} 20 | export const nav = () => {} 21 | export const redirect = () => {} 22 | -------------------------------------------------------------------------------- /src/app/(app)/(home)/components/types.tsx: -------------------------------------------------------------------------------- 1 | import type { 2 | RecentComment, 3 | RecentLike, 4 | RecentNote, 5 | RecentPost, 6 | RecentRecent, 7 | } from '@mx-space/api-client' 8 | 9 | export type ReactActivityType = 10 | | ({ 11 | bizType: 'comment' 12 | } & RecentComment) 13 | | ({ 14 | bizType: 'note' 15 | } & RecentNote) 16 | | ({ 17 | bizType: 'post' 18 | } & RecentPost) 19 | | ({ 20 | bizType: 'recent' 21 | } & RecentRecent) 22 | | ({ 23 | bizType: 'like' 24 | } & RecentLike) 25 | -------------------------------------------------------------------------------- /src/components/modules/dashboard/writing/PresentComponentFab.tsx: -------------------------------------------------------------------------------- 1 | import type { FC } from 'react' 2 | 3 | import { FABPortable } from '~/components/ui/fab' 4 | import { PresentSheet } from '~/components/ui/sheet' 5 | import { Noop } from '~/lib/noop' 6 | 7 | export const PresentComponentFab: FC<{ 8 | Component: FC 9 | }> = ({ Component }) => ( 10 | 11 | 12 | 13 | 14 | 15 | ) 16 | -------------------------------------------------------------------------------- /src/components/icons/pen.tsx: -------------------------------------------------------------------------------- 1 | import type { SVGProps } from 'react' 2 | 3 | export function MdiFountainPenTip(props: SVGProps) { 4 | return ( 5 | 6 | 10 | 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /src/components/modules/comment/CommentBox/AuthedInputSkeleton.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import clsx from 'clsx' 4 | 5 | export const CommentBoxAuthedInputSkeleton = () => { 6 | const color = 'bg-gray-200/50 dark:bg-zinc-800/50' 7 | return ( 8 |
9 |
15 |
16 |
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /src/providers/root/debug-provider.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { ReactQueryDevtools } from '@tanstack/react-query-devtools' 4 | import type { PropsWithChildren, ReactElement } from 'react' 5 | import { Suspense } from 'react' 6 | 7 | export const DebugProvider = ({ 8 | children, 9 | }: PropsWithChildren): ReactElement => ( 10 | <> 11 | 12 |
13 | 14 |
15 |
16 | {children} 17 | 18 | ) 19 | -------------------------------------------------------------------------------- /src/components/layout/container/Normal.tsx: -------------------------------------------------------------------------------- 1 | import { clsxm } from '~/lib/helper' 2 | 3 | import { HeaderHideBg } from '../header/hooks' 4 | 5 | export const NormalContainer: Component = (props) => { 6 | const { children, className } = props 7 | 8 | return ( 9 |
16 | {children} 17 | 18 | 19 |
20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /src/components/layout/root/Root.tsx: -------------------------------------------------------------------------------- 1 | import { ClientOnly } from '~/components/common/ClientOnly' 2 | import { FABContainer } from '~/components/ui/fab' 3 | 4 | import { Content } from '../content/Content' 5 | import { Footer } from '../footer' 6 | import { Header } from '../header' 7 | 8 | export const Root: Component = ({ children }) => { 9 | return ( 10 | <> 11 |
12 | {children} 13 | 14 |