├── .nvmrc ├── apps ├── mobile │ ├── web-app │ │ ├── html-renderer │ │ │ ├── src │ │ │ │ ├── index.css │ │ │ │ ├── components │ │ │ │ │ ├── shiki │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── hooks.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── __internal │ │ │ │ │ │ └── ctx.ts │ │ │ │ │ └── p.tsx │ │ │ │ ├── utils.ts │ │ │ │ ├── common │ │ │ │ │ └── ProviderComposer.tsx │ │ │ │ └── atoms │ │ │ │ │ └── index.ts │ │ │ ├── postcss.config.cjs │ │ │ ├── index.html │ │ │ ├── types │ │ │ │ └── index.ts │ │ │ ├── tailwind.config.ts │ │ │ └── global.d.ts │ │ └── package.json │ ├── src │ │ ├── components │ │ │ ├── ui │ │ │ │ ├── slider │ │ │ │ │ └── index.tsx │ │ │ │ ├── pressable │ │ │ │ │ ├── enum.ts │ │ │ │ │ ├── NativePressable.types.tsx │ │ │ │ │ ├── IosItemPressable.tsx │ │ │ │ │ ├── NativePressable.ios.tsx │ │ │ │ │ ├── IosItemPressable.ios.tsx │ │ │ │ │ └── NativePressable.tsx │ │ │ │ ├── modal │ │ │ │ │ └── imperative-modal │ │ │ │ │ │ └── index.tsx │ │ │ │ ├── tabview │ │ │ │ │ └── types.ts │ │ │ │ ├── grouped │ │ │ │ │ ├── GroupedInsetListCardItemStyle.tsx │ │ │ │ │ └── constants.ts │ │ │ │ ├── typography │ │ │ │ │ └── MarkdownNative.tsx │ │ │ │ ├── toast │ │ │ │ │ ├── constants.ts │ │ │ │ │ └── ctx.tsx │ │ │ │ ├── qrcode │ │ │ │ │ └── constants.ts │ │ │ │ └── overlay │ │ │ │ │ └── Overlay.tsx │ │ │ ├── common │ │ │ │ ├── FullWindowOverlay.tsx │ │ │ │ └── FullWindowOverlay.ios.tsx │ │ │ ├── native │ │ │ │ └── webview │ │ │ │ │ ├── atom.ts │ │ │ │ │ └── native-webview.tsx │ │ │ └── layouts │ │ │ │ ├── tabbar │ │ │ │ ├── ReactNativeTab.tsx │ │ │ │ └── contexts │ │ │ │ │ ├── BottomTabBarHeightContext.tsx │ │ │ │ │ ├── BottomTabBarVisibleContext.tsx │ │ │ │ │ └── BottomTabBarBackgroundContext.tsx │ │ │ │ ├── views │ │ │ │ └── NavigationHeaderContext.tsx │ │ │ │ └── header │ │ │ │ └── hooks.ts │ │ ├── constants │ │ │ ├── ui.ts │ │ │ └── spring.ts │ │ ├── lib │ │ │ ├── event-bus.ts │ │ │ ├── navigation │ │ │ │ ├── readme.md │ │ │ │ ├── config.ts │ │ │ │ ├── NavigationInstanceContext.ts │ │ │ │ ├── GroupedNavigationRouteContext.ts │ │ │ │ ├── bottom-tab │ │ │ │ │ └── TabScreenContext.tsx │ │ │ │ └── ScreenNameContext.tsx │ │ │ ├── url-builder.ts │ │ │ ├── op.ts │ │ │ └── native │ │ │ │ └── user-agent.ts │ │ ├── modules │ │ │ ├── subscription │ │ │ │ ├── constants.ts │ │ │ │ ├── items │ │ │ │ │ └── types.tsx │ │ │ │ └── ctx.ts │ │ │ ├── entry-list │ │ │ │ ├── types.ts │ │ │ │ └── EntryListContext.tsx │ │ │ ├── screen │ │ │ │ └── PagerListContext.ts │ │ │ ├── settings │ │ │ │ └── routes │ │ │ │ │ ├── Feeds.tsx │ │ │ │ │ ├── Achievement.tsx │ │ │ │ │ ├── Notifications.tsx │ │ │ │ │ └── navigateToPlanScreen.ts │ │ │ └── discover │ │ │ │ └── DiscoverContent.tsx │ │ ├── global.css │ │ ├── theme │ │ │ ├── web.ts │ │ │ ├── colors.ts │ │ │ └── utils.ts │ │ ├── interfaces │ │ │ └── settings │ │ │ │ └── data.ts │ │ ├── polyfill │ │ │ └── index.ts │ │ ├── screens │ │ │ └── (modal) │ │ │ │ └── onboarding │ │ │ │ └── EditProfileScreen.tsx │ │ ├── database │ │ │ └── index.ts │ │ ├── initialize │ │ │ ├── device.ts │ │ │ └── dayjs.ts │ │ ├── store │ │ │ └── image │ │ │ │ └── hooks.ts │ │ └── atoms │ │ │ └── hooks │ │ │ └── useDeviceType.ts │ ├── .env.example │ ├── native │ │ ├── android │ │ │ └── src │ │ │ │ └── main │ │ │ │ ├── AndroidManifest.xml │ │ │ │ └── java │ │ │ │ └── expo │ │ │ │ └── modules │ │ │ │ └── follownative │ │ │ │ └── tabbar │ │ │ │ ├── TabScreenView.kt │ │ │ │ ├── TabBarPortalView.kt │ │ │ │ ├── TabScreenModule.kt │ │ │ │ └── TabBarPortalModule.kt │ │ ├── .eslintrc.js │ │ ├── ios │ │ │ ├── Packages │ │ │ │ └── ImageViewer_swift │ │ │ │ │ ├── ImageItem.swift │ │ │ │ │ ├── ImageViewerOption.swift │ │ │ │ │ ├── ImageCarouselViewControllerProtocol.swift │ │ │ │ │ └── SimpleImageDatasource.swift │ │ │ ├── Controllers │ │ │ │ └── RNSViewController.swift │ │ │ ├── Extensions │ │ │ │ └── UIImage.swift │ │ │ └── Modules │ │ │ │ ├── TabBar │ │ │ │ ├── TabBarPortalModule.swift │ │ │ │ └── TabScreenView.swift │ │ │ │ └── PagerView │ │ │ │ └── EnhancePageViewModule.swift │ │ └── .npmignore │ ├── assets │ │ ├── icon.png │ │ ├── icon-dev.png │ │ ├── icon-staging.png │ │ ├── splash-icon.png │ │ ├── adaptive-icon.png │ │ └── font │ │ │ └── sn-pro │ │ │ ├── SNPro-Bold.otf │ │ │ ├── SNPro-Book.otf │ │ │ ├── SNPro-Thin.otf │ │ │ ├── SNPro-Black.otf │ │ │ ├── SNPro-Heavy.otf │ │ │ ├── SNPro-Light.otf │ │ │ ├── SNPro-Medium.otf │ │ │ ├── SNPro-Regular.otf │ │ │ ├── SNPro-Semibold.otf │ │ │ ├── SNPro-BlackItalic.otf │ │ │ ├── SNPro-BoldItalic.otf │ │ │ ├── SNPro-BookItalic.otf │ │ │ ├── SNPro-HeavyItalic.otf │ │ │ ├── SNPro-LightItalic.otf │ │ │ ├── SNPro-ThinItalic.otf │ │ │ ├── SNPro-MediumItalic.otf │ │ │ ├── SNPro-RegularItalic.otf │ │ │ └── SNPro-SemiboldItalic.otf │ ├── changelog │ │ ├── 0.2.6.md │ │ ├── 0.2.8.md │ │ ├── next.md │ │ ├── next.template.md │ │ └── 0.2.5.md │ ├── .watchmanconfig │ ├── ios │ │ ├── Assets.xcassets │ │ │ ├── Contents.json │ │ │ ├── home_5_cute_fi.imageset │ │ │ │ ├── home_5_cute_fi.pdf │ │ │ │ └── Contents.json │ │ │ ├── home_5_cute_re.imageset │ │ │ │ ├── home_5_cute_re.pdf │ │ │ │ └── Contents.json │ │ │ ├── search_3_cute_fi.imageset │ │ │ │ ├── search_3_cute_fi.pdf │ │ │ │ └── Contents.json │ │ │ ├── search_3_cute_re.imageset │ │ │ │ ├── search_3_cute_re.pdf │ │ │ │ └── Contents.json │ │ │ ├── settings_1_cute_fi.imageset │ │ │ │ ├── settings_1_cute_fi.pdf │ │ │ │ └── Contents.json │ │ │ ├── settings_1_cute_re.imageset │ │ │ │ ├── settings_1_cute_re.pdf │ │ │ │ └── Contents.json │ │ │ ├── black_board_2_cute_fi.imageset │ │ │ │ ├── black_board_2_cute_fi.pdf │ │ │ │ └── Contents.json │ │ │ └── black_board_2_cute_re.imageset │ │ │ │ ├── black_board_2_cute_re.pdf │ │ │ │ └── Contents.json │ │ ├── Folo │ │ │ ├── Images.xcassets │ │ │ │ ├── Contents.json │ │ │ │ ├── AppIcon.appiconset │ │ │ │ │ ├── App-Icon-1024x1024@1x.png │ │ │ │ │ └── Contents.json │ │ │ │ └── SplashScreenBackground.colorset │ │ │ │ │ └── Contents.json │ │ │ ├── Folo-Bridging-Header.h │ │ │ └── Folo.entitlements │ │ ├── sentry.properties │ │ ├── Podfile.properties.json │ │ ├── Folo.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── .gitignore │ ├── shim-env.d.ts │ ├── postcss.config.js │ ├── nativewind-env.d.ts │ ├── tailwind.dom.config.ts │ ├── global.d.ts │ ├── plugins │ │ └── network_security_config.xml │ └── babel.config.js ├── ssr │ ├── src │ │ ├── global.d.ts │ │ ├── lib │ │ │ ├── load-env.ts │ │ │ └── not-found.ts │ │ └── meta-handler.map.ts │ ├── client │ │ ├── lib │ │ │ ├── store.ts │ │ │ └── url-builder.ts │ │ ├── configs.ts │ │ ├── pages │ │ │ ├── (main) │ │ │ │ ├── index.tsx │ │ │ │ └── layout.tsx │ │ │ └── (login) │ │ │ │ └── login │ │ │ │ └── index.tsx │ │ ├── App.tsx │ │ ├── atoms │ │ │ ├── server-configs.ts │ │ │ └── user.ts │ │ ├── @types │ │ │ └── i18next.d.ts │ │ ├── initialize │ │ │ ├── op.ts │ │ │ ├── index.ts │ │ │ ├── helper.ts │ │ │ └── analytics.ts │ │ └── global.d.ts │ ├── public │ │ ├── favicon.ico │ │ ├── icon-192x192.png │ │ ├── icon-512x512.png │ │ ├── manifest.json │ │ └── icon.svg │ ├── .env.example │ ├── vercel.json │ ├── postcss.config.cjs │ ├── global.ts │ ├── api │ │ └── index.ts │ ├── scripts │ │ └── cleanup-vercel-build.ts │ └── note.md └── desktop │ ├── layer │ ├── renderer │ │ ├── src │ │ │ ├── sw.ts │ │ │ ├── queries │ │ │ │ ├── index.ts │ │ │ │ ├── types.d.ts │ │ │ │ ├── settings.ts │ │ │ │ ├── _.ts │ │ │ │ └── server-configs.ts │ │ │ ├── components │ │ │ │ ├── ui │ │ │ │ │ ├── paper │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── fab │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── modal │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── stacked │ │ │ │ │ │ │ ├── atom.ts │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ └── bus.ts │ │ │ │ │ │ └── inspire │ │ │ │ │ │ │ └── InPeekModal.tsx │ │ │ │ │ ├── background │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── code-highlighter │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── shiki │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── auto-completion │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── keyboard-recorder │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── ai-summary-card │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── media │ │ │ │ │ │ ├── MediaInfoRecord.tsx │ │ │ │ │ │ ├── MediaContainerWidthContext.tsx │ │ │ │ │ │ ├── MediaInfoRecordContext.tsx │ │ │ │ │ │ └── MediaContainerWidthProvider.tsx │ │ │ │ │ ├── markdown │ │ │ │ │ │ ├── renderers │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ └── ctx.tsx │ │ │ │ │ │ └── types.ts │ │ │ │ │ └── hover-preview │ │ │ │ │ │ └── index.ts │ │ │ │ ├── common │ │ │ │ │ ├── Fragment.tsx │ │ │ │ │ └── ProviderComposer.tsx │ │ │ │ └── errors │ │ │ │ │ └── enum.ts │ │ │ ├── modules │ │ │ │ ├── discover │ │ │ │ │ ├── Inbox │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── ai-task │ │ │ │ │ ├── index.ts │ │ │ │ │ └── components │ │ │ │ │ │ └── index.ts │ │ │ │ ├── settings │ │ │ │ │ ├── tabs │ │ │ │ │ │ └── ai │ │ │ │ │ │ │ ├── byok │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ └── constants.ts │ │ │ │ │ │ │ ├── memory │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ ├── tasks │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ ├── usage │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ ├── components │ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ │ └── charts │ │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ └── types.ts │ │ │ │ │ │ │ ├── shortcuts │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── context.tsx │ │ │ │ │ ├── modal │ │ │ │ │ │ └── use-setting-modal-hack.ts │ │ │ │ │ └── constants.ts │ │ │ │ ├── app-layout │ │ │ │ │ ├── subview │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── subscription-column │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── ai-enhanced-timeline │ │ │ │ │ │ └── index.tsx │ │ │ │ ├── claim │ │ │ │ │ └── index.ts │ │ │ │ ├── power │ │ │ │ │ └── transaction-section │ │ │ │ │ │ ├── tx-table │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── index.ts │ │ │ │ ├── profile │ │ │ │ │ └── user-profile-modal │ │ │ │ │ │ └── index.ts │ │ │ │ ├── ai-chat │ │ │ │ │ ├── components │ │ │ │ │ │ ├── context-bar │ │ │ │ │ │ │ ├── menus │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ └── blocks │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── welcome │ │ │ │ │ │ │ ├── DefaultWelcomeContent.tsx │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── displays │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ └── shared │ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ │ └── EmptyState.tsx │ │ │ │ │ │ ├── message │ │ │ │ │ │ │ ├── animated │ │ │ │ │ │ │ │ └── constants.ts │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── layouts │ │ │ │ │ │ │ └── shared │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── editor │ │ │ │ │ │ ├── plugins │ │ │ │ │ │ │ ├── shared │ │ │ │ │ │ │ │ └── components │ │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ │ ├── file-upload │ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ │ └── types.ts │ │ │ │ │ │ │ ├── mention │ │ │ │ │ │ │ │ └── hooks │ │ │ │ │ │ │ │ │ └── dateMentionSearch.ts │ │ │ │ │ │ │ ├── shortcut │ │ │ │ │ │ │ │ ├── utils │ │ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ │ │ └── positioning.ts │ │ │ │ │ │ │ │ └── constants.ts │ │ │ │ │ │ │ └── selection │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── constants │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── store │ │ │ │ │ │ ├── slices │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── event-system │ │ │ │ │ │ │ └── types.ts │ │ │ │ │ ├── hooks │ │ │ │ │ │ └── useAIConfiguration.ts │ │ │ │ │ └── types │ │ │ │ │ │ └── ChatSession.ts │ │ │ │ ├── entry-content │ │ │ │ │ └── components │ │ │ │ │ │ ├── entry-content │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── types.tsx │ │ │ │ │ │ ├── entry-read-history │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── entry-header │ │ │ │ │ │ ├── types.tsx │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── layouts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── types.ts │ │ │ │ │ │ └── shared │ │ │ │ │ │ └── index.ts │ │ │ │ ├── upgrade │ │ │ │ │ └── lazy │ │ │ │ │ │ ├── index.electron.ts │ │ │ │ │ │ └── index.ts │ │ │ │ ├── subscription-column │ │ │ │ │ ├── subscription-list │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── hook.ts │ │ │ │ │ ├── sort-by │ │ │ │ │ │ └── types.tsx │ │ │ │ │ ├── context.ts │ │ │ │ │ └── styles.ts │ │ │ │ ├── ai-chat-session │ │ │ │ │ └── index.ts │ │ │ │ ├── entry-column │ │ │ │ │ ├── components │ │ │ │ │ │ └── entry-column-wrapper │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ └── types.tsx │ │ │ │ │ ├── atoms │ │ │ │ │ │ ├── ai-timeline.ts │ │ │ │ │ │ └── social-media-content-width.ts │ │ │ │ │ ├── star-icon.tsx │ │ │ │ │ ├── Items │ │ │ │ │ │ └── list-item.tsx │ │ │ │ │ ├── styles.ts │ │ │ │ │ └── layouts │ │ │ │ │ │ └── AppendTaildingDivider.tsx │ │ │ │ ├── app-tip │ │ │ │ │ ├── index.ts │ │ │ │ │ └── constants.ts │ │ │ │ ├── renderer │ │ │ │ │ └── types.ts │ │ │ │ ├── action │ │ │ │ │ └── utils.ts │ │ │ │ └── user │ │ │ │ │ └── utils.ts │ │ │ ├── lib │ │ │ │ ├── jotai.ts │ │ │ │ ├── app.ts │ │ │ │ ├── client.ts │ │ │ │ ├── log.ts │ │ │ │ └── parsers.ts │ │ │ ├── pages │ │ │ │ ├── (main) │ │ │ │ │ ├── (layer) │ │ │ │ │ │ ├── timeline │ │ │ │ │ │ │ └── [timelineId] │ │ │ │ │ │ │ │ └── [feedId] │ │ │ │ │ │ │ │ ├── [entryId] │ │ │ │ │ │ │ │ └── index.tsx │ │ │ │ │ │ │ │ └── layout.tsx │ │ │ │ │ │ └── (subview) │ │ │ │ │ │ │ └── layout.tsx │ │ │ │ │ └── layout.tsx │ │ │ │ └── settings │ │ │ │ │ ├── layout.tsx │ │ │ │ │ └── index.tsx │ │ │ ├── constants │ │ │ │ ├── env.ts │ │ │ │ ├── index.ts │ │ │ │ ├── ui.ts │ │ │ │ ├── copy.ts │ │ │ │ └── dom.ts │ │ │ ├── assets │ │ │ │ ├── rsshub-icon.png │ │ │ │ └── rsshub.svg │ │ │ ├── atoms │ │ │ │ ├── lang.ts │ │ │ │ ├── preview.ts │ │ │ │ ├── user.ts │ │ │ │ └── dom.ts │ │ │ ├── store │ │ │ │ └── search │ │ │ │ │ ├── helper.ts │ │ │ │ │ └── constants.ts │ │ │ ├── wdyr.ts │ │ │ ├── env.d.ts │ │ │ ├── initialize │ │ │ │ ├── migrates │ │ │ │ │ └── helper.ts │ │ │ │ ├── op.ts │ │ │ │ └── helper.ts │ │ │ ├── hooks │ │ │ │ ├── common │ │ │ │ │ └── index.ts │ │ │ │ └── biz │ │ │ │ │ └── useReduceMotion.ts │ │ │ ├── styles │ │ │ │ ├── main.css │ │ │ │ └── cursor.css │ │ │ ├── providers │ │ │ │ ├── hotkey-provider.tsx │ │ │ │ └── user-provider.tsx │ │ │ └── errors │ │ │ │ └── CustomSafeError.ts │ │ ├── public │ │ │ ├── favicon.ico │ │ │ ├── og-image.png │ │ │ ├── favicon-dev.ico │ │ │ ├── pwa-192x192.png │ │ │ ├── pwa-512x512.png │ │ │ ├── pwa-64x64.png │ │ │ ├── icon-192x192.png │ │ │ ├── icon-512x512.png │ │ │ ├── opengraph-image.png │ │ │ ├── maskable-icon-512x512.png │ │ │ ├── apple-touch-icon-180x180.png │ │ │ └── icon.svg │ │ └── setup-file.ts │ └── main │ │ ├── src │ │ ├── index.ts │ │ ├── constants │ │ │ └── system.ts │ │ ├── @types │ │ │ └── constants.ts │ │ ├── bootstrap.ts │ │ ├── lib │ │ │ └── i18n.ts │ │ └── updater │ │ │ └── windows-updater.ts │ │ ├── export.ts │ │ ├── global.d.ts │ │ ├── preload │ │ └── index.d.ts │ │ └── vitest.config.ts │ ├── dev-only │ └── dev-app-update.yml │ ├── build │ ├── dev.pfx │ ├── entitlements.mac.plist │ └── entitlements.mas.child.plist │ ├── changelog │ ├── 0.6.0.md │ ├── 0.2.9.md │ ├── 0.3.10.md │ ├── 0.3.11.md │ ├── 0.3.12.md │ ├── 0.3.9.md │ ├── 0.3.5.md │ ├── 0.2.8.md │ ├── 0.3.8.md │ ├── next.md │ ├── next.template.md │ ├── 0.3.13.md │ ├── 0.3.7.md │ ├── 0.2.2.md │ ├── 0.2.5.md │ ├── 0.3.6.md │ ├── 0.3.3.md │ ├── 0.2.3.md │ ├── 0.2.4.md │ ├── 0.2.6.md │ ├── 0.7.0.md │ ├── 0.3.2.md │ ├── 0.8.0.md │ ├── 0.9.0.md │ └── 0.2.1.md │ ├── resources │ ├── app-update.yml │ ├── icon.icns │ ├── icon.ico │ ├── icon.png │ ├── icon-dev.png │ ├── icon-tray.ico │ ├── icon-tray.png │ ├── icon-staging.icns │ ├── icon-staging.ico │ ├── icon-staging.png │ └── icon-tray-staging.ico │ ├── static │ ├── dmg-icon.icns │ ├── dmg-background.png │ ├── dmg-background@2x.png │ └── appx │ │ ├── SampleAppx.44x44.png │ │ ├── SampleAppx.50x50.png │ │ ├── SampleAppx.150x150.png │ │ └── SampleAppx.310x150.png │ ├── postcss.config.cjs │ ├── .env.example │ └── vite.config.electron-render.ts ├── locales └── lang │ ├── ja.json │ ├── en.json │ ├── zh-CN.json │ └── zh-TW.json ├── packages ├── internal │ ├── models │ │ ├── src │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── components │ │ ├── src │ │ │ ├── ui │ │ │ │ ├── sheet │ │ │ │ │ ├── index.ts │ │ │ │ │ └── context.tsx │ │ │ │ ├── collapse │ │ │ │ │ └── index.ts │ │ │ │ ├── link │ │ │ │ │ └── index.ts │ │ │ │ ├── context-menu │ │ │ │ │ └── index.ts │ │ │ │ ├── typography │ │ │ │ │ └── index.ts │ │ │ │ ├── scroll-area │ │ │ │ │ ├── index.ts │ │ │ │ │ └── ctx.ts │ │ │ │ ├── divider │ │ │ │ │ └── index.ts │ │ │ │ ├── radio-group │ │ │ │ │ └── index.ts │ │ │ │ ├── z-index │ │ │ │ │ ├── ctx.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── input │ │ │ │ │ └── index.ts │ │ │ │ ├── skeleton │ │ │ │ │ └── index.tsx │ │ │ │ ├── portal │ │ │ │ │ ├── provider.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── segment │ │ │ │ │ └── ctx.tsx │ │ │ │ ├── lexical-rich-editor │ │ │ │ │ ├── plugins │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── katex │ │ │ │ │ └── lazy.tsx │ │ │ │ ├── platform-icon │ │ │ │ │ ├── icons.ts │ │ │ │ │ └── collections │ │ │ │ │ │ └── zotero.tsx │ │ │ │ └── navigation-menu │ │ │ │ │ └── style.ts │ │ │ ├── common │ │ │ │ ├── Focusable │ │ │ │ │ └── index.ts │ │ │ │ └── Fragment.ts │ │ │ ├── atoms │ │ │ │ └── mouse.ts │ │ │ ├── hooks │ │ │ │ ├── useMedia.ts │ │ │ │ ├── useMobile.ts │ │ │ │ └── useMouse.ts │ │ │ └── icons │ │ │ │ └── resize.tsx │ │ ├── assets │ │ │ └── index.css │ │ └── tsconfig.json │ ├── utils │ │ ├── src │ │ │ ├── data-structure │ │ │ │ └── index.ts │ │ │ ├── noop.ts │ │ │ ├── ns.ts │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── database │ │ ├── src │ │ │ ├── constant.ts │ │ │ ├── drizzle │ │ │ │ ├── 0012_magenta_thing.sql │ │ │ │ ├── 0001_bored_hobgoblin.sql │ │ │ │ ├── 0007_curvy_tarantula.sql │ │ │ │ ├── 0014_chemical_shocker.sql │ │ │ │ ├── 0016_curious_carnage.sql │ │ │ │ ├── 0015_colorful_warbird.sql │ │ │ │ ├── 0019_wonderful_shape.sql │ │ │ │ ├── 0031_kind_ikaris.sql │ │ │ │ ├── 0032_orange_prima.sql │ │ │ │ ├── 0034_curly_darkstar.sql │ │ │ │ ├── 0036_entry_tag_summary.sql │ │ │ │ ├── 0022_tiny_northstar.sql │ │ │ │ ├── 0002_smart_power_man.sql │ │ │ │ ├── 0020_little_marauders.sql │ │ │ │ ├── 0009_lucky_power_man.sql │ │ │ │ ├── 0008_last_the_santerians.sql │ │ │ │ ├── 0011_mysterious_stark_industries.sql │ │ │ │ ├── 0026_numerous_slyde.sql │ │ │ │ ├── 0006_exotic_kid_colt.sql │ │ │ │ ├── 0004_majestic_thunderbolt_ross.sql │ │ │ │ ├── 0023_pink_namor.sql │ │ │ │ ├── 0018_dashing_the_fury.sql │ │ │ │ └── 0005_tense_sleepwalker.sql │ │ │ ├── services │ │ │ │ └── internal │ │ │ │ │ └── base.ts │ │ │ └── types.ts │ │ ├── tsconfig.json │ │ └── drizzle.config.ts │ ├── logger │ │ ├── electron.ts │ │ ├── web.ts │ │ ├── tsconfig.json │ │ └── package.json │ ├── shared │ │ └── src │ │ │ ├── index.ts │ │ │ └── global.d.ts │ ├── store │ │ ├── src │ │ │ ├── modules │ │ │ │ ├── summary │ │ │ │ │ ├── enum.ts │ │ │ │ │ ├── getters.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── collection │ │ │ │ │ ├── types.ts │ │ │ │ │ └── getter.ts │ │ │ │ ├── user │ │ │ │ │ ├── constants.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── getters.ts │ │ │ │ ├── inbox │ │ │ │ │ ├── getters.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── feed │ │ │ │ │ ├── types.ts │ │ │ │ │ └── selectors.ts │ │ │ │ ├── image │ │ │ │ │ ├── getters.ts │ │ │ │ │ └── hooks.ts │ │ │ │ ├── list │ │ │ │ │ ├── types.ts │ │ │ │ │ └── getters.ts │ │ │ │ ├── translation │ │ │ │ │ └── types.ts │ │ │ │ └── unread │ │ │ │ │ ├── utils.ts │ │ │ │ │ └── types.ts │ │ │ ├── lib │ │ │ │ └── base.ts │ │ │ ├── @types │ │ │ │ ├── default-resource.ts │ │ │ │ └── i18next.d.ts │ │ │ └── types.ts │ │ └── tsconfig.json │ ├── hooks │ │ ├── src │ │ │ ├── useVideo.ts │ │ │ ├── useAnyPointDown.ts │ │ │ ├── useTypescriptHappyCallback.ts │ │ │ ├── usePrevious.ts │ │ │ ├── useOnce.ts │ │ │ ├── optimistic │ │ │ │ └── index.ts │ │ │ └── useRefValue.ts │ │ └── tsconfig.json │ ├── tracker │ │ ├── tsconfig.json │ │ ├── src │ │ │ ├── adapters │ │ │ │ └── index.ts │ │ │ └── types.ts │ │ └── package.json │ ├── constants │ │ ├── src │ │ │ ├── index.ts │ │ │ └── app.ts │ │ ├── tsconfig.json │ │ └── package.json │ ├── types │ │ ├── package.json │ │ └── vite-env.d.ts │ └── atoms │ │ └── tsconfig.json └── readability │ ├── tsdown.config.ts │ ├── tsconfig.json │ └── bump.config.ts ├── .npmrc ├── .gitattributes ├── .cursorignore ├── .github ├── copilot-instructions.md ├── advanced-issue-labeler.yml └── ISSUE_TEMPLATE │ └── config.yml ├── vitest.workspace.js ├── vitest.workspace.ts ├── .editorconfig ├── changelogithub.config.ts ├── .vscode └── extensions.json ├── scripts ├── mitproxy.py ├── lib.ts └── skip-main-app-vercel-build.sh ├── .prettierrc.mjs ├── tsslint.config.ts ├── SECURITY.md ├── tsconfig.json ├── .prettierignore ├── icons └── mgc │ ├── left_small_sharp.svg │ ├── right_small_sharp.svg │ ├── loading_3_cute_li.svg │ ├── line_cute_re.svg │ ├── loading_3_cute_re.svg │ ├── attachment_cute_re.svg │ └── music_2_cute_fi.svg └── patches └── jsonpointer.patch /.nvmrc: -------------------------------------------------------------------------------- 1 | stable 2 | -------------------------------------------------------------------------------- /apps/mobile/web-app/html-renderer/src/index.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /locales/lang/ja.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "日本語" 3 | } 4 | -------------------------------------------------------------------------------- /packages/internal/models/src/index.ts: -------------------------------------------------------------------------------- 1 | export {} 2 | -------------------------------------------------------------------------------- /locales/lang/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "English" 3 | } 4 | -------------------------------------------------------------------------------- /locales/lang/zh-CN.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "简体中文" 3 | } 4 | -------------------------------------------------------------------------------- /locales/lang/zh-TW.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "繁體中文" 3 | } 4 | -------------------------------------------------------------------------------- /apps/ssr/src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare const __DEV__: boolean 2 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/sw.ts: -------------------------------------------------------------------------------- 1 | import "./workers/sw/index" 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | node-linker=hoisted 3 | save-exact=true 4 | -------------------------------------------------------------------------------- /apps/mobile/src/components/ui/slider/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./Slider" 2 | -------------------------------------------------------------------------------- /packages/internal/components/src/ui/sheet/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Sheet" 2 | -------------------------------------------------------------------------------- /packages/internal/utils/src/data-structure/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./set" 2 | -------------------------------------------------------------------------------- /apps/desktop/dev-only/dev-app-update.yml: -------------------------------------------------------------------------------- 1 | provider: custom 2 | channel: latest 3 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/queries/index.ts: -------------------------------------------------------------------------------- 1 | export * as Queries from "./_" 2 | -------------------------------------------------------------------------------- /apps/mobile/src/constants/ui.ts: -------------------------------------------------------------------------------- 1 | export const TIMELINE_VIEW_SELECTOR_HEIGHT = 58 2 | -------------------------------------------------------------------------------- /apps/ssr/client/lib/store.ts: -------------------------------------------------------------------------------- 1 | export { jotaiStore } from "@follow/utils/jotai" 2 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/components/ui/paper/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Paper" 2 | -------------------------------------------------------------------------------- /apps/mobile/.env.example: -------------------------------------------------------------------------------- 1 | EXPO_PUBLIC_APP_CHECK_DEBUG_TOKEN= 2 | SENTRY_AUTH_TOKEN= 3 | -------------------------------------------------------------------------------- /apps/mobile/native/android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /apps/mobile/src/lib/event-bus.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line unicorn/no-empty-file 2 | -------------------------------------------------------------------------------- /apps/mobile/src/modules/subscription/constants.ts: -------------------------------------------------------------------------------- 1 | export const ViewTabHeight = 35 2 | -------------------------------------------------------------------------------- /packages/internal/components/src/ui/collapse/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./CollapseCss" 2 | -------------------------------------------------------------------------------- /packages/internal/components/src/ui/link/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./LinkWithTooltip" 2 | -------------------------------------------------------------------------------- /packages/internal/database/src/constant.ts: -------------------------------------------------------------------------------- 1 | export const SQLITE_DB_NAME = "follow.db" 2 | -------------------------------------------------------------------------------- /packages/internal/logger/electron.ts: -------------------------------------------------------------------------------- 1 | export { initialize, log } from "electron-log" 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | *.splinecode filter=lfs diff=lfs merge=lfs -text 3 | -------------------------------------------------------------------------------- /apps/desktop/layer/main/src/index.ts: -------------------------------------------------------------------------------- 1 | import "./before-bootstrap" 2 | import "./bootstrap" 3 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/components/ui/fab/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./FABContainer" 2 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/components/ui/modal/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./stacked" 2 | -------------------------------------------------------------------------------- /apps/mobile/web-app/html-renderer/src/components/shiki/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Shiki" 2 | -------------------------------------------------------------------------------- /packages/internal/components/src/ui/context-menu/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./context-menu" 2 | -------------------------------------------------------------------------------- /apps/mobile/src/global.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /apps/mobile/src/theme/web.ts: -------------------------------------------------------------------------------- 1 | export { useCSSInjection } from "react-native-uikit-colors/web" 2 | -------------------------------------------------------------------------------- /packages/internal/components/src/ui/typography/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./EllipsisWithTooltip" 2 | -------------------------------------------------------------------------------- /.cursorignore: -------------------------------------------------------------------------------- 1 | # Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv) 2 | -------------------------------------------------------------------------------- /apps/desktop/build/dev.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/desktop/build/dev.pfx -------------------------------------------------------------------------------- /apps/desktop/changelog/0.6.0.md: -------------------------------------------------------------------------------- 1 | # What's New in v0.6.0 2 | 3 | This version has been withdrawn. 4 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/components/ui/background/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./WindowUnderBlur" 2 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/components/ui/code-highlighter/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./shiki" 2 | -------------------------------------------------------------------------------- /apps/mobile/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/assets/icon.png -------------------------------------------------------------------------------- /apps/ssr/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/ssr/public/favicon.ico -------------------------------------------------------------------------------- /packages/internal/components/src/ui/scroll-area/index.ts: -------------------------------------------------------------------------------- 1 | export * as ScrollArea from "./ScrollArea" 2 | -------------------------------------------------------------------------------- /packages/internal/database/src/drizzle/0012_magenta_thing.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `entries` ADD `sources` text; -------------------------------------------------------------------------------- /packages/internal/shared/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./constants" 2 | export * from "./language" 3 | -------------------------------------------------------------------------------- /.github/copilot-instructions.md: -------------------------------------------------------------------------------- 1 | Before you start, you need to read and follow the rules in @../CLAUDE.md 2 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/components/ui/auto-completion/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./AutoCompletion" 2 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/components/ui/code-highlighter/shiki/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Shiki" 2 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/components/ui/keyboard-recorder/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./KeyRecorder" 2 | -------------------------------------------------------------------------------- /apps/mobile/changelog/0.2.6.md: -------------------------------------------------------------------------------- 1 | # What's New in v0.2.6 2 | 3 | Fixed several bugs, improved stability. 4 | -------------------------------------------------------------------------------- /packages/internal/database/src/drizzle/0001_bored_hobgoblin.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `inboxes` DROP COLUMN `user_id`; -------------------------------------------------------------------------------- /packages/internal/database/src/drizzle/0007_curvy_tarantula.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `lists` ADD `entry_ids` text; -------------------------------------------------------------------------------- /packages/internal/database/src/drizzle/0014_chemical_shocker.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `lists` DROP COLUMN `entry_ids`; -------------------------------------------------------------------------------- /packages/internal/database/src/drizzle/0016_curious_carnage.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `entries` ADD `settings` text; -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/discover/Inbox/index.ts: -------------------------------------------------------------------------------- 1 | export { InboxTable } from "./InboxTable" 2 | -------------------------------------------------------------------------------- /apps/desktop/resources/app-update.yml: -------------------------------------------------------------------------------- 1 | owner: RSSNext 2 | repo: follow 3 | provider: custom 4 | private: false 5 | -------------------------------------------------------------------------------- /apps/desktop/resources/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/desktop/resources/icon.icns -------------------------------------------------------------------------------- /apps/desktop/resources/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/desktop/resources/icon.ico -------------------------------------------------------------------------------- /apps/desktop/resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/desktop/resources/icon.png -------------------------------------------------------------------------------- /apps/mobile/assets/icon-dev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/assets/icon-dev.png -------------------------------------------------------------------------------- /apps/mobile/src/components/common/FullWindowOverlay.tsx: -------------------------------------------------------------------------------- 1 | export { Fragment as FullWindowOverlay } from "react" 2 | -------------------------------------------------------------------------------- /apps/ssr/public/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/ssr/public/icon-192x192.png -------------------------------------------------------------------------------- /apps/ssr/public/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/ssr/public/icon-512x512.png -------------------------------------------------------------------------------- /packages/internal/database/src/drizzle/0015_colorful_warbird.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `entries` ADD `source_content` text; -------------------------------------------------------------------------------- /packages/internal/database/src/drizzle/0019_wonderful_shape.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `users` ADD `email_verified` integer; -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/ai-task/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./components" 2 | export * from "./query" 3 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/settings/tabs/ai/byok/index.ts: -------------------------------------------------------------------------------- 1 | export { ByokSection } from "./ByokSection" 2 | -------------------------------------------------------------------------------- /apps/desktop/resources/icon-dev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/desktop/resources/icon-dev.png -------------------------------------------------------------------------------- /apps/desktop/static/dmg-icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/desktop/static/dmg-icon.icns -------------------------------------------------------------------------------- /apps/mobile/assets/icon-staging.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/assets/icon-staging.png -------------------------------------------------------------------------------- /apps/mobile/assets/splash-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/assets/splash-icon.png -------------------------------------------------------------------------------- /apps/mobile/src/interfaces/settings/data.ts: -------------------------------------------------------------------------------- 1 | export interface DataSettings { 2 | sendAnonymousData: boolean 3 | } 4 | -------------------------------------------------------------------------------- /apps/mobile/src/modules/entry-list/types.ts: -------------------------------------------------------------------------------- 1 | export type EntryExtraData = { 2 | entryIds: string[] | null 3 | } 4 | -------------------------------------------------------------------------------- /packages/internal/components/src/common/Focusable/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Focusable" 2 | export * from "./hooks" 3 | -------------------------------------------------------------------------------- /packages/internal/components/src/ui/divider/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Divider" 2 | export * from "./PanelSplitter" 3 | -------------------------------------------------------------------------------- /packages/internal/database/src/drizzle/0031_kind_ikaris.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `entries` ADD `readability_updated_at` integer; -------------------------------------------------------------------------------- /apps/desktop/layer/main/export.ts: -------------------------------------------------------------------------------- 1 | // Export types for renderer to use 2 | export type { IpcServices } from "./src/ipc" 3 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/app-layout/subview/index.tsx: -------------------------------------------------------------------------------- 1 | export { SubviewLayout } from "./SubviewLayout" 2 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/claim/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./feed-claim-modal" 2 | export * from "./hooks" 3 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/power/transaction-section/tx-table/index.ts: -------------------------------------------------------------------------------- 1 | export { TxTable } from "./TxTable" 2 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/profile/user-profile-modal/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./UserProfileModalContent" 2 | -------------------------------------------------------------------------------- /apps/desktop/resources/icon-tray.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/desktop/resources/icon-tray.ico -------------------------------------------------------------------------------- /apps/desktop/resources/icon-tray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/desktop/resources/icon-tray.png -------------------------------------------------------------------------------- /apps/mobile/.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["node_modules", "dist", ".git", "ios", "android", "native"] 3 | } 4 | -------------------------------------------------------------------------------- /apps/mobile/assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/assets/adaptive-icon.png -------------------------------------------------------------------------------- /apps/mobile/src/components/common/FullWindowOverlay.ios.tsx: -------------------------------------------------------------------------------- 1 | export { FullWindowOverlay } from "react-native-screens" 2 | -------------------------------------------------------------------------------- /apps/mobile/src/theme/colors.ts: -------------------------------------------------------------------------------- 1 | export const accentColor = "#FF5C00" 2 | 3 | export * from "react-native-uikit-colors" 4 | -------------------------------------------------------------------------------- /apps/mobile/src/theme/utils.ts: -------------------------------------------------------------------------------- 1 | export { getCurrentColors, getSystemBackgroundColor } from "react-native-uikit-colors" 2 | -------------------------------------------------------------------------------- /packages/internal/database/src/drizzle/0032_orange_prima.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `subscriptions` ADD `hide_from_timeline` integer; -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/ai-chat/components/context-bar/menus/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./ShortcutsMenuContent" 2 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/ai-chat/editor/plugins/shared/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./MentionLikePill" 2 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/entry-content/components/entry-content/index.ts: -------------------------------------------------------------------------------- 1 | export * from "../../EntryContent" 2 | -------------------------------------------------------------------------------- /apps/desktop/resources/icon-staging.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/desktop/resources/icon-staging.icns -------------------------------------------------------------------------------- /apps/desktop/resources/icon-staging.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/desktop/resources/icon-staging.ico -------------------------------------------------------------------------------- /apps/desktop/resources/icon-staging.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/desktop/resources/icon-staging.png -------------------------------------------------------------------------------- /apps/desktop/static/dmg-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/desktop/static/dmg-background.png -------------------------------------------------------------------------------- /apps/ssr/.env.example: -------------------------------------------------------------------------------- 1 | VITE_WEB_URL=http://localhost:2233 2 | VITE_API_URL=http://localhost:3000 3 | 4 | VITE_EDITOR=cursor 5 | -------------------------------------------------------------------------------- /packages/internal/database/src/drizzle/0034_curly_darkstar.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `ai_chat_messages` DROP COLUMN `rich_text_schema`; -------------------------------------------------------------------------------- /packages/internal/database/src/services/internal/base.ts: -------------------------------------------------------------------------------- 1 | export interface Resetable { 2 | reset: () => Promise 3 | } 4 | -------------------------------------------------------------------------------- /apps/desktop/changelog/0.2.9.md: -------------------------------------------------------------------------------- 1 | # What's new in v0.2.9 2 | 3 | ## New Features 4 | 5 | ## Improvements 6 | 7 | ## Bug Fixes 8 | -------------------------------------------------------------------------------- /apps/desktop/changelog/0.3.10.md: -------------------------------------------------------------------------------- 1 | # What's new in v0.3.10 2 | 3 | ## New Features 4 | 5 | ## Improvements 6 | 7 | ## Bug Fixes 8 | -------------------------------------------------------------------------------- /apps/desktop/changelog/0.3.11.md: -------------------------------------------------------------------------------- 1 | # What's new in v0.3.11 2 | 3 | ## New Features 4 | 5 | ## Improvements 6 | 7 | ## Bug Fixes 8 | -------------------------------------------------------------------------------- /apps/desktop/changelog/0.3.12.md: -------------------------------------------------------------------------------- 1 | # What's new in v0.3.12 2 | 3 | ## New Features 4 | 5 | ## Improvements 6 | 7 | ## Bug Fixes 8 | -------------------------------------------------------------------------------- /apps/desktop/changelog/0.3.9.md: -------------------------------------------------------------------------------- 1 | # What's new in v0.3.9 2 | 3 | ## New Features 4 | 5 | ## Improvements 6 | 7 | ## Bug Fixes 8 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/components/ui/ai-summary-card/index.ts: -------------------------------------------------------------------------------- 1 | export { AISummaryCardBase } from "./AISummaryCardBase" 2 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/settings/tabs/ai/memory/index.ts: -------------------------------------------------------------------------------- 1 | export { UserMemorySection } from "./UserMemorySection" 2 | -------------------------------------------------------------------------------- /apps/desktop/static/dmg-background@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/desktop/static/dmg-background@2x.png -------------------------------------------------------------------------------- /apps/mobile/ios/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /apps/mobile/src/polyfill/index.ts: -------------------------------------------------------------------------------- 1 | // import "core-js/proposals/promise-with-resolvers"; 2 | import "./promise-with-resolvers" 3 | -------------------------------------------------------------------------------- /packages/internal/database/src/drizzle/0036_entry_tag_summary.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `entries` ADD `tags` text;--> statement-breakpoint 2 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/lib/jotai.ts: -------------------------------------------------------------------------------- 1 | export { createAtomAccessor, createAtomHooks, jotaiStore } from "@follow/utils/jotai" 2 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/ai-chat/components/context-bar/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./blocks" 2 | export * from "./menus" 3 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/power/transaction-section/index.ts: -------------------------------------------------------------------------------- 1 | export { TransactionsSection } from "./TransactionsSection" 2 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/pages/(main)/(layer)/timeline/[timelineId]/[feedId]/[entryId]/index.tsx: -------------------------------------------------------------------------------- 1 | export const Component = null 2 | -------------------------------------------------------------------------------- /apps/desktop/resources/icon-tray-staging.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/desktop/resources/icon-tray-staging.ico -------------------------------------------------------------------------------- /apps/desktop/static/appx/SampleAppx.44x44.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/desktop/static/appx/SampleAppx.44x44.png -------------------------------------------------------------------------------- /apps/desktop/static/appx/SampleAppx.50x50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/desktop/static/appx/SampleAppx.50x50.png -------------------------------------------------------------------------------- /apps/mobile/assets/font/sn-pro/SNPro-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/assets/font/sn-pro/SNPro-Bold.otf -------------------------------------------------------------------------------- /apps/mobile/assets/font/sn-pro/SNPro-Book.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/assets/font/sn-pro/SNPro-Book.otf -------------------------------------------------------------------------------- /apps/mobile/assets/font/sn-pro/SNPro-Thin.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/assets/font/sn-pro/SNPro-Thin.otf -------------------------------------------------------------------------------- /apps/mobile/ios/Folo/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "expo" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /apps/mobile/src/components/ui/pressable/enum.ts: -------------------------------------------------------------------------------- 1 | export enum ItemPressableStyle { 2 | UnStyled, 3 | Plain, 4 | Grouped, 5 | } 6 | -------------------------------------------------------------------------------- /apps/mobile/web-app/html-renderer/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./heading" 2 | export * from "./link" 3 | export * from "./p" 4 | -------------------------------------------------------------------------------- /vitest.workspace.js: -------------------------------------------------------------------------------- 1 | import { defineWorkspace } from "vitest/config" 2 | // NOT work 3 | export default defineWorkspace(["apps/*"]) 4 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/desktop/layer/renderer/public/favicon.ico -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/public/og-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/desktop/layer/renderer/public/og-image.png -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/app-layout/subscription-column/index.tsx: -------------------------------------------------------------------------------- 1 | export { MainDestopLayout } from "../MainDestopLayout" 2 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/settings/tabs/ai/tasks/index.ts: -------------------------------------------------------------------------------- 1 | export { TaskSchedulingSection } from "./TaskSchedulingSection" 2 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/upgrade/lazy/index.electron.ts: -------------------------------------------------------------------------------- 1 | export { default as AppNotificationContainer } from "../container" 2 | -------------------------------------------------------------------------------- /apps/desktop/static/appx/SampleAppx.150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/desktop/static/appx/SampleAppx.150x150.png -------------------------------------------------------------------------------- /apps/desktop/static/appx/SampleAppx.310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/desktop/static/appx/SampleAppx.310x150.png -------------------------------------------------------------------------------- /apps/mobile/assets/font/sn-pro/SNPro-Black.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/assets/font/sn-pro/SNPro-Black.otf -------------------------------------------------------------------------------- /apps/mobile/assets/font/sn-pro/SNPro-Heavy.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/assets/font/sn-pro/SNPro-Heavy.otf -------------------------------------------------------------------------------- /apps/mobile/assets/font/sn-pro/SNPro-Light.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/assets/font/sn-pro/SNPro-Light.otf -------------------------------------------------------------------------------- /apps/mobile/assets/font/sn-pro/SNPro-Medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/assets/font/sn-pro/SNPro-Medium.otf -------------------------------------------------------------------------------- /apps/mobile/src/components/ui/modal/imperative-modal/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./modal" 2 | export * as ModalTemplate from "./templates" 3 | -------------------------------------------------------------------------------- /apps/mobile/src/components/ui/tabview/types.ts: -------------------------------------------------------------------------------- 1 | export type Tab = { 2 | name: string 3 | activeColor?: string 4 | value: string 5 | } 6 | -------------------------------------------------------------------------------- /apps/ssr/src/lib/load-env.ts: -------------------------------------------------------------------------------- 1 | import { config } from "dotenv-flow" 2 | 3 | config({ 4 | files: [".env.development.local", ".env"], 5 | }) 6 | -------------------------------------------------------------------------------- /vitest.workspace.ts: -------------------------------------------------------------------------------- 1 | import { defineWorkspace } from "vitest/config" 2 | 3 | // NOT work 4 | export default defineWorkspace(["apps/*"]) 5 | -------------------------------------------------------------------------------- /apps/desktop/layer/main/global.d.ts: -------------------------------------------------------------------------------- 1 | import "../../types/vite" 2 | 3 | declare global { 4 | const GIT_COMMIT_HASH: string 5 | } 6 | export {} 7 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/public/favicon-dev.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/desktop/layer/renderer/public/favicon-dev.ico -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/public/pwa-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/desktop/layer/renderer/public/pwa-192x192.png -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/public/pwa-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/desktop/layer/renderer/public/pwa-512x512.png -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/public/pwa-64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/desktop/layer/renderer/public/pwa-64x64.png -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/settings/tabs/ai/usage/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./components" 2 | export * from "./UsageAnalysisSection" 3 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/subscription-column/subscription-list/index.ts: -------------------------------------------------------------------------------- 1 | export { SubscriptionList } from "./SubscriptionList" 2 | -------------------------------------------------------------------------------- /apps/mobile/assets/font/sn-pro/SNPro-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/assets/font/sn-pro/SNPro-Regular.otf -------------------------------------------------------------------------------- /apps/mobile/assets/font/sn-pro/SNPro-Semibold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/assets/font/sn-pro/SNPro-Semibold.otf -------------------------------------------------------------------------------- /apps/desktop/layer/main/src/constants/system.ts: -------------------------------------------------------------------------------- 1 | import { machineIdSync } from "node-machine-id" 2 | 3 | export const DEVICE_ID = machineIdSync() 4 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/public/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/desktop/layer/renderer/public/icon-192x192.png -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/public/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/desktop/layer/renderer/public/icon-512x512.png -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/constants/env.ts: -------------------------------------------------------------------------------- 1 | import { env } from "@follow/shared/env.desktop" 2 | 3 | export const WEB_URL = env.VITE_WEB_URL 4 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/ai-chat-session/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./query" 2 | export * from "./service" 3 | export * from "./store" 4 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/entry-content/components/entry-read-history/index.ts: -------------------------------------------------------------------------------- 1 | export { EntryReadHistory } from "./EntryReadHistory" 2 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/pages/(main)/(layer)/(subview)/layout.tsx: -------------------------------------------------------------------------------- 1 | export { SubviewLayout as Component } from "~/modules/app-layout/subview" 2 | -------------------------------------------------------------------------------- /apps/mobile/assets/font/sn-pro/SNPro-BlackItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/assets/font/sn-pro/SNPro-BlackItalic.otf -------------------------------------------------------------------------------- /apps/mobile/assets/font/sn-pro/SNPro-BoldItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/assets/font/sn-pro/SNPro-BoldItalic.otf -------------------------------------------------------------------------------- /apps/mobile/assets/font/sn-pro/SNPro-BookItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/assets/font/sn-pro/SNPro-BookItalic.otf -------------------------------------------------------------------------------- /apps/mobile/assets/font/sn-pro/SNPro-HeavyItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/assets/font/sn-pro/SNPro-HeavyItalic.otf -------------------------------------------------------------------------------- /apps/mobile/assets/font/sn-pro/SNPro-LightItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/assets/font/sn-pro/SNPro-LightItalic.otf -------------------------------------------------------------------------------- /apps/mobile/assets/font/sn-pro/SNPro-ThinItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/assets/font/sn-pro/SNPro-ThinItalic.otf -------------------------------------------------------------------------------- /apps/mobile/src/lib/navigation/readme.md: -------------------------------------------------------------------------------- 1 | ## Docs 2 | 3 | https://www.notion.so/rss3/React-Native-Navigation-Yet-1c035ea049b48068bdc1c21009961622 4 | -------------------------------------------------------------------------------- /apps/ssr/client/configs.ts: -------------------------------------------------------------------------------- 1 | export const siteConfig = { 2 | repoUrl: "https://github.com/RSSNext/Follow", 3 | appUrl: "https://app.folo.is", 4 | } 5 | -------------------------------------------------------------------------------- /packages/internal/components/src/atoms/mouse.ts: -------------------------------------------------------------------------------- 1 | import { atom } from "jotai" 2 | 3 | export const mouseAtom = atom({ 4 | x: 0, 5 | y: 0, 6 | }) 7 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/public/opengraph-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/desktop/layer/renderer/public/opengraph-image.png -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/assets/rsshub-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/desktop/layer/renderer/src/assets/rsshub-icon.png -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/components/ui/media/MediaInfoRecord.tsx: -------------------------------------------------------------------------------- 1 | export type MediaInfoRecord = Record 2 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./app" 2 | export * from "./copy" 3 | export * from "./hotkeys" 4 | export * from "./ui" 5 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/ai-chat/components/welcome/DefaultWelcomeContent.tsx: -------------------------------------------------------------------------------- 1 | export const DefaultWelcomeContent: React.FC = () => null 2 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/app-layout/ai-enhanced-timeline/index.tsx: -------------------------------------------------------------------------------- 1 | export { AIEnhancedTimelineLayout } from "./AIEnhancedTimelineLayout" 2 | -------------------------------------------------------------------------------- /apps/mobile/assets/font/sn-pro/SNPro-MediumItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/assets/font/sn-pro/SNPro-MediumItalic.otf -------------------------------------------------------------------------------- /apps/mobile/assets/font/sn-pro/SNPro-RegularItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/assets/font/sn-pro/SNPro-RegularItalic.otf -------------------------------------------------------------------------------- /apps/mobile/assets/font/sn-pro/SNPro-SemiboldItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/assets/font/sn-pro/SNPro-SemiboldItalic.otf -------------------------------------------------------------------------------- /apps/mobile/ios/Folo/Folo-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | -------------------------------------------------------------------------------- /apps/mobile/src/screens/(modal)/onboarding/EditProfileScreen.tsx: -------------------------------------------------------------------------------- 1 | export { EditProfileScreen as default } from "@/src/modules/settings/routes/EditProfile" 2 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/atoms/lang.ts: -------------------------------------------------------------------------------- 1 | import { atom } from "jotai" 2 | 3 | export const langLoadingLockMapAtom = atom({} as Record) 4 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/ai-chat/components/context-bar/blocks/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./ContextBlock" 2 | export * from "./TitleComponents" 3 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/ai-task/components/index.ts: -------------------------------------------------------------------------------- 1 | export { AITaskModal } from "./ai-task-modal" 2 | export { AITaskList } from "./task-list" 3 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/entry-column/components/entry-column-wrapper/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./EntryColumnWrapper" 2 | export * from "./types" 3 | -------------------------------------------------------------------------------- /apps/mobile/native/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ["universe/native", "universe/web"], 4 | ignorePatterns: ["build"], 5 | } 6 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/public/maskable-icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/desktop/layer/renderer/public/maskable-icon-512x512.png -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/entry-column/atoms/ai-timeline.ts: -------------------------------------------------------------------------------- 1 | import { atom } from "jotai" 2 | 3 | export const aiTimelineEnabledAtom = atom(false) 4 | -------------------------------------------------------------------------------- /apps/mobile/src/components/ui/grouped/GroupedInsetListCardItemStyle.tsx: -------------------------------------------------------------------------------- 1 | export enum GroupedInsetListCardItemStyle { 2 | NavigationLink = "NavigationLink", 3 | } 4 | -------------------------------------------------------------------------------- /apps/ssr/client/pages/(main)/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | export const Component = () => { 4 | return
(*‘ v`*) Hello, Folo
5 | } 6 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/components/ui/markdown/renderers/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./BlockImage" 2 | export * from "./MarkdownLink" 3 | export * from "./MarkdownP" 4 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/app-tip/index.ts: -------------------------------------------------------------------------------- 1 | export { AppTipModalContent } from "./AppTipModalContent" 2 | export { APP_TIP_DEBUG_EVENT } from "./constants" 3 | -------------------------------------------------------------------------------- /apps/ssr/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrites": [ 3 | { 4 | "source": "/((?!external-dist|dist-external).*)", 5 | "destination": "/api" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/public/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/desktop/layer/renderer/public/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/discover/types.ts: -------------------------------------------------------------------------------- 1 | export type ParsedFeedItem = { 2 | url: string 3 | title: string | null 4 | category?: string | null 5 | } 6 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/upgrade/lazy/index.ts: -------------------------------------------------------------------------------- 1 | import { lazy } from "react" 2 | 3 | export const AppNotificationContainer = lazy(() => import("../container")) 4 | -------------------------------------------------------------------------------- /apps/mobile/ios/sentry.properties: -------------------------------------------------------------------------------- 1 | defaults.url=https://sentry.io/ 2 | defaults.org=follow-rg 3 | defaults.project=react-native 4 | # Using SENTRY_AUTH_TOKEN environment variable -------------------------------------------------------------------------------- /packages/internal/store/src/modules/summary/enum.ts: -------------------------------------------------------------------------------- 1 | export enum SummaryGeneratingStatus { 2 | Pending = "pending", 3 | Success = "success", 4 | Error = "error", 5 | } 6 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/components/ui/hover-preview/index.ts: -------------------------------------------------------------------------------- 1 | export { EntryPreviewCard } from "./EntryPreviewCard" 2 | export { FeedPreviewCard } from "./FeedPreviewCard" 3 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/entry-column/atoms/social-media-content-width.ts: -------------------------------------------------------------------------------- 1 | import { atom } from "jotai" 2 | 3 | export const socialMediaContentWidthAtom = atom(0) 4 | -------------------------------------------------------------------------------- /apps/mobile/shim-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare namespace NodeJS { 4 | export type ProcessEnv = Record 5 | } 6 | -------------------------------------------------------------------------------- /apps/desktop/changelog/0.3.5.md: -------------------------------------------------------------------------------- 1 | # What's new in v0.3.5 2 | 3 | ## New Features 4 | 5 | - Restore audio and notification views 6 | 7 | ## Improvements 8 | 9 | ## Bug Fixes 10 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/queries/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface MutationBaseProps { 2 | onSuccess?: () => void 3 | onError?: (error: Error) => void 4 | } 5 | 6 | export {} 7 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/store/search/helper.ts: -------------------------------------------------------------------------------- 1 | import type { SearchInstance } from "./types" 2 | 3 | export const defineSearchInstance = (instance: SearchInstance) => instance 4 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/wdyr.ts: -------------------------------------------------------------------------------- 1 | if (import.meta.env.DEV) { 2 | const { scan } = await import("react-scan") 3 | scan({ enabled: false, log: false, showToolbar: true }) 4 | } 5 | -------------------------------------------------------------------------------- /packages/internal/components/assets/index.css: -------------------------------------------------------------------------------- 1 | @import "./colors.css"; 2 | @import "./tailwind.css"; 3 | @import "./font.css"; 4 | @import "tailwindcss-uikit-colors/macos/selector.css"; 5 | -------------------------------------------------------------------------------- /packages/internal/components/src/hooks/useMedia.ts: -------------------------------------------------------------------------------- 1 | import { useMediaQuery } from "usehooks-ts" 2 | 3 | export const useIsPrinting = () => { 4 | return useMediaQuery("print") 5 | } 6 | -------------------------------------------------------------------------------- /packages/internal/hooks/src/useVideo.ts: -------------------------------------------------------------------------------- 1 | import createHTMLMediaHook from "./factory/createHTMLMediaHook" 2 | 3 | export const useVideo = createHTMLMediaHook("video") 4 | -------------------------------------------------------------------------------- /packages/internal/store/src/modules/collection/types.ts: -------------------------------------------------------------------------------- 1 | import type { CollectionSchema } from "@follow/database/schemas/types" 2 | 3 | export type CollectionModel = CollectionSchema 4 | -------------------------------------------------------------------------------- /packages/internal/store/src/modules/user/constants.ts: -------------------------------------------------------------------------------- 1 | export const isNewUserQueryKey = ["user", "isNewUser"] 2 | export const isOnboardingFinishedStorageKey = "isOnboardingFinished" 3 | -------------------------------------------------------------------------------- /apps/desktop/changelog/0.2.8.md: -------------------------------------------------------------------------------- 1 | # What's new in v0.2.8 2 | 3 | ## New Features 4 | 5 | - Register or Login with email and password 6 | 7 | ## Improvements 8 | 9 | ## Bug Fixes 10 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/ai-chat/components/displays/index.ts: -------------------------------------------------------------------------------- 1 | export { AIChainOfThought } from "./AIChainOfThought" 2 | export { AIReasoningPart } from "./AIReasoningPart" 3 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/settings/context.tsx: -------------------------------------------------------------------------------- 1 | import { createContext } from "react" 2 | 3 | export const IsInSettingIndependentWindowContext = createContext(false) 4 | -------------------------------------------------------------------------------- /apps/mobile/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: { 4 | config: "./tailwind.dom.config.ts", 5 | }, 6 | autoprefixer: {}, 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /apps/mobile/src/modules/subscription/items/types.tsx: -------------------------------------------------------------------------------- 1 | export type SubscriptionItemBaseProps = { 2 | isFirst: boolean 3 | isLast: boolean 4 | id: string 5 | className?: string 6 | } 7 | -------------------------------------------------------------------------------- /packages/internal/store/src/lib/base.ts: -------------------------------------------------------------------------------- 1 | export interface Hydratable { 2 | hydrate: () => Promise 3 | } 4 | 5 | export interface Resetable { 6 | reset: () => Promise 7 | } 8 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/pages/(main)/(layer)/timeline/[timelineId]/[feedId]/layout.tsx: -------------------------------------------------------------------------------- 1 | export { AIEnhancedTimelineLayout as Component } from "~/modules/app-layout/ai-enhanced-timeline" 2 | -------------------------------------------------------------------------------- /apps/mobile/ios/Assets.xcassets/home_5_cute_fi.imageset/home_5_cute_fi.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/ios/Assets.xcassets/home_5_cute_fi.imageset/home_5_cute_fi.pdf -------------------------------------------------------------------------------- /apps/mobile/ios/Assets.xcassets/home_5_cute_re.imageset/home_5_cute_re.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/ios/Assets.xcassets/home_5_cute_re.imageset/home_5_cute_re.pdf -------------------------------------------------------------------------------- /apps/mobile/native/ios/Packages/ImageViewer_swift/ImageItem.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public enum ImageItem { 4 | case image(UIImage?) 5 | case url(URL, placeholder: UIImage?) 6 | } 7 | -------------------------------------------------------------------------------- /apps/mobile/web-app/html-renderer/src/utils.ts: -------------------------------------------------------------------------------- 1 | declare const __RN__: any 2 | 3 | // eslint-disable-next-line unicorn/no-typeof-undefined 4 | export const isInRn = typeof __RN__ !== "undefined" 5 | -------------------------------------------------------------------------------- /apps/ssr/client/pages/(login)/login/index.tsx: -------------------------------------------------------------------------------- 1 | import { Login } from "@client/modules/login" 2 | import * as React from "react" 3 | 4 | export function Component() { 5 | return 6 | } 7 | -------------------------------------------------------------------------------- /packages/internal/components/src/ui/radio-group/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./context" 2 | export * as MotionRadio from "./motion" 3 | export * from "./RadioCard" 4 | export * from "./RadioGroup" 5 | -------------------------------------------------------------------------------- /packages/internal/database/src/drizzle/0022_tiny_northstar.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `lists` ADD `subscription_count` integer;--> statement-breakpoint 2 | ALTER TABLE `lists` ADD `purchase_amount` text; 3 | -------------------------------------------------------------------------------- /packages/internal/logger/web.ts: -------------------------------------------------------------------------------- 1 | export const log = (...args: any[]) => { 2 | // eslint-disable-next-line no-console 3 | console.log(...args) 4 | } 5 | 6 | export const initialize = () => {} 7 | -------------------------------------------------------------------------------- /packages/internal/tracker/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@follow/configs/tsconfig.extend.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "declaration": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/components/ui/media/MediaContainerWidthContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext } from "react" 2 | 3 | export const MediaContainerWidthContext = createContext(0) 4 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/ai-chat/editor/plugins/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./file-upload" 2 | export * from "./mention" 3 | export * from "./selection" 4 | export * from "./shortcut" 5 | -------------------------------------------------------------------------------- /packages/internal/database/src/drizzle/0002_smart_power_man.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `unread` ( 2 | `id` text PRIMARY KEY NOT NULL, 3 | `subscription_id` text NOT NULL, 4 | `count` integer NOT NULL 5 | ); 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/lib/app.ts: -------------------------------------------------------------------------------- 1 | export const removeAppSkeleton = () => { 2 | try { 3 | document.querySelector("#app-skeleton")?.remove() 4 | } catch { 5 | // ignore 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/entry-content/components/entry-header/types.tsx: -------------------------------------------------------------------------------- 1 | export interface EntryHeaderProps { 2 | entryId: string 3 | className?: string 4 | compact?: boolean 5 | } 6 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/settings/tabs/ai/usage/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./DetailedUsageModal" 2 | export * from "./UsageProgressRing" 3 | export * from "./UsageWarningBanner" 4 | -------------------------------------------------------------------------------- /apps/mobile/ios/Assets.xcassets/search_3_cute_fi.imageset/search_3_cute_fi.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/ios/Assets.xcassets/search_3_cute_fi.imageset/search_3_cute_fi.pdf -------------------------------------------------------------------------------- /apps/mobile/ios/Assets.xcassets/search_3_cute_re.imageset/search_3_cute_re.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/ios/Assets.xcassets/search_3_cute_re.imageset/search_3_cute_re.pdf -------------------------------------------------------------------------------- /apps/mobile/nativewind-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // NOTE: This file should not be edited and should be committed with your source code. It is generated by NativeWind. 4 | -------------------------------------------------------------------------------- /packages/internal/database/src/drizzle/0020_little_marauders.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `summaries` ADD `readability_summary` text;--> statement-breakpoint 2 | ALTER TABLE `translations` ADD `readability_content` text; -------------------------------------------------------------------------------- /packages/internal/store/src/modules/inbox/getters.ts: -------------------------------------------------------------------------------- 1 | import { useInboxStore } from "./store" 2 | 3 | export function getInboxList() { 4 | return Object.values(useInboxStore.getState().inboxes) 5 | } 6 | -------------------------------------------------------------------------------- /packages/internal/store/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@follow/configs/tsconfig.extend.json", 3 | "compilerOptions": { 4 | "types": ["vite/client"] 5 | }, 6 | "include": ["src/**/*"] 7 | } 8 | -------------------------------------------------------------------------------- /apps/mobile/ios/Assets.xcassets/settings_1_cute_fi.imageset/settings_1_cute_fi.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/ios/Assets.xcassets/settings_1_cute_fi.imageset/settings_1_cute_fi.pdf -------------------------------------------------------------------------------- /apps/mobile/ios/Assets.xcassets/settings_1_cute_re.imageset/settings_1_cute_re.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/ios/Assets.xcassets/settings_1_cute_re.imageset/settings_1_cute_re.pdf -------------------------------------------------------------------------------- /apps/mobile/ios/Folo/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/ios/Folo/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png -------------------------------------------------------------------------------- /apps/mobile/web-app/html-renderer/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: { 4 | config: "./tailwind.config.ts", 5 | }, 6 | autoprefixer: {}, 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /changelogithub.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | tagFilter: (tag: string) => 3 | (tag.startsWith("mobile/v") || tag.startsWith("desktop/v")) && !tag.includes("nightly"), 4 | dry: !process.env.CI, 5 | } 6 | -------------------------------------------------------------------------------- /packages/internal/database/src/drizzle/0009_lucky_power_man.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `summaries` ( 2 | `entry_id` text PRIMARY KEY NOT NULL, 3 | `summary` text, 4 | `created_at` text, 5 | `language` text 6 | ); 7 | -------------------------------------------------------------------------------- /packages/internal/store/src/modules/feed/types.ts: -------------------------------------------------------------------------------- 1 | import type { FeedSchema } from "@follow/database/schemas/types" 2 | 3 | export type FeedModel = FeedSchema & { 4 | type: "feed" 5 | nonce?: string 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "johnsoncodehk.vscode-tsslint", 5 | "esbenp.prettier-vscode", 6 | "bradlc.vscode-tailwindcss" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/ai-chat/constants/index.ts: -------------------------------------------------------------------------------- 1 | export const SCROLLED_BEYOND_THRESHOLD = 100 2 | 3 | export const AI_CHAT_SPECIAL_ID_PREFIX = { 4 | TIMELINE_SUMMARY: "timeline-summary:", 5 | } 6 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/settings/tabs/ai/usage/components/charts/index.ts: -------------------------------------------------------------------------------- 1 | export { BarList } from "./BarList" 2 | export { Sparkline } from "./Sparkline" 3 | export { TinyBars } from "./TinyBars" 4 | -------------------------------------------------------------------------------- /apps/mobile/src/components/ui/pressable/NativePressable.types.tsx: -------------------------------------------------------------------------------- 1 | import type { ViewProps } from "react-native" 2 | 3 | export interface NativePressableProps extends ViewProps { 4 | onPress?: () => any 5 | } 6 | -------------------------------------------------------------------------------- /packages/internal/constants/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./app" 2 | export * from "./auth-providers" 3 | export * from "./enums" 4 | export * from "./rsshub" 5 | export * from "./social" 6 | export * from "./tabs" 7 | -------------------------------------------------------------------------------- /apps/mobile/src/components/native/webview/atom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from "jotai" 2 | import { Dimensions } from "react-native" 3 | 4 | export const sharedWebViewHeightAtom = atom(Dimensions.get("window").height) 5 | -------------------------------------------------------------------------------- /apps/mobile/src/lib/navigation/config.ts: -------------------------------------------------------------------------------- 1 | import type { ScreenStackHeaderConfigProps } from "react-native-screens" 2 | 3 | export const defaultHeaderConfig: ScreenStackHeaderConfigProps = { 4 | hidden: true, 5 | } 6 | -------------------------------------------------------------------------------- /apps/mobile/tailwind.dom.config.ts: -------------------------------------------------------------------------------- 1 | import { extendConfig } from "@follow/configs/tailwindcss/web" 2 | 3 | export default extendConfig({ 4 | darkMode: "media", 5 | content: ["../../packages/**/*.{ts,tsx}"], 6 | }) 7 | -------------------------------------------------------------------------------- /apps/ssr/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | "tailwindcss/nesting": {}, 5 | 6 | ...(process.env.NODE_ENV === "production" ? { cssnano: {} } : {}), 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /apps/mobile/ios/Assets.xcassets/black_board_2_cute_fi.imageset/black_board_2_cute_fi.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/ios/Assets.xcassets/black_board_2_cute_fi.imageset/black_board_2_cute_fi.pdf -------------------------------------------------------------------------------- /apps/mobile/ios/Assets.xcassets/black_board_2_cute_re.imageset/black_board_2_cute_re.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RSSNext/Folo/HEAD/apps/mobile/ios/Assets.xcassets/black_board_2_cute_re.imageset/black_board_2_cute_re.pdf -------------------------------------------------------------------------------- /apps/mobile/src/modules/screen/PagerListContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from "react" 2 | 3 | export const PagerListVisibleContext = createContext(true) 4 | export const PagerListWillVisibleContext = createContext(false) 5 | -------------------------------------------------------------------------------- /packages/internal/database/src/drizzle/0008_last_the_santerians.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `collections` ( 2 | `feed_id` text, 3 | `entry_id` text PRIMARY KEY NOT NULL, 4 | `created_at` text, 5 | `view` integer NOT NULL 6 | ); 7 | -------------------------------------------------------------------------------- /packages/internal/store/src/@types/default-resource.ts: -------------------------------------------------------------------------------- 1 | import settings_en from "../../../../../locales/settings/en.json" 2 | 3 | export const defaultResources = { 4 | en: { 5 | settings: settings_en, 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /apps/mobile/src/lib/url-builder.ts: -------------------------------------------------------------------------------- 1 | import { UrlBuilder as UrlBuilderClass } from "@follow/utils/url-builder" 2 | 3 | import { proxyEnv } from "./proxy-env" 4 | 5 | export const UrlBuilder = new UrlBuilderClass(proxyEnv.WEB_URL) 6 | -------------------------------------------------------------------------------- /packages/internal/database/src/drizzle/0011_mysterious_stark_industries.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `images` ( 2 | `url` text PRIMARY KEY NOT NULL, 3 | `colors` text NOT NULL, 4 | `created_at` integer DEFAULT (CURRENT_TIMESTAMP) 5 | ); 6 | -------------------------------------------------------------------------------- /packages/internal/database/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@follow/configs/tsconfig.extend.json", 3 | "compilerOptions": { 4 | "types": ["vite/client"] 5 | }, 6 | "include": ["src/**/*", "drizzle.config.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /scripts/mitproxy.py: -------------------------------------------------------------------------------- 1 | from mitmproxy import http 2 | 3 | def request(flow: http.HTTPFlow) -> None: 4 | if flow.request.pretty_host == "app.folo.is": 5 | flow.request.host = "localhost" 6 | flow.request.port = 2233 -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/atoms/preview.ts: -------------------------------------------------------------------------------- 1 | import { atom } from "jotai" 2 | 3 | import { createAtomHooks } from "~/lib/jotai" 4 | 5 | export const [, , , , previewBackPath, setPreviewBackPath] = createAtomHooks(atom()) 6 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/components/ui/modal/stacked/atom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from "jotai" 2 | 3 | import type { ModalProps } from "./types" 4 | 5 | export const modalStackAtom = atom([] as (ModalProps & { id: string })[]) 6 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/ai-chat/editor/plugins/file-upload/index.ts: -------------------------------------------------------------------------------- 1 | export { FileAttachmentNode } from "./FileAttachmentNode" 2 | export { FileUploadPlugin } from "./FileUploadPlugin" 3 | export type * from "./types" 4 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/ai-chat/editor/plugins/mention/hooks/dateMentionSearch.ts: -------------------------------------------------------------------------------- 1 | export { MAX_INLINE_DATE_SUGGESTIONS } from "./dateMentionConfig" 2 | export { createDateMentionBuilder } from "./dateMentionParsers" 3 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/ai-chat/editor/plugins/shortcut/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./positioning" 2 | export * from "./shortcutTextValue" 3 | export * from "./textReplacement" 4 | export * from "./triggerDetection" 5 | -------------------------------------------------------------------------------- /apps/ssr/client/lib/url-builder.ts: -------------------------------------------------------------------------------- 1 | import { env } from "@follow/shared/env.ssr" 2 | import { UrlBuilder as UrlBuilderClass } from "@follow/utils/url-builder" 3 | 4 | export const UrlBuilder = new UrlBuilderClass(env.VITE_WEB_URL) 5 | -------------------------------------------------------------------------------- /packages/internal/database/src/drizzle/0026_numerous_slyde.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `users` ADD `bio` text;--> statement-breakpoint 2 | ALTER TABLE `users` ADD `website` text;--> statement-breakpoint 3 | ALTER TABLE `users` ADD `social_links` text; -------------------------------------------------------------------------------- /packages/internal/store/src/modules/image/getters.ts: -------------------------------------------------------------------------------- 1 | import { useImagesStore } from "./store" 2 | 3 | const get = useImagesStore.getState 4 | 5 | export const getImageInfo = (url: string) => { 6 | return get().images[url] 7 | } 8 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/constants/ui.ts: -------------------------------------------------------------------------------- 1 | export const ElECTRON_CUSTOM_TITLEBAR_HEIGHT = 30 2 | // export const ELECTRON_WINDOWS_RADIUS = 12 3 | 4 | export const readableContentMaxWidthClassName = "max-w-[clamp(45ch,60vw,65ch)]" 5 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/ai-chat/components/message/animated/constants.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_ANIMATION = "mask-left-to-right 0.5s ease-in-out" 2 | export const ANIMATION_STYLE = { 3 | animation: DEFAULT_ANIMATION, 4 | } 5 | -------------------------------------------------------------------------------- /apps/mobile/src/lib/navigation/NavigationInstanceContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from "react" 2 | 3 | import type { Navigation } from "./Navigation" 4 | 5 | export const NavigationInstanceContext = createContext(null!) 6 | -------------------------------------------------------------------------------- /packages/internal/components/src/ui/z-index/ctx.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, use } from "react" 2 | 3 | export const ZIndexContext = createContext(0) 4 | 5 | export const useCorrectZIndex = (zIndex: number) => use(ZIndexContext) + zIndex 6 | -------------------------------------------------------------------------------- /.prettierrc.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | semi: false, 3 | singleQuote: false, 4 | printWidth: 100, 5 | tabWidth: 2, 6 | trailingComma: "all", 7 | objectWrap: "preserve", 8 | plugins: ["prettier-plugin-tailwindcss"], 9 | } 10 | -------------------------------------------------------------------------------- /apps/desktop/changelog/0.3.8.md: -------------------------------------------------------------------------------- 1 | # What's new in v0.3.8 2 | 3 | ## New Features 4 | 5 | ## Improvements 6 | 7 | ## Bug Fixes 8 | 9 | - The app is stuck after closing the dialog 10 | - Can not sign in with email in the desktop app 11 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/settings/tabs/ai/shortcuts/index.ts: -------------------------------------------------------------------------------- 1 | export { AIShortcutsSection } from "./AIShortcutsSection" 2 | export { ShortcutItem } from "./ShortcutItem" 3 | export { ShortcutModalContent } from "./ShortcutModalContent" 4 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/pages/settings/layout.tsx: -------------------------------------------------------------------------------- 1 | // NOTE: we disable directly nav to setting routes. 2 | // because we need to open the setting modal in the main window when the main window exists, 3 | export const Component = () => null 4 | -------------------------------------------------------------------------------- /apps/mobile/src/database/index.ts: -------------------------------------------------------------------------------- 1 | import * as FileSystem from "expo-file-system" 2 | 3 | export const getDbPath = () => { 4 | return `${FileSystem.documentDirectory}SQLite/follow.db` 5 | } 6 | if (__DEV__) console.info("SQLite:", getDbPath()) 7 | -------------------------------------------------------------------------------- /packages/internal/database/drizzle.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "drizzle-kit" 2 | 3 | export default defineConfig({ 4 | dialect: "sqlite", 5 | driver: "expo", 6 | schema: "./src/schemas/index.ts", 7 | out: "./src/drizzle", 8 | }) 9 | -------------------------------------------------------------------------------- /packages/internal/database/src/drizzle/0006_exotic_kid_colt.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `entries` ADD `feed_id` text;--> statement-breakpoint 2 | ALTER TABLE `entries` ADD `inbox_handle` text;--> statement-breakpoint 3 | ALTER TABLE `entries` ADD `read` integer; -------------------------------------------------------------------------------- /packages/internal/utils/src/noop.ts: -------------------------------------------------------------------------------- 1 | export const noop = () => {} 2 | // eslint-disable-next-line unicorn/no-thenable 3 | export const thenable: any = { then: noop } 4 | export const emptyObject = {} 5 | 6 | export const alwaysFalse = () => false 7 | -------------------------------------------------------------------------------- /apps/mobile/changelog/0.2.8.md: -------------------------------------------------------------------------------- 1 | # What's New in v0.2.8 2 | 3 | ## Shiny new things 4 | 5 | ## Improvements 6 | 7 | ## No longer broken 8 | 9 | ## Thanks 10 | 11 | Special thanks to volunteer contributors @ for their valuable contributions 12 | -------------------------------------------------------------------------------- /apps/mobile/ios/Podfile.properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo.jsEngine": "hermes", 3 | "EX_DEV_CLIENT_NETWORK_INSPECTOR": "true", 4 | "newArchEnabled": "true", 5 | "ios.useFrameworks": "static", 6 | "apple.privacyManifestAggregationEnabled": "true" 7 | } 8 | -------------------------------------------------------------------------------- /apps/mobile/src/lib/navigation/GroupedNavigationRouteContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from "react" 2 | 3 | import type { Route } from "./ChainNavigationContext" 4 | 5 | export const GroupedNavigationRouteContext = createContext(null!) 6 | -------------------------------------------------------------------------------- /apps/desktop/changelog/next.md: -------------------------------------------------------------------------------- 1 | # What's new in vNEXT_VERSION 2 | 3 | ## Shiny new things 4 | 5 | ## Improvements 6 | 7 | ## No longer broken 8 | 9 | ## Thanks 10 | 11 | Special thanks to volunteer contributors @ for their valuable contributions 12 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/env.d.ts: -------------------------------------------------------------------------------- 1 | declare const APP_VERSION: string 2 | declare const APP_NAME: string 3 | declare const RELEASE_CHANNEL: string 4 | declare const I18N_COMPLETENESS_MAP: Record 5 | declare const CHANGELOG_CONTENT: string 6 | -------------------------------------------------------------------------------- /apps/mobile/changelog/next.md: -------------------------------------------------------------------------------- 1 | # What's New in vNEXT_VERSION 2 | 3 | ## Shiny new things 4 | 5 | ## Improvements 6 | 7 | ## No longer broken 8 | 9 | ## Thanks 10 | 11 | Special thanks to volunteer contributors @ for their valuable contributions 12 | -------------------------------------------------------------------------------- /apps/mobile/web-app/html-renderer/src/components/shiki/hooks.ts: -------------------------------------------------------------------------------- 1 | import { useIsDark } from "@follow/hooks" 2 | 3 | export const useShikiDefaultTheme = () => { 4 | const isDark = useIsDark() 5 | 6 | return isDark ? "github-dark" : "github-light" 7 | } 8 | -------------------------------------------------------------------------------- /packages/internal/database/src/drizzle/0004_majestic_thunderbolt_ross.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `users` ( 2 | `id` text PRIMARY KEY NOT NULL, 3 | `email` text NOT NULL, 4 | `handle` text, 5 | `name` text, 6 | `image` text, 7 | `is_me` integer NOT NULL 8 | ); 9 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/components/ui/media/MediaInfoRecordContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext } from "react" 2 | 3 | import type { MediaInfoRecord } from "./MediaInfoRecord" 4 | 5 | export const MediaInfoRecordContext = createContext({}) 6 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/ai-chat/components/welcome/index.ts: -------------------------------------------------------------------------------- 1 | export { DefaultWelcomeContent } from "./DefaultWelcomeContent" 2 | export { EntrySummaryCard } from "./EntrySummaryCard" 3 | export { EntryWelcomeContent } from "./EntryWelcomeContent" 4 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/entry-column/star-icon.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@follow/utils/utils" 2 | 3 | export const StarIcon: Component = ({ className }) => ( 4 | 5 | ) 6 | -------------------------------------------------------------------------------- /apps/desktop/changelog/next.template.md: -------------------------------------------------------------------------------- 1 | # What's new in vNEXT_VERSION 2 | 3 | ## Shiny new things 4 | 5 | ## Improvements 6 | 7 | ## No longer broken 8 | 9 | ## Thanks 10 | 11 | Special thanks to volunteer contributors @ for their valuable contributions 12 | -------------------------------------------------------------------------------- /apps/mobile/changelog/next.template.md: -------------------------------------------------------------------------------- 1 | # What's New in vNEXT_VERSION 2 | 3 | ## Shiny new things 4 | 5 | ## Improvements 6 | 7 | ## No longer broken 8 | 9 | ## Thanks 10 | 11 | Special thanks to volunteer contributors @ for their valuable contributions 12 | -------------------------------------------------------------------------------- /apps/ssr/global.ts: -------------------------------------------------------------------------------- 1 | Object.assign(globalThis, { 2 | APP_NAME: "Folo", 3 | ELECTRON: false, 4 | }) 5 | 6 | try { 7 | void __DEV__ 8 | } catch { 9 | Object.assign(globalThis, { 10 | __DEV__: process.env.NODE_ENV === "development", 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /packages/internal/components/src/ui/input/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./DateTimePicker" 2 | export * from "./Input" 3 | export * from "./InputV2" 4 | export * from "./OTP" 5 | export * from "./TextArea" 6 | export * from "./TextAreaWrapper" 7 | export * from "./TimeSelect" 8 | -------------------------------------------------------------------------------- /packages/internal/hooks/src/useAnyPointDown.ts: -------------------------------------------------------------------------------- 1 | import { useEventListener } from "usehooks-ts" 2 | 3 | export const useAnyPointDown = (handler: (event: PointerEvent) => void) => { 4 | useEventListener("pointerdown", (event) => { 5 | handler(event) 6 | }) 7 | } 8 | -------------------------------------------------------------------------------- /tsslint.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "@tsslint/config" 2 | import { convertRules } from "@tsslint/eslint" 3 | 4 | export default defineConfig({ 5 | rules: await convertRules({ 6 | "react-x/no-leaked-conditional-rendering": "error", 7 | }), 8 | }) 9 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/initialize/migrates/helper.ts: -------------------------------------------------------------------------------- 1 | export interface DefineMigrationOptions { 2 | version: string 3 | migrate: () => void | Promise 4 | } 5 | export const defineMigration = (options: DefineMigrationOptions) => { 6 | return options 7 | } 8 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/renderer/types.ts: -------------------------------------------------------------------------------- 1 | import type { FeedViewType } from "@follow-app/client-sdk" 2 | 3 | export type EntryContentRendererProps = { 4 | view: FeedViewType 5 | feedId: string 6 | entryId: string 7 | children: Nullable 8 | } 9 | -------------------------------------------------------------------------------- /apps/mobile/web-app/html-renderer/src/components/__internal/ctx.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from "react" 2 | 3 | export const IsInParagraphContext = createContext(false) 4 | 5 | export const MarkdownRenderContainerRefContext = createContext(null) 6 | -------------------------------------------------------------------------------- /packages/internal/database/src/drizzle/0023_pink_namor.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `feeds` ADD `subscription_count` integer;--> statement-breakpoint 2 | ALTER TABLE `feeds` ADD `updates_per_week` integer;--> statement-breakpoint 3 | ALTER TABLE `feeds` ADD `latest_entry_published_at` text; -------------------------------------------------------------------------------- /packages/readability/tsdown.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsdown" 2 | 3 | export default defineConfig({ 4 | entry: ["src/index.ts"], 5 | outDir: "dist", 6 | dts: true, 7 | clean: true, 8 | format: ["cjs", "esm"], 9 | treeshake: true, 10 | }) 11 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | We always recommend using the latest version of Follow to ensure you get all security updates. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | Please report security vulnerabilities to follow@rss3.io. 10 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/components/ui/modal/inspire/InPeekModal.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, use } from "react" 2 | 3 | export const InPeekModal = createContext(false) 4 | InPeekModal.displayName = "InPeekModal" 5 | export const useInPeekModal = () => use(InPeekModal) 6 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/entry-column/components/entry-column-wrapper/types.tsx: -------------------------------------------------------------------------------- 1 | export interface EntryColumnWrapperProps extends ComponentType { 2 | onScroll?: (e: React.UIEvent) => void 3 | 4 | ref?: React.Ref 5 | } 6 | -------------------------------------------------------------------------------- /apps/mobile/web-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@follow/rn-micro-web-app", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "cd html-renderer && vite build", 7 | "typecheck": "cd html-renderer && tsc --noEmit" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/internal/store/src/modules/inbox/types.ts: -------------------------------------------------------------------------------- 1 | import type { InboxSchema } from "@follow/database/schemas/types" 2 | 3 | export type InboxModel = InboxSchema & { 4 | type: "inbox" 5 | // for easier type checking, do not exist actually 6 | ownerUserId?: string 7 | } 8 | -------------------------------------------------------------------------------- /packages/internal/types/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@follow/types", 3 | "private": true, 4 | "sideEffects": false, 5 | "exports": { 6 | "./global": "./global.d.ts", 7 | "./react": "./react-global.d.ts", 8 | "./vite": "./vite-env.d.ts" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@electron-toolkit/tsconfig/tsconfig.node.json", 3 | "compilerOptions": { 4 | "target": "ES2022", 5 | "noUncheckedIndexedAccess": true, 6 | "noImplicitOverride": true 7 | }, 8 | "exclude": ["apps", "packages"] 9 | } 10 | -------------------------------------------------------------------------------- /apps/desktop/changelog/0.3.13.md: -------------------------------------------------------------------------------- 1 | # What's new in v0.3.13 2 | 3 | ## New Features 4 | 5 | - New Action Language setting 6 | - Export entry content to a PDF file 7 | 8 | ## Improvements 9 | 10 | ## Bug Fixes 11 | 12 | - Unable to update subscription information in time 13 | -------------------------------------------------------------------------------- /apps/desktop/layer/main/preload/index.d.ts: -------------------------------------------------------------------------------- 1 | import type { ElectronAPI } from "@electron-toolkit/preload" 2 | 3 | declare global { 4 | interface Window { 5 | electron?: ElectronAPI 6 | api?: { canWindowBlur: boolean } 7 | platform: NodeJS.Platform 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/atoms/user.ts: -------------------------------------------------------------------------------- 1 | import { atom } from "jotai" 2 | 3 | import { createAtomHooks } from "~/lib/jotai" 4 | 5 | export const [, , useLoginModalShow, useSetLoginModalShow, getLoginModalShow, setLoginModalShow] = 6 | createAtomHooks(atom(false)) 7 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/components/common/Fragment.tsx: -------------------------------------------------------------------------------- 1 | import type { FC, ReactNode } from "react" 2 | import { Fragment } from "react" 3 | 4 | export const SafeFragment: FC<{ children: ReactNode }> = ({ children, ..._rest }) => ( 5 | {children} 6 | ) 7 | -------------------------------------------------------------------------------- /packages/internal/components/src/common/Fragment.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { createElement } from "react" 3 | 4 | export const PassviseFragment = ({ children }: { children: React.ReactNode }) => { 5 | return createElement(React.Fragment, null, children) 6 | } 7 | -------------------------------------------------------------------------------- /packages/internal/hooks/src/useTypescriptHappyCallback.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 | -------------------------------------------------------------------------------- /packages/readability/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@follow/configs/tsconfig.extend.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "paths": {} 8 | }, 9 | "include": ["src/**/*"] 10 | } 11 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/queries/settings.ts: -------------------------------------------------------------------------------- 1 | import { followClient } from "~/lib/api-client" 2 | import { defineQuery } from "~/lib/defineQuery" 3 | 4 | export const settings = { 5 | get: () => defineQuery(["settings"], async () => await followClient.api.settings.get()), 6 | } 7 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/ai-chat/editor/plugins/selection/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./insertSelectedTextNode" 2 | export * from "./selectedTextBridge" 3 | export * from "./SelectedTextNode" 4 | export * from "./SelectedTextNodeComponent" 5 | export * from "./SelectedTextPlugin" 6 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/app-tip/constants.ts: -------------------------------------------------------------------------------- 1 | export const APP_TIP_STORAGE_PREFIX = "follow:ai-onboarding:dismissed" 2 | 3 | export const APP_TIP_DEBUG_EVENT = "follow:ai-onboarding:debug-open" 4 | 5 | export const APP_TIP_DISMISS_EVENT = "follow:ai-onboarding:dismiss-change" 6 | -------------------------------------------------------------------------------- /apps/mobile/ios/Assets.xcassets/home_5_cute_fi.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "home_5_cute_fi.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /scripts/lib.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from "node:child_process" 2 | 3 | export const getGitHash = () => { 4 | try { 5 | return execSync("git rev-parse HEAD").toString().trim() 6 | } catch (e) { 7 | console.error("Failed to get git hash", e) 8 | return "" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/internal/database/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { BaseSQLiteDatabase } from "drizzle-orm/sqlite-core/db" 2 | 3 | import type * as schema from "./schemas" 4 | 5 | export type DB = 6 | | BaseSQLiteDatabase<"async", any, typeof schema> 7 | | BaseSQLiteDatabase<"sync", any, typeof schema> 8 | -------------------------------------------------------------------------------- /packages/internal/hooks/src/usePrevious.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react" 2 | 3 | export const usePrevious = (value: T): T | undefined => { 4 | const ref = useRef(undefined) 5 | useEffect(() => { 6 | ref.current = value 7 | }) 8 | return ref.current 9 | } 10 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | pnpm-lock.yaml 2 | 3 | CHANGELOG.md 4 | 5 | apps/external/postcss.config.cjs 6 | 7 | apps/mobile/android 8 | apps/mobile/ios 9 | apps/mobile/native/example 10 | 11 | apps/mobile/native/android 12 | apps/mobile/native/ios 13 | apps/mobile/.expo 14 | 15 | generated-routes.ts 16 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/components/ui/modal/stacked/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./context" 2 | export * from "./helper" 3 | // NOTE: This one can easily cause a circular dependency 4 | // export * from "./hooks" 5 | export * from "./modal" 6 | export * from "./provider" 7 | export * from "./types" 8 | -------------------------------------------------------------------------------- /apps/mobile/global.d.ts: -------------------------------------------------------------------------------- 1 | import type { DOMProps } from "expo/dom" 2 | import type { FC } from "react" 3 | import type WebView from "react-native-webview" 4 | 5 | declare global { 6 | export type WebComponent

= FC

> 7 | } 8 | export {} 9 | -------------------------------------------------------------------------------- /packages/internal/components/src/ui/skeleton/index.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@follow/utils/utils" 2 | 3 | export function Skeleton({ className, ...props }: React.HTMLAttributes) { 4 | return

5 | } 6 | -------------------------------------------------------------------------------- /packages/internal/hooks/src/useOnce.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react" 2 | 3 | export const useOnce = (fn: () => any) => { 4 | const isDone = useRef(false) 5 | useEffect(() => { 6 | if (isDone.current) return 7 | fn() 8 | isDone.current = true 9 | }, []) 10 | } 11 | -------------------------------------------------------------------------------- /apps/desktop/changelog/0.3.7.md: -------------------------------------------------------------------------------- 1 | # What's new in v0.3.7 2 | 3 | ## New Features 4 | 5 | - Revert to the previous list display styles. 6 | ![Timeline selector3](https://github.com/RSSNext/assets/blob/main/timeline-selector3.png?raw=true) 7 | - The entries of the list is now displayed directly in the timeline. 8 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/queries/_.ts: -------------------------------------------------------------------------------- 1 | export { auth } from "./auth" 2 | export { discover } from "./discover" 3 | export { entries } from "./entries" 4 | export { feed } from "./feed" 5 | export { invitations } from "./invitations" 6 | export { rsshub } from "./rsshub" 7 | export { wallet } from "./wallet" 8 | -------------------------------------------------------------------------------- /apps/mobile/native/ios/Packages/ImageViewer_swift/ImageViewerOption.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | public enum ImageViewerOption { 4 | 5 | case contentMode(UIView.ContentMode) 6 | case onPreview((Int) -> Void) 7 | case onClosePreview(() -> Void) 8 | case onIndexChange((Int) -> Void) 9 | } 10 | -------------------------------------------------------------------------------- /packages/internal/shared/src/global.d.ts: -------------------------------------------------------------------------------- 1 | import type { ElectronAPI } from "@electron-toolkit/preload" 2 | 3 | declare global { 4 | interface Window { 5 | electron?: ElectronAPI 6 | api?: { canWindowBlur: boolean } 7 | } 8 | 9 | export const ELECTRON: boolean 10 | } 11 | 12 | export {} 13 | -------------------------------------------------------------------------------- /packages/internal/store/src/@types/i18next.d.ts: -------------------------------------------------------------------------------- 1 | import type { defaultResources as resources } from "./default-resource" 2 | 3 | declare module "i18next" { 4 | interface CustomTypeOptions { 5 | ns: ["settings"] 6 | resources: (typeof resources)["en"] 7 | defaultNS: "settings" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /apps/desktop/changelog/0.2.2.md: -------------------------------------------------------------------------------- 1 | # What's new in v0.2.2 2 | 3 | ## New Features 4 | 5 | - And Or conditions for actions 6 | - Add achievement badge 7 | 8 | ## Improvements 9 | 10 | - electron: Prompt when opening an external link whether to open the app 11 | - Improve the smoothness of some animations 12 | -------------------------------------------------------------------------------- /apps/mobile/native/.npmignore: -------------------------------------------------------------------------------- 1 | # Exclude all top-level hidden directories by convention 2 | /.*/ 3 | 4 | # Exclude tarballs generated by `npm pack` 5 | /*.tgz 6 | 7 | __mocks__ 8 | __tests__ 9 | 10 | /babel.config.js 11 | /android/src/androidTest/ 12 | /android/src/test/ 13 | /android/build/ 14 | /example/ 15 | -------------------------------------------------------------------------------- /apps/mobile/native/ios/Controllers/RNSViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RNSViewController.swift 3 | // FollowNative 4 | // 5 | // Created by Innei on 2025/2/27. 6 | // 7 | 8 | import UIKit 9 | 10 | class RNSViewController: UIViewController { 11 | @objc public let screenView: UIView? = nil 12 | 13 | } 14 | -------------------------------------------------------------------------------- /apps/desktop/build/entitlements.mac.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-jit 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/ai-chat/components/displays/shared/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./AnalyticsMetrics" 2 | export * from "./CategoryTag" 3 | export * from "./DisplayHeader" 4 | export * from "./EmptyState" 5 | export * from "./FeedItemCard" 6 | export * from "./GroupedContent" 7 | export * from "./StatCard" 8 | -------------------------------------------------------------------------------- /packages/internal/components/src/ui/z-index/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { ZIndexContext } from "./ctx" 4 | 5 | export const ZIndexProvider: Component<{ 6 | zIndex: number 7 | }> = (props) => { 8 | return {props.children} 9 | } 10 | -------------------------------------------------------------------------------- /packages/readability/bump.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "nbump" 2 | 3 | export default defineConfig({ 4 | leading: ["npm run build"], 5 | tag: false, 6 | push: false, 7 | commit: false, 8 | allowDirty: true, 9 | changelog: false, 10 | publish: true, 11 | allowedBranches: ["dev"], 12 | }) 13 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/entry-content/components/entry-header/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./AIEntryHeader" 2 | export * from "./EntryHeader" 3 | export * from "./internal/context" 4 | export * from "./internal/EntryHeaderActionsContainer" 5 | export * from "./internal/EntryHeaderMeta" 6 | export * from "./types" 7 | -------------------------------------------------------------------------------- /apps/mobile/ios/Folo.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/internal/components/src/hooks/useMobile.ts: -------------------------------------------------------------------------------- 1 | import { useViewport } from "./useViewport" 2 | 3 | export const useMobile = () => { 4 | return useViewport((v) => v.w < 1024 && v.w !== 0) 5 | } 6 | 7 | export const isMobile = () => { 8 | const w = window.innerWidth 9 | return w < 1024 && w !== 0 10 | } 11 | -------------------------------------------------------------------------------- /apps/desktop/layer/main/src/@types/constants.ts: -------------------------------------------------------------------------------- 1 | const langs = ["en", "zh-CN", "zh-TW", "ja"] as const 2 | export const currentSupportedLanguages = [...langs].sort() as string[] 3 | export type MainSupportedLanguages = (typeof langs)[number] 4 | 5 | export const ns = ["native"] as const 6 | export const defaultNS = "native" as const 7 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/pages/settings/index.tsx: -------------------------------------------------------------------------------- 1 | import { nextFrame } from "@follow/utils/dom" 2 | import { useLayoutEffect } from "react" 3 | 4 | export const Component = () => { 5 | useLayoutEffect(() => { 6 | nextFrame(() => window.router.navigate("/settings/general")) 7 | }, []) 8 | return null 9 | } 10 | -------------------------------------------------------------------------------- /apps/ssr/api/index.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | // eslint-disable-next-line antfu/no-import-dist 3 | import { createApp } from "../dist/server/index.mjs" 4 | 5 | export default async function handler(req: any, res: any) { 6 | const app = await createApp() 7 | await app.ready() 8 | app.server.emit("request", req, res) 9 | } 10 | -------------------------------------------------------------------------------- /icons/mgc/left_small_sharp.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/internal/components/src/ui/portal/provider.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, use } from "react" 2 | 3 | export const useRootPortal = () => { 4 | const ctx = use(RootPortalContext) 5 | 6 | return ctx || document.body 7 | } 8 | 9 | export const RootPortalContext = createContext(undefined) 10 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/components/ui/markdown/renderers/ctx.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, use } from "react" 2 | 3 | /** 4 | * @internal 5 | */ 6 | export const IsInParagraphContext = createContext(false) 7 | 8 | export const useIsInParagraphContext = () => { 9 | return use(IsInParagraphContext) 10 | } 11 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/ai-chat/store/slices/index.ts: -------------------------------------------------------------------------------- 1 | export type { ChatSlice } from "../chat-core/types" 2 | export { 3 | type BlockSlice as ContextSlice, 4 | createBlockSlice as createContextSlice, 5 | } from "./block.slice" 6 | export { createChatSlice } from "./chat.slice" 7 | export { type ChatStatus } from "ai" 8 | -------------------------------------------------------------------------------- /icons/mgc/right_small_sharp.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/internal/components/src/ui/segment/ctx.tsx: -------------------------------------------------------------------------------- 1 | import { createContext } from "use-context-selector" 2 | 3 | export interface SegmentGroupContextValue { 4 | value: string 5 | setValue: (value: string) => void 6 | componentId: string 7 | } 8 | export const SegmentGroupContext = createContext(null!) 9 | -------------------------------------------------------------------------------- /packages/internal/utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@follow/configs/tsconfig.extend.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "declaration": false, 6 | "types": ["@follow/types/global", "@follow/types/vite"], 7 | "paths": { 8 | "@follow/utils/*": ["./src/*"] 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /apps/mobile/plugins/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /apps/mobile/src/initialize/device.ts: -------------------------------------------------------------------------------- 1 | import { getDeviceTypeAsync } from "expo-device" 2 | 3 | import { appAtoms } from "../atoms/app" 4 | import { jotaiStore } from "../lib/jotai" 5 | 6 | export async function initDeviceType() { 7 | const type = await getDeviceTypeAsync() 8 | jotaiStore.set(appAtoms.deviceType, type) 9 | } 10 | -------------------------------------------------------------------------------- /apps/ssr/client/App.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Outlet } from "react-router" 3 | 4 | import { RootProviders } from "./providers/root-providers" 5 | 6 | function App() { 7 | return ( 8 | 9 | 10 | 11 | ) 12 | } 13 | 14 | export { App as Component } 15 | -------------------------------------------------------------------------------- /apps/ssr/client/atoms/server-configs.ts: -------------------------------------------------------------------------------- 1 | import { createAtomHooks } from "@follow/utils/jotai" 2 | import type { StatusConfigs } from "@follow-app/client-sdk" 3 | import { atom } from "jotai" 4 | 5 | export const [, , useServerConfigs, , getServerConfigs, setServerConfigs] = createAtomHooks( 6 | atom>(null), 7 | ) 8 | -------------------------------------------------------------------------------- /apps/ssr/scripts/cleanup-vercel-build.ts: -------------------------------------------------------------------------------- 1 | import { rmSync } from "node:fs" 2 | import { fileURLToPath } from "node:url" 3 | 4 | import { dirname, resolve } from "pathe" 5 | 6 | const __dirname = dirname(fileURLToPath(import.meta.url)) 7 | rmSync(resolve(__dirname, "../.generated"), { recursive: true, force: true }) 8 | // restore env file 9 | -------------------------------------------------------------------------------- /packages/internal/store/src/modules/summary/getters.ts: -------------------------------------------------------------------------------- 1 | import type { SupportedActionLanguage } from "@follow/shared/language" 2 | 3 | import { useSummaryStore } from "./store" 4 | 5 | export const getSummary = (entryId: string, language: SupportedActionLanguage) => { 6 | return useSummaryStore.getState().data[entryId]?.[language] 7 | } 8 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/hooks/common/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./useBizQuery" 2 | export * from "./useContextMenu" 3 | export * from "./useI18n" 4 | export * from "./useLoginModal" 5 | export * from "./usePreventOverscrollBounce" 6 | export * from "./useRecaptchaToken" 7 | export * from "./useRequireLogin" 8 | export * from "./useSyncTheme" 9 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/ai-chat/editor/plugins/shortcut/utils/positioning.ts: -------------------------------------------------------------------------------- 1 | import type { LexicalEditor } from "lexical" 2 | 3 | import { calculateDropdownPosition } from "../../shared/utils/positioning" 4 | 5 | export const calculateShortcutDropdownPosition = (editor: LexicalEditor) => 6 | calculateDropdownPosition(editor) 7 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/entry-content/components/layouts/index.ts: -------------------------------------------------------------------------------- 1 | export { ArticleLayout } from "./ArticleLayout" 2 | export { getEntryContentLayout } from "./factory" 3 | export { PicturesLayout } from "./PicturesLayout" 4 | export { SocialMediaLayout } from "./SocialMediaLayout" 5 | export { VideosLayout } from "./VideosLayout" 6 | -------------------------------------------------------------------------------- /apps/mobile/ios/Folo/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "filename": "App-Icon-1024x1024@1x.png", 5 | "idiom": "universal", 6 | "platform": "ios", 7 | "size": "1024x1024" 8 | } 9 | ], 10 | "info": { 11 | "version": 1, 12 | "author": "expo" 13 | } 14 | } -------------------------------------------------------------------------------- /apps/mobile/src/components/layouts/tabbar/ReactNativeTab.tsx: -------------------------------------------------------------------------------- 1 | import { TabBarPortal } from "@/src/lib/navigation/bottom-tab/TabBarPortal" 2 | 3 | import { BottomTabs } from "./BottomTabs" 4 | 5 | export const ReactNativeTab = () => { 6 | return ( 7 | 8 | 9 | 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /apps/mobile/src/components/layouts/tabbar/contexts/BottomTabBarHeightContext.tsx: -------------------------------------------------------------------------------- 1 | import type { Dispatch, SetStateAction } from "react" 2 | import { createContext } from "react" 3 | 4 | export const BottomTabBarHeightContext = createContext(0) 5 | export const SetBottomTabBarHeightContext = createContext>>(null!) 6 | -------------------------------------------------------------------------------- /packages/internal/store/src/modules/user/types.ts: -------------------------------------------------------------------------------- 1 | import type { UserSchema } from "@follow/database/schemas/types" 2 | 3 | export interface UserProfileEditable { 4 | email: string 5 | name: string 6 | handle: string 7 | image: string 8 | bio?: string 9 | website?: string 10 | socialLinks?: UserSchema["socialLinks"] 11 | } 12 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/lib/client.ts: -------------------------------------------------------------------------------- 1 | import type { IpcServices } from "@follow/electron-main" 2 | import type { IpcRenderer } from "electron" 3 | import { createIpcProxy } from "electron-ipc-decorator/client" 4 | 5 | export const ipcServices = createIpcProxy( 6 | window.electron?.ipcRenderer as unknown as IpcRenderer, 7 | ) 8 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/action/utils.ts: -------------------------------------------------------------------------------- 1 | export const generateExportFilename = () => { 2 | const now = new Date() 3 | const dateStr = now.toISOString().split("T")[0] // YYYY-MM-DD 4 | const timeStr = now.toTimeString().split(" ")[0]?.replaceAll(":", "-") // HH-MM-SS 5 | return `follow-actions-${dateStr}-${timeStr}.json` 6 | } 7 | -------------------------------------------------------------------------------- /apps/mobile/src/components/ui/typography/MarkdownNative.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo } from "react" 2 | 3 | import { renderMarkdown } from "@/src/lib/markdown" 4 | 5 | export const MarkdownNative: WebComponent<{ 6 | value: string 7 | }> = ({ value }) => { 8 | return useMemo(() => { 9 | return renderMarkdown(value) 10 | }, [value]) 11 | } 12 | -------------------------------------------------------------------------------- /apps/mobile/src/modules/entry-list/EntryListContext.tsx: -------------------------------------------------------------------------------- 1 | import type { FeedViewType } from "@follow/constants" 2 | import { createContext, use } from "react" 3 | 4 | export const EntryListContextViewContext = createContext(null!) 5 | 6 | export const useEntryListContextView = () => { 7 | return use(EntryListContextViewContext) 8 | } 9 | -------------------------------------------------------------------------------- /apps/mobile/src/modules/settings/routes/Feeds.tsx: -------------------------------------------------------------------------------- 1 | import { View } from "react-native" 2 | 3 | import { Text } from "@/src/components/ui/typography/Text" 4 | 5 | export const FeedsScreen = () => { 6 | return ( 7 | 8 | Feeds Settings 9 | 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /packages/internal/logger/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@follow/configs/tsconfig.extend.json", 3 | "compilerOptions": { 4 | "noEmit": true, 5 | "baseUrl": ".", 6 | "jsx": "preserve", 7 | "declaration": true, 8 | "paths": { 9 | "@pkg": ["../../package.json"] 10 | } 11 | }, 12 | "include": ["**/*"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/internal/store/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { ModuleAPIs } from "@follow-app/client-sdk" 2 | 3 | export type GeneralMutationOptions = { 4 | onSuccess?: () => void 5 | onError?: (errorMessage: Error) => void 6 | } 7 | 8 | export type GeneralQueryOptions = { 9 | enabled?: boolean 10 | } 11 | 12 | export type FollowAPI = ModuleAPIs 13 | -------------------------------------------------------------------------------- /apps/ssr/client/@types/i18next.d.ts: -------------------------------------------------------------------------------- 1 | import type { defaultNS, ns } from "./constants" 2 | import type { defaultResources as resources } from "./default-resource" 3 | 4 | declare module "i18next" { 5 | interface CustomTypeOptions { 6 | ns: typeof ns 7 | resources: (typeof resources)["en"] 8 | defaultNS: typeof defaultNS 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/ssr/client/pages/(main)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { NotFound } from "@client/components/common/404" 2 | import * as React from "react" 3 | import { Outlet } from "react-router" 4 | 5 | export const Component = () => { 6 | if (document.documentElement.dataset.notFound === "true") { 7 | return 8 | } 9 | return 10 | } 11 | -------------------------------------------------------------------------------- /apps/desktop/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | const isWebBuild = !!process.env.WEB_BUILD || !!process.env.VERCEL 2 | 3 | module.exports = { 4 | plugins: { 5 | tailwindcss: {}, 6 | "tailwindcss/nesting": {}, 7 | 8 | ...(isWebBuild ? { autoprefixer: {} } : {}), 9 | ...(process.env.NODE_ENV === "production" ? { cssnano: {} } : {}), 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /apps/mobile/web-app/html-renderer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /apps/mobile/web-app/html-renderer/types/index.ts: -------------------------------------------------------------------------------- 1 | export interface MediaModel { 2 | url: string 3 | type: "photo" | "video" 4 | preview_image_url?: string 5 | width?: number 6 | height?: number 7 | blurhash?: string 8 | } 9 | 10 | export interface EntryModel { 11 | content?: string 12 | title?: string 13 | media?: MediaModel[] 14 | } 15 | -------------------------------------------------------------------------------- /packages/internal/components/src/hooks/useMouse.ts: -------------------------------------------------------------------------------- 1 | import { jotaiStore } from "@follow/utils" 2 | import { useAtomValue } from "jotai" 3 | 4 | import { mouseAtom } from "../atoms/mouse" 5 | 6 | export const useMousePosition = () => { 7 | return useAtomValue(mouseAtom) 8 | } 9 | 10 | export const getMousePosition = () => jotaiStore.get(mouseAtom) 11 | -------------------------------------------------------------------------------- /packages/internal/components/src/ui/scroll-area/ctx.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from "react" 2 | 3 | export const ScrollElementContext = createContext(document.documentElement) 4 | 5 | export const ScrollElementEventsContext = createContext<{ 6 | onUpdateMaxScroll?: () => void 7 | }>({ 8 | onUpdateMaxScroll: undefined, 9 | }) 10 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/components/errors/enum.ts: -------------------------------------------------------------------------------- 1 | export enum ErrorComponentType { 2 | Modal = "Modal", 3 | Page = "Page", 4 | 5 | // Feed 6 | FeedFoundCanBeFollow = "FeedFoundCanBeFollow", 7 | FeedNotFound = "FeedNotFound", 8 | // Section 9 | RSSHubDiscoverError = "RSSHubDiscoverError", 10 | EntryNotFound = "EntryNotFound", 11 | } 12 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/entry-column/Items/list-item.tsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from "@follow/components/ui/skeleton/index.js" 2 | 3 | export const ListItemSkeleton = ( 4 |
5 | 6 |
7 | ) 8 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/settings/tabs/ai/index.ts: -------------------------------------------------------------------------------- 1 | export { UserMemorySection } from "./memory/UserMemorySection" 2 | export { PanelStyleSection } from "./PanelStyleSection" 3 | export { PersonalizePromptSection } from "./PersonalizePromptSection" 4 | export { AIShortcutsSection } from "./shortcuts" 5 | export { UsageAnalysisSection } from "./usage" 6 | -------------------------------------------------------------------------------- /apps/mobile/src/components/layouts/views/NavigationHeaderContext.tsx: -------------------------------------------------------------------------------- 1 | import type { Dispatch, SetStateAction } from "react" 2 | import { createContext } from "react" 3 | 4 | export const NavigationHeaderHeightContext = createContext(null!) 5 | export const SetNavigationHeaderHeightContext = createContext>>( 6 | null!, 7 | ) 8 | -------------------------------------------------------------------------------- /packages/internal/store/src/modules/list/types.ts: -------------------------------------------------------------------------------- 1 | import type { ListSchema } from "@follow/database/schemas/types" 2 | 3 | export type CreateListModel = Pick & { 4 | title: string 5 | } 6 | 7 | export type ListModel = Omit & { 8 | feedIds: string[] 9 | type: "list" 10 | } 11 | -------------------------------------------------------------------------------- /packages/internal/store/src/modules/translation/types.ts: -------------------------------------------------------------------------------- 1 | export const translationFields = ["title", "description", "content", "readabilityContent"] as const 2 | export type TranslationField = (typeof translationFields)[number] 3 | export type TranslationFieldArray = Array 4 | export type EntryTranslation = Record 5 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/constants/copy.ts: -------------------------------------------------------------------------------- 1 | import { IN_ELECTRON } from "@follow/shared/constants" 2 | 3 | const OpenInBrowser = (_t?: any) => 4 | IN_ELECTRON 5 | ? tShortcuts("command.subscription.open_in_browser.title") 6 | : tShortcuts("command.subscription.open_in_tab.title") 7 | 8 | export const COPY_MAP = { 9 | OpenInBrowser, 10 | } 11 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/entry-content/components/layouts/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Shared props interface for all entry content layout components 3 | */ 4 | export interface EntryLayoutProps { 5 | entryId: string 6 | compact?: boolean 7 | noMedia?: boolean 8 | translation?: { 9 | content?: string 10 | title?: string 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /apps/mobile/ios/Assets.xcassets/home_5_cute_re.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "home_5_cute_re.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /apps/mobile/src/modules/settings/routes/Achievement.tsx: -------------------------------------------------------------------------------- 1 | import { View } from "react-native" 2 | 3 | import { Text } from "@/src/components/ui/typography/Text" 4 | 5 | export const AchievementScreen = () => { 6 | return ( 7 | 8 | Achievement Screen 9 | 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /apps/ssr/client/initialize/op.ts: -------------------------------------------------------------------------------- 1 | import { env } from "@follow/shared/env.ssr" 2 | import { OpenPanel } from "@openpanel/web" 3 | 4 | export const op = new OpenPanel({ 5 | clientId: env.VITE_OPENPANEL_CLIENT_ID ?? "", 6 | trackScreenViews: true, 7 | trackOutgoingLinks: true, 8 | trackAttributes: true, 9 | apiUrl: env.VITE_OPENPANEL_API_URL, 10 | }) 11 | -------------------------------------------------------------------------------- /apps/ssr/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "theme_color": "#ff5c00", 3 | "name": "Folo", 4 | "icons": [ 5 | { 6 | "src": "/icon-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/icon-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /packages/internal/utils/src/ns.ts: -------------------------------------------------------------------------------- 1 | const ns = "follow" 2 | export const getStorageNS = (key: string) => `${ns}:${key}` 3 | 4 | export const clearStorage = () => { 5 | for (let i = 0; i < localStorage.length; i++) { 6 | const key = localStorage.key(i) 7 | if (key && key.startsWith(ns)) { 8 | localStorage.removeItem(key) 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /apps/desktop/changelog/0.2.5.md: -------------------------------------------------------------------------------- 1 | # What's new in v0.2.5 2 | 3 | ## New Features 4 | 5 | - Customizable columns for masonry view 6 | - Manually trigger AI summary or translation 7 | 8 | ![](https://fastly.jsdelivr.net/gh/RSSNext/assets@main/masonry.mp4) 9 | 10 | ## Improvements 11 | 12 | ## Bug Fixes 13 | 14 | - Fixed some display and operation issues on mobile 15 | -------------------------------------------------------------------------------- /apps/desktop/changelog/0.3.6.md: -------------------------------------------------------------------------------- 1 | # What's new in v0.3.6 2 | 3 | ## New Features 4 | 5 | - Added a quick selector to the timeline column. 6 | 7 | ![](https://github.com/RSSNext/assets/raw/refs/heads/main/timeline-selector.mp4) 8 | 9 | ## Improvements 10 | 11 | ## Bug Fixes 12 | 13 | - Resolved the issue of being unable to reset it to empty after using the proxy. 14 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/ai-chat/components/message/index.ts: -------------------------------------------------------------------------------- 1 | export { AIMarkdownStreamingMessage } from "./AIMarkdownMessage" 2 | export { AIMessageParts } from "./AIMessageParts" 3 | export { ManageMemoryCard } from "./ManageMemoryCard" 4 | export { SaveMemoryCard } from "./SaveMemoryCard" 5 | export { ToolInvocationComponent } from "./ToolInvocationComponent" 6 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/user/utils.ts: -------------------------------------------------------------------------------- 1 | import type { UserModel } from "@follow/store/user/store" 2 | 3 | export const deduplicateUsers = (users: UserModel[]): UserModel[] => { 4 | const userMap = new Map() 5 | users.forEach((user) => { 6 | userMap.set(user.id, user) 7 | }) 8 | return Array.from(userMap.values()) 9 | } 10 | -------------------------------------------------------------------------------- /apps/mobile/ios/Assets.xcassets/search_3_cute_fi.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "search_3_cute_fi.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /apps/mobile/ios/Assets.xcassets/search_3_cute_re.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "search_3_cute_re.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /apps/mobile/src/modules/settings/routes/Notifications.tsx: -------------------------------------------------------------------------------- 1 | import { View } from "react-native" 2 | 3 | import { Text } from "@/src/components/ui/typography/Text" 4 | 5 | export const NotificationsScreen = () => { 6 | return ( 7 | 8 | Notifications Settings 9 | 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /apps/mobile/src/store/image/hooks.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query" 2 | 3 | import { imageSyncService } from "./store" 4 | 5 | export const usePrefetchImageColors = (url?: string | null) => { 6 | useQuery({ 7 | queryKey: ["image", "colors", url], 8 | queryFn: () => imageSyncService.getColors(url), 9 | enabled: !!url, 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /apps/ssr/client/global.d.ts: -------------------------------------------------------------------------------- 1 | // data hydrate 2 | 3 | // e.g. window.__HYDRATE__['feeds.$get,query:id=41223694984583197'] 4 | declare global { 5 | interface Window { 6 | __HYDRATE__: Record 7 | } 8 | 9 | export const GIT_COMMIT_SHA: string 10 | export const APP_VERSION: string 11 | export const APP_NAME: string 12 | } 13 | 14 | export {} 15 | -------------------------------------------------------------------------------- /packages/internal/constants/src/app.ts: -------------------------------------------------------------------------------- 1 | export const APPLE_APP_STORE_ID = "6739802604" 2 | export const GOOGLE_PLAY_PACKAGE_ID = "is.follow" 3 | 4 | export const APP_STORE_URLS = { 5 | iOS: `https://apps.apple.com/us/app/folo-follow-everything/id${APPLE_APP_STORE_ID}`, 6 | Android: `https://play.google.com/store/apps/details?id=${GOOGLE_PLAY_PACKAGE_ID}`, 7 | } as const 8 | -------------------------------------------------------------------------------- /apps/desktop/changelog/0.3.3.md: -------------------------------------------------------------------------------- 1 | # What's new in v0.3.3 2 | 3 | ## New Features 4 | 5 | ## Improvements 6 | 7 | - Merge actions for toggling state 8 | - Action supports matching custom title 9 | 10 | ## Bug Fixes 11 | 12 | - `Failed to set voice` error message appears every time the app starts 13 | - Can not replay TTS 14 | - Login state lost when restarting the app 15 | -------------------------------------------------------------------------------- /apps/mobile/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true) 3 | return { 4 | presets: [ 5 | ["babel-preset-expo", { jsxImportSource: "nativewind", unstable_transformImportMeta: true }], 6 | "nativewind/babel", 7 | ], 8 | plugins: [["inline-import", { extensions: [".sql"] }], "react-native-worklets/plugin"], 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /apps/mobile/ios/Assets.xcassets/settings_1_cute_fi.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "settings_1_cute_fi.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /apps/mobile/ios/Assets.xcassets/settings_1_cute_re.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "settings_1_cute_re.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /apps/mobile/src/components/layouts/tabbar/contexts/BottomTabBarVisibleContext.tsx: -------------------------------------------------------------------------------- 1 | import type { Dispatch, SetStateAction } from "react" 2 | import { createContext } from "react" 3 | 4 | export const SetBottomTabBarVisibleContext = createContext>>( 5 | () => {}, 6 | ) 7 | 8 | export const BottomTabBarVisibleContext = createContext(true) 9 | -------------------------------------------------------------------------------- /packages/internal/components/src/ui/lexical-rich-editor/plugins/index.ts: -------------------------------------------------------------------------------- 1 | export { CodeHighlightingPlugin } from "./code-highlighting" 2 | export { ExitCodeBoundaryPlugin } from "./exit-code" 3 | export { KeyboardPlugin } from "./keyboard" 4 | export { StringLengthChangePlugin } from "./string-length-change" 5 | export { TripleBacktickTogglePlugin } from "./triple-backtick-toggle" 6 | -------------------------------------------------------------------------------- /apps/desktop/changelog/0.2.3.md: -------------------------------------------------------------------------------- 1 | # What's new in v0.2.3 2 | 3 | ## New Features 4 | 5 | - Hold shift to quickly select multiple Feeds 6 | - Collect entry from List 7 | 8 | ## Improvements 9 | 10 | ## Bug Fixes 11 | 12 | - Action settings are invalid after loading archive entry 13 | - Redundant parameter on the transform form 14 | - Can't play normal video in video view 15 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/hooks/biz/useReduceMotion.ts: -------------------------------------------------------------------------------- 1 | import { useReducedMotion } from "motion/react" 2 | 3 | import { useUISettingKey } from "~/atoms/settings/ui" 4 | 5 | export const useReduceMotion = () => { 6 | const appReduceMotion = useUISettingKey("reduceMotion") 7 | const reduceMotion = useReducedMotion() 8 | return appReduceMotion || reduceMotion 9 | } 10 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/ai-chat/components/layouts/shared/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | EmptyState, 3 | type EmptyStateProps, 4 | SessionItem, 5 | type SessionItemProps, 6 | } from "./ChatSessionComponents" 7 | export { useChatSessionHandlers, type UseChatSessionHandlersProps } from "./useChatSessionHandlers" 8 | export { isTaskSession, isUnreadSession } from "./utils" 9 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/entry-content/components/entry-content/types.tsx: -------------------------------------------------------------------------------- 1 | // Export types that were defined in the original file 2 | export interface EntryContentProps { 3 | entryId: string 4 | noMedia?: boolean 5 | compact?: boolean 6 | classNames?: EntryContentClassNames 7 | } 8 | export interface EntryContentClassNames { 9 | header?: string 10 | } 11 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/store/search/constants.ts: -------------------------------------------------------------------------------- 1 | const SearchTypeBase = { 2 | Feed: 1, 3 | Entry: 1 << 1, 4 | Subscription: 1 << 2, 5 | } 6 | 7 | export const SearchType = { 8 | ...SearchTypeBase, 9 | All: Object.values(SearchTypeBase).reduce((acc, cur) => acc | cur, 0), 10 | } 11 | 12 | export type SearchType = (typeof SearchType)[keyof typeof SearchType] 13 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/styles/main.css: -------------------------------------------------------------------------------- 1 | @import "./base.css"; 2 | @import "./additional.css"; 3 | @import "./scrollbar.css"; 4 | @import "./cursor.css"; 5 | 6 | @media print { 7 | * { 8 | overflow: visible !important; 9 | page-break-after: avoid; 10 | page-break-before: avoid; 11 | break-inside: avoid; 12 | height: max-content; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/mobile/ios/Assets.xcassets/black_board_2_cute_fi.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "black_board_2_cute_fi.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /apps/mobile/ios/Assets.xcassets/black_board_2_cute_re.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "black_board_2_cute_re.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "template-rendering-intent" : "template" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/internal/store/src/modules/list/getters.ts: -------------------------------------------------------------------------------- 1 | import { useListStore } from "./store" 2 | 3 | export const getListById = (id: string) => { 4 | const get = () => useListStore.getState() 5 | return get().lists[id] 6 | } 7 | 8 | export const getListFeedIds = (id: string) => { 9 | const get = () => useListStore.getState() 10 | return get().lists[id]?.feedIds 11 | } 12 | -------------------------------------------------------------------------------- /apps/desktop/.env.example: -------------------------------------------------------------------------------- 1 | VITE_WEB_URL=http://localhost:5173 2 | VITE_API_URL=http://localhost:3000 3 | VITE_IMGPROXY_URL=http://localhost:2873 4 | VITE_SENTRY_DSN= 5 | VITE_BUILD_TYPE=production 6 | VITE_INBOXES_EMAIL=@follow.re 7 | VITE_OPENPANEL_CLIENT_ID= 8 | VITE_OPENPANEL_API_URL= 9 | 10 | VITE_EDITOR=cursor 11 | 12 | VITE_PUBLIC_POSTHOG_KEY= 13 | VITE_PUBLIC_POSTHOG_HOST= 14 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/initialize/op.ts: -------------------------------------------------------------------------------- 1 | import { env } from "@follow/shared/env.desktop" 2 | import { OpenPanel } from "@openpanel/web" 3 | 4 | export const op = new OpenPanel({ 5 | clientId: env.VITE_OPENPANEL_CLIENT_ID ?? "", 6 | trackScreenViews: true, 7 | trackOutgoingLinks: true, 8 | trackAttributes: true, 9 | apiUrl: env.VITE_OPENPANEL_API_URL, 10 | }) 11 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/queries/server-configs.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query" 2 | 3 | import { followApi } from "~/lib/api-client" 4 | 5 | export const useServerConfigsQuery = () => { 6 | const { data } = useQuery({ 7 | queryKey: ["server-configs"], 8 | queryFn: () => followApi.status.getConfigs(), 9 | }) 10 | return data?.data 11 | } 12 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/styles/cursor.css: -------------------------------------------------------------------------------- 1 | html, 2 | #shadow-html { 3 | --cursor-button: var(--pointer); 4 | --cursor-select: var(--pointer); 5 | --cursor-checkbox: var(--pointer); 6 | --cursor-link: var(--pointer); 7 | --cursor-menu: default; 8 | --cursor-radio: var(--pointer); 9 | --cursor-switch: var(--pointer); 10 | --cursor-card: var(--pointer); 11 | } 12 | -------------------------------------------------------------------------------- /apps/mobile/native/android/src/main/java/expo/modules/follownative/tabbar/TabScreenView.kt: -------------------------------------------------------------------------------- 1 | package expo.modules.follownative.tabbar 2 | 3 | import android.content.Context 4 | import expo.modules.kotlin.AppContext 5 | import expo.modules.kotlin.views.ExpoView 6 | 7 | 8 | class TabScreenView(context: Context, appContext: AppContext) : ExpoView(context, appContext) { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /icons/mgc/loading_3_cute_li.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/internal/hooks/src/optimistic/index.ts: -------------------------------------------------------------------------------- 1 | export { createOptimisticConfig } from "./config" 2 | export { optimisticStrategies } from "./strategies" 3 | export type { 4 | OptimisticContext, 5 | OptimisticItem, 6 | OptimisticMutationConfig, 7 | StrategyConfig, 8 | WithOptimistic, 9 | } from "./types" 10 | export { useOptimisticMutation } from "./useOptimisticMutation" 11 | -------------------------------------------------------------------------------- /packages/internal/store/src/modules/collection/getter.ts: -------------------------------------------------------------------------------- 1 | import { useCollectionStore } from "./store" 2 | 3 | export const isEntryStarred = (entryId: string): boolean => { 4 | return !!useCollectionStore.getState().collections[entryId] 5 | } 6 | 7 | export const getEntryCollections = (entryId: string) => { 8 | return useCollectionStore.getState().collections[entryId] 9 | } 10 | -------------------------------------------------------------------------------- /apps/desktop/build/entitlements.mas.child.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.inherit 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /apps/desktop/layer/main/src/bootstrap.ts: -------------------------------------------------------------------------------- 1 | import { app } from "electron" 2 | import squirrelStartup from "electron-squirrel-startup" 3 | 4 | import { DEVICE_ID } from "./constants/system" 5 | import { BootstrapManager } from "./manager/bootstrap" 6 | 7 | console.info("[main] device id:", DEVICE_ID) 8 | if (squirrelStartup) { 9 | app.quit() 10 | } 11 | 12 | BootstrapManager.start() 13 | -------------------------------------------------------------------------------- /apps/ssr/client/initialize/index.ts: -------------------------------------------------------------------------------- 1 | import { initI18n } from "@client/i18n" 2 | import { initializeDayjs } from "@follow/components/dayjs" 3 | 4 | import { initAnalytics } from "./analytics" 5 | import { initSentry } from "./sentry" 6 | 7 | export const initialize = async () => { 8 | initAnalytics() 9 | initializeDayjs() 10 | await Promise.all([initI18n(), initSentry()]) 11 | } 12 | -------------------------------------------------------------------------------- /apps/ssr/src/lib/not-found.ts: -------------------------------------------------------------------------------- 1 | import { FetchError } from "ofetch" 2 | 3 | export class NotFoundError extends Error { 4 | constructor(reason: string) { 5 | super(`Page not found: ${reason}`) 6 | } 7 | } 8 | export const callNotFound = (e: any) => { 9 | if (e instanceof FetchError && e.status === 404) { 10 | throw new NotFoundError(e.message) 11 | } 12 | throw e 13 | } 14 | -------------------------------------------------------------------------------- /packages/internal/tracker/src/adapters/index.ts: -------------------------------------------------------------------------------- 1 | export type { IdentifyPayload, TrackerAdapter, TrackPayload } from "./base" 2 | export { FirebaseAdapter, type FirebaseAdapterConfig } from "./firebase" 3 | export { OpenPanelAdapter, type OpenPanelAdapterConfig } from "./openpanel" 4 | export { PostHogAdapter, type PostHogAdapterConfig } from "./posthog" 5 | export { ProxyAdapter } from "./proxy" 6 | -------------------------------------------------------------------------------- /packages/internal/types/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | interface ImportMetaEnv { 4 | VITE_WEB_URL: string 5 | VITE_API_URL: string 6 | VITE_SENTRY_DSN: string 7 | VITE_OPENPANEL_CLIENT_ID: string 8 | VITE_OPENPANEL_API_URL: string 9 | VITE_FIREBASE_CONFIG: string 10 | } 11 | 12 | interface ImportMeta { 13 | readonly env: ImportMetaEnv 14 | } 15 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/entry-content/components/layouts/shared/index.ts: -------------------------------------------------------------------------------- 1 | export { AuthorHeader } from "./AuthorHeader" 2 | export { ContentBody } from "./ContentBody" 3 | export { MediaTranscript } from "./MediaTranscript" 4 | export { TranscriptToggle } from "./TranscriptToggle" 5 | export { useTranscription } from "./useTranscription" 6 | export { VideoPlayer } from "./VideoPlayer" 7 | -------------------------------------------------------------------------------- /apps/mobile/src/lib/op.ts: -------------------------------------------------------------------------------- 1 | import { OpenPanel } from "@follow/tracker/src/op" 2 | 3 | import { proxyEnv } from "./proxy-env" 4 | 5 | export const op = new OpenPanel({ 6 | clientId: proxyEnv.OPENPANEL_CLIENT_ID ?? "", 7 | apiUrl: proxyEnv.OPENPANEL_API_URL, 8 | headers: { 9 | Origin: "https://app.folo.is", 10 | }, 11 | sdk: "react-native", 12 | sdkVersion: "1.0.0", 13 | }) 14 | -------------------------------------------------------------------------------- /apps/mobile/src/modules/subscription/ctx.ts: -------------------------------------------------------------------------------- 1 | import type { FeedViewType } from "@follow/constants" 2 | import { createContext } from "react" 3 | 4 | // TODO: remove this context 5 | const ViewPageCurrentViewContext = createContext(null!) 6 | export const ViewPageCurrentViewProvider = ViewPageCurrentViewContext.Provider 7 | export const GroupedContext = createContext(null) 8 | -------------------------------------------------------------------------------- /packages/internal/components/src/icons/resize.tsx: -------------------------------------------------------------------------------- 1 | import type { SVGProps } from "react" 2 | 3 | export function LetsIconsResizeDownRightLight(props: SVGProps) { 4 | return ( 5 | 6 | 7 | 8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /packages/internal/tracker/src/types.ts: -------------------------------------------------------------------------------- 1 | export type Optional = Pick, K> & Omit 2 | 3 | export type IdentifyPayload = { 4 | id: string 5 | name?: string | null 6 | email?: string | null 7 | image?: string | null 8 | handle?: string | null 9 | } 10 | 11 | export type Tracker = (code: number, properties?: Record) => Promise 12 | -------------------------------------------------------------------------------- /apps/desktop/changelog/0.2.4.md: -------------------------------------------------------------------------------- 1 | # What's new in v0.2.4-web 2 | 3 | ## New Features 4 | 5 | 🎉 HUGE NEWS! Follow finally goes mobile! 6 | 7 | Ever wished you could Follow your favorite feeds while lounging on your couch? Well, now you can! We've made Follow fully responsive and mobile-friendly. Whether you're on your phone during your commute or browsing from bed (we won't judge), Follow's got your back! 8 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/components/ui/modal/stacked/bus.ts: -------------------------------------------------------------------------------- 1 | import { createEventBus } from "@follow/utils/event-bus" 2 | 3 | export const ModalEventBus = createEventBus<{ 4 | DISMISS: ModalDisposeEvent 5 | RE_PRESENT: ModalRePresentEvent 6 | }>() 7 | 8 | export type ModalDisposeEvent = { 9 | id: string 10 | } 11 | 12 | export type ModalRePresentEvent = { 13 | id: string 14 | } 15 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/constants/dom.ts: -------------------------------------------------------------------------------- 1 | export const ENTRY_CONTENT_RENDER_CONTAINER_ID = "follow-entry-render" 2 | 3 | export const LOGO_MOBILE_ID = "follow-logo-mobile" 4 | 5 | export const ENTRY_COLUMN_LIST_SCROLLER_ID = "entry-column-scroller" 6 | 7 | export const APP_GRID_CONTAINER_ID = "follow-app-grid-container" 8 | 9 | export const ROOT_CONTAINER_ID = "follow-root-container" 10 | -------------------------------------------------------------------------------- /apps/mobile/src/lib/navigation/bottom-tab/TabScreenContext.tsx: -------------------------------------------------------------------------------- 1 | import type { PrimitiveAtom } from "jotai" 2 | import { createContext } from "react" 3 | 4 | export interface TabScreenContextType { 5 | tabScreenIndex: number 6 | 7 | titleAtom: PrimitiveAtom 8 | identifierAtom: PrimitiveAtom 9 | } 10 | export const TabScreenContext = createContext(null!) 11 | -------------------------------------------------------------------------------- /apps/mobile/src/modules/discover/DiscoverContent.tsx: -------------------------------------------------------------------------------- 1 | import { View } from "react-native" 2 | 3 | import { Category } from "@/src/modules/discover/Category" 4 | import { Trending } from "@/src/modules/discover/Trending" 5 | 6 | export function DiscoverContent() { 7 | return ( 8 | 9 | 10 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /apps/ssr/client/initialize/helper.ts: -------------------------------------------------------------------------------- 1 | import { tracker } from "@follow/tracker" 2 | import type { AuthUser } from "@follow-app/client-sdk" 3 | 4 | export const setIntegrationIdentify = async (user: AuthUser) => { 5 | tracker.identify(user) 6 | 7 | await import("@sentry/react").then(({ setTag }) => { 8 | setTag("user_id", user.id) 9 | setTag("user_name", user.name) 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /packages/internal/components/src/ui/katex/lazy.tsx: -------------------------------------------------------------------------------- 1 | import { lazy, Suspense } from "react" 2 | 3 | import type { KateX } from "./index" 4 | 5 | const LazyKateX_ = lazy(() => import("./index").then((mod) => ({ default: mod.KateX }))) 6 | export const LazyKateX: typeof KateX = (props) => { 7 | return ( 8 | 9 | 10 | 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /packages/internal/store/src/modules/feed/selectors.ts: -------------------------------------------------------------------------------- 1 | import type { FeedModel } from "./types" 2 | 3 | export const feedIconSelector = (feed: FeedModel) => { 4 | return { 5 | type: feed.type, 6 | ownerUserId: feed.ownerUserId, 7 | id: feed.id, 8 | title: feed.title, 9 | url: (feed as any).url || "", 10 | image: feed.image, 11 | siteUrl: feed.siteUrl, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.github/advanced-issue-labeler.yml: -------------------------------------------------------------------------------- 1 | policy: 2 | - section: 3 | - id: [platform] 4 | block-list: ["None", "Other"] 5 | label: 6 | - name: "platform: desktop" 7 | keys: ["Desktop - macOS", "Desktop - Windows", "Desktop - Linux", "Desktop - Web"] 8 | - name: "platform: mobile" 9 | keys: ["Mobile - iOS", "Mobile - Android", "Mobile - Web"] 10 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/settings/tabs/ai/usage/types.ts: -------------------------------------------------------------------------------- 1 | // Chart Data Types 2 | export interface ChartDataPoint { 3 | label: string 4 | value: number 5 | } 6 | 7 | export interface BarListItem { 8 | label: string 9 | value: number 10 | right?: string 11 | } 12 | 13 | // Component Props Types 14 | export interface TokenCount { 15 | value: string 16 | unit: string 17 | } 18 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/providers/hotkey-provider.tsx: -------------------------------------------------------------------------------- 1 | import { HotkeysProvider } from "react-hotkeys-hook" 2 | 3 | import { GlobalHotkeysProvider } from "./global-hotkeys-provider" 4 | 5 | export const HotkeyProvider: Component = ({ children }) => { 6 | return ( 7 | 8 | {children} 9 | 10 | 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /apps/mobile/src/components/layouts/tabbar/contexts/BottomTabBarBackgroundContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext } from "react" 2 | import type { SharedValue } from "react-native-reanimated" 3 | 4 | interface TabBarBackgroundContextType { 5 | opacity: SharedValue 6 | } 7 | 8 | export const BottomTabBarBackgroundContext = createContext({ 9 | opacity: null!, 10 | }) 11 | -------------------------------------------------------------------------------- /apps/mobile/src/components/ui/pressable/IosItemPressable.tsx: -------------------------------------------------------------------------------- 1 | import type { FC } from "react" 2 | import type { ViewProps } from "react-native" 3 | 4 | const NativeItemPressable: FC< 5 | ViewProps & { 6 | onItemPress?: () => any 7 | touchHighlight?: boolean 8 | } 9 | > = () => { 10 | throw new Error("NativeItemPressable is not supported on iOS") 11 | } 12 | export { NativeItemPressable } 13 | -------------------------------------------------------------------------------- /apps/mobile/src/initialize/dayjs.ts: -------------------------------------------------------------------------------- 1 | import dayjs from "dayjs" 2 | import duration from "dayjs/plugin/duration" 3 | import localizedFormat from "dayjs/plugin/localizedFormat" 4 | import relativeTime from "dayjs/plugin/relativeTime" 5 | // Initialize dayjs 6 | export const initializeDayjs = () => { 7 | dayjs.extend(duration) 8 | dayjs.extend(relativeTime) 9 | dayjs.extend(localizedFormat) 10 | } 11 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/errors/CustomSafeError.ts: -------------------------------------------------------------------------------- 1 | import { nextFrame } from "@follow/utils/dom" 2 | 3 | import { createErrorToaster } from "~/lib/error-parser" 4 | 5 | export class CustomSafeError extends Error { 6 | constructor(message: string, toast?: boolean) { 7 | super(message) 8 | if (toast) { 9 | nextFrame(() => createErrorToaster(message)(this)) 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/initialize/helper.ts: -------------------------------------------------------------------------------- 1 | import { tracker } from "@follow/tracker" 2 | import type { AuthUser } from "@follow-app/client-sdk" 3 | 4 | export const setIntegrationIdentify = async (user: AuthUser) => { 5 | tracker.identify(user) 6 | await import("@sentry/react").then(({ setTag }) => { 7 | setTag("user_id", user.id) 8 | setTag("user_name", user.name) 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/entry-column/styles.ts: -------------------------------------------------------------------------------- 1 | import { readableContentMaxWidthClassName } from "~/constants/ui" 2 | 3 | export const girdClassNames = tw`grid grid-cols-1 @lg:grid-cols-2 @3xl:grid-cols-3 @6xl:grid-cols-4 @7xl:grid-cols-5 gap-1.5` 4 | 5 | // Shared max-width styles for readable content 6 | export const readableContentMaxWidth = tw`${readableContentMaxWidthClassName} mx-auto px-3` 7 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/settings/modal/use-setting-modal-hack.ts: -------------------------------------------------------------------------------- 1 | // HACK: Use expose the navigate function in the window object, avoid to import `router` circular issue. 2 | import type { SettingModalOptions } from "./useSettingModal" 3 | 4 | const showSettings = (args?: SettingModalOptions) => window.router.showSettings.call(null, args) 5 | 6 | export const useSettingModal = () => showSettings 7 | -------------------------------------------------------------------------------- /apps/mobile/web-app/html-renderer/src/common/ProviderComposer.tsx: -------------------------------------------------------------------------------- 1 | import type { JSX } from "react" 2 | import * as React from "react" 3 | 4 | export const ProviderComposer: Component<{ 5 | contexts: JSX.Element[] 6 | }> = ({ contexts, children }) => { 7 | return contexts.reduceRight((kids: any, parent: any) => { 8 | return React.cloneElement(parent, { children: kids }) 9 | }, children) 10 | } 11 | -------------------------------------------------------------------------------- /packages/internal/components/src/ui/sheet/context.tsx: -------------------------------------------------------------------------------- 1 | import { atom } from "jotai" 2 | import { createContext, use } from "react" 3 | 4 | interface SheetContextValue { 5 | dismiss: () => void 6 | } 7 | export const SheetContext = createContext(null) 8 | 9 | export const useSheetContext = () => use(SheetContext) 10 | export const sheetStackAtom = atom([] as HTMLDivElement[]) 11 | -------------------------------------------------------------------------------- /apps/desktop/changelog/0.2.6.md: -------------------------------------------------------------------------------- 1 | # What's new in v0.2.6 2 | 3 | We've made some ux optimizations: 4 | 5 | - [Mobile]: Now when returning to the list from the entry details, it will not return to the top of the page. 6 | - [Social Media]: Optimized the arrangement of multiple images with different aspect ratios. 7 | - [Social Media]: Long text auto-collapse. 8 | 9 | And many other bug fixes and improvements. 10 | -------------------------------------------------------------------------------- /apps/mobile/src/atoms/hooks/useDeviceType.ts: -------------------------------------------------------------------------------- 1 | import { jotaiStore } from "@follow/utils" 2 | import { useAtomValue } from "jotai" 3 | 4 | import { appAtoms } from "../app" 5 | 6 | export const useDeviceType = () => { 7 | const deviceType = useAtomValue(appAtoms.deviceType) 8 | return deviceType 9 | } 10 | 11 | export const getDeviceType = () => { 12 | return jotaiStore.get(appAtoms.deviceType) 13 | } 14 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/components/ui/media/MediaContainerWidthProvider.tsx: -------------------------------------------------------------------------------- 1 | import { MediaContainerWidthContext } from "./MediaContainerWidthContext" 2 | 3 | export const MediaContainerWidthProvider = ({ 4 | children, 5 | width, 6 | }: { 7 | children: React.ReactNode 8 | width: number 9 | }) => { 10 | return {children} 11 | } 12 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/ai-chat/editor/plugins/file-upload/types.ts: -------------------------------------------------------------------------------- 1 | export interface FileUploadPluginConfig { 2 | /** 3 | * Enable drag and drop file upload 4 | */ 5 | enableDragDrop?: boolean 6 | /** 7 | * Enable paste file upload 8 | */ 9 | enablePaste?: boolean 10 | } 11 | 12 | export interface FileDropZoneState { 13 | isDragOver: boolean 14 | dragCounter: number 15 | } 16 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/discover/utils.ts: -------------------------------------------------------------------------------- 1 | import type { RSSHubParameter, RSSHubParameterObject } from "@follow/models/rsshub" 2 | 3 | export const normalizeRSSHubParameters = ( 4 | parameters: RSSHubParameter, 5 | ): RSSHubParameterObject | null => 6 | parameters 7 | ? typeof parameters === "string" 8 | ? { description: parameters, default: null } 9 | : parameters 10 | : null 11 | -------------------------------------------------------------------------------- /packages/internal/atoms/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@follow/configs/tsconfig.extend.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "declaration": true, 6 | "types": ["@follow/types/react", "@follow/types/global", "vite/client"], 7 | "paths": { 8 | "@follow/atoms/*": ["./src/*"], 9 | "@pkg": ["../../package.json"] 10 | } 11 | }, 12 | "include": ["src/**/*"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/internal/database/src/drizzle/0018_dashing_the_fury.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `translations` ( 2 | `entry_id` text PRIMARY KEY NOT NULL, 3 | `language` text NOT NULL, 4 | `title` text NOT NULL, 5 | `description` text NOT NULL, 6 | `content` text NOT NULL, 7 | `created_at` text NOT NULL 8 | ); 9 | --> statement-breakpoint 10 | CREATE UNIQUE INDEX `translation-unique-index` ON `translations` (`entry_id`,`language`); -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 💬 Follow's Discord Server 4 | url: https://discord.gg/tUDVZjEr 5 | about: Want to discuss / chat with the community? Here you go! 6 | - name: Discuss an issue 7 | url: https://github.com/RSSNext/Follow/discussions 8 | about: For general questions, ideas, or non-bug related discussions, please use GitHub Discussions. 9 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/components/common/ProviderComposer.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import type { JSX } from "react" 4 | import { cloneElement } from "react" 5 | 6 | export const ProviderComposer: Component<{ 7 | contexts: JSX.Element[] 8 | }> = ({ contexts, children }) => 9 | contexts.reduceRight( 10 | (kids: any, parent: any) => cloneElement(parent, { children: kids }), 11 | children, 12 | ) 13 | -------------------------------------------------------------------------------- /apps/mobile/native/android/src/main/java/expo/modules/follownative/tabbar/TabBarPortalView.kt: -------------------------------------------------------------------------------- 1 | package expo.modules.follownative.tabbar 2 | 3 | import android.content.Context 4 | import expo.modules.kotlin.AppContext 5 | import expo.modules.kotlin.views.ExpoView 6 | import androidx.core.view.isNotEmpty 7 | 8 | class TabBarPortalView(context: Context, appContext: AppContext) : ExpoView(context, appContext) { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /apps/mobile/native/ios/Extensions/UIImage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage.swift 3 | // FollowNative 4 | // 5 | // Created by Innei on 2025/3/12. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIImage { 11 | func withAlpha(_ alpha: CGFloat) -> UIImage { 12 | return UIGraphicsImageRenderer(size: size).image { _ in 13 | draw(at: .zero, blendMode: .normal, alpha: alpha) 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /apps/mobile/src/components/ui/toast/constants.ts: -------------------------------------------------------------------------------- 1 | import { CheckCircleFilledIcon } from "@/src/icons/check_circle_filled" 2 | import { CloseCircleFillIcon } from "@/src/icons/close_circle_fill" 3 | import { InfoCircleFillIcon } from "@/src/icons/info_circle_fill" 4 | 5 | export const toastTypeToIcons = { 6 | success: CheckCircleFilledIcon, 7 | error: CloseCircleFillIcon, 8 | info: InfoCircleFillIcon, 9 | } as const 10 | -------------------------------------------------------------------------------- /packages/internal/store/src/modules/user/getters.ts: -------------------------------------------------------------------------------- 1 | import { useUserStore } from "./store" 2 | 3 | export const whoami = () => { 4 | return useUserStore.getState().whoami 5 | } 6 | 7 | export const role = () => { 8 | return useUserStore.getState().role 9 | } 10 | 11 | export const getUserList = (userIds: string[]) => { 12 | return userIds.map((id) => useUserStore.getState().users[id]).filter((i) => !!i) 13 | } 14 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/lib/log.ts: -------------------------------------------------------------------------------- 1 | import { log } from "@follow/logger" 2 | 3 | export const appLog = (...args: any[]) => { 4 | if (ELECTRON) log(...args) 5 | console.info( 6 | `%c ${APP_NAME} %c`, 7 | "color: #fff; margin: 0; padding: 5px 0; background: #ff5c00; border-radius: 3px;", 8 | ...args.reduce((acc, cur) => { 9 | acc.push("", cur) 10 | return acc 11 | }, []), 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /apps/mobile/src/components/layouts/header/hooks.ts: -------------------------------------------------------------------------------- 1 | import { use } from "react" 2 | 3 | import { NavigationHeaderHeightContext } from "../views/NavigationHeaderContext" 4 | 5 | export const useNavigationHeaderHeight = () => { 6 | const headerHeight = use(NavigationHeaderHeightContext) 7 | if (!headerHeight) { 8 | throw new Error("NavigationHeaderHeightContext is not found") 9 | } 10 | return headerHeight 11 | } 12 | -------------------------------------------------------------------------------- /apps/ssr/client/atoms/user.ts: -------------------------------------------------------------------------------- 1 | import { createAtomHooks } from "@follow/utils/jotai" 2 | import type { AuthUser } from "@follow-app/client-sdk" 3 | import { atom } from "jotai" 4 | 5 | export const [, , useWhoami, , whoami, setWhoami] = createAtomHooks(atom>(null)) 6 | 7 | export const [, , useLoginModalShow, useSetLoginModalShow, getLoginModalShow, setLoginModalShow] = 8 | createAtomHooks(atom(false)) 9 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/subscription-column/hook.ts: -------------------------------------------------------------------------------- 1 | import { useDndContext } from "@dnd-kit/core" 2 | 3 | import { useFeedAreaScrollProgressValue } from "./atom" 4 | 5 | export function useShouldFreeUpSpace() { 6 | const dndContext = useDndContext() 7 | const isDragging = !!dndContext.active 8 | const scrollProgress = useFeedAreaScrollProgressValue() 9 | return isDragging && scrollProgress === 0 10 | } 11 | -------------------------------------------------------------------------------- /apps/mobile/web-app/html-renderer/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import { extendConfig } from "@follow/configs/tailwindcss/web" 2 | import path from "pathe" 3 | 4 | const rootDir = path.resolve(__dirname, "../../../..") 5 | 6 | export default extendConfig({ 7 | darkMode: "media", 8 | future: { hoverOnlyWhenSupported: true }, 9 | content: ["./src/**/*.{ts,tsx}", path.resolve(rootDir, "packages/components/src/**/*.{ts,tsx}")], 10 | }) 11 | -------------------------------------------------------------------------------- /apps/ssr/client/initialize/analytics.ts: -------------------------------------------------------------------------------- 1 | import { env } from "@follow/shared/env.ssr" 2 | import { setOpenPanelTracker } from "@follow/tracker" 3 | 4 | import { op } from "./op" 5 | 6 | export const initAnalytics = () => { 7 | if (env.VITE_OPENPANEL_CLIENT_ID === undefined) return 8 | 9 | setOpenPanelTracker(op) 10 | op.setGlobalProperties({ 11 | build: "external-web", 12 | hash: GIT_COMMIT_SHA, 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /icons/mgc/line_cute_re.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icons/mgc/loading_3_cute_re.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/desktop/layer/main/src/lib/i18n.ts: -------------------------------------------------------------------------------- 1 | import i18next from "i18next" 2 | 3 | import { resources } from "../@types/resources" 4 | 5 | export const defaultNS = "native" 6 | 7 | export const i18n = i18next.createInstance() as typeof i18next 8 | 9 | i18n.init({ 10 | fallbackLng: { 11 | default: ["en"], 12 | "zh-TW": ["zh-CN", "en"], 13 | }, 14 | defaultNS, 15 | resources, 16 | }) 17 | 18 | export const { t } = i18n 19 | -------------------------------------------------------------------------------- /apps/desktop/layer/main/src/updater/windows-updater.ts: -------------------------------------------------------------------------------- 1 | import { app } from "electron" 2 | import { NsisUpdater } from "electron-updater" 3 | import { DownloadedUpdateHelper } from "electron-updater/out/DownloadedUpdateHelper" 4 | 5 | export class WindowsUpdater extends NsisUpdater { 6 | protected override downloadedUpdateHelper: DownloadedUpdateHelper = new DownloadedUpdateHelper( 7 | app.getPath("sessionData"), 8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/settings/tabs/ai/byok/constants.ts: -------------------------------------------------------------------------------- 1 | import type { ByokProviderName } from "@follow/shared/settings/interface" 2 | 3 | export const PROVIDER_OPTIONS: { value: ByokProviderName; label: string }[] = [ 4 | { value: "openai", label: "OpenAI" }, 5 | { value: "google", label: "Google" }, 6 | { value: "vercel-ai-gateway", label: "Vercel AI Gateway" }, 7 | { value: "openrouter", label: "OpenRouter" }, 8 | ] 9 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/subscription-column/sort-by/types.tsx: -------------------------------------------------------------------------------- 1 | import type { FeedViewType } from "@follow/constants" 2 | 3 | export type FeedListProps = { 4 | view: FeedViewType 5 | data: Record 6 | categoryOpenStateData: Record 7 | } 8 | export type SortBy = "count" | "alphabetical" 9 | 10 | export type ListListProps = { 11 | view: FeedViewType 12 | data: string[] 13 | } 14 | -------------------------------------------------------------------------------- /apps/mobile/ios/Folo/Folo.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | com.apple.developer.applesignin 8 | 9 | Default 10 | 11 | 12 | -------------------------------------------------------------------------------- /apps/mobile/native/android/src/main/java/expo/modules/follownative/tabbar/TabScreenModule.kt: -------------------------------------------------------------------------------- 1 | package expo.modules.follownative.tabbar 2 | 3 | import expo.modules.kotlin.modules.Module 4 | import expo.modules.kotlin.modules.ModuleDefinition 5 | 6 | class TabScreenModule : Module() { 7 | override fun definition() = ModuleDefinition { 8 | Name("TabScreen") 9 | 10 | View(TabScreenView::class) { 11 | 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/mobile/native/ios/Packages/ImageViewer_swift/ImageCarouselViewControllerProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageCarouselViewControllerProtocol.swift 3 | // FollowNative 4 | // 5 | // Created by Innei on 2025/3/12. 6 | // 7 | 8 | import UIKit 9 | 10 | protocol ImageCarouselViewControllerProtocol { 11 | func saveImageToPhotos() 12 | func copyImageToClipboard() 13 | func shareImage() 14 | 15 | func isLoadError() -> Bool 16 | } 17 | -------------------------------------------------------------------------------- /apps/ssr/note.md: -------------------------------------------------------------------------------- 1 | Rewrite: 2 | 3 | Test url: 4 | 5 | http://localhost:2234/share/feeds/41223694984583197 6 | http://localhost:2234/share/feeds/41375451836487680?view=2 7 | http://localhost:2234/share/feeds/41147805276726317?view=2 8 | http://localhost:2234/share/lists/61046160274909184 9 | 10 | http://localhost:2234/og/list/61046160274909184 11 | http://localhost:2234/og/user/Innei 12 | http://localhost:2234/og/feed/41223694984583170 13 | -------------------------------------------------------------------------------- /apps/desktop/changelog/0.7.0.md: -------------------------------------------------------------------------------- 1 | # What's new in v0.7.0 2 | 3 | ## Shiny new things 4 | 5 | - Add custom integration configurations to adapt to more apps 6 | 7 | ## Improvements 8 | 9 | - Redesign integration settings page for better user experience and support integration settings export and import. 10 | 11 | ## No longer broken 12 | 13 | ## Thanks 14 | 15 | Special thanks to volunteer contributors @ for their valuable contributions 16 | -------------------------------------------------------------------------------- /apps/desktop/vite.config.electron-render.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "pathe" 2 | import { defineConfig } from "vite" 3 | 4 | import config from "./configs/vite.electron-render.config" 5 | import compressAndFingerprintPlugin from "./plugins/vite/compress" 6 | 7 | export default defineConfig({ 8 | ...config, 9 | base: "./", 10 | plugins: [...config.plugins, compressAndFingerprintPlugin(resolve(import.meta.dirname, "dist"))], 11 | }) 12 | -------------------------------------------------------------------------------- /apps/mobile/src/components/ui/qrcode/constants.ts: -------------------------------------------------------------------------------- 1 | export const OUTER_EYE_SIZE_IN_BITS = 7 2 | export const INNER_EYE_SIZE_IN_BITS = 3 3 | 4 | export const EYES_POSITIONS = ["topLeft", "topRight", "bottomLeft"] 5 | 6 | // QR code error collection percentages 7 | export const QR_ECL_PERS = { 8 | L: 0.03, 9 | M: 0.06, 10 | Q: 0.1, 11 | H: 0.14, 12 | low: 0.03, 13 | medium: 0.06, 14 | quartile: 0.1, 15 | high: 0.14, 16 | } 17 | -------------------------------------------------------------------------------- /icons/mgc/attachment_cute_re.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/internal/store/src/modules/image/hooks.ts: -------------------------------------------------------------------------------- 1 | import { useCallback } from "react" 2 | 3 | import { useImagesStore } from "./store" 4 | 5 | export const useImageColors = (url?: string | null) => { 6 | return useImagesStore( 7 | useCallback( 8 | (state) => { 9 | if (!url) { 10 | return 11 | } 12 | return state.images[url]?.colors 13 | }, 14 | [url], 15 | ), 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/atoms/dom.ts: -------------------------------------------------------------------------------- 1 | import { atom } from "jotai" 2 | 3 | import { createAtomHooks } from "~/lib/jotai" 4 | 5 | export const [, , useMainContainerElement, , getMainContainerElement, setMainContainerElement] = 6 | createAtomHooks(atom(null)) 7 | 8 | export const [, , useRootContainerElement, , getRootContainerElement, setRootContainerElement] = 9 | createAtomHooks(atom(null)) 10 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/ai-chat/hooks/useAIConfiguration.ts: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query" 2 | 3 | import { followApi } from "~/lib/api-client" 4 | 5 | export const useAIConfiguration = () => { 6 | return useQuery({ 7 | queryKey: ["aiConfiguration"], 8 | queryFn: async () => { 9 | return followApi.ai.config() 10 | }, 11 | staleTime: 5 * 60 * 1000, 12 | retry: false, 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/subscription-column/context.ts: -------------------------------------------------------------------------------- 1 | import type { DraggableAttributes, DraggableSyntheticListeners } from "@dnd-kit/core" 2 | import type { CSSProperties } from "react" 3 | import { createContext } from "react" 4 | 5 | export const DraggableContext = createContext<{ 6 | attributes: DraggableAttributes 7 | listeners: DraggableSyntheticListeners 8 | style?: CSSProperties | undefined 9 | } | null>(null) 10 | -------------------------------------------------------------------------------- /packages/internal/components/src/ui/lexical-rich-editor/index.ts: -------------------------------------------------------------------------------- 1 | export { createDefaultLexicalEditor, createLexicalEditor } from "./editor" 2 | export { LexicalRichEditor } from "./LexicalRichEditor" 3 | export { LexicalRichEditorTextArea } from "./LexicalRichEditorTextArea" 4 | export { LexicalRichEditorNodes } from "./nodes" 5 | export { KeyboardPlugin } from "./plugins" 6 | export { defaultLexicalTheme } from "./theme" 7 | export type * from "./types" 8 | -------------------------------------------------------------------------------- /packages/internal/models/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@follow/configs/tsconfig.extend.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "jsx": "preserve", 6 | "declaration": true, 7 | "types": ["@follow/types/react", "@follow/types/global", "vite/client"], 8 | "paths": { 9 | "@follow/models/*": ["./src/*"], 10 | "@pkg": ["../../package.json"] 11 | } 12 | }, 13 | "include": ["src/**/*"] 14 | } 15 | -------------------------------------------------------------------------------- /apps/mobile/native/android/src/main/java/expo/modules/follownative/tabbar/TabBarPortalModule.kt: -------------------------------------------------------------------------------- 1 | package expo.modules.follownative.tabbar 2 | 3 | import expo.modules.kotlin.modules.Module 4 | import expo.modules.kotlin.modules.ModuleDefinition 5 | 6 | class TabBarPortalModule : Module() { 7 | override fun definition() = ModuleDefinition { 8 | Name("TabBarPortal") 9 | 10 | View(TabBarPortalView::class) { 11 | 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/mobile/src/components/ui/grouped/constants.ts: -------------------------------------------------------------------------------- 1 | import { PixelRatio } from "react-native" 2 | 3 | const pixelRatio = PixelRatio.get() 4 | export const GROUPED_ICON_TEXT_GAP = 36 / pixelRatio 5 | 6 | export const GROUPED_LIST_MARGIN = 48 / pixelRatio 7 | 8 | export const GROUPED_LIST_ITEM_PADDING = 52 / pixelRatio 9 | 10 | export const GROUPED_SECTION_TOP_MARGIN = 87 / pixelRatio 11 | export const GROUPED_SECTION_BOTTOM_MARGIN = 36 / pixelRatio 12 | -------------------------------------------------------------------------------- /apps/mobile/web-app/html-renderer/src/atoms/index.ts: -------------------------------------------------------------------------------- 1 | import { atom } from "jotai" 2 | 3 | import type { EntryModel } from "../../types" 4 | 5 | export const entryAtom = atom(null) 6 | 7 | export const codeThemeLightAtom = atom(null) 8 | export const codeThemeDarkAtom = atom(null) 9 | export const readerRenderInlineStyleAtom = atom(false) 10 | export const noMediaAtom = atom(false) 11 | -------------------------------------------------------------------------------- /packages/internal/store/src/modules/summary/utils.ts: -------------------------------------------------------------------------------- 1 | import type { SupportedActionLanguage } from "@follow/shared/language" 2 | 3 | export function getGenerateSummaryStatusId( 4 | entryId: string, 5 | actionLanguage: SupportedActionLanguage, 6 | target: "content" | "readabilityContent", 7 | ): StatusID { 8 | return `${entryId}-${actionLanguage}-${target}` as StatusID 9 | } 10 | 11 | export type StatusID = `${string}-${string}-${string}` 12 | -------------------------------------------------------------------------------- /packages/internal/tracker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@follow/tracker", 3 | "version": "0.0.1", 4 | "private": true, 5 | "main": "./src/index.ts", 6 | "peerDependencies": { 7 | "posthog-js": "1.255.0", 8 | "posthog-react-native": "4.6.1" 9 | }, 10 | "devDependencies": { 11 | "@follow-app/client-sdk": "catalog:", 12 | "@follow/configs": "workspace:*", 13 | "@react-native-firebase/analytics": "22.2.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /apps/desktop/changelog/0.3.2.md: -------------------------------------------------------------------------------- 1 | # What's new in v0.3.2 2 | 3 | ## New Features 4 | 5 | - Added support for Two-Factor Authentication (2FA) for login and large transactions. 6 | 7 | ## Improvements 8 | 9 | - Enhanced detection for translation needs and improved display of translated text. 10 | 11 | ## Bug Fixes 12 | 13 | - Resolved an issue where some read marks were missed during fast scrolling. 14 | - Unable to copy inbox email address correctly 15 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/assets/rsshub.svg: -------------------------------------------------------------------------------- 1 | RSSHub -------------------------------------------------------------------------------- /packages/internal/components/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@follow/configs/tsconfig.extend.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "jsx": "preserve", 6 | "declaration": true, 7 | "types": ["@follow/types/react", "@follow/types/global", "vite/client"], 8 | "paths": { 9 | "@follow/components/*": ["./src/*"], 10 | "@pkg": ["../../package.json"] 11 | } 12 | }, 13 | "include": ["src/**/*"] 14 | } 15 | -------------------------------------------------------------------------------- /packages/internal/constants/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@follow/configs/tsconfig.extend.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "declaration": true, 6 | "types": ["@follow/types/react", "@follow/types/global", "vite/client"], 7 | "paths": { 8 | "@follow/constants/*": ["./src/*"], 9 | "@pkg": ["../../package.json"] 10 | } 11 | }, 12 | "include": ["src/**/*", "../../../../locales/**/*.json"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/internal/hooks/src/useRefValue.ts: -------------------------------------------------------------------------------- 1 | import { useLayoutEffect, useRef } from "react" 2 | 3 | export const useRefValue = ( 4 | value: S, 5 | ): Readonly<{ 6 | // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type 7 | current: S extends Function ? S : Readonly 8 | }> => { 9 | const ref = useRef(value) 10 | 11 | useLayoutEffect(() => { 12 | ref.current = value 13 | }, [value]) 14 | return ref as any 15 | } 16 | -------------------------------------------------------------------------------- /apps/mobile/src/components/ui/pressable/NativePressable.ios.tsx: -------------------------------------------------------------------------------- 1 | import { NativeItemPressable } from "./IosItemPressable" 2 | import type { NativePressableProps } from "./NativePressable.types" 3 | 4 | export const NativePressable = ({ children, onPress, ...props }: NativePressableProps) => { 5 | return ( 6 | 7 | {children} 8 | 9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /apps/mobile/src/lib/native/user-agent.ts: -------------------------------------------------------------------------------- 1 | import { nativeApplicationVersion, nativeBuildVersion } from "expo-application" 2 | import Constants from "expo-constants" 3 | 4 | const expoUserAgentPromise = Constants.getWebViewUserAgentAsync() 5 | 6 | export const getUserAgent = async () => { 7 | const expoUserAgent = await expoUserAgentPromise 8 | return `${expoUserAgent ? `${expoUserAgent} ` : ""}Folo/${nativeApplicationVersion}.${nativeBuildVersion}` 9 | } 10 | -------------------------------------------------------------------------------- /packages/internal/store/src/modules/unread/utils.ts: -------------------------------------------------------------------------------- 1 | // Inbox subscription's feedId is `inbox-${inboxId}`, we need to convert it between unread and entry store. 2 | export const INBOX_PREFIX_ID = "inbox-" 3 | export const getInboxHandleOrFeedIdFromFeedId = (id: string) => 4 | id.startsWith(INBOX_PREFIX_ID) ? id.slice(INBOX_PREFIX_ID.length) : id 5 | export const getInboxFeedIdWithPrefix = (id: string) => 6 | id.startsWith(INBOX_PREFIX_ID) ? id : INBOX_PREFIX_ID + id 7 | -------------------------------------------------------------------------------- /packages/internal/utils/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./attribution" 2 | export * from "./bind-this" 3 | export * from "./cjk" 4 | export * from "./color" 5 | export * from "./data-structure/set" 6 | export * from "./dom" 7 | export * from "./duration" 8 | export * from "./jotai" 9 | export * from "./noop" 10 | export * from "./path-parser" 11 | export * from "./react" 12 | export * from "./resize" 13 | export * from "./url-for-video" 14 | export * from "./utils" 15 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/ai-chat/editor/index.ts: -------------------------------------------------------------------------------- 1 | import { LexicalRichEditorNodes } from "@follow/components/ui/lexical-rich-editor/nodes.js" 2 | 3 | import { FileAttachmentNode, MentionNode, SelectedTextNode, ShortcutNode } from "./plugins" 4 | 5 | export * from "./plugins" 6 | 7 | export const LexicalAIEditorNodes = [ 8 | ...LexicalRichEditorNodes, 9 | MentionNode, 10 | ShortcutNode, 11 | FileAttachmentNode, 12 | SelectedTextNode, 13 | ] 14 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/ai-chat/editor/plugins/shortcut/constants.ts: -------------------------------------------------------------------------------- 1 | import { createCommand } from "lexical" 2 | 3 | import type { ShortcutData } from "./types" 4 | 5 | export const SHORTCUT_COMMAND = createCommand("SHORTCUT_COMMAND") 6 | 7 | export const DEFAULT_MAX_SHORTCUT_SUGGESTIONS = 10 8 | 9 | export const SHORTCUT_TRIGGER_PATTERN = 10 | /(?:^|\s)(\/[\w\-\s\u4e00-\u9fff\u3400-\u4dbf\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]*)$/ 11 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/providers/user-provider.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react" 2 | 3 | import { setIntegrationIdentify } from "~/initialize/helper" 4 | import { useSession } from "~/queries/auth" 5 | 6 | export const UserProvider = () => { 7 | const { session } = useSession() 8 | 9 | useEffect(() => { 10 | if (!session?.user) return 11 | 12 | setIntegrationIdentify(session.user) 13 | }, [session?.user]) 14 | 15 | return null 16 | } 17 | -------------------------------------------------------------------------------- /packages/internal/components/src/ui/platform-icon/icons.ts: -------------------------------------------------------------------------------- 1 | export * from "./collections/cubox" 2 | export * from "./collections/eagle" 3 | export * from "./collections/instapaper" 4 | export * from "./collections/obsidian" 5 | export * from "./collections/outline" 6 | export * from "./collections/readeck" 7 | export * from "./collections/readwise" 8 | export * from "./collections/rss3" 9 | export * from "./collections/rsshub" 10 | export * from "./collections/zotero" 11 | -------------------------------------------------------------------------------- /packages/internal/hooks/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@follow/configs/tsconfig.extend.json", 3 | "compilerOptions": { 4 | "noEmit": true, 5 | "baseUrl": ".", 6 | "jsx": "preserve", 7 | "declaration": true, 8 | "types": ["@follow/types/react", "@follow/types/global", "vite/client"], 9 | "paths": { 10 | "@follow/hooks/*": ["./src/*"], 11 | "@pkg": ["../../package.json"] 12 | } 13 | }, 14 | "include": ["src/**/*"] 15 | } 16 | -------------------------------------------------------------------------------- /packages/internal/logger/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@follow/logger", 3 | "private": true, 4 | "exports": { 5 | ".": { 6 | "types": "./electron.ts", 7 | "web": "./web.ts", 8 | "default": "./electron.ts" 9 | } 10 | }, 11 | "scripts": { 12 | "typecheck": "tsc --noEmit" 13 | }, 14 | "dependencies": { 15 | "electron-log": "5.4.3" 16 | }, 17 | "devDependencies": { 18 | "@follow/configs": "workspace:*" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/ai-chat/store/event-system/types.ts: -------------------------------------------------------------------------------- 1 | import type { ChatStatus } from "ai" 2 | 3 | import type { BizUIMessage } from "../types" 4 | 5 | // Event types and payloads 6 | export interface ChatStateEvents { 7 | messages: { messages: UI_MESSAGE[] } 8 | status: { status: ChatStatus } 9 | error: { error: Error | undefined } 10 | } 11 | 12 | export type ChatStateEventType = keyof ChatStateEvents 13 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/ai-chat/types/ChatSession.ts: -------------------------------------------------------------------------------- 1 | import type { SerializedEditorState } from "lexical" 2 | 3 | export interface ChatSession { 4 | chatId: string 5 | title?: string 6 | createdAt: Date 7 | updatedAt: Date 8 | isLocal: boolean 9 | syncStatus: "local" | "synced" 10 | } 11 | 12 | export type RichTextPart = { 13 | type: "data-rich-text" 14 | data: { 15 | state: SerializedEditorState 16 | text: string 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/entry-column/layouts/AppendTaildingDivider.tsx: -------------------------------------------------------------------------------- 1 | import { DividerVertical } from "@follow/components/ui/divider/Divider.js" 2 | import * as React from "react" 3 | 4 | export const AppendTaildingDivider = ({ children }: { children: React.ReactNode }) => ( 5 | <> 6 | {children} 7 | {React.Children.toArray(children).filter(Boolean).length > 0 && ( 8 | 9 | )} 10 | 11 | ) 12 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/settings/constants.ts: -------------------------------------------------------------------------------- 1 | export const SETTING_MODAL_ID = "setting-modal" 2 | 3 | export const GUEST_ALLOWED_SETTING_TABS = ["general", "appearance", "about", "shortcuts"] as const 4 | 5 | const GUEST_ALLOWED_SETTING_TABS_SET = new Set(GUEST_ALLOWED_SETTING_TABS) 6 | 7 | export const isGuestAccessibleSettingTab = (tab?: string | null) => { 8 | if (!tab) return false 9 | return GUEST_ALLOWED_SETTING_TABS_SET.has(tab) 10 | } 11 | -------------------------------------------------------------------------------- /apps/mobile/native/ios/Modules/TabBar/TabBarPortalModule.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabBarPortalModule.swift 3 | // FollowNative 4 | // 5 | // Created by Innei on 2025/3/17. 6 | // 7 | 8 | import ExpoModulesCore 9 | import UIKit 10 | 11 | public class TabBarPortalModule: Module { 12 | public func definition() -> ModuleDefinition { 13 | Name("TabBarPortal") 14 | 15 | View(TabBarPortalView.self) {} 16 | } 17 | } 18 | 19 | class TabBarPortalView: ExpoView { 20 | 21 | } 22 | -------------------------------------------------------------------------------- /apps/mobile/src/components/native/webview/native-webview.tsx: -------------------------------------------------------------------------------- 1 | import { requireNativeView } from "expo" 2 | import type * as React from "react" 3 | import type { ViewProps } from "react-native" 4 | 5 | export const NativeWebView: React.ComponentType< 6 | ViewProps & { 7 | onContentHeightChange?: (e: { nativeEvent: { height: number } }) => void 8 | onSeekAudio?: (e: { time: number }) => void 9 | url?: string 10 | } 11 | > = requireNativeView("FOSharedWebView") 12 | -------------------------------------------------------------------------------- /apps/mobile/web-app/html-renderer/src/components/p.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { IsInParagraphContext } from "./__internal/ctx" 4 | 5 | export const MarkdownP: Component< 6 | React.DetailedHTMLProps, HTMLParagraphElement> 7 | > = ({ children, ...props }) => { 8 | return ( 9 |

10 | {children} 11 |

12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /apps/mobile/changelog/0.2.5.md: -------------------------------------------------------------------------------- 1 | # What's New in v0.2.5 2 | 3 | ## Shiny new things 4 | 5 | - Add option for independent content font size and presets 6 | 7 | ## Improvements 8 | 9 | ## No longer broken 10 | 11 | - Address the issue of being unable to follow the feed in specific scenarios 12 | - Fixed the issue where entries opened from notifications could not be loaded 13 | 14 | ## Thanks 15 | 16 | Special thanks to volunteer contributors @ for their valuable contributions 17 | -------------------------------------------------------------------------------- /apps/mobile/ios/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | .xcode.env.local 25 | 26 | # Bundle artifacts 27 | *.jsbundle 28 | 29 | # CocoaPods 30 | /Pods/ 31 | -------------------------------------------------------------------------------- /apps/mobile/src/components/ui/pressable/IosItemPressable.ios.tsx: -------------------------------------------------------------------------------- 1 | import { requireNativeView } from "expo" 2 | import { cssInterop } from "nativewind" 3 | import type { ViewProps } from "react-native" 4 | 5 | const NativeItemPressable = requireNativeView< 6 | ViewProps & { 7 | onItemPress?: () => any 8 | touchHighlight?: boolean 9 | } 10 | >("ItemPressable") 11 | cssInterop(NativeItemPressable, { 12 | className: "style", 13 | }) 14 | export { NativeItemPressable } 15 | -------------------------------------------------------------------------------- /apps/mobile/src/constants/spring.ts: -------------------------------------------------------------------------------- 1 | import type { WithSpringConfig } from "react-native-reanimated" 2 | 3 | export const gentleSpringPreset: WithSpringConfig = { 4 | damping: 15, 5 | stiffness: 100, 6 | mass: 1, 7 | } 8 | 9 | export const softSpringPreset: WithSpringConfig = { 10 | damping: 20, 11 | stiffness: 80, 12 | mass: 1, 13 | } 14 | 15 | export const quickSpringPreset: WithSpringConfig = { 16 | damping: 10, 17 | stiffness: 200, 18 | mass: 1, 19 | } 20 | -------------------------------------------------------------------------------- /apps/mobile/src/lib/navigation/ScreenNameContext.tsx: -------------------------------------------------------------------------------- 1 | import type { PrimitiveAtom } from "jotai" 2 | import { useAtomValue } from "jotai" 3 | import { createContext, use } from "react" 4 | 5 | export const ScreenNameContext = createContext>(null!) 6 | 7 | export const useScreenName = () => { 8 | const name = use(ScreenNameContext) 9 | if (!name) { 10 | throw new Error("ScreenNameContext not mounted") 11 | } 12 | return useAtomValue(name) 13 | } 14 | -------------------------------------------------------------------------------- /apps/mobile/web-app/html-renderer/global.d.ts: -------------------------------------------------------------------------------- 1 | import "vite/client" 2 | import "../../../../packages/types/react-global" 3 | import "../../../../packages/types/global" 4 | 5 | interface Bridge { 6 | measure: () => void 7 | setContentHeight: (height: number) => void 8 | previewImage: (data: { imageUrls: string[]; index: number }) => void 9 | seekAudio: (time: number) => void 10 | } 11 | 12 | declare global { 13 | export const bridge: Bridge 14 | } 15 | 16 | export {} 17 | -------------------------------------------------------------------------------- /packages/internal/constants/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@follow/constants", 3 | "type": "module", 4 | "private": true, 5 | "sideEffects": false, 6 | "exports": { 7 | ".": "./src/index.ts" 8 | }, 9 | "main": "./src/index.ts", 10 | "scripts": { 11 | "typecheck": "tsc --noEmit" 12 | }, 13 | "dependencies": { 14 | "@follow-app/client-sdk": "catalog:", 15 | "@follow/configs": "workspace:*", 16 | "@follow/types": "workspace:*" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/lib/parsers.ts: -------------------------------------------------------------------------------- 1 | import { isTwitterUrl, isXUrl } from "@follow/utils/link-parser" 2 | 3 | export const parseSocialMedia = (parsedUrl?: string | null) => { 4 | if (!parsedUrl) return 5 | 6 | const isX = isXUrl(parsedUrl).validate || isTwitterUrl(parsedUrl).validate 7 | 8 | if (isX) { 9 | return { 10 | type: "x", 11 | meta: { 12 | handle: new URL(parsedUrl).pathname.split("/").pop(), 13 | }, 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/subscription-column/styles.ts: -------------------------------------------------------------------------------- 1 | import { IN_ELECTRON } from "@follow/shared/constants" 2 | import { clsx } from "@follow/utils/utils" 3 | 4 | export const feedColumnStyles = { 5 | item: clsx( 6 | !IN_ELECTRON && tw`duration-200 hover:bg-theme-item-hover`, 7 | tw`flex w-full cursor-menu items-center rounded-md pr-2.5 text-base lg:text-sm font-medium !leading-loose`, 8 | tw`data-[active=true]:bg-theme-item-active`, 9 | ), 10 | } 11 | -------------------------------------------------------------------------------- /apps/desktop/changelog/0.8.0.md: -------------------------------------------------------------------------------- 1 | # What's new in v0.8.0 2 | 3 | ## Shiny new things 4 | 5 | - Smart onboarding that gets you started in seconds. 6 | - One place for all your feeds. 7 | - A cleaner, consistent reading experience for social media posts, picture galleries, videos, and articles. 8 | - Support subtitles for podcasts and videos. 9 | - Redesigned Actions for easier setup. 10 | 11 | ## Improvements 12 | 13 | - We have made countless improvements and bug fixes in this version. 14 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/components/ui/markdown/types.ts: -------------------------------------------------------------------------------- 1 | export type MarkdownImage = { 2 | url: string 3 | width?: number | undefined 4 | height?: number | undefined 5 | preview_image_url?: string | undefined 6 | blurhash?: string | undefined 7 | } 8 | 9 | export interface MarkdownRenderActions { 10 | transformUrl: (url?: string) => string | undefined 11 | isAudio: (url?: string) => boolean 12 | ensureAndRenderTimeStamp: (children: string) => React.ReactNode 13 | } 14 | -------------------------------------------------------------------------------- /apps/mobile/src/modules/settings/routes/navigateToPlanScreen.ts: -------------------------------------------------------------------------------- 1 | import { Navigation } from "@/src/lib/navigation/Navigation" 2 | 3 | export const navigateToPlanScreen = () => { 4 | return import("./Plan") 5 | .then(({ PlanScreen }) => { 6 | Navigation.rootNavigation.pushControllerView(PlanScreen) 7 | }) 8 | .catch((error) => { 9 | if (__DEV__) { 10 | console.error("Failed to open plan screen", error) 11 | } 12 | throw error 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /apps/ssr/src/meta-handler.map.ts: -------------------------------------------------------------------------------- 1 | // This file is generated by `pnpm run meta` 2 | import i0 from "../client/pages/(login)/login/metadata" 3 | import i3 from "../client/pages/(main)/share/feeds/[id]/metadata" 4 | import i2 from "../client/pages/(main)/share/lists/[id]/metadata" 5 | import i1 from "../client/pages/(main)/share/users/[id]/metadata" 6 | 7 | export default { 8 | "/login": i0, 9 | "/share/users/:id": i1, 10 | "/share/lists/:id": i2, 11 | "/share/feeds/:id": i3, 12 | } 13 | -------------------------------------------------------------------------------- /apps/mobile/native/ios/Modules/PagerView/EnhancePageViewModule.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnhancePageViewModule.swift 3 | // FollowNative 4 | // 5 | // Created by Innei on 2025/3/31. 6 | // 7 | 8 | import ExpoModulesCore 9 | 10 | public class EnhancePageViewModule: Module { 11 | public func definition() -> ModuleDefinition { 12 | Name("EnhancePageView") 13 | 14 | View(EnhancePageView.self) { 15 | } 16 | } 17 | } 18 | 19 | 20 | class EnhancePageView: ExpoView { 21 | 22 | } 23 | -------------------------------------------------------------------------------- /apps/mobile/native/ios/Packages/ImageViewer_swift/SimpleImageDatasource.swift: -------------------------------------------------------------------------------- 1 | class SimpleImageDatasource:ImageDataSource { 2 | 3 | private(set) var imageItems:[ImageItem] 4 | 5 | init(imageItems: [ImageItem]) { 6 | self.imageItems = imageItems 7 | } 8 | 9 | func numberOfImages() -> Int { 10 | return imageItems.count 11 | } 12 | 13 | func imageItem(at index: Int) -> ImageItem { 14 | return imageItems[index] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /apps/mobile/src/components/ui/pressable/NativePressable.tsx: -------------------------------------------------------------------------------- 1 | import { Pressable } from "react-native" 2 | 3 | import type { NativePressableProps } from "./NativePressable.types" 4 | 5 | /** 6 | * In order to resolve the conflict between the gesture handling of the native view and the RCTSurfaceGestureHandler in React Native. 7 | * 8 | */ 9 | export const NativePressable = ({ children, ...props }: NativePressableProps) => { 10 | return {children} 11 | } 12 | -------------------------------------------------------------------------------- /packages/internal/components/src/ui/platform-icon/collections/zotero.tsx: -------------------------------------------------------------------------------- 1 | import type { SVGProps } from "react" 2 | 3 | export function SimpleIconsZotero(props: SVGProps) { 4 | return ( 5 | 6 | 10 | 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /scripts/skip-main-app-vercel-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | LAST_DEPLOY_COMMIT=$(git rev-parse HEAD^) 4 | 5 | CHANGED_FILES=$(git diff --name-only $LAST_DEPLOY_COMMIT HEAD) 6 | 7 | ONLY_SERVER_CHANGES=true 8 | for file in $CHANGED_FILES; do 9 | if [[ $file != apps/ssr/* ]]; then 10 | ONLY_SERVER_CHANGES=false 11 | break 12 | fi 13 | done 14 | 15 | if [ "$ONLY_SERVER_CHANGES" = true ]; then 16 | 17 | echo "skip" 18 | exit 0 19 | else 20 | echo "continue" 21 | exit 1 22 | fi 23 | -------------------------------------------------------------------------------- /apps/ssr/public/icon.svg: -------------------------------------------------------------------------------- 1 | Folo -------------------------------------------------------------------------------- /packages/internal/components/src/ui/portal/index.tsx: -------------------------------------------------------------------------------- 1 | import type { FC, PropsWithChildren } from "react" 2 | import { createPortal } from "react-dom" 3 | 4 | import { useRootPortal } from "./provider" 5 | 6 | export const RootPortal: FC< 7 | { 8 | to?: HTMLElement | null 9 | } & PropsWithChildren 10 | > = (props) => { 11 | const to = useRootPortal() 12 | 13 | if (props.to === null) return props.children 14 | 15 | return createPortal(props.children, props.to || to || document.body) 16 | } 17 | -------------------------------------------------------------------------------- /packages/internal/database/src/drizzle/0005_tense_sleepwalker.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `entries` ( 2 | `id` text PRIMARY KEY NOT NULL, 3 | `title` text, 4 | `url` text, 5 | `content` text, 6 | `description` text, 7 | `guid` text NOT NULL, 8 | `author` text, 9 | `author_url` text, 10 | `author_avatar` text, 11 | `inserted_at` integer NOT NULL, 12 | `published_at` integer NOT NULL, 13 | `media` text, 14 | `categories` text, 15 | `attachments` text, 16 | `extra` text, 17 | `language` text 18 | ); 19 | -------------------------------------------------------------------------------- /apps/desktop/changelog/0.9.0.md: -------------------------------------------------------------------------------- 1 | # What's new in v0.9.0 2 | 3 | ## Improvements 4 | 5 | - Removed the limit on the maximum number of views (b69a935) 6 | - Allow hiding the “All” view (0882e47) 7 | - Introduced a right-click menu for views, allowing quick access to hide or open settings (60dcd42) 8 | - Added a smooth sliding animation when opening entry details (1720ebb) 9 | 10 | ## No longer broken 11 | 12 | - Fixed an issue where the scrollbar didn’t reset when switching between entry details (21124f5) 13 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/setup-file.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import "fake-indexeddb/auto" 3 | 4 | import { enableMapSet } from "immer" 5 | 6 | globalThis.window = { 7 | location: new URL("https://example.com"), 8 | __dbIsReady: true, 9 | addEventListener: () => {}, 10 | get navigator() { 11 | return globalThis.navigator 12 | }, 13 | } 14 | 15 | if (!globalThis.navigator) { 16 | globalThis.navigator = { 17 | onLine: true, 18 | userAgent: "node", 19 | } 20 | } 21 | enableMapSet() 22 | -------------------------------------------------------------------------------- /apps/mobile/ios/Folo/Images.xcassets/SplashScreenBackground.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors": [ 3 | { 4 | "color": { 5 | "components": { 6 | "alpha": "1.000", 7 | "blue": "1.00000000000000", 8 | "green": "1.00000000000000", 9 | "red": "1.00000000000000" 10 | }, 11 | "color-space": "srgb" 12 | }, 13 | "idiom": "universal" 14 | } 15 | ], 16 | "info": { 17 | "version": 1, 18 | "author": "expo" 19 | } 20 | } -------------------------------------------------------------------------------- /apps/mobile/src/components/ui/overlay/Overlay.tsx: -------------------------------------------------------------------------------- 1 | import type { FC } from "react" 2 | import { FadeIn, FadeOut } from "react-native-reanimated" 3 | 4 | import { ReAnimatedPressable } from "../../common/AnimatedComponents" 5 | 6 | export const Overlay: FC<{ onPress: () => void }> = ({ onPress }) => { 7 | return ( 8 | 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /icons/mgc/music_2_cute_fi.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/internal/components/src/ui/navigation-menu/style.ts: -------------------------------------------------------------------------------- 1 | import { cva } from "class-variance-authority" 2 | 3 | export const navigationMenuTriggerStyle = cva( 4 | "group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50", 5 | ) 6 | -------------------------------------------------------------------------------- /apps/desktop/changelog/0.2.1.md: -------------------------------------------------------------------------------- 1 | # What's new in v0.2.1 2 | 3 | ## New Features 4 | 5 | - Added zoom functionality for viewing pictures. 6 | - Set `Show Unread Only` as the default option. 7 | - Merged the redirect page with the login page. 8 | - Introduced entry conditions for actions. 9 | - Added `all` language filter in dicover page. 10 | 11 | ## Improvements 12 | 13 | - Enhanced the logic for multi-selection and dragging. 14 | 15 | ## Bug Fixes 16 | 17 | - Resolved issue with loading Instagram images. 18 | -------------------------------------------------------------------------------- /apps/desktop/layer/main/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from "node:url" 2 | 3 | import tsconfigPath from "vite-tsconfig-paths" 4 | import { defineProject } from "vitest/config" 5 | 6 | const __dirname = fileURLToPath(new URL(".", import.meta.url)) 7 | 8 | export default defineProject({ 9 | root: "./", 10 | test: { 11 | globals: true, 12 | environment: "node", 13 | }, 14 | 15 | plugins: [ 16 | tsconfigPath({ 17 | projects: ["./tsconfig.json"], 18 | }), 19 | ], 20 | }) 21 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/modules/ai-chat/components/displays/shared/EmptyState.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@follow/utils/utils" 2 | 3 | export interface EmptyStateProps { 4 | message: string 5 | icon?: string 6 | className?: string 7 | } 8 | 9 | export const EmptyState = ({ message, icon, className }: EmptyStateProps) => ( 10 |
11 | {icon && {icon}} 12 | {message} 13 |
14 | ) 15 | -------------------------------------------------------------------------------- /apps/mobile/src/components/ui/toast/ctx.tsx: -------------------------------------------------------------------------------- 1 | import type { PrimitiveAtom } from "jotai" 2 | import { createContext } from "react" 3 | 4 | import type { ToastProps, ToastRef } from "./types" 5 | 6 | export const ToastContainerContext = createContext>(null!) 7 | 8 | type Disposer = () => void 9 | interface ToastActionContext { 10 | register: (currentIndex: number, ref: ToastRef) => Disposer 11 | } 12 | 13 | export const ToastActionContext = createContext(null!) 14 | -------------------------------------------------------------------------------- /packages/internal/store/src/modules/unread/types.ts: -------------------------------------------------------------------------------- 1 | export interface PublishAtTimeRangeFilter { 2 | startTime: number 3 | endTime: number 4 | } 5 | 6 | export interface InsertedBeforeTimeRangeFilter { 7 | insertedBefore: number 8 | } 9 | 10 | export interface UnreadUpdateOptions { 11 | reset?: boolean 12 | } 13 | 14 | export type FeedIdOrInboxHandle = string 15 | export type UnreadStoreModel = Record 16 | export interface UnreadState { 17 | data: UnreadStoreModel 18 | } 19 | -------------------------------------------------------------------------------- /patches/jsonpointer.patch: -------------------------------------------------------------------------------- 1 | diff --git a/package.json b/package.json 2 | index a832ba9fc48f08300d8e5e595409c0488217af86..23af6ad8e16a8add0580151e8124f71451ebfc94 100644 3 | --- a/package.json 4 | +++ b/package.json 5 | @@ -23,8 +23,8 @@ 6 | "engines": { 7 | "node": ">=0.10.0" 8 | }, 9 | - "main": "./jsonpointer", 10 | - "typings": "jsonpointer.d.ts", 11 | + "main": "./jsonpointer.js", 12 | + "typings": "./jsonpointer.d.ts", 13 | "files": [ 14 | "jsonpointer.js", 15 | "jsonpointer.d.ts" 16 | -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/public/icon.svg: -------------------------------------------------------------------------------- 1 | Folo -------------------------------------------------------------------------------- /apps/desktop/layer/renderer/src/pages/(main)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { withResponsiveComponent } from "@follow/components/utils/selector.js" 2 | 3 | import { MainDestopLayout } from "~/modules/app-layout/subscription-column/index" 4 | 5 | export const Component = withResponsiveComponent( 6 | () => Promise.resolve({ default: MainDestopLayout }), 7 | async () => { 8 | const { default: DownloadPage } = await import("~/modules/download") 9 | return { default: DownloadPage } 10 | }, 11 | (w) => w < 768, 12 | ) 13 | -------------------------------------------------------------------------------- /apps/mobile/native/ios/Modules/TabBar/TabScreenView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabScreenView.swift 3 | // FollowNative 4 | // 5 | // Created by Innei on 2025/3/16. 6 | // 7 | 8 | import ExpoModulesCore 9 | import UIKit 10 | 11 | class TabScreenView: ExpoView { 12 | weak var ownerViewController: UIViewController? 13 | 14 | var icon: String? 15 | var activeIcon: String? 16 | var title: String? 17 | 18 | required init(appContext: AppContext? = nil) { 19 | super.init(appContext: appContext) 20 | } 21 | 22 | } 23 | --------------------------------------------------------------------------------