settingModalPresent("wallet")}
- >
+
navigate("/power")}>
,
{
diff --git a/apps/renderer/src/router.web.tsx b/apps/renderer/src/router.web.tsx
new file mode 100644
index 000000000..0bd90846b
--- /dev/null
+++ b/apps/renderer/src/router.web.tsx
@@ -0,0 +1,29 @@
+import { IN_ELECTRON } from "@follow/shared/constants"
+import { wrapCreateBrowserRouter } from "@sentry/react"
+import { createBrowserRouter, createHashRouter } from "react-router-dom"
+
+import { ErrorElement } from "./components/common/ErrorElement"
+import { NotFound } from "./components/common/NotFound"
+import { buildGlobRoutes } from "./lib/route-builder"
+
+const globTree = import.meta.glob("./pages/**/*.tsx")
+const tree = buildGlobRoutes(globTree)
+
+let routerCreator =
+ IN_ELECTRON || globalThis["__DEBUG_PROXY__"] ? createHashRouter : createBrowserRouter
+if (window.SENTRY_RELEASE) {
+ routerCreator = wrapCreateBrowserRouter(routerCreator)
+}
+
+export const router = routerCreator([
+ {
+ path: "/",
+ lazy: () => import("./App"),
+ children: tree,
+ errorElement:
,
+ },
+ {
+ path: "*",
+ element:
,
+ },
+])
diff --git a/apps/renderer/src/store/entry/helper.ts b/apps/renderer/src/store/entry/helper.ts
index a211cfb62..81618116d 100644
--- a/apps/renderer/src/store/entry/helper.ts
+++ b/apps/renderer/src/store/entry/helper.ts
@@ -7,9 +7,6 @@ import { useSubscriptionStore } from "../subscription"
import { useEntryStore } from "./store"
import type { EntryFilter } from "./types"
-type EntryId = string
-type FeedId = string
-
export const getFilteredFeedIds = (feedIds: string[], filter?: EntryFilter) => {
const state = useEntryStore.getState()
const ids = [] as string[]
@@ -33,11 +30,18 @@ export const getFilteredFeedIds = (feedIds: string[], filter?: EntryFilter) => {
}
const unread = create({
- fetcher: async (ids: ([FeedId, EntryId, boolean] | [FeedId, EntryId])[]) => {
+ fetcher: async (
+ ids: {
+ entryId: string
+ isInbox: boolean
+ isPrivate?: boolean
+ }[],
+ ) => {
await apiClient.reads.$post({
json: {
- entryIds: ids.map((i) => i[1]),
- isInbox: ids[0][2],
+ entryIds: ids.map((i) => i.entryId),
+ isInbox: ids[0].isInbox,
+ readHistories: ids.filter((i) => !i.isPrivate).map((i) => i.entryId),
},
})
diff --git a/apps/renderer/src/store/entry/store.ts b/apps/renderer/src/store/entry/store.ts
index 52fe53e40..b290137cd 100644
--- a/apps/renderer/src/store/entry/store.ts
+++ b/apps/renderer/src/store/entry/store.ts
@@ -11,6 +11,7 @@ import { EntryService } from "~/services"
import { feedActions } from "../feed"
import { imageActions } from "../image"
import { inboxActions } from "../inbox"
+import { getSubscriptionByFeedId } from "../subscription"
import { feedUnreadActions } from "../unread"
import { createZustandStore, doMutationAndTransaction } from "../utils/helper"
import { internal_batchMarkRead } from "./helper"
@@ -349,6 +350,7 @@ class EntryActions {
async markRead({ feedId, entryId, read }: { feedId: string; entryId: string; read: boolean }) {
const entry = get().flatMapEntries[entryId]
const isInbox = entry?.entries && "inboxHandle" in entry.entries
+ const subscription = getSubscriptionByFeedId(feedId)
if (read && entry?.read) {
return
@@ -364,7 +366,11 @@ class EntryActions {
// Send api request
async () => {
if (read) {
- await internal_batchMarkRead([feedId, entryId, isInbox])
+ await internal_batchMarkRead({
+ entryId,
+ isInbox,
+ isPrivate: subscription?.isPrivate,
+ })
} else {
await apiClient.reads.$delete({
json: {
diff --git a/apps/renderer/src/store/subscription/hooks.ts b/apps/renderer/src/store/subscription/hooks.ts
index 748fee22e..f69a4d340 100644
--- a/apps/renderer/src/store/subscription/hooks.ts
+++ b/apps/renderer/src/store/subscription/hooks.ts
@@ -1,7 +1,7 @@
import { FEED_COLLECTION_LIST, ROUTE_FEED_IN_FOLDER } from "~/constants"
import { FeedViewType } from "~/lib/enum"
-import { useSubscriptionStore } from "../subscription"
+import { subscriptionCategoryExistSelector, useSubscriptionStore } from "../subscription"
type FeedId = string
export const useFeedIdByView = (view: FeedViewType) =>
@@ -64,3 +64,6 @@ export const useFeedSubscriptionCount = () =>
(s) => !!s.feedId && !s.listId && !s.inboxId && state.subscriptionIdSet.has(s.feedId),
).length,
)
+
+export const useSubscriptionCategoryExist = (name: string) =>
+ useSubscriptionStore(subscriptionCategoryExistSelector(name))
diff --git a/apps/renderer/src/store/subscription/index.ts b/apps/renderer/src/store/subscription/index.ts
index a1fdac565..6a6c423c6 100644
--- a/apps/renderer/src/store/subscription/index.ts
+++ b/apps/renderer/src/store/subscription/index.ts
@@ -1,2 +1,3 @@
export * from "./hooks"
+export * from "./selector"
export * from "./store"
diff --git a/apps/renderer/src/store/subscription/selector.ts b/apps/renderer/src/store/subscription/selector.ts
new file mode 100644
index 000000000..a0fbadbf2
--- /dev/null
+++ b/apps/renderer/src/store/subscription/selector.ts
@@ -0,0 +1,5 @@
+import type { useSubscriptionStore } from "./store"
+
+type State = ReturnType
+export const subscriptionCategoryExistSelector = (name: string) => (state: State) =>
+ state.categories.has(name)
diff --git a/apps/renderer/src/store/subscription/store.ts b/apps/renderer/src/store/subscription/store.ts
index 508c4cc6e..a218d85b0 100644
--- a/apps/renderer/src/store/subscription/store.ts
+++ b/apps/renderer/src/store/subscription/store.ts
@@ -17,6 +17,7 @@ import { inboxActions } from "../inbox"
import { listActions } from "../list"
import { feedUnreadActions } from "../unread"
import { createImmerSetter, createZustandStore, doMutationAndTransaction } from "../utils/helper"
+import { subscriptionCategoryExistSelector } from "./selector"
export type SubscriptionFlatModel = Omit & {
defaultCategory?: string
@@ -41,7 +42,11 @@ interface SubscriptionState {
*/
categoryOpenStateByView: Record>
/**
- * Store the subscription ids that current user followed.
+ * All named categories names set
+ */
+ categories: Set
+ /**
+ * All subscription ids set
*/
subscriptionIdSet: Set
}
@@ -90,7 +95,8 @@ export const useSubscriptionStore = createZustandStore("subsc
data: {},
feedIdByView: { ...emptyDataIdByView },
categoryOpenStateByView: { ...emptyCategoryOpenStateByView },
- subscriptionIdSet: new Set(),
+ categories: new Set(),
+ subscriptionIdSet: new Set(),
}))
const set = useSubscriptionStore.setState
@@ -118,6 +124,19 @@ class SubscriptionActions {
})
}
})
+
+ useSubscriptionStore.subscribe((state, prev) => {
+ if (state.data === prev.data) return
+
+ const categories = new Set()
+ for (const subscription of Object.values(state.data)) {
+ subscription.category && categories.add(subscription.category)
+ }
+ set((state) => ({
+ ...state,
+ categories,
+ }))
+ })
}
async fetchByView(view?: FeedViewType) {
const res = await apiClient.subscriptions.$get({
@@ -453,7 +472,6 @@ class SubscriptionActions {
}),
async () =>
// Db
-
SubscriptionService.renameCategory(whoami()!.id, subscriptionIds, newCategory),
)
}
@@ -472,3 +490,6 @@ export const isListSubscription = (feedId?: FeedId) => {
if (!subscription) return false
return "listId" in subscription && !!subscription.listId
}
+
+export const subscriptionCategoryExist = (name: string) =>
+ subscriptionCategoryExistSelector(name)(get())
diff --git a/changelogithub.config.ts b/changelogithub.config.ts
index 40322ada1..726e583e3 100644
--- a/changelogithub.config.ts
+++ b/changelogithub.config.ts
@@ -1,4 +1,4 @@
export default {
tagFilter: (tag: string) => tag.startsWith("v") && !tag.includes("nightly"),
- dry: true,
+ dry: !process.env.CI,
}
diff --git a/electron.vite.config.ts b/electron.vite.config.ts
index 200a59db6..7d159b193 100644
--- a/electron.vite.config.ts
+++ b/electron.vite.config.ts
@@ -3,6 +3,7 @@ import { resolve } from "node:path"
import { defineConfig } from "electron-vite"
import { viteRenderBaseConfig } from "./configs/vite.render.config"
+import { createPlatformSpecificImportPlugin } from "./plugins/vite/specific-import"
export default defineConfig({
main: {
@@ -41,6 +42,8 @@ export default defineConfig({
renderer: {
...viteRenderBaseConfig,
+ plugins: [...viteRenderBaseConfig.plugins, createPlatformSpecificImportPlugin(true)],
+
root: "apps/renderer",
build: {
outDir: "dist/renderer",
diff --git a/forge.config.ts b/forge.config.ts
index e579a2809..3eea4a775 100644
--- a/forge.config.ts
+++ b/forge.config.ts
@@ -154,6 +154,7 @@ const config: ForgeConfig = {
new MakerSquirrel({
name: "Follow",
setupIcon: "resources/icon.ico",
+ iconUrl: "https://app.follow.is/favicon.ico",
}),
new MakerAppImage({
options: {
diff --git a/locales/app/en.json b/locales/app/en.json
index 3587f6756..76c64a96d 100644
--- a/locales/app/en.json
+++ b/locales/app/en.json
@@ -287,6 +287,7 @@
"words.search": "Search",
"words.starred": "Starred",
"words.title": "Title",
+ "words.transform": "Transform",
"words.trending": "Trending",
"words.undo": "Undo",
"words.unread": "Unread",
diff --git a/locales/app/zh-HK.json b/locales/app/zh-HK.json
index a4d0646de..509e3234e 100644
--- a/locales/app/zh-HK.json
+++ b/locales/app/zh-HK.json
@@ -54,9 +54,9 @@
"entry_actions.copied_notify": "{{which}}已複製到剪貼板",
"entry_actions.copy_link": "複製連結",
"entry_actions.copy_title": "複製標題",
- "entry_actions.failed_to_save_to_eagle": "無法保存至 Eagle。",
- "entry_actions.failed_to_save_to_instapaper": "無法保存至 Instapaper。",
- "entry_actions.failed_to_save_to_readwise": "無法保存至 Readwise。",
+ "entry_actions.failed_to_save_to_eagle": "無法保存至 Eagle",
+ "entry_actions.failed_to_save_to_instapaper": "無法保存至 Instapaper",
+ "entry_actions.failed_to_save_to_readwise": "無法保存至 Readwise",
"entry_actions.mark_as_read": "標記為已讀",
"entry_actions.mark_as_unread": "標記為未讀",
"entry_actions.open_in_browser": "在{{which}}中開啟",
@@ -64,17 +64,17 @@
"entry_actions.save_media_to_eagle": "保存媒體至 Eagle",
"entry_actions.save_to_instapaper": "保存至 Instapaper",
"entry_actions.save_to_readwise": "保存至 Readwise",
- "entry_actions.saved_to_eagle": "已保存至 Eagle。",
- "entry_actions.saved_to_instapaper": "已保存至 Instapaper。",
- "entry_actions.saved_to_readwise": "已保存至 Readwise。",
+ "entry_actions.saved_to_eagle": "已保存至 Eagle",
+ "entry_actions.saved_to_instapaper": "已保存至 Instapaper",
+ "entry_actions.saved_to_readwise": "已保存至 Readwise",
"entry_actions.share": "分享",
"entry_actions.star": "收藏",
- "entry_actions.starred": "已收藏。",
+ "entry_actions.starred": "已收藏",
"entry_actions.tip": "贊助",
"entry_actions.unstar": "取消收藏",
- "entry_actions.unstarred": "已取消收藏。",
+ "entry_actions.unstarred": "已取消收藏",
"entry_actions.view_source_content": "查看原始內容",
- "entry_column.filtered_content_tip": "你已隱藏篩選內容。",
+ "entry_column.filtered_content_tip": "你已隱藏篩選內容",
"entry_column.filtered_content_tip_2": "除了以上顯示的條目,還有一些已篩選的內容。",
"entry_column.refreshing": "刷新新條目中...",
"entry_content.ai_summary": "AI 摘要",
@@ -85,12 +85,12 @@
"entry_content.readability_notice": "此內容由 Readability 提供。如果你發現排版異常,請到來源網站查看原始內容。",
"entry_content.render_error": "渲染錯誤:",
"entry_content.report_issue": "報告問題",
- "entry_content.support_amount": "{{amount}} 人支持了此訂閱源的創作者。",
+ "entry_content.support_amount": "{{amount}} 人支持了此訂閱源的創作者",
"entry_content.support_creator": "支持創作者",
"entry_content.web_app_notice": "網頁應用可能不支持此內容類型。你可以下載桌面應用程式。",
"entry_list.zero_unread": "全部已讀",
"entry_list_header.daily_report": "每日報告",
- "entry_list_header.hide_no_image_items": "隱藏沒有圖片的條目。",
+ "entry_list_header.hide_no_image_items": "隱藏沒有圖片的條目",
"entry_list_header.items": "項目",
"entry_list_header.new_entries_available": "有新條目可用",
"entry_list_header.refetch": "重新抓取",
@@ -105,12 +105,12 @@
"entry_list_header.unread": "未讀",
"feed_claim_modal.choose_verification_method": "有三種驗證方法可選,你可以選擇其中一種進行驗證。",
"feed_claim_modal.claim_button": "認領",
- "feed_claim_modal.content_instructions": "複製以下內容並貼到你最新的 RSS 訂閱源中。",
+ "feed_claim_modal.content_instructions": "複製以下內容並貼到你最新的 RSS 訂閱源中",
"feed_claim_modal.description_current": "當前描述:",
- "feed_claim_modal.description_instructions": "複製以下內容並貼到你的 RSS 訂閱源中的 欄位。",
+ "feed_claim_modal.description_instructions": "複製以下內容並貼到你的 RSS 訂閱源中的 欄位",
"feed_claim_modal.failed_to_load": "無法加載認領信息",
"feed_claim_modal.rss_format_choice": "RSS 生成器通常有兩種格式可選。請根據需要複製以下的 XML 和 JSON 格式。",
- "feed_claim_modal.rss_instructions": "複製以下代碼並貼到你的 RSS 生成器中。",
+ "feed_claim_modal.rss_instructions": "複製以下代碼並貼到你的 RSS 生成器中",
"feed_claim_modal.rss_json_format": "JSON 格式",
"feed_claim_modal.rss_xml_format": "XML 格式",
"feed_claim_modal.rsshub_notice": "此訂閱源由 RSSHub 提供,緩存時間為 1 小時。發布內容後,請等待最多 1 小時更改才能生效。",
@@ -122,23 +122,23 @@
"feed_form.add_follow": "新增關注",
"feed_form.category": "分類",
"feed_form.category_description": "默認情況下,你的關注會按網站分組。",
- "feed_form.error_fetching_feed": "獲取訂閱源時出錯。",
+ "feed_form.error_fetching_feed": "獲取訂閱源時出錯",
"feed_form.fee": "跟隨費用",
"feed_form.fee_description": "要跟隨此清單,您必須向清單創建者支付費用。",
- "feed_form.feed_not_found": "未找到相關資訊流。",
+ "feed_form.feed_not_found": "未找到相關資訊流",
"feed_form.feedback": "反饋",
"feed_form.follow": "關注",
"feed_form.follow_with_fee": "跟隨需要 {{fee}} Power",
- "feed_form.followed": "🎉 已關注。",
+ "feed_form.followed": "🎉 已關注",
"feed_form.private_follow": "私人關注",
- "feed_form.private_follow_description": "此關注是否在你的個人資料頁面上公開可見。",
+ "feed_form.private_follow_description": "此關注是否在你的個人資料頁面上公開可見",
"feed_form.retry": "重試",
"feed_form.title": "標題",
"feed_form.title_description": "此資訊流的自定義標題。留空則使用默認標題。",
"feed_form.unfollow": "取消關注",
"feed_form.update": "更新",
"feed_form.update_follow": "更新關注",
- "feed_form.updated": "🎉 已更新。",
+ "feed_form.updated": "🎉 已更新",
"feed_form.view": "視圖",
"feed_item.claimed_by_owner": "訂閲源所有者",
"feed_item.claimed_by_unknown": "未知所有者",
@@ -152,7 +152,7 @@
"feed_view_type.pictures": "圖片",
"feed_view_type.social_media": "社交媒體",
"feed_view_type.videos": "視頻",
- "mark_all_read_button.auto_confirm_info": "將在 3 秒後自動確認。",
+ "mark_all_read_button.auto_confirm_info": "將在 3 秒後自動確認",
"mark_all_read_button.confirm": "確認",
"mark_all_read_button.confirm_mark_all": "確定將 標記為已讀?",
"mark_all_read_button.confirm_mark_all_info": "確定將所有標記為已讀?",
@@ -163,7 +163,7 @@
"notify.unSupportWidth": "{{app_name}} 目前尚未支援流動裝置",
"notify.unSupportWidth_1": "您的裝置寬度為 {{width}},低於支援的最小寬度 {{minWidth}}",
"notify.unSupportWidth_2": "請切換至桌面應用程式繼續使用 {{app_name}}
下載連結:",
- "notify.unfollow_feed": "已取消關注 。",
+ "notify.unfollow_feed": "已取消關注 ",
"notify.update_info": "{{app_name}} 已準備好更新!",
"notify.update_info_1": "點擊以重新啟動",
"player.back_10s": "倒退 10 秒",
@@ -181,7 +181,7 @@
"player.volume": "音量",
"resize.tooltip.double_click_to_collapse": "雙擊以摺疊",
"resize.tooltip.drag_to_resize": "拖曳以調整大小",
- "search.empty.no_results": "未找到結果。",
+ "search.empty.no_results": "未找到結果",
"search.group.entries": "條目",
"search.group.feeds": "訂閱源",
"search.options.all": "所有",
@@ -239,7 +239,7 @@
"tip_modal.feed_owner": "訂閱源擁有者",
"tip_modal.low_balance": "你的餘額不足以覆蓋此贊助。請調整金額。",
"tip_modal.no_wallet": "你尚未擁有錢包。請創建一個錢包以進行贊助。",
- "tip_modal.tip_amount_sent": "已經發送給作者。",
+ "tip_modal.tip_amount_sent": "已經發送給作者",
"tip_modal.tip_now": "立刻贊助",
"tip_modal.tip_sent": "贊助成功!感謝你的支持。",
"tip_modal.tip_support": "⭐ 贊助以顯示你的支持!",
@@ -284,6 +284,7 @@
"words.search": "搜尋",
"words.starred": "收藏",
"words.title": "標題",
+ "words.transform": "轉換",
"words.trending": "熱門",
"words.undo": "撤銷",
"words.unread": "未讀",
diff --git a/locales/common/zh-HK.json b/locales/common/zh-HK.json
index 8f7c2df5a..a948663a4 100644
--- a/locales/common/zh-HK.json
+++ b/locales/common/zh-HK.json
@@ -16,6 +16,7 @@
"words.back": "返回",
"words.copy": "複製",
"words.create": "創建",
+ "words.delete": "刪除",
"words.edit": "編輯",
"words.entry": "條目",
"words.id": "ID",
diff --git a/locales/external/en.json b/locales/external/en.json
index b0a4619fd..09d413db7 100644
--- a/locales/external/en.json
+++ b/locales/external/en.json
@@ -8,7 +8,7 @@
"feed.follower_one": "follower",
"feed.follower_other": "followers",
"feed.followsAndFeeds": "{{subscriptionCount}} {{subscriptionNoun}} and {{feedsCount}} {{feedsNoun}} on {{appName}}",
- "feed.followsAndReads": "{{subscriptionCount}} {{subscriptionNoun}} with {{readCount}} {{readNoun}} on {{appName}}",
+ "feed.followsAndReads": "{{subscriptionCount}} {{subscriptionNoun}} with {{readCount}} recent {{readNoun}} on {{appName}}",
"feed.read_one": "read",
"feed.read_other": "reads",
"feed.view_feed_url": "View Feed URL",
diff --git a/locales/external/zh-CN.json b/locales/external/zh-CN.json
index 567d90f1f..8857c7e35 100644
--- a/locales/external/zh-CN.json
+++ b/locales/external/zh-CN.json
@@ -8,9 +8,9 @@
"feed.follower_one": "关注者",
"feed.follower_other": "关注者",
"feed.followsAndFeeds": "在 {{appName}} 上有 {{subscriptionCount}} 个{{subscriptionNoun}}和 {{feedsCount}} 个{{feedsNoun}}",
- "feed.followsAndReads": "在 {{appName}} 上有 {{subscriptionCount}} 个{{subscriptionNoun}},{{readCount}} 篇{{readNoun}}",
- "feed.read_one": "文章",
- "feed.read_other": "文章",
+ "feed.followsAndReads": "在 {{appName}} 上有 {{subscriptionCount}} 个{{subscriptionNoun}},{{readCount}} 次近期{{readNoun}}",
+ "feed.read_one": "阅读",
+ "feed.read_other": "阅读",
"feed.view_feed_url": "查看链接",
"header.app": "应用",
"header.download": "下载",
diff --git a/locales/settings/zh-CN.json b/locales/settings/zh-CN.json
index d53db49b8..aee5edf90 100644
--- a/locales/settings/zh-CN.json
+++ b/locales/settings/zh-CN.json
@@ -89,6 +89,9 @@
"general.app": "应用程序",
"general.data_persist.description": "在本地保留数据以启用离线访问和本地搜索",
"general.data_persist.label": "保留数据以供离线使用",
+ "general.export.button": "导出",
+ "general.export.description": "将你的订阅源导出到 OPML 文件。",
+ "general.export.label": "导出订阅源",
"general.group_by_date.description": "按日期对条目进行分组",
"general.group_by_date.label": "按日期分组",
"general.language": "语言",
diff --git a/locales/settings/zh-HK.json b/locales/settings/zh-HK.json
index 61124c83d..385e08ac9 100644
--- a/locales/settings/zh-HK.json
+++ b/locales/settings/zh-HK.json
@@ -89,6 +89,9 @@
"general.app": "應用程式",
"general.data_persist.description": "本地保存數據以啟用離線存取和本地搜尋。",
"general.data_persist.label": "離線使用時保留數據",
+ "general.export.button": "匯出",
+ "general.export.description": "匯出你的訂閱到 OPML 文件",
+ "general.export.label": "匯出訂閱",
"general.group_by_date.description": "按日期分組條目。",
"general.group_by_date.label": "按日期分組",
"general.language": "語言",
@@ -159,6 +162,8 @@
"lists.create": "建立新清單",
"lists.created.error": "建立清單失敗。",
"lists.created.success": "成功建立清單!",
+ "lists.delete.error": "刪除清單失敗。",
+ "lists.delete.success": "成功刪除清單!",
"lists.description": "描述",
"lists.earnings": "收益",
"lists.edit.error": "編輯清單失敗。",
@@ -184,7 +189,7 @@
"lists.submit": "提交",
"lists.subscriptions": "訂閱",
"lists.title": "標題",
- "lists.view": "查看",
+ "lists.view": "視圖",
"profile.avatar.label": "頭像",
"profile.handle.description": "你的唯一識別符。",
"profile.handle.label": "識別符",
@@ -226,6 +231,7 @@
"wallet.transactions.to": "發送至",
"wallet.transactions.tx": "交易",
"wallet.transactions.type": "類型",
+ "wallet.transactions.types.all": "全部",
"wallet.transactions.types.burn": "銷燬",
"wallet.transactions.types.mint": "鑄造",
"wallet.transactions.types.purchase": "購買",
diff --git a/package.json b/package.json
index e3ed3504c..41d218a66 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "Follow",
"type": "module",
- "version": "0.0.1-alpha.19",
+ "version": "0.0.1-alpha.21",
"private": true,
"packageManager": "pnpm@9.11.0",
"description": "Next generation information browser",
diff --git a/packages/shared/src/env.ts b/packages/shared/src/env.ts
index 963343424..ae29ed510 100644
--- a/packages/shared/src/env.ts
+++ b/packages/shared/src/env.ts
@@ -11,27 +11,7 @@ export const env = createEnv({
VITE_SENTRY_DSN: z.string().optional(),
VITE_POSTHOG_KEY: z.string().optional(),
VITE_INBOXES_EMAIL: z.string().default("@follow.re"),
- VITE_FIREBASE_CONFIG: z
- .string()
- .transform((content) => {
- try {
- return JSON.parse(content)
- } catch {
- return z.NEVER
- }
- })
- .pipe(
- z.object({
- apiKey: z.string(),
- authDomain: z.string(),
- projectId: z.string(),
- storageBucket: z.string(),
- messagingSenderId: z.string(),
- appId: z.string(),
- measurementId: z.string(),
- }),
- )
- .optional(),
+ VITE_FIREBASE_CONFIG: z.string().optional(),
},
emptyStringAsUndefined: true,
diff --git a/packages/shared/src/hono.ts b/packages/shared/src/hono.ts
index 9184f21f3..aa6922d00 100644
--- a/packages/shared/src/hono.ts
+++ b/packages/shared/src/hono.ts
@@ -5174,6 +5174,7 @@ declare const _routes: hono_hono_base.HonoBase importer.endsWith(ext))) return null
+
+ if (importer.includes("node_modules")) return null
+ const [path, query] = source.split("?")
+
+ if (path.startsWith(".") || path.startsWith("/")) {
+ const priorities = isElectron
+ ? [".electron.ts", ".electron.tsx", ".electron.js", ".electron.jsx"]
+ : [".web.ts", ".web.tsx", ".web.js", ".web.jsx"]
+
+ for (const ext of priorities) {
+ const resolvedPath = await this.resolve(
+ `${path}${ext}${query ? `?${query}` : ""}`,
+ importer,
+ {
+ skipSelf: true,
+ },
+ )
+
+ if (resolvedPath) {
+ return resolvedPath.id
+ }
+ }
+ }
+
+ return null
+ },
+ }
+}
diff --git a/types/vite.d.ts b/types/vite.d.ts
index bf3f5370b..43f292ae0 100644
--- a/types/vite.d.ts
+++ b/types/vite.d.ts
@@ -6,6 +6,7 @@ interface ImportMetaEnv {
VITE_IMGPROXY_URL: string
VITE_SENTRY_DSN: string
VITE_POSTHOG_KEY: string
+ VITE_FIREBASE_CONFIG: string
}
interface ImportMeta {
diff --git a/vercel.json b/vercel.json
index 5e537787a..de5710a18 100644
--- a/vercel.json
+++ b/vercel.json
@@ -9,7 +9,7 @@
"destination": "/__debug_proxy.html"
},
{
- "source": "/(.*)",
+ "source": "/((?!assets|vendor|locales/).*)",
"destination": "/index.html"
}
],
@@ -19,7 +19,40 @@
"headers": [
{
"key": "Cache-Control",
- "value": "public, max-age=31536000, immutable"
+ "value": "public, max-age=604800, immutable"
+ },
+ {
+ "key": "CDN-Cache-Control",
+ "value": "max-age=604800"
+ },
+ {
+ "key": "Vercel-CDN-Cache-Control",
+ "value": "max-age=604800"
+ },
+ {
+ "key": "Cloudflare-CDN-Cache-Control",
+ "value": "max-age=604800"
+ }
+ ]
+ },
+ {
+ "source": "/assets/(.*)",
+ "headers": [
+ {
+ "key": "Cache-Control",
+ "value": "public, max-age=604800, immutable"
+ },
+ {
+ "key": "CDN-Cache-Control",
+ "value": "max-age=604800"
+ },
+ {
+ "key": "Vercel-CDN-Cache-Control",
+ "value": "max-age=604800"
+ },
+ {
+ "key": "Cloudflare-CDN-Cache-Control",
+ "value": "max-age=604800"
}
]
}
diff --git a/vite.config.ts b/vite.config.ts
index bce3faf32..f64089379 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -11,6 +11,7 @@ import { viteRenderBaseConfig } from "./configs/vite.render.config"
import type { env as EnvType } from "./packages/shared/src/env"
import { createDependencyChunksPlugin } from "./plugins/vite/deps"
import { htmlInjectPlugin } from "./plugins/vite/html-inject"
+import { createPlatformSpecificImportPlugin } from "./plugins/vite/specific-import"
const __dirname = fileURLToPath(new URL(".", import.meta.url))
const isCI = process.env.CI === "true" || process.env.CI === "1"
@@ -75,7 +76,8 @@ export default ({ mode }) => {
devPrint(),
createDependencyChunksPlugin([
// React framework
- ["react", "react-dom", "react-router-dom", "react-error-boundary", "react-dom/server"],
+ ["react", "react-dom"],
+ ["react-error-boundary", "react-dom/server", "react-router-dom"],
// Data Statement
["zustand", "jotai", "use-context-selector", "immer", "dexie"],
// Remark
@@ -131,7 +133,6 @@ export default ({ mode }) => {
"@tanstack/react-query-persist-client",
"@tanstack/query-sync-storage-persister",
],
- ["blurhash", "react-blurhash"],
["tldts"],
["shiki", "@shikijs/transformers"],
["@sentry/react", "posthog-js"],
@@ -139,6 +140,8 @@ export default ({ mode }) => {
["swiper"],
]),
+
+ createPlatformSpecificImportPlugin(false),
],
define: {