Skip to content

Commit 1ce3f5b

Browse files
authored
feat(integration): Add readeck integration (#1972)
1 parent a44d598 commit 1ce3f5b

File tree

19 files changed

+222
-6
lines changed

19 files changed

+222
-6
lines changed

apps/renderer/src/atoms/settings/integration.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ export const createDefaultSettings = (): IntegrationSettings => ({
2323
outlineEndpoint: "",
2424
outlineToken: "",
2525
outlineCollection: "",
26+
27+
// readeck
28+
enableReadeck: false,
29+
readeckEndpoint: "",
30+
readeckToken: "",
2631
})
2732

2833
export const {

apps/renderer/src/hooks/biz/useEntryActions.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view?: Fee
112112
id: COMMAND_ID.integration.saveToOutline,
113113
onClick: runCmdFn(COMMAND_ID.integration.saveToOutline, [{ entryId }]),
114114
},
115+
{
116+
id: COMMAND_ID.integration.saveToReadeck,
117+
onClick: runCmdFn(COMMAND_ID.integration.saveToReadeck, [{ entryId }]),
118+
},
115119
{
116120
id: COMMAND_ID.entry.tip,
117121
onClick: runCmdFn(COMMAND_ID.entry.tip, [{ entryId, feedId: feed?.id }]),

apps/renderer/src/modules/command/commands/id.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export const COMMAND_ID = {
2121
saveToInstapaper: "integration:save-to-instapaper",
2222
saveToObsidian: "integration:save-to-obsidian",
2323
saveToOutline: "integration:save-to-outline",
24+
saveToReadeck: "integration:save-to-readeck",
2425
},
2526
list: {
2627
edit: "list:edit",

apps/renderer/src/modules/command/commands/integration.tsx

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
SimpleIconsInstapaper,
44
SimpleIconsObsidian,
55
SimpleIconsOutline,
6+
SimpleIconsReadeck,
67
SimpleIconsReadwise,
78
} from "@follow/components/ui/platform-icon/icons.js"
89
import { IN_ELECTRON } from "@follow/shared/constants"
@@ -30,6 +31,7 @@ export const useRegisterIntegrationCommands = () => {
3031
useRegisterInstapaperCommands()
3132
useRegisterObsidianCommands()
3233
useRegisterOutlineCommands()
34+
useRegisterReadeckCommands()
3335
}
3436

3537
const useRegisterEagleCommands = () => {
@@ -380,3 +382,71 @@ const useRegisterOutlineCommands = () => {
380382
},
381383
)
382384
}
385+
386+
const useRegisterReadeckCommands = () => {
387+
const { t } = useTranslation()
388+
389+
const enableReadeck = useIntegrationSettingKey("enableReadeck")
390+
const readeckEndpoint = useIntegrationSettingKey("readeckEndpoint")
391+
const readeckToken = useIntegrationSettingKey("readeckToken")
392+
const readeckAvailable = enableReadeck && !!readeckEndpoint && !!readeckToken
393+
394+
useRegisterCommandEffect(
395+
!readeckAvailable
396+
? []
397+
: defineFollowCommand({
398+
id: COMMAND_ID.integration.saveToReadeck,
399+
label: t("entry_actions.save_to_readeck"),
400+
icon: <SimpleIconsReadeck />,
401+
run: async ({ entryId }) => {
402+
const entry = useEntryStore.getState().flatMapEntries[entryId]
403+
if (!entry) {
404+
toast.error("Failed to save to Readeck: entry is not available", { duration: 3000 })
405+
return
406+
}
407+
try {
408+
window.analytics?.capture("integration", {
409+
type: "readeck",
410+
event: "save",
411+
})
412+
const data = new FormData()
413+
if (entry.entries.url) {
414+
data.set("url", entry.entries.url)
415+
}
416+
if (entry.entries.title) {
417+
data.set("title", entry.entries.title)
418+
}
419+
const response = await ofetch.raw(
420+
`${readeckEndpoint.replace(/\/$/, "")}/api/bookmarks`,
421+
{
422+
method: "POST",
423+
body: data,
424+
headers: {
425+
Authorization: `Bearer ${readeckToken}`,
426+
},
427+
},
428+
)
429+
430+
toast.success(
431+
<>
432+
{t("entry_actions.saved_to_readeck")},{" "}
433+
<a target="_blank" className="underline" href={response.headers.get("Location")!}>
434+
view
435+
</a>
436+
</>,
437+
{
438+
duration: 3000,
439+
},
440+
)
441+
} catch {
442+
toast.error(t("entry_actions.failed_to_save_to_readeck"), {
443+
duration: 3000,
444+
})
445+
}
446+
},
447+
}),
448+
{
449+
deps: [readeckAvailable],
450+
},
451+
)
452+
}

apps/renderer/src/modules/command/commands/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,11 +116,17 @@ export type SaveToOutlineCommand = Command<{
116116
fn: (payload: { entryId: string }) => void
117117
}>
118118

119+
export type SaveToReadeckCommand = Command<{
120+
id: typeof COMMAND_ID.integration.saveToReadeck
121+
fn: (payload: { entryId: string }) => void
122+
}>
123+
119124
export type IntegrationCommand =
120125
| SaveToEagleCommand
121126
| SaveToReadwiseCommand
122127
| SaveToInstapaperCommand
123128
| SaveToObsidianCommand
124129
| SaveToOutlineCommand
130+
| SaveToReadeckCommand
125131

126132
export type BasicCommand = EntryCommand | IntegrationCommand

apps/renderer/src/modules/settings/tabs/integration.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
SimpleIconsInstapaper,
55
SimpleIconsObsidian,
66
SimpleIconsOutline,
7+
SimpleIconsReadeck,
78
SimpleIconsReadwise,
89
} from "@follow/components/ui/platform-icon/icons.js"
910
import { useEffect } from "react"
@@ -149,7 +150,30 @@ export const SettingIntegration = () => {
149150
vertical: true,
150151
description: t("integration.outline.collection.description"),
151152
}),
152-
153+
{
154+
type: "title",
155+
value: (
156+
<span className="flex items-center gap-2 font-bold">
157+
<SimpleIconsReadeck />
158+
{t("integration.readeck.title")}
159+
</span>
160+
),
161+
},
162+
defineSettingItem("enableReadeck", {
163+
label: t("integration.readeck.enable.label"),
164+
description: t("integration.readeck.enable.description"),
165+
}),
166+
defineSettingItem("readeckEndpoint", {
167+
label: t("integration.readeck.endpoint.label"),
168+
vertical: true,
169+
description: t("integration.readeck.endpoint.description"),
170+
}),
171+
defineSettingItem("readeckToken", {
172+
label: t("integration.readeck.token.label"),
173+
vertical: true,
174+
type: "password",
175+
description: t("integration.readeck.token.description"),
176+
}),
153177
BottomTip,
154178
]}
155179
/>

locales/app/en.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@
112112
"entry_actions.failed_to_save_to_instapaper": "Failed to save to Instapaper.",
113113
"entry_actions.failed_to_save_to_obsidian": "Failed to save to Obsidian",
114114
"entry_actions.failed_to_save_to_outline": "Failed to save to Outline.",
115+
"entry_actions.failed_to_save_to_readeck": "Failed to save to Readeck.",
115116
"entry_actions.failed_to_save_to_readwise": "Failed to save to Readwise.",
116117
"entry_actions.mark_as_read": "Mark as Read",
117118
"entry_actions.mark_as_unread": "Mark as Unread",
@@ -121,11 +122,13 @@
121122
"entry_actions.save_to_instapaper": "Save To Instapaper",
122123
"entry_actions.save_to_obsidian": "Save to Obsidian",
123124
"entry_actions.save_to_outline": "Save To Outline",
125+
"entry_actions.save_to_readeck": "Save To Readeck",
124126
"entry_actions.save_to_readwise": "Save To Readwise",
125127
"entry_actions.saved_to_eagle": "Saved To Eagle.",
126128
"entry_actions.saved_to_instapaper": "Saved To Instapaper.",
127129
"entry_actions.saved_to_obsidian": "Saved to Obsidian",
128130
"entry_actions.saved_to_outline": "Saved To Outline.",
131+
"entry_actions.saved_to_readeck": "Saved To Readeck.",
129132
"entry_actions.saved_to_readwise": "Saved To Readwise.",
130133
"entry_actions.share": "Share",
131134
"entry_actions.star": "Star",
@@ -244,7 +247,7 @@
244247
"new_user_guide.step.behavior.unread_question.option2": "Balanced: Automatically marked as read when hovered over or scrolled out of view.",
245248
"new_user_guide.step.behavior.unread_question.option3": "Conservative: Marked as read only when clicked.",
246249
"new_user_guide.step.features.actions.description": "Action rules allow you to perform different actions for different feeds.\n- Using AI to summarize or translate.\n- Configure how to read entries.\n- Enable notifications for new entries or silence them.\n- Rewrite or block specific entries.\n- Send new entries to a webhook address.",
247-
"new_user_guide.step.features.integration.description": "Integrations allow you to save entries to other services. Currently supported services are:\n- Eagle\n- Readwise\n- Instapaper\n- Obsidian",
250+
"new_user_guide.step.features.integration.description": "Integrations allow you to save entries to other services. Currently supported services are:\n- Eagle\n- Readwise\n- Instapaper\n- Obsidian\n- Outline\n- Readeck",
248251
"new_user_guide.step.migrate.profile": "Setup your profile",
249252
"new_user_guide.step.migrate.title": "Migrate from OPML file",
250253
"new_user_guide.step.migrate.wallet": "Check your wallet",

locales/app/ja.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@
112112
"entry_actions.failed_to_save_to_instapaper": "Instapaper への保存に失敗しました。",
113113
"entry_actions.failed_to_save_to_obsidian": "Obsidian への保存に失敗しました。",
114114
"entry_actions.failed_to_save_to_outline": "Outline への保存に失敗しました。",
115+
"entry_actions.failed_to_save_to_readeck": "Readeck への保存に失敗しました。",
115116
"entry_actions.failed_to_save_to_readwise": "Readwise への保存に失敗しました。",
116117
"entry_actions.mark_as_read": "既読にする",
117118
"entry_actions.mark_as_unread": "未読にする",
@@ -121,11 +122,13 @@
121122
"entry_actions.save_to_instapaper": "Instapaper に保存",
122123
"entry_actions.save_to_obsidian": "Obsidian に保存",
123124
"entry_actions.save_to_outline": "Outline に保存",
125+
"entry_actions.save_to_readeck": "Readeck に保存",
124126
"entry_actions.save_to_readwise": "Readwise に保存",
125127
"entry_actions.saved_to_eagle": "Eagle に保存されました。",
126128
"entry_actions.saved_to_instapaper": "Instapaper に保存されました。",
127129
"entry_actions.saved_to_obsidian": "Obsidian に保存されました。",
128130
"entry_actions.saved_to_outline": "Outline に保存されました。",
131+
"entry_actions.saved_to_readeck": "Readeck に保存されました。",
129132
"entry_actions.saved_to_readwise": "Readwise に保存されました。",
130133
"entry_actions.share": "共有",
131134
"entry_actions.star": "スター",
@@ -241,7 +244,7 @@
241244
"new_user_guide.step.behavior.unread_question.option2": "バランス: ホバーしたりスクロールが終わると自動で既読にします。",
242245
"new_user_guide.step.behavior.unread_question.option3": "コンサバ: クリックしたときのみ既読にします。",
243246
"new_user_guide.step.features.actions.description": "アクションルールはフィードごとに違ったアクションを実行できます。\n - AI を使った要約や翻訳 \n - エントリーの読み方の設定 \n - 新着エントリーの通知の有効/無効 \n - 特定のエントリーの書き換えやブロック \n - 新着エントリーの Webhook アドレスへの送信など",
244-
"new_user_guide.step.features.integration.description": "統合により、他のサービスにエントリーを保存することができます。現在対応しているサービスは以下のとおりです:\n- Eagle \n- Readwise \n- Instapaper \n- Obsidian \n- Outline",
247+
"new_user_guide.step.features.integration.description": "統合により、他のサービスにエントリーを保存することができます。現在対応しているサービスは以下のとおりです:\n- Eagle \n- Readwise \n- Instapaper \n- Obsidian \n- Outline \n- Readeck",
245248
"new_user_guide.step.migrate.profile": "プロファイルをセットアップ",
246249
"new_user_guide.step.migrate.title": "OPML ファイルを構成する",
247250
"new_user_guide.step.migrate.wallet": "ウォレットをチェック",

locales/app/zh-CN.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@
112112
"entry_actions.failed_to_save_to_instapaper": "保存到 Instapaper 失败。",
113113
"entry_actions.failed_to_save_to_obsidian": "保存到 Obsidian 失败。",
114114
"entry_actions.failed_to_save_to_outline": "保存到 Outline 失败。",
115+
"entry_actions.failed_to_save_to_readeck": "保存到 Readeck 失败。",
115116
"entry_actions.failed_to_save_to_readwise": "保存到 Readwise 失败。",
116117
"entry_actions.mark_as_read": "标记为已读",
117118
"entry_actions.mark_as_unread": "标记为未读",
@@ -121,11 +122,13 @@
121122
"entry_actions.save_to_instapaper": "保存到 Instapaper",
122123
"entry_actions.save_to_obsidian": "保存到 Obsidian",
123124
"entry_actions.save_to_outline": "保存到 Outline",
125+
"entry_actions.save_to_readeck": "保存到 Readeck",
124126
"entry_actions.save_to_readwise": "保存到 Readwise",
125127
"entry_actions.saved_to_eagle": "已保存到 Eagle。",
126128
"entry_actions.saved_to_instapaper": "已保存到 Instapaper。",
127129
"entry_actions.saved_to_obsidian": "已保存到 Obsidian。",
128130
"entry_actions.saved_to_outline": "已保存到 Outline。",
131+
"entry_actions.saved_to_readeck": "已保存到 Readeck。",
129132
"entry_actions.saved_to_readwise": "已保存到 Readwise。",
130133
"entry_actions.share": "分享",
131134
"entry_actions.star": "收藏",
@@ -242,7 +245,7 @@
242245
"new_user_guide.step.behavior.unread_question.option2": "平衡:悬停或滚出视野时自动标记为已读。",
243246
"new_user_guide.step.behavior.unread_question.option3": "被动:仅在点击时标记为已读。",
244247
"new_user_guide.step.features.actions.description": "自动化规则允许你对不同的订阅执行不同的操作。\n- 使用 AI 进行摘要或翻译\n- 配置阅读条目的方式\n- 启用新条目的通知或静音\n- 重写或屏蔽特定条目\n- 将新条目发送到 webhook 地址",
245-
"new_user_guide.step.features.integration.description": "集成允许你将条目保存到其他服务。目前支持的服务有:\n- Eagle\n- Readwise\n- Instapaper\n- Obsidian",
248+
"new_user_guide.step.features.integration.description": "集成允许你将条目保存到其他服务。目前支持的服务有:\n- Eagle\n- Readwise\n- Instapaper\n- Obsidian\n- Outline\n- Readeck",
246249
"new_user_guide.step.migrate.profile": "设置你的个人资料",
247250
"new_user_guide.step.migrate.title": "从 OPML 文件导入",
248251
"new_user_guide.step.migrate.wallet": "检查你的钱包",

locales/app/zh-HK.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@
112112
"entry_actions.failed_to_save_to_instapaper": "無法儲存至 Instapaper",
113113
"entry_actions.failed_to_save_to_obsidian": "無法儲存至 Obsidian ",
114114
"entry_actions.failed_to_save_to_outline": "無法儲存至 Outline",
115+
"entry_actions.failed_to_save_to_readeck": "無法儲存至 Readeck",
115116
"entry_actions.failed_to_save_to_readwise": "無法儲存至 Readwise",
116117
"entry_actions.mark_as_read": "標記為已讀",
117118
"entry_actions.mark_as_unread": "標記為未讀",
@@ -121,11 +122,13 @@
121122
"entry_actions.save_to_instapaper": "儲存至 Instapaper",
122123
"entry_actions.save_to_obsidian": "儲存至 Obsidian",
123124
"entry_actions.save_to_outline": "儲存至 Outline",
125+
"entry_actions.save_to_readeck": "儲存至 Readeck",
124126
"entry_actions.save_to_readwise": "儲存至 Readwise",
125127
"entry_actions.saved_to_eagle": "已儲存至 Eagle",
126128
"entry_actions.saved_to_instapaper": "已儲存至 Instapaper",
127129
"entry_actions.saved_to_obsidian": "已儲存至 Obsidian",
128130
"entry_actions.saved_to_outline": "已儲存至 Outline",
131+
"entry_actions.saved_to_readeck": "已儲存至 Readeck",
129132
"entry_actions.saved_to_readwise": "已儲存至 Readwise",
130133
"entry_actions.share": "分享",
131134
"entry_actions.star": "收藏",
@@ -244,7 +247,7 @@
244247
"new_user_guide.step.behavior.unread_question.option2": "平衡:懸停或滾出視野時自動標記為已讀",
245248
"new_user_guide.step.behavior.unread_question.option3": "被動:僅在點擊時標記為已讀",
246249
"new_user_guide.step.features.actions.description": "自動化規則允許你對不同的訂閱執行不同的動作\n- 使用 AI 進行摘要或翻譯\n- 配置閱讀條目的方式\n- 啟用新條目的通知或靜音\n- 重寫或封鎖特定條目\n- 將新條目發送到 webhook 地址",
247-
"new_user_guide.step.features.integration.description": "集成允許你將條目儲存到其他服務。目前支持的服務有:\n- Eagle\n- Readwise\n- Instapaper\n- Obsidian",
250+
"new_user_guide.step.features.integration.description": "集成允許你將條目儲存到其他服務。目前支持的服務有:\n- Eagle\n- Readwise\n- Instapaper\n- Obsidian\n- Outline\n- Readeck",
248251
"new_user_guide.step.migrate.profile": "設定你的個人資料",
249252
"new_user_guide.step.migrate.title": "從 OPML 檔案匯入",
250253
"new_user_guide.step.migrate.wallet": "檢查你的銀包",

locales/app/zh-TW.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@
112112
"entry_actions.failed_to_save_to_instapaper": "無法儲存到 Instapaper。",
113113
"entry_actions.failed_to_save_to_obsidian": "儲存到 Obsidian 失敗",
114114
"entry_actions.failed_to_save_to_outline": "無法儲存到 Outline。",
115+
"entry_actions.failed_to_save_to_readeck": "無法儲存到 Readeck。",
115116
"entry_actions.failed_to_save_to_readwise": "無法儲存到 Readwise。",
116117
"entry_actions.mark_as_read": "標記為已讀",
117118
"entry_actions.mark_as_unread": "標記為未讀",
@@ -121,11 +122,13 @@
121122
"entry_actions.save_to_instapaper": "儲存到 Instapaper",
122123
"entry_actions.save_to_obsidian": "儲存到 Obsidian",
123124
"entry_actions.save_to_outline": "儲存到 Outline",
125+
"entry_actions.save_to_readeck": "儲存到 Readeck",
124126
"entry_actions.save_to_readwise": "儲存到 Readwise",
125127
"entry_actions.saved_to_eagle": "已儲存至 Eagle。",
126128
"entry_actions.saved_to_instapaper": "已儲存至 Instapaper。",
127129
"entry_actions.saved_to_obsidian": "已儲存到 Obsidian",
128130
"entry_actions.saved_to_outline": "已儲存至 Outline。",
131+
"entry_actions.saved_to_readeck": "已儲存至 Readeck。",
129132
"entry_actions.saved_to_readwise": "已儲存至 Readwise。",
130133
"entry_actions.share": "分享",
131134
"entry_actions.star": "收藏",
@@ -244,7 +247,7 @@
244247
"new_user_guide.step.behavior.unread_question.option2": "平衡:滑過或離開視圖時自動標記為已讀。",
245248
"new_user_guide.step.behavior.unread_question.option3": "保守:僅在點擊時標記為已讀。",
246249
"new_user_guide.step.features.actions.description": "指令允許您為不同的訂閱源設定操作。\n- 使用 AI 進行總結或翻譯。\n- 設定條目的閱讀方式。\n- 為新條目啟用通知或靜音。\n- 覆寫或封鎖特定條目。\n- 將新條目發送到 webhook。",
247-
"new_user_guide.step.features.integration.description": "整合功能允許您將條目保存到其他服務。目前支持的服務包括:\n- Eagle\n- Readwise\n- Instapaper\n- Obsidian",
250+
"new_user_guide.step.features.integration.description": "整合功能允許您將條目保存到其他服務。目前支持的服務包括:\n- Eagle\n- Readwise\n- Instapaper\n- Obsidian\n- Outline\n- Readeck",
248251
"new_user_guide.step.migrate.profile": "設定您的個人資料",
249252
"new_user_guide.step.migrate.title": "從 OPML 文件導入",
250253
"new_user_guide.step.migrate.wallet": "檢查您的錢包",

locales/settings/en.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,13 @@
184184
"integration.outline.title": "Outline",
185185
"integration.outline.token.description": "You can get it from your Outline account settings.",
186186
"integration.outline.token.label": "Outline API Key",
187+
"integration.readeck.enable.description": "Display 'Save to Readeck' button when available.",
188+
"integration.readeck.enable.label": "Enable",
189+
"integration.readeck.endpoint.description": "The URL is 'https://<YOUR_READECK_DOMAIN>'.",
190+
"integration.readeck.endpoint.label": "Readeck API Base URL",
191+
"integration.readeck.title": "Readeck",
192+
"integration.readeck.token.description": "You can get it from your Readeck account settings.",
193+
"integration.readeck.token.label": "Readeck API Token",
187194
"integration.readwise.enable.description": "Display 'Save to Readwise' button when available.",
188195
"integration.readwise.enable.label": "Enable",
189196
"integration.readwise.title": "Readwise",

0 commit comments

Comments
 (0)