Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add export to Obsidian feature #998

Merged
merged 23 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions apps/main/src/tipc/app.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import fs from "node:fs/promises"
import path from "node:path"

import { getRendererHandlers } from "@egoist/tipc/main"
Expand Down Expand Up @@ -45,7 +46,7 @@

invalidateQuery: t.procedure
.input<(string | number | undefined)[]>()
.action(async ({ input }) => {

Check warning on line 49 in apps/main/src/tipc/app.ts

View workflow job for this annotation

GitHub Actions / Format, Lint and Typecheck (lts/*)

Async arrow function has no 'await' expression
const mainWindow = getMainWindow()
if (!mainWindow) return
const handlers = getRendererHandlers<RendererHandlers>(mainWindow.webContents)
Expand All @@ -54,7 +55,7 @@

windowAction: t.procedure
.input<{ action: "close" | "minimize" | "maximum" }>()
.action(async ({ input, context }) => {

Check warning on line 58 in apps/main/src/tipc/app.ts

View workflow job for this annotation

GitHub Actions / Format, Lint and Typecheck (lts/*)

Async arrow function has no 'await' expression
if (context.sender.getType() === "window") {
const window: BrowserWindow | null = (context.sender as Sender).getOwnerBrowserWindow()

Expand Down Expand Up @@ -137,7 +138,7 @@
}
}
}),
getWindowIsMaximized: t.procedure.input<void>().action(async ({ context }) => {

Check warning on line 141 in apps/main/src/tipc/app.ts

View workflow job for this annotation

GitHub Actions / Format, Lint and Typecheck (lts/*)

Async arrow function has no 'await' expression
const window: BrowserWindow | null = (context.sender as Sender).getOwnerBrowserWindow()

if (isWindows11 && window) {
Expand All @@ -156,17 +157,17 @@

return window?.isMaximized()
}),
quitAndInstall: t.procedure.action(async () => {

Check warning on line 160 in apps/main/src/tipc/app.ts

View workflow job for this annotation

GitHub Actions / Format, Lint and Typecheck (lts/*)

Async arrow function has no 'await' expression
quitAndInstall()
}),

cleanAuthSessionToken: t.procedure.action(async () => {

Check warning on line 164 in apps/main/src/tipc/app.ts

View workflow job for this annotation

GitHub Actions / Format, Lint and Typecheck (lts/*)

Async arrow function has no 'await' expression
cleanAuthSessionToken()
cleanUser()
}),
/// clipboard

readClipboard: t.procedure.action(async () => clipboard.readText()),

Check warning on line 170 in apps/main/src/tipc/app.ts

View workflow job for this annotation

GitHub Actions / Format, Lint and Typecheck (lts/*)

Async arrow function has no 'await' expression
/// search
search: t.procedure
.input<{
Expand All @@ -185,7 +186,7 @@
requestId = webContents.findInPage(input.text, input.options)
return promise
}),
clearSearch: t.procedure.action(async ({ context: { sender: webContents } }) => {

Check warning on line 189 in apps/main/src/tipc/app.ts

View workflow job for this annotation

GitHub Actions / Format, Lint and Typecheck (lts/*)

Async arrow function has no 'await' expression
webContents.stopFindInPage("keepSelection")
}),

Expand All @@ -212,12 +213,12 @@
})
}),

getAppPath: t.procedure.action(async () => app.getAppPath()),

Check warning on line 216 in apps/main/src/tipc/app.ts

View workflow job for this annotation

GitHub Actions / Format, Lint and Typecheck (lts/*)

Async arrow function has no 'await' expression
resolveAppAsarPath: t.procedure.input<string>().action(async ({ input }) => {

Check warning on line 217 in apps/main/src/tipc/app.ts

View workflow job for this annotation

GitHub Actions / Format, Lint and Typecheck (lts/*)

Async arrow function has no 'await' expression
return path.resolve(app.getAppPath(), input)
}),

switchAppLocale: t.procedure.input<string>().action(async ({ input }) => {

Check warning on line 221 in apps/main/src/tipc/app.ts

View workflow job for this annotation

GitHub Actions / Format, Lint and Typecheck (lts/*)

Async arrow function has no 'await' expression
i18n.changeLanguage(input)
registerMenuAndContextMenu()

Expand All @@ -225,6 +226,46 @@
}),

clearAllData: t.procedure.action(clearAllData),

saveToObsidian: t.procedure
.input<{
url: string
title: string
content: string
author: string
publishedAt: string
vaultPath: string
}>()
.action(async ({ input }) => {
try {
const { url, title, content, author, publishedAt, vaultPath } = input

const safeTitle = (title || "untitled").trim()
const fileName = `${safeTitle
.replaceAll(/[^a-z0-9]/gi, "_")
.toLowerCase()
.slice(0, 100)}.md`
const filePath = path.join(vaultPath, fileName)

const markdown = `---
url: ${url}
author: ${author}
publishedAt: ${publishedAt}
---

# ${title}

${content}
`

await fs.writeFile(filePath, markdown, "utf-8")
return { success: true }
} catch (error) {
console.error("Failed to save to Obsidian:", error)
const errorMessage = error instanceof Error ? error.message : String(error)
return { success: false, error: errorMessage }
}
}),
}

interface Sender extends Electron.WebContents {
Expand Down
4 changes: 4 additions & 0 deletions apps/renderer/src/atoms/settings/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ export const createDefaultSettings = (): IntegrationSettings => ({
enableOmnivore: false,
omnivoreEndpoint: "",
omnivoreToken: "",

// obsidian
enableObsidian: false,
obsidianVaultPath: "",
})

export const {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { SVGProps } from "react"

export function SimpleIconsObsidian(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 512 512"
{...props}
>
<path
fill="currentColor"
d="M359.9 434.3c-2.6 19.1-21.3 34-40 28.9-26.4-7.3-57-18.7-84.7-20.8l-42.3-3.2a27.9 27.9 0 0 1-18-8.4l-73-75a27.9 27.9 0 0 1-5.4-31s45.1-99 46.8-104.2c1.7-5.1 7.8-50 11.4-74.2a28 28 0 0 1 9-16.6l86.2-77.5a28 28 0 0 1 40.6 3.5l72.5 92a29.7 29.7 0 0 1 6.2 18.3c0 17.4 1.5 53.2 11.1 76.3a303 303 0 0 0 35.6 58.5 14 14 0 0 1 1.1 15.7c-6.4 10.8-18.9 31.4-36.7 57.9a143.3 143.3 0 0 0-20.4 59.8Z"
/>
</svg>
)
}
1 change: 1 addition & 0 deletions apps/renderer/src/components/ui/platform-icon/icons.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from "./collections/eagle"
export * from "./collections/instapaper"
export * from "./collections/obsidian"
export * from "./collections/omnivore"
export * from "./collections/readwise"
export * from "./collections/rss3"
68 changes: 68 additions & 0 deletions apps/renderer/src/hooks/biz/useEntryActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { mountLottie } from "~/components/ui/lottie-container"
import {
SimpleIconsEagle,
SimpleIconsInstapaper,
SimpleIconsObsidian,
SimpleIconsOmnivore,
SimpleIconsReadwise,
} from "~/components/ui/platform-icon/icons"
Expand Down Expand Up @@ -171,6 +172,45 @@ export const useEntryActions = ({
const enableOmnivore = useIntegrationSettingKey("enableOmnivore")
const omnivoreToken = useIntegrationSettingKey("omnivoreToken")
const omnivoreEndpoint = useIntegrationSettingKey("omnivoreEndpoint")
const enableObsidian = useIntegrationSettingKey("enableObsidian")
const obsidianVaultPath = useIntegrationSettingKey("obsidianVaultPath")

const obsidianEnabledQuery = useQuery({
queryKey: ["obsidian-enabled"],
queryFn: () => {
return !!enableObsidian && !!obsidianVaultPath
},
refetchOnWindowFocus: false,
refetchOnMount: false,
staleTime: Infinity,
})
hyoban marked this conversation as resolved.
Show resolved Hide resolved

const saveToObsidian = useMutation({
mutationKey: ["save-to-obsidian"],
mutationFn: async (data: {
url: string
title: string
content: string
author: string
publishedAt: string
vaultPath: string
}) => {
if (!obsidianEnabledQuery.data) {
throw new Error("Obsidian feature is not enabled")
}
return await tipcClient?.saveToObsidian(data)
},
onSuccess: () => {
toast.success(t("entry_actions.saved_to_obsidian"), {
duration: 3000,
})
},
onError: () => {
toast.error(t("entry_actions.failed_to_save_to_obsidian"), {
duration: 3000,
})
},
})

const checkEagle = useQuery({
queryKey: ["check-eagle"],
Expand Down Expand Up @@ -370,6 +410,24 @@ export const useEntryActions = ({
}
},
},
{
name: t("entry_actions.save_to_obsidian"),
icon: <SimpleIconsObsidian />,
key: "saveToObsidian",
hide: !obsidianEnabledQuery.data || !populatedEntry?.entries?.url,
onClick: () => {
if (!populatedEntry?.entries?.url || !obsidianVaultPath) return

saveToObsidian.mutate({
url: populatedEntry.entries.url,
title: populatedEntry.entries.title || "",
content: populatedEntry.entries.content || "",
author: populatedEntry.entries.author || "",
publishedAt: populatedEntry.entries.publishedAt || "",
vaultPath: obsidianVaultPath,
})
},
},
{
key: "tip",
shortcut: shortcuts.entry.tip.key,
Expand Down Expand Up @@ -534,6 +592,8 @@ export const useEntryActions = ({
enableInstapaper,
instapaperPassword,
instapaperUsername,
enableObsidian,
obsidianVaultPath,
feed?.ownerUserId,
type,
showSourceContent,
Expand All @@ -543,9 +603,17 @@ export const useEntryActions = ({
showSourceContentModal,
read,
unread,
enableOmnivore,
isInbox,
omnivoreEndpoint,
omnivoreToken,
obsidianEnabledQuery.data,
saveToObsidian,
])

return {
items,
saveToObsidian,
obsidianEnabled: obsidianEnabledQuery.data,
}
}
19 changes: 19 additions & 0 deletions apps/renderer/src/modules/settings/tabs/integration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Divider } from "~/components/ui/divider"
import {
SimpleIconsEagle,
SimpleIconsInstapaper,
SimpleIconsObsidian,
SimpleIconsOmnivore,
SimpleIconsReadwise,
} from "~/components/ui/platform-icon/icons"
Expand Down Expand Up @@ -151,6 +152,24 @@ export const SettingIntegration = () => {
</>
),
}),
{
type: "title",
value: (
<span className="flex items-center gap-2 font-bold">
<SimpleIconsObsidian />
{t("integration.obsidian.title")}
</span>
),
},
defineSettingItem("enableObsidian", {
label: t("integration.obsidian.enable.label"),
description: t("integration.obsidian.enable.description"),
}),
defineSettingItem("obsidianVaultPath", {
label: t("integration.obsidian.vaultPath.label"),
vertical: true,
description: t("integration.obsidian.vaultPath.description"),
}),

BottomTip,
]}
Expand Down
3 changes: 3 additions & 0 deletions locales/app/ar-DZ.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,18 @@
"entry_actions.copy_link": "نسخ الرابط",
"entry_actions.failed_to_save_to_eagle": "فشل في الحفظ إلى Eagle.",
"entry_actions.failed_to_save_to_instapaper": "فشل في الحفظ إلى Instapaper.",
"entry_actions.failed_to_save_to_obsidian": "فشل في الحفظ إلى Obsidian.",
"entry_actions.failed_to_save_to_readwise": "فشل في الحفظ إلى Readwise.",
"entry_actions.mark_as_read": "تحديد كمقروء",
"entry_actions.mark_as_unread": "تحديد كغير مقروء",
"entry_actions.open_in_browser": "فتح في المتصفح",
"entry_actions.save_media_to_eagle": "حفظ الوسائط إلى Eagle",
"entry_actions.save_to_instapaper": "حفظ إلى Instapaper",
"entry_actions.save_to_obsidian": "حفظ إلى Obsidian",
"entry_actions.save_to_readwise": "حفظ إلى Readwise",
"entry_actions.saved_to_eagle": "تم الحفظ إلى Eagle.",
"entry_actions.saved_to_instapaper": "تم الحفظ إلى Instapaper.",
"entry_actions.saved_to_obsidian": "تم الحفظ إلى Obsidian.",
"entry_actions.saved_to_readwise": "تم الحفظ إلى Readwise.",
"entry_actions.share": "مشاركة",
"entry_actions.star": "تمييز بنجمة",
Expand Down
3 changes: 3 additions & 0 deletions locales/app/ar-IQ.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,18 @@
"entry_actions.copy_link": "نسخ الرابط",
"entry_actions.failed_to_save_to_eagle": "فشل في الحفظ إلى Eagle.",
"entry_actions.failed_to_save_to_instapaper": "فشل في الحفظ إلى Instapaper.",
"entry_actions.failed_to_save_to_obsidian": "فشل في الحفظ إلى Obsidian.",
"entry_actions.failed_to_save_to_readwise": "فشل في الحفظ إلى Readwise.",
"entry_actions.mark_as_read": "تحديد كمقروء",
"entry_actions.mark_as_unread": "تحديد كغير مقروء",
"entry_actions.open_in_browser": "فتح في المتصفح",
"entry_actions.save_media_to_eagle": "حفظ الوسائط إلى Eagle",
"entry_actions.save_to_instapaper": "حفظ إلى Instapaper",
"entry_actions.save_to_obsidian": "حفظ إلى Obsidian",
"entry_actions.save_to_readwise": "حفظ إلى Readwise",
"entry_actions.saved_to_eagle": "تم الحفظ إلى Eagle.",
"entry_actions.saved_to_instapaper": "تم الحفظ إلى Instapaper.",
"entry_actions.saved_to_obsidian": "تم الحفظ إلى Obsidian.",
"entry_actions.saved_to_readwise": "تم الحفظ إلى Readwise.",
"entry_actions.share": "مشاركة",
"entry_actions.star": "إضافة إلى المفضلة",
Expand Down
5 changes: 4 additions & 1 deletion locales/app/ar-KW.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,18 @@
"entry_actions.copy_link": "نسخ الرابط",
"entry_actions.failed_to_save_to_eagle": "فشل في الحفظ إلى Eagle.",
"entry_actions.failed_to_save_to_instapaper": "فشل في الحفظ إلى Instapaper.",
"entry_actions.failed_to_save_to_obsidian": "فشل في الحفظ إلى Obsidian",
"entry_actions.failed_to_save_to_readwise": "فشل في الحفظ إلى Readwise.",
"entry_actions.mark_as_read": "وضع علامة كمقروء",
"entry_actions.mark_as_unread": "وضع علامة كغير مقروء",
"entry_actions.open_in_browser": "فتح في المتصفح",
"entry_actions.save_media_to_eagle": "حفظ الوسائط إلى Eagle",
"entry_actions.save_to_instapaper": "حفظ إلى Instapaper",
"entry_actions.save_to_obsidian": "حفظ إلى Obsidian",
"entry_actions.save_to_readwise": "حفظ إلى Readwise",
"entry_actions.saved_to_eagle": "تم الحفظ إلى Eagle.",
"entry_actions.saved_to_instapaper": "تم الحفظ إلى Instapaper.",
"entry_actions.saved_to_obsidian": "تم الحفظ إلى Obsidian.",
"entry_actions.saved_to_readwise": "تم الحفظ إلى Readwise.",
"entry_actions.share": "مشاركة",
"entry_actions.star": "تفضيل",
Expand Down Expand Up @@ -132,7 +135,7 @@
"search.options.all": "الكل",
"search.options.entries": "إدخالات",
"search.options.feeds": "تغذيات",
"search.options.search_type": "نوع البحث",
"search.options.search_type": "نوع ال��حث",
"search.placeholder": "بحث...",
"search.result_count_local_mode": "(وضع محلي)",
"search.tooltip.local_search": "يغطي هذا البحث البيانات المتاحة محليًا. حاول إعادة الجلب لتضمين أحدث البيانات.",
Expand Down
5 changes: 4 additions & 1 deletion locales/app/ar-MA.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,19 @@
"entry_actions.copy_link": "نسخ الرابط",
"entry_actions.failed_to_save_to_eagle": "فشل الحفظ إلى Eagle.",
"entry_actions.failed_to_save_to_instapaper": "فشل الحفظ إلى Instapaper.",
"entry_actions.failed_to_save_to_obsidian": "فشل الحفظ في Obsidian",
"entry_actions.failed_to_save_to_readwise": "فشل الحفظ إلى Readwise.",
"entry_actions.mark_as_read": "وضع علامة كمقروء",
"entry_actions.mark_as_unread": "وضع علامة كغير مقروء",
"entry_actions.open_in_browser": "فتح في المتصفح",
"entry_actions.save_media_to_eagle": "حفظ الوسائط إلى Eagle",
"entry_actions.save_to_instapaper": "حفظ إلى Instapaper",
"entry_actions.save_to_obsidian": "حفظ في Obsidian",
"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_obsidian": "تم الحفظ في Obsidian",
"entry_actions.saved_to_readwise": "تم الحفظ في Readwise.",
"entry_actions.share": "مشاركة",
"entry_actions.star": "إضافة إلى المفضلة",
"entry_actions.starred": "مضاف إلى المفضلة.",
Expand Down
3 changes: 3 additions & 0 deletions locales/app/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,18 @@
"entry_actions.copy_link": "Link kopieren",
"entry_actions.failed_to_save_to_eagle": "Speichern in Eagle fehlgeschlagen.",
"entry_actions.failed_to_save_to_instapaper": "Speichern in Instapaper fehlgeschlagen.",
"entry_actions.failed_to_save_to_obsidian": "Speichern in Obsidian fehlgeschlagen",
"entry_actions.failed_to_save_to_readwise": "Speichern in Readwise fehlgeschlagen.",
"entry_actions.mark_as_read": "Als gelesen markieren",
"entry_actions.mark_as_unread": "Als ungelesen markieren",
"entry_actions.open_in_browser": "Im Browser öffnen",
"entry_actions.save_media_to_eagle": "Medien in Eagle speichern",
"entry_actions.save_to_instapaper": "In Instapaper speichern",
"entry_actions.save_to_obsidian": "In Obsidian speichern",
"entry_actions.save_to_readwise": "In Readwise speichern",
"entry_actions.saved_to_eagle": "In Eagle gespeichert.",
"entry_actions.saved_to_instapaper": "In Instapaper gespeichert.",
"entry_actions.saved_to_obsidian": "In Obsidian gespeichert",
"entry_actions.saved_to_readwise": "In Readwise gespeichert.",
"entry_actions.share": "Teilen",
"entry_actions.star": "Favorisieren",
Expand Down
3 changes: 3 additions & 0 deletions locales/app/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"entry_actions.copy_title": "Copy Title",
"entry_actions.failed_to_save_to_eagle": "Failed to save to Eagle.",
"entry_actions.failed_to_save_to_instapaper": "Failed to save to Instapaper.",
"entry_actions.failed_to_save_to_obsidian": "Failed to save to Obsidian",
"entry_actions.failed_to_save_to_omnivore": "Failed to save to Omnivore.",
"entry_actions.failed_to_save_to_readwise": "Failed to save to Readwise.",
"entry_actions.mark_as_read": "Mark as Read",
Expand All @@ -64,10 +65,12 @@
"entry_actions.recent_reader": "Recent reader:",
"entry_actions.save_media_to_eagle": "Save Media To Eagle",
"entry_actions.save_to_instapaper": "Save To Instapaper",
"entry_actions.save_to_obsidian": "Save to Obsidian",
"entry_actions.save_to_omnivore": "Save To Omnivore",
"entry_actions.save_to_readwise": "Save To Readwise",
"entry_actions.saved_to_eagle": "Saved To Eagle.",
"entry_actions.saved_to_instapaper": "Saved To Instapaper.",
"entry_actions.saved_to_obsidian": "Saved to Obsidian",
"entry_actions.saved_to_omnivore": "Saved To Omnivore.",
"entry_actions.saved_to_readwise": "Saved To Readwise.",
"entry_actions.share": "Share",
Expand Down
3 changes: 3 additions & 0 deletions locales/app/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@
"discover.select_placeholder": "Seleccionar",
"early_access": "Versión de acceso anticipado",
"entry_actions.copy_link": "Copiar enlace",
"entry_actions.failed_to_save_to_obsidian": "Error al guardar en Obsidian",
"entry_actions.mark_as_read": "Marcar como leído",
"entry_actions.mark_as_unread": "Marcar como no leído",
"entry_actions.open_in_browser": "Abrir en el navegador",
"entry_actions.save_media_to_eagle": "Guardar medios en Eagle",
"entry_actions.save_to_instapaper": "Guardar en Instapaper",
"entry_actions.save_to_obsidian": "Guardar en Obsidian",
"entry_actions.save_to_readwise": "Guardar en Readwise",
"entry_actions.saved_to_obsidian": "Guardado en Obsidian",
"entry_actions.share": "Compartir",
"entry_actions.star": "Agregar a favoritos",
"entry_actions.tip": "Dar propina",
Expand Down
3 changes: 3 additions & 0 deletions locales/app/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"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_obsidian": "保存到 Obsidian 失败",
"entry_actions.failed_to_save_to_omnivore": "保存到 Omnivore 失败",
"entry_actions.failed_to_save_to_readwise": "保存到 Readwise 失败",
"entry_actions.mark_as_read": "标记为已读",
Expand All @@ -64,10 +65,12 @@
"entry_actions.recent_reader": "最近阅读者:",
"entry_actions.save_media_to_eagle": "保存到 Eagle",
"entry_actions.save_to_instapaper": "保存到 Instapaper",
"entry_actions.save_to_obsidian": "保存到 Obsidian",
"entry_actions.save_to_omnivore": "保存到 Omnivore",
"entry_actions.save_to_readwise": "保存到 Readwise",
"entry_actions.saved_to_eagle": "保存到 Eagle",
"entry_actions.saved_to_instapaper": "保存到 Instapaper.",
"entry_actions.saved_to_obsidian": "已保存到 Obsidian",
"entry_actions.saved_to_omnivore": "保存到 Omnivore.",
"entry_actions.saved_to_readwise": "保存到 Readwise.",
"entry_actions.share": "分享",
Expand Down
Loading
Loading