From ccb5a20abe4e0fbb4587eb2a08a93410ae9fa64e Mon Sep 17 00:00:00 2001 From: Jiaxin Peng Date: Thu, 23 Nov 2023 22:11:04 +0000 Subject: [PATCH] add custom frontmatter --- README.md | 3 + src/lang/locale/en.ts | 3 + src/lang/locale/ja.ts | 3 + src/lang/locale/zh.ts | 3 + src/ui/settingTabs.ts | 25 ++- src/upload/uploadCommand.ts | 35 ++++ .../upoload_custom/BaseUpload2NotionCustom.ts | 90 ++++++++++ .../upoload_custom/Upload2NotionCustom.ts | 157 ++++++++++++++++++ .../upoload_custom/getMarkdownCustom.ts | 43 +++++ 9 files changed, 360 insertions(+), 2 deletions(-) create mode 100644 src/upload/upoload_custom/BaseUpload2NotionCustom.ts create mode 100644 src/upload/upoload_custom/Upload2NotionCustom.ts create mode 100644 src/upload/upoload_custom/getMarkdownCustom.ts diff --git a/README.md b/README.md index 71e3aba..6c3851a 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,9 @@ Thus, based on the [original author's work](https://github.com/EasyChris/obsidia **Now, support both NotionNext and General databases.** ## Update + +### 1.2.0 + ### 1.1.1 - Fix the setting display bug in Japanese. - Add Japanese translation. diff --git a/src/lang/locale/en.ts b/src/lang/locale/en.ts index e109408..c54cfde 100644 --- a/src/lang/locale/en.ts +++ b/src/lang/locale/en.ts @@ -31,6 +31,9 @@ export const en = { NotionCustomTitleName: "Preferred title name", NotionCustomTitleNameDesc: "Enter the preferred title name for the first column of the Notion database (default: title)", NotionCustomTitleText: "Enter the name", + NotionCustomValues: "Customise values property", + NotionCustomValuesDesc: "Modify the column name of the Notion database,one per line", + NotionCustomValuesText: "Enter all properties that you want to sync", NotYetFinish: "Not finished. This function will be available in the next version", PlaceHolder: "Enter database Name", diff --git a/src/lang/locale/ja.ts b/src/lang/locale/ja.ts index 875e543..e2d6362 100644 --- a/src/lang/locale/ja.ts +++ b/src/lang/locale/ja.ts @@ -29,6 +29,9 @@ export const ja = { NotionCustomTitleName: "希望のタイトル名", NotionCustomTitleNameDesc: "Notionデータベースの最初の列のための希望のタイトル名を入力(デフォルト:title)", NotionCustomTitleText: "名前を入力", + NotionCustomValues: "値のカスタマイズ", + NotionCustomValuesDesc: "Notionデータベースの列名を変更、1行に1つ", + NotionCustomValuesText: "同期したいすべてのプロパティを入力", NotYetFinish: "未完了。この機能は次のバージョンで利用可能になります", PlaceHolder: "データベース名を入力", "notion-logo": "NotionNextで共有", diff --git a/src/lang/locale/zh.ts b/src/lang/locale/zh.ts index 56ff64b..a525aba 100644 --- a/src/lang/locale/zh.ts +++ b/src/lang/locale/zh.ts @@ -31,6 +31,9 @@ export const zh = { NotionCustomTitleName: "想要修改的表头名", NotionCustomTitleNameDesc: "输入你想要修改的notion数据库的表头名(默认:title)", NotionCustomTitleText: "输入表头名", + NotionCustomValues: "自定义Notion 数据库表头", + NotionCustomValuesDesc: "自定义Notion 数据库表头,每行一个", + NotionCustomValuesText: "输入你想要同步的所有属性", NotYetFinish: "未完成。此功能将在之后版本中提供", PlaceHolder: "输入数据库名称", "notion-logo": "分享到NotionNext", diff --git a/src/ui/settingTabs.ts b/src/ui/settingTabs.ts index 67862ba..4478cfc 100644 --- a/src/ui/settingTabs.ts +++ b/src/ui/settingTabs.ts @@ -10,12 +10,13 @@ export interface PluginSettings { notionUser: string; proxy: string; GeneralButton: boolean; - tagButton: boolean; + tagButton: boolean; CustomTitleButton: boolean; CustomTitleName: string; notionAPIGeneral: string; databaseIDGeneral: string; CustomButton: boolean; + CustomValues: string; notionAPICustom: string; databaseIDCustom: string; [key: string]: any; @@ -29,12 +30,13 @@ export const DEFAULT_SETTINGS: PluginSettings = { notionUser: "", proxy: "", GeneralButton: true, - tagButton: true, + tagButton: true, CustomTitleButton: false, CustomTitleName: "", notionAPIGeneral: "", databaseIDGeneral: "", CustomButton: false, + CustomValues: "", notionAPICustom: "", databaseIDCustom: "", }; @@ -108,8 +110,10 @@ export class ObsidianSettingTab extends PluginSettingTab { // name should follow the result of the title button if (value) { this.updateSettingEl(CustomNameEl, this.plugin.settings.CustomTitleButton) + this.updateSettingEl(CustomValuesEl, this.plugin.settings.CustomTitleButton) } else { this.updateSettingEl(CustomNameEl, value) + this.updateSettingEl(CustomValuesEl, value) } this.updateSettingEl(notionAPIGeneralEl, value) @@ -139,6 +143,8 @@ export class ObsidianSettingTab extends PluginSettingTab { this.updateSettingEl(CustomNameEl, value) + this.updateSettingEl(CustomValuesEl, value) + await this.plugin.saveSettings(); await this.plugin.commands.updateCommand(); }) @@ -148,6 +154,21 @@ export class ObsidianSettingTab extends PluginSettingTab { const CustomNameEl = this.createStyleDiv('custom-name', (this.plugin.settings.CustomTitleButton && this.plugin.settings.GeneralButton)); this.createSettingEl(CustomNameEl, i18nConfig.NotionCustomTitleName, i18nConfig.NotionCustomTitleNameDesc, 'text', i18nConfig.NotionCustomTitleText, this.plugin.settings.CustomTitleName, 'CustomTitleName') + // Custom database properties + const CustomValuesEl = this.createStyleDiv('custom-values', (this.plugin.settings.CustomTitleButton && this.plugin.settings.GeneralButton)); + new Setting(CustomValuesEl) + .setName(i18nConfig.NotionCustomValues) + .setDesc(i18nConfig.NotionCustomValuesDesc) + .addTextArea((text) => + text + .setPlaceholder(i18nConfig.NotionCustomValuesText) + .setValue(this.plugin.settings.CustomValues) + .onChange(async (value) => { + this.plugin.settings.CustomValues = value; + await this.plugin.saveSettings(); + await this.plugin.commands.updateCommand(); + }) + ); // new Setting(containerEl) // .setName("Convert tags(optional)") // .setDesc("Transfer the Obsidian tags to the Notion table. It requires the column with the name 'Tags'") diff --git a/src/upload/uploadCommand.ts b/src/upload/uploadCommand.ts index fbd2c78..0230fff 100644 --- a/src/upload/uploadCommand.ts +++ b/src/upload/uploadCommand.ts @@ -2,10 +2,12 @@ import { i18nConfig } from "../lang/I18n"; import { App, Notice } from "obsidian"; import { Upload2NotionNext } from "./upload_next/Upload2NotionNext"; import { Upload2NotionGeneral } from "./upload_general/Upload2NotionGeneral"; +import { Upload2NotionCustom } from "./upoload_custom/Upload2NotionCustom"; import { PluginSettings } from "../ui/settingTabs"; import ObsidianSyncNotionPlugin from "../main"; import { getNowFileMarkdownContentNext } from "./upload_next/getMarkdownNext"; import { getNowFileMarkdownContentGeneral } from "./upload_general/getMarkdownGeneral"; +import {getNowFileMarkdownContentCustom} from "./upoload_custom/getMarkdownCustom"; export async function uploadCommandNext( plugin: ObsidianSyncNotionPlugin, @@ -78,3 +80,36 @@ export async function uploadCommandGeneral( } } + + +export async function uploadCommandCustom( + plugin: ObsidianSyncNotionPlugin, + settings: PluginSettings, + app: App, +) { + + const { notionAPIGeneral, databaseIDGeneral } = settings; + + // Check if the user has set up the Notion API and database ID + if (notionAPIGeneral === "" || databaseIDGeneral === "") { + const setAPIMessage = i18nConfig["set-api-id"]; + new Notice(setAPIMessage); + return; + } + + const { markDownData, nowFile, cover, tags ,customValues} = await getNowFileMarkdownContentCustom(app, settings) + + if (markDownData) { + const { basename } = nowFile; + + const upload = new Upload2NotionCustom(plugin); + const res = await upload.syncMarkdownToNotionCustom(basename, cover, tags, customValues, markDownData, nowFile, this.app); + + if (res.status === 200) { + new Notice(`${i18nConfig["sync-success"]}${basename}`); + } else { + new Notice(`${i18nConfig["sync-fail"]}${basename}`, 5000); + } + + } +} diff --git a/src/upload/upoload_custom/BaseUpload2NotionCustom.ts b/src/upload/upoload_custom/BaseUpload2NotionCustom.ts new file mode 100644 index 0000000..cf135ac --- /dev/null +++ b/src/upload/upoload_custom/BaseUpload2NotionCustom.ts @@ -0,0 +1,90 @@ +import {App, Notice, requestUrl, TFile} from "obsidian"; +import {Client} from '@notionhq/client'; +import {markdownToBlocks,} from "@tryfabric/martian"; +import * as yamlFrontMatter from "yaml-front-matter"; +// import * as yaml from "yaml" +import MyPlugin from "src/main"; + +export class UploadBaseCustom { + plugin: MyPlugin; + notion: Client; + agent: any; + + constructor(plugin: MyPlugin) { + this.plugin = plugin; + } + + async deletePage(notionID: string) { + return requestUrl({ + url: `https://api.notion.com/v1/blocks/${notionID}`, + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer ' + this.plugin.settings.notionAPINext, + 'Notion-Version': '2022-06-28', + }, + body: '' + }); + } + + async getDataBase(databaseID: string) { + const response = await requestUrl({ + url: `https://api.notion.com/v1/databases/${databaseID}`, + method: 'GET', + headers: { + 'Authorization': 'Bearer ' + this.plugin.settings.notionAPINext, + 'Notion-Version': '2022-06-28', + } + } + ) + + // Check if cover is present in the JSON response and then get the URL + if (response.json.cover && response.json.cover.external) { + return response.json.cover.external.url; + } else { + return null; // or some other default value, if you prefer + } + } + + // async updateYamlInfo(yamlContent: string, nowFile: TFile, res: any, app: App, settings: any) { + // let {url, id} = res.json + // // replace www to notionID + // const {notionUser} = this.plugin.settings; + // + // if (notionUser !== "") { + // // replace url str "www" to notionID + // url = url.replace("www.notion.so", `${notionUser}.notion.site`) + // } + // + // await app.fileManager.processFrontMatter(nowFile, yamlContent => { + // if (yamlContent['notionID']) { + // delete yamlContent['notionID'] + // } + // if (yamlContent['link']) { + // delete yamlContent['link'] + // } + // // add new notionID and link + // yamlContent.notionID = id; + // yamlContent.link = url; + // }); + // + // try { + // await navigator.clipboard.writeText(url) + // } catch (error) { + // new Notice(`复制链接失败,请手动复制${error}`) + // } + // // const __content = yamlContent.__content; + // // delete yamlContent.__content + // // const yamlhead = yaml.stringify(yamlContent) + // // // if yamlhead hava last \n remove it + // // const yamlhead_remove_n = yamlhead.replace(/\n$/, '') + // // // if __content have start \n remove it + // // const __content_remove_n = __content.replace(/^\n/, '') + // // const content = '---\n' +yamlhead_remove_n +'\n---\n' + __content_remove_n; + // // try { + // // await nowFile.vault.modify(nowFile, content) + // // } catch (error) { + // // new Notice(`write file error ${error}`) + // // } + // } +} diff --git a/src/upload/upoload_custom/Upload2NotionCustom.ts b/src/upload/upoload_custom/Upload2NotionCustom.ts new file mode 100644 index 0000000..f84d92e --- /dev/null +++ b/src/upload/upoload_custom/Upload2NotionCustom.ts @@ -0,0 +1,157 @@ +import { App, Notice, requestUrl, TFile } from "obsidian"; +import { Client } from "@notionhq/client"; +import { markdownToBlocks } from "@tryfabric/martian"; +import * as yamlFrontMatter from "yaml-front-matter"; +// import * as yaml from "yaml" +import MyPlugin from "src/main"; +import { PluginSettings } from "../../ui/settingTabs"; +import { updateYamlInfo } from "../updateYaml"; +import {UploadBaseCustom} from "./BaseUpload2NotionCustom"; + +export class Upload2NotionCustom extends UploadBaseCustom { + settings: PluginSettings; + + constructor(plugin: MyPlugin) { + super(plugin); + } + + // 因为需要解析notion的block进行对比,非常的麻烦, + // 暂时就直接删除,新建一个page + async updatePage( + notionID: string, + title: string, + cover: string, + tags: string[], + customValues: Record, + childArr: any, + ) { + await this.deletePage(notionID); + + const databasecover = await this.getDataBase( + this.plugin.settings.databaseIDGeneral, + ); + + if (cover == null) { + cover = databasecover; + } + + return await this.createPage(title, cover, tags, customValues, childArr); + } + + async createPage( + title: string, + cover: string, + tags: string[], + customValues: Record, + childArr: any, + ) { + const bodyString: any = { + parent: { + database_id: this.plugin.settings.databaseIDGeneral, + }, + properties: { + [this.plugin.settings.CustomTitleButton + ? this.plugin.settings.CustomTitleName + : "title"]: { + title: [ + { + text: { + content: title, + }, + }, + ], + }, + ...(Object.keys(customValues).reduce((acc, key) => { + acc[key] = { + rich_text: [ + { + text: { + content: customValues[key] || '', + }, + }, + ], + }; + return acc; + }, {} as Record)), + }, + children: childArr, + }; + + if (cover) { + bodyString.cover = { + type: "external", + external: { + url: cover, + }, + }; + } + + if (!bodyString.cover && this.plugin.settings.bannerUrl) { + bodyString.cover = { + type: "external", + external: { + url: this.plugin.settings.bannerUrl, + }, + }; + } + + try { + return await requestUrl({ + url: `https://api.notion.com/v1/pages`, + method: "POST", + headers: { + "Content-Type": "application/json", + // 'User-Agent': 'obsidian.md', + Authorization: + "Bearer " + this.plugin.settings.notionAPIGeneral, + "Notion-Version": "2022-06-28", + }, + body: JSON.stringify(bodyString), + }); + } catch (error) { + new Notice(`network error ${error}`); + } + } + + async syncMarkdownToNotionCustom( + title: string, + cover: string, + tags: string[], + customValues: Record, + markdown: string, + nowFile: TFile, + app: App, + ): Promise { + const options = { + notionLimits: { + truncate: false, + } + } + let res: any; + const yamlContent: any = yamlFrontMatter.loadFront(markdown); + const __content = yamlContent.__content; + const file2Block = markdownToBlocks(__content, options); + const frontmasster = + app.metadataCache.getFileCache(nowFile)?.frontmatter; + const notionID = frontmasster ? frontmasster.notionID : null; + + if (notionID) { + res = await this.updatePage( + notionID, + title, + cover, + tags, + customValues, + file2Block, + ); + } else { + res = await this.createPage(title, cover, tags, customValues, file2Block); + } + if (res.status === 200) { + await updateYamlInfo(markdown, nowFile, res, app, this.plugin); + } else { + new Notice(`${res.text}`); + } + return res; + } +} diff --git a/src/upload/upoload_custom/getMarkdownCustom.ts b/src/upload/upoload_custom/getMarkdownCustom.ts new file mode 100644 index 0000000..ad0b323 --- /dev/null +++ b/src/upload/upoload_custom/getMarkdownCustom.ts @@ -0,0 +1,43 @@ +import {App, Notice} from "obsidian"; +import {i18nConfig} from "../../lang/I18n"; +import {PluginSettings} from "../../ui/settingTabs"; + +export async function getNowFileMarkdownContentCustom( + app: App, + settings: PluginSettings, +) { + const nowFile = app.workspace.getActiveFile(); + let cover = ''; + let tags = []; + let customValues: Record = {}; + + const FileCache = app.metadataCache.getFileCache(nowFile); + try { + cover = FileCache.frontmatter.coverurl; + tags = FileCache.frontmatter.tags; + + // split the CustomValues into an array + const customValuesNames = settings.CustomValues.split('\n').map(value => value.trim()); + + // get the custom values from the frontmatter + customValuesNames.forEach(valueName => { + customValues[valueName] = FileCache.frontmatter[valueName]; + }); + } catch (error) { + new Notice(i18nConfig["set-tags-fail"]); + } + + if (nowFile) { + const markDownData = await nowFile.vault.read(nowFile); + return { + markDownData, + nowFile, + cover, + tags, + customValues, + }; + } else { + new Notice(i18nConfig["open-file"]); + return; + } +}