diff --git a/.gitignore b/.gitignore index e09a007..74d0a62 100644 --- a/.gitignore +++ b/.gitignore @@ -1,22 +1,23 @@ -# vscode -.vscode +# Dependencies +node_modules/ -# Intellij -*.iml -.idea - -# npm -node_modules - -# Don't include the compiled main.js file in the repo. -# They should be uploaded to GitHub releases instead. +# Build files main.js +*.js.map -# Exclude sourcemaps -*.map +# IDE +.idea/ +.vscode/ -# obsidian -data.json - -# Exclude macOS Finder (System Explorer) View States +# OS .DS_Store +Thumbs.db + +# Obsidian +.obsidian/ + +# Encrypted settings +settings.encrypted + +# Legacy settings +data.json \ No newline at end of file diff --git a/.npmrc b/.npmrc index b973752..492ed86 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,2 @@ -tag-version-prefix="" \ No newline at end of file +@types:registry=https://registry.npmjs.org/ +registry=https://registry.npmjs.org/ \ No newline at end of file diff --git a/README.md b/README.md index c773152..8b8860a 100644 --- a/README.md +++ b/README.md @@ -1,94 +1,157 @@ -# Obsidian Sample Plugin +# Obsidian Notion 同步插件 -This is a sample plugin for Obsidian (https://obsidian.md). +一个用于将 Obsidian 笔记同步到 Notion 数据库的插件。 -This project uses TypeScript to provide type checking and documentation. -The repo depends on the latest plugin API (obsidian.d.ts) in TypeScript Definition format, which contains TSDoc comments describing what it does. +## 功能特点 -This sample plugin demonstrates some of the basic functionality the plugin API can do. -- Adds a ribbon icon, which shows a Notice when clicked. -- Adds a command "Open Sample Modal" which opens a Modal. -- Adds a plugin setting tab to the settings page. -- Registers a global click event and output 'click' to the console. -- Registers a global interval which logs 'setInterval' to the console. +- 一键同步笔记到 Notion +- 支持多种 Markdown 元素 +- 右键菜单集成 +- 命令面板支持 +- 安全的令牌存储 +- 可配置的同步设置 -## First time developing plugins? +## 设置指南 -Quick starting guide for new plugin devs: +### 1. 创建 Notion Integration -- Check if [someone already developed a plugin for what you want](https://obsidian.md/plugins)! There might be an existing plugin similar enough that you can partner up with. -- Make a copy of this repo as a template with the "Use this template" button (login to GitHub if you don't see it). -- Clone your repo to a local development folder. For convenience, you can place this folder in your `.obsidian/plugins/your-plugin-name` folder. -- Install NodeJS, then run `npm i` in the command line under your repo folder. -- Run `npm run dev` to compile your plugin from `main.ts` to `main.js`. -- Make changes to `main.ts` (or create new `.ts` files). Those changes should be automatically compiled into `main.js`. -- Reload Obsidian to load the new version of your plugin. -- Enable plugin in settings window. -- For updates to the Obsidian API run `npm update` in the command line under your repo folder. +1. 访问 [Notion Integrations](https://www.notion.so/my-integrations) +2. 点击"New integration" +3. 输入集成名称(如"Obsidian Sync") +4. 选择数据库所在的工作区 +5. 设置权限(至少需要读写内容权限) +6. 保存并复制 Integration Token -## Releasing new releases +### 2. 准备 Notion Database -- Update your `manifest.json` with your new version number, such as `1.0.1`, and the minimum Obsidian version required for your latest release. -- Update your `versions.json` file with `"new-plugin-version": "minimum-obsidian-version"` so older versions of Obsidian can download an older version of your plugin that's compatible. -- Create new GitHub release using your new version number as the "Tag version". Use the exact version number, don't include a prefix `v`. See here for an example: https://github.com/obsidianmd/obsidian-sample-plugin/releases -- Upload the files `manifest.json`, `main.js`, `styles.css` as binary attachments. Note: The manifest.json file must be in two places, first the root path of your repository and also in the release. -- Publish the release. +1. 在 Notion 中创建新数据库(或使用现有数据库) +2. 数据库必须包含"Name"属性(title 类型) +3. 获取 Database ID: + - 以全页面视图打开数据库 + - URL 格式如:`https://notion.so/workspace/1234...abcd` + - 复制最后一部分(32个字符)- 这就是 Database ID -> You can simplify the version bump process by running `npm version patch`, `npm version minor` or `npm version major` after updating `minAppVersion` manually in `manifest.json`. -> The command will bump version in `manifest.json` and `package.json`, and add the entry for the new version to `versions.json` +### 3. 连接 Database 与 Integration -## Adding your plugin to the community plugin list +1. 在 Notion 中打开数据库 +2. 点击右上角的"..." +3. 进入"Connections" +4. 找到并添加你的 integration -- Check the [plugin guidelines](https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines). -- Publish an initial version. -- Make sure you have a `README.md` file in the root of your repo. -- Make a pull request at https://github.com/obsidianmd/obsidian-releases to add your plugin. +### 4. 配置插件 -## How to use +1. 打开 Obsidian 设置 +2. 进入"第三方插件" → "Notion 同步" +3. 输入 Integration Token +4. 输入 Database ID +5. 根据需要配置其他设置 -- Clone this repo. -- Make sure your NodeJS is at least v16 (`node --version`). -- `npm i` or `yarn` to install dependencies. -- `npm run dev` to start compilation in watch mode. +## 使用方法 -## Manually installing the plugin +### 基本同步 -- Copy over `main.js`, `styles.css`, `manifest.json` to your vault `VaultFolder/.obsidian/plugins/your-plugin-id/`. +1. 打开要同步的笔记 +2. 使用以下方法之一: + - 在文件菜单中点击"同步到 Notion"(右键) + - 使用命令面板(Ctrl/Cmd + P)搜索"同步到 Notion" -## Improve code quality with eslint (optional) -- [ESLint](https://eslint.org/) is a tool that analyzes your code to quickly find problems. You can run ESLint against your plugin to find common bugs and ways to improve your code. -- To use eslint with this project, make sure to install eslint from terminal: - - `npm install -g eslint` -- To use eslint to analyze this project use this command: - - `eslint main.ts` - - eslint will then create a report with suggestions for code improvement by file and line number. -- If your source code is in a folder, such as `src`, you can use eslint with this command to analyze all files in that folder: - - `eslint .\src\` +### 支持的元素 -## Funding URL +- 标题(H1-H3) +- 段落 +- 无序列表(支持多级) +- 有序列表 +- 基本文本格式 -You can include funding URLs where people who use your plugin can financially support it. +### 多级列表处理 -The simple way is to set the `fundingUrl` field to your link in your `manifest.json` file: +插件支持三种方式处理多级列表: -```json -{ - "fundingUrl": "https://buymeacoffee.com" -} -``` +1. **保持原有层级**(默认) + ```markdown + - 一级项目 + - 二级项目 + - 三级项目 + ``` + 同步到 Notion 后保持原有的层级结构: + ``` + ? 一级项目 + ? 二级项目 + ? 三级项目 + ``` -If you have multiple URLs, you can also do: +2. **转为平级结构** + ```markdown + - 一级项目 + - 二级项目 + - 三级项目 + ``` + 同步到 Notion 后转换为: + ``` + ? 一级项目 + ? 二级项目 + ? 三级项目 + ``` -```json -{ - "fundingUrl": { - "Buy Me a Coffee": "https://buymeacoffee.com", - "GitHub Sponsor": "https://github.com/sponsors", - "Patreon": "https://www.patreon.com/" - } -} -``` +3. **忽略子级内容** + ```markdown + - 一级项目 + - 二级项目(会被忽略) + - 三级项目(会被忽略) + - 另一个一级项目 + ``` + 同步到 Notion 后只保留顶级项目: + ``` + ? 一级项目 + ? 另一个一级项目 + ``` -## API Documentation +选择合适的处理方式: +- 如果你的 Notion 数据库需要保持文档的完整层级结构,选择"保持原有层级" +- 如果你希望简化列表结构便于在 Notion 中查看,选择"转为平级结构" +- 如果你只关注顶层信息,选择"忽略子级内容" -See https://github.com/obsidianmd/obsidian-api +### 设置说明 + +- **Integration Token**:Notion 集成令牌(安全加密存储) +- **Database ID**:目标 Notion 数据库标识符 +- **列表处理方式**:控制多级列表的同步行为 + - 保持原有层级:完整保留列表的层级关系 + - 转为平级结构:将多级列表转换为同级项目 + - 忽略子级内容:仅同步顶层列表项 + +## 故障排除 + +### 常见问题 + +1. **认证失败** + - 验证 Integration Token 是否正确 + - 检查 Token 是否具有适当权限 + +2. **找不到数据库** + - 验证 Database ID 是否正确 + - 确保 Integration 已被授权访问数据库 + +3. **同步失败** + - 检查网络连接 + - 确认文件大小在限制内(500KB) + - 确保内容格式受支持 + +## 安全性 + +- Integration Token 采用加密存储 +- 不向第三方发送数据 +- 所有通信直接与 Notion API 进行 + +## 许可证 + +MIT 许可证 - 详见 [LICENSE](LICENSE) + +## 支持 + +- [报告问题](https://github.com/e6g2cyvryi/obsidian-notion-sync/issues) +- [功能建议](https://github.com/e6g2cyvryi/obsidian-notion-sync/issues) + +## 技术支持 + +基于 [Obsidian Plugin API](https://github.com/obsidianmd/obsidian-api) 和 [Notion API](https://developers.notion.com/) 构建 \ No newline at end of file diff --git a/cleanup.md b/cleanup.md new file mode 100644 index 0000000..8ceb2c6 --- /dev/null +++ b/cleanup.md @@ -0,0 +1,15 @@ +# 娴嬭瘯娓呯悊姝ラ + +1. Obsidian 绔 +- 绂佺敤鎻掍欢 +- 鍒犻櫎娴嬭瘯鏂囨。 +- 娓呴櫎鎻掍欢璁剧疆 + +2. Notion 绔 +- 娓呴櫎娴嬭瘯鏁版嵁搴撲腑鐨勫悓姝ヨ褰 +- 淇濈暀鏁版嵁搴撶粨鏋 + +3. 鏈湴鏂囦欢 +- 鍒犻櫎 main.js +- 鍒犻櫎 *.js.map +- 鍒犻櫎 data.json \ No newline at end of file diff --git a/debugging-tips.md b/debugging-tips.md new file mode 100644 index 0000000..c7bb6e2 --- /dev/null +++ b/debugging-tips.md @@ -0,0 +1,24 @@ +# 璋冭瘯鎶宸 + +1. 瀹炴椂鏃ュ織鏌ョ湅 +- 淇濇寔寮鍙戣呭伐鍏锋墦寮 +- 鍦 Console 闈㈡澘鏌ョ湅鏃ュ織 +- 浣跨敤 console.log() 娣诲姞涓存椂璋冭瘯淇℃伅 + +2. 鐑噸杞 +- 淇敼浠g爜鍚庝繚瀛 +- npm run dev 浼氳嚜鍔ㄩ噸鏂版瀯寤 +- 鍦 Obsidian 涓鐢ㄥ苟閲嶆柊鍚敤鎻掍欢 + +3. 甯歌闂鎺掓煡 +- 濡傛灉鎻掍欢娌℃湁鍑虹幇鍦ㄥ垪琛ㄤ腑锛屾鏌 manifest.json +- 濡傛灉鏃犳硶鍚敤鎻掍欢锛屾鏌 main.js 鏄惁鐢熸垚 +- 濡傛灉鍚屾澶辫触锛屾鏌 Notion API 鍝嶅簲 + +4. 鏂囦欢浣嶇疆楠岃瘉 +纭繚浠ヤ笅鏂囦欢瀛樺湪涓斾綅缃纭細 +.obsidian/plugins/obsidian-notion-sync/ +鈹溾攢鈹 manifest.json +鈹溾攢鈹 main.js +鈹溾攢鈹 styles.css (濡傛灉鏈) +鈹斺攢鈹 package.json \ No newline at end of file diff --git a/esbuild.config.mjs b/esbuild.config.mjs index a5de8b8..f70aa8e 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -5,45 +5,31 @@ import builtins from "builtin-modules"; const banner = `/* THIS IS A GENERATED/BUNDLED FILE BY ESBUILD -if you want to view the source, please visit the github repository of this plugin */ `; -const prod = (process.argv[2] === "production"); +const prod = (process.argv[2] === 'production'); -const context = await esbuild.context({ - banner: { - js: banner, - }, - entryPoints: ["main.ts"], - bundle: true, - external: [ - "obsidian", - "electron", - "@codemirror/autocomplete", - "@codemirror/collab", - "@codemirror/commands", - "@codemirror/language", - "@codemirror/lint", - "@codemirror/search", - "@codemirror/state", - "@codemirror/view", - "@lezer/common", - "@lezer/highlight", - "@lezer/lr", - ...builtins], - format: "cjs", - target: "es2018", - logLevel: "info", - sourcemap: prod ? false : "inline", - treeShaking: true, - outfile: "main.js", - minify: prod, -}); - -if (prod) { - await context.rebuild(); - process.exit(0); -} else { - await context.watch(); -} +esbuild.build({ + banner: { + js: banner, + }, + entryPoints: ['src/main.ts'], + bundle: true, + external: [ + 'obsidian', + '@codemirror/autocomplete', + '@codemirror/closebrackets', + '@codemirror/collab', + '@codemirror/fold', + '@codemirror/gutter', + '@codemirror/history', + '@codemirror/language', + ...builtins], + format: 'cjs', + target: 'es2020', // 更新目标环境为 ES2020 以支持 async generator + logLevel: "info", + sourcemap: prod ? false : 'inline', + treeShaking: true, + outfile: 'main.js', +}).catch(() => process.exit(1)); diff --git a/manifest.json b/manifest.json index dfa940e..2f3da4c 100644 --- a/manifest.json +++ b/manifest.json @@ -1,11 +1,10 @@ { - "id": "sample-plugin", - "name": "Sample Plugin", - "version": "1.0.0", - "minAppVersion": "0.15.0", - "description": "Demonstrates some of the capabilities of the Obsidian API.", - "author": "Obsidian", - "authorUrl": "https://obsidian.md", - "fundingUrl": "https://obsidian.md/pricing", - "isDesktopOnly": false -} + "id": "obsidian-notion-sync", + "name": "Notion Sync", + "version": "1.0.0", + "minAppVersion": "0.15.0", + "description": "Sync your Obsidian notes with Notion", + "author": "Jorahil", + "authorUrl": "https://github.com/e6g2cyvryi", + "isDesktopOnly": true +} diff --git a/notion-setup-guide.md b/notion-setup-guide.md new file mode 100644 index 0000000..9d8ccb7 --- /dev/null +++ b/notion-setup-guide.md @@ -0,0 +1,25 @@ +# Notion 閰嶇疆鎸囧崡 + +## 1. 鍒涘缓 Notion Integration +1. 璁块棶 https://www.notion.so/my-integrations +2. 鐐瑰嚮 "New integration" +3. 濉啓鍚嶇О锛堝 "Obsidian Sync"锛 +4. 閫夋嫨鍏宠仈鐨勫伐浣滃尯 +5. 鎻愪氦鍚庤幏鍙 Integration Token锛堜互 "secret_" 寮澶达級 + +## 2. 鍒涘缓鐩爣鏁版嵁搴 +1. 鍦 Notion 涓垱寤轰竴涓柊椤甸潰 +2. 娣诲姞涓涓柊鐨勬暟鎹簱锛堝叏椤甸潰锛 +3. 娣诲姞浠ヤ笅灞炴э細 + - Name (鏍囬绫诲瀷锛岄粯璁ゅ瓨鍦) + - Tags (澶氶夌被鍨) + - LastSync (鏃ユ湡绫诲瀷) + +## 3. 閰嶇疆鏁版嵁搴撴潈闄 +1. 鎵撳紑鏁版嵁搴撻〉闈 +2. 鐐瑰嚮鍙充笂瑙掔殑 "Share" 鎸夐挳 +3. 鍦 "Connections" 閮ㄥ垎娣诲姞浣犲垱寤虹殑 Integration + +## 4. 鑾峰彇鏁版嵁搴 ID +1. 鍦ㄦ祻瑙堝櫒涓墦寮鏁版嵁搴撻〉闈 +2. URL 涓舰濡 "https://www.notion.so/xxx/yyyyyyy?v=zzz" 鐨 yyyyyyy 閮ㄥ垎灏辨槸鏁版嵁搴 ID \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b07e56b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1034 @@ +{ + "name": "obsidian-notion-sync", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "obsidian-notion-sync", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@notionhq/client": "^2.2.0", + "node-fetch": "^2.6.7" + }, + "devDependencies": { + "@types/node": "^16.11.6", + "@types/node-fetch": "^2.6.4", + "builtin-modules": "^3.3.0", + "esbuild": "^0.17.3", + "obsidian": "^1.4.11", + "tslib": "^2.4.0", + "typescript": "^4.7.4" + } + }, + "node_modules/@codemirror/state": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.1.tgz", + "integrity": "sha512-3rA9lcwciEB47ZevqvD8qgbzhM9qMb8vCcQCNmDfVRPQG4JT9mSb0Jg8H7YjKGGQcFnLN323fj9jdnG59Kx6bg==", + "dev": true, + "peer": true, + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.36.2", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.2.tgz", + "integrity": "sha512-DZ6ONbs8qdJK0fdN7AB82CgI6tYXf4HWk1wSVa0+9bhVznCuuvhQtX8bFBoy3dv8rZSQqUd8GvhVAcielcidrA==", + "dev": true, + "peer": true, + "dependencies": { + "@codemirror/state": "^6.5.0", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", + "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", + "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", + "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", + "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", + "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", + "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", + "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", + "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", + "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", + "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", + "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", + "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", + "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", + "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", + "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", + "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", + "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", + "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", + "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", + "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", + "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", + "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "dev": true, + "peer": true + }, + "node_modules/@notionhq/client": { + "version": "2.2.15", + "resolved": "https://registry.npmjs.org/@notionhq/client/-/client-2.2.15.tgz", + "integrity": "sha512-XhdSY/4B1D34tSco/GION+23GMjaS9S2zszcqYkMHo8RcWInymF6L1x+Gk7EmHdrSxNFva2WM8orhC4BwQCwgw==", + "dependencies": { + "@types/node-fetch": "^2.5.10", + "node-fetch": "^2.6.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@types/codemirror": { + "version": "5.60.8", + "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz", + "integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==", + "dev": true, + "dependencies": { + "@types/tern": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true + }, + "node_modules/@types/node": { + "version": "16.18.123", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.123.tgz", + "integrity": "sha512-/n7I6V/4agSpJtFDKKFEa763Hc1z3hmvchobHS1TisCOTKD5nxq8NJ2iK7SRIMYL276Q9mgWOx2AWp5n2XI6eA==" + }, + "node_modules/@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/tern": { + "version": "0.23.9", + "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz", + "integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==", + "dev": true, + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/esbuild": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", + "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.19", + "@esbuild/android-arm64": "0.17.19", + "@esbuild/android-x64": "0.17.19", + "@esbuild/darwin-arm64": "0.17.19", + "@esbuild/darwin-x64": "0.17.19", + "@esbuild/freebsd-arm64": "0.17.19", + "@esbuild/freebsd-x64": "0.17.19", + "@esbuild/linux-arm": "0.17.19", + "@esbuild/linux-arm64": "0.17.19", + "@esbuild/linux-ia32": "0.17.19", + "@esbuild/linux-loong64": "0.17.19", + "@esbuild/linux-mips64el": "0.17.19", + "@esbuild/linux-ppc64": "0.17.19", + "@esbuild/linux-riscv64": "0.17.19", + "@esbuild/linux-s390x": "0.17.19", + "@esbuild/linux-x64": "0.17.19", + "@esbuild/netbsd-x64": "0.17.19", + "@esbuild/openbsd-x64": "0.17.19", + "@esbuild/sunos-x64": "0.17.19", + "@esbuild/win32-arm64": "0.17.19", + "@esbuild/win32-ia32": "0.17.19", + "@esbuild/win32-x64": "0.17.19" + } + }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/obsidian": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.7.2.tgz", + "integrity": "sha512-k9hN9brdknJC+afKr5FQzDRuEFGDKbDjfCazJwpgibwCAoZNYHYV8p/s3mM8I6AsnKrPKNXf8xGuMZ4enWelZQ==", + "dev": true, + "dependencies": { + "@types/codemirror": "5.60.8", + "moment": "2.29.4" + }, + "peerDependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, + "node_modules/style-mod": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", + "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==", + "dev": true, + "peer": true + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "dev": true, + "peer": true + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + }, + "dependencies": { + "@codemirror/state": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.1.tgz", + "integrity": "sha512-3rA9lcwciEB47ZevqvD8qgbzhM9qMb8vCcQCNmDfVRPQG4JT9mSb0Jg8H7YjKGGQcFnLN323fj9jdnG59Kx6bg==", + "dev": true, + "peer": true, + "requires": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "@codemirror/view": { + "version": "6.36.2", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.2.tgz", + "integrity": "sha512-DZ6ONbs8qdJK0fdN7AB82CgI6tYXf4HWk1wSVa0+9bhVznCuuvhQtX8bFBoy3dv8rZSQqUd8GvhVAcielcidrA==", + "dev": true, + "peer": true, + "requires": { + "@codemirror/state": "^6.5.0", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "@esbuild/android-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", + "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", + "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", + "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", + "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", + "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", + "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", + "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", + "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", + "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", + "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", + "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", + "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", + "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", + "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", + "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", + "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", + "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", + "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", + "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", + "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", + "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", + "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", + "dev": true, + "optional": true + }, + "@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "dev": true, + "peer": true + }, + "@notionhq/client": { + "version": "2.2.15", + "resolved": "https://registry.npmjs.org/@notionhq/client/-/client-2.2.15.tgz", + "integrity": "sha512-XhdSY/4B1D34tSco/GION+23GMjaS9S2zszcqYkMHo8RcWInymF6L1x+Gk7EmHdrSxNFva2WM8orhC4BwQCwgw==", + "requires": { + "@types/node-fetch": "^2.5.10", + "node-fetch": "^2.6.1" + } + }, + "@types/codemirror": { + "version": "5.60.8", + "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz", + "integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==", + "dev": true, + "requires": { + "@types/tern": "*" + } + }, + "@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true + }, + "@types/node": { + "version": "16.18.123", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.123.tgz", + "integrity": "sha512-/n7I6V/4agSpJtFDKKFEa763Hc1z3hmvchobHS1TisCOTKD5nxq8NJ2iK7SRIMYL276Q9mgWOx2AWp5n2XI6eA==" + }, + "@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "requires": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "@types/tern": { + "version": "0.23.9", + "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz", + "integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==", + "dev": true, + "requires": { + "@types/estree": "*" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, + "esbuild": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", + "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.17.19", + "@esbuild/android-arm64": "0.17.19", + "@esbuild/android-x64": "0.17.19", + "@esbuild/darwin-arm64": "0.17.19", + "@esbuild/darwin-x64": "0.17.19", + "@esbuild/freebsd-arm64": "0.17.19", + "@esbuild/freebsd-x64": "0.17.19", + "@esbuild/linux-arm": "0.17.19", + "@esbuild/linux-arm64": "0.17.19", + "@esbuild/linux-ia32": "0.17.19", + "@esbuild/linux-loong64": "0.17.19", + "@esbuild/linux-mips64el": "0.17.19", + "@esbuild/linux-ppc64": "0.17.19", + "@esbuild/linux-riscv64": "0.17.19", + "@esbuild/linux-s390x": "0.17.19", + "@esbuild/linux-x64": "0.17.19", + "@esbuild/netbsd-x64": "0.17.19", + "@esbuild/openbsd-x64": "0.17.19", + "@esbuild/sunos-x64": "0.17.19", + "@esbuild/win32-arm64": "0.17.19", + "@esbuild/win32-ia32": "0.17.19", + "@esbuild/win32-x64": "0.17.19" + } + }, + "form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "dev": true + }, + "node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "obsidian": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.7.2.tgz", + "integrity": "sha512-k9hN9brdknJC+afKr5FQzDRuEFGDKbDjfCazJwpgibwCAoZNYHYV8p/s3mM8I6AsnKrPKNXf8xGuMZ4enWelZQ==", + "dev": true, + "requires": { + "@types/codemirror": "5.60.8", + "moment": "2.29.4" + } + }, + "style-mod": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", + "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==", + "dev": true, + "peer": true + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true + }, + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true + }, + "w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "dev": true, + "peer": true + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } +} diff --git a/package.json b/package.json index 6a00766..6a6c06e 100644 --- a/package.json +++ b/package.json @@ -1,24 +1,25 @@ { - "name": "obsidian-sample-plugin", - "version": "1.0.0", - "description": "This is a sample plugin for Obsidian (https://obsidian.md)", - "main": "main.js", - "scripts": { - "dev": "node esbuild.config.mjs", - "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", - "version": "node version-bump.mjs && git add manifest.json versions.json" - }, - "keywords": [], - "author": "", - "license": "MIT", - "devDependencies": { - "@types/node": "^16.11.6", - "@typescript-eslint/eslint-plugin": "5.29.0", - "@typescript-eslint/parser": "5.29.0", - "builtin-modules": "3.3.0", - "esbuild": "0.17.3", - "obsidian": "latest", - "tslib": "2.4.0", - "typescript": "4.7.4" - } -} + "name": "obsidian-notion-sync", + "version": "1.0.0", + "description": "Sync Obsidian notes with Notion", + "main": "main.js", + "scripts": { + "dev": "node esbuild.config.mjs", + "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", + "version": "node version-bump.mjs && git add manifest.json versions.json" + }, + "keywords": ["obsidian", "notion", "sync", "plugin"], + "author": "Jorahil", + "license": "MIT", + "dependencies": { + "@notionhq/client": "^2.2.0" + }, + "devDependencies": { + "@types/node": "^16.11.6", + "builtin-modules": "^3.3.0", + "esbuild": "^0.17.3", + "obsidian": "^1.4.11", + "tslib": "^2.4.0", + "typescript": "^4.7.4" + } +} \ No newline at end of file diff --git a/src/global.d.ts b/src/global.d.ts new file mode 100644 index 0000000..c8d1e92 --- /dev/null +++ b/src/global.d.ts @@ -0,0 +1,32 @@ +import { App, TFile, Notice, PluginSettingTab, Setting } from 'obsidian'; +import { Client as NotionClient } from '@notionhq/client'; + +declare global { + interface Window { + app: App; + } +} + +declare module "obsidian" { + interface App { + workspace: Workspace; + vault: Vault; + } + + interface Workspace { + getActiveFile(): TFile | null; + on(name: string, callback: (menu: Menu, file: TFile) => any): EventRef; + } + + interface Vault { + read(file: TFile): Promise; + } +} + +declare module "@notionhq/client" { + interface NotionClientTypes { + // 濡傛灉闇瑕佹墿灞 Notion 瀹㈡埛绔被鍨嬶紝鍦ㄨ繖閲屾坊鍔 + } +} + +export {}; \ No newline at end of file diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..972ce21 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,75 @@ +import { Plugin, Menu, TFile, Notice } from 'obsidian'; +import { NotionSyncSettings } from './types'; +import { NotionSyncSettingTab, DEFAULT_SETTINGS } from './settings'; +import { NotionService } from './services/notion-service'; +import { SyncService } from './services/sync-service'; +import { DataValidator } from './utils/validator'; +import { StorageService } from './services/storage-service'; + +export default class NotionSyncPlugin extends Plugin { + settings: NotionSyncSettings; + notionService: NotionService; + syncService: SyncService; + private storageService: StorageService; + + async onload() { + try { + this.storageService = new StorageService(this.app.vault.adapter); + await this.loadSettings(); + + this.notionService = new NotionService(this.settings.notionToken); + this.syncService = new SyncService(this.app, this.notionService, this.settings); + + // 娣诲姞璁剧疆鏍囩椤 + this.addSettingTab(new NotionSyncSettingTab(this.app, this)); + + // 娣诲姞鍚屾鍛戒护 + this.addCommand({ + id: 'sync-to-notion', + name: '鍚屾褰撳墠鏂囦欢鍒 Notion', + callback: async () => { + const file = this.app.workspace.getActiveFile(); + if (file) { + await this.syncService.syncFile(file); + } else { + new Notice('璇峰厛鎵撳紑瑕佸悓姝ョ殑鏂囦欢'); + } + } + }); + + // 娣诲姞鏂囦欢鑿滃崟椤 + this.registerEvent( + this.app.workspace.on('file-menu', (menu: Menu, file: TFile) => { + if (file && file instanceof TFile) { + menu.addItem((item) => { + item + .setTitle('鍚屾鍒 Notion') + .setIcon('upload-cloud') + .onClick(async () => { + await this.syncService.syncFile(file); + }); + }); + } + }) + ); + } catch (error) { + new Notice('鎻掍欢鍔犺浇澶辫触'); + console.error('Plugin load error:', error); + } + } + + async loadSettings() { + const savedSettings = await this.storageService.loadSettings(); + this.settings = Object.assign({}, DEFAULT_SETTINGS, savedSettings); + } + + async saveSettings() { + if (this.settings) { + await this.storageService.saveSettings(this.settings); + } + } + + async onunload() { + // Obsidian 浼氳嚜鍔ㄥ鐞嗘彃浠剁殑娓呯悊宸ヤ綔 + } +} \ No newline at end of file diff --git a/src/services/notion-service.ts b/src/services/notion-service.ts new file mode 100644 index 0000000..7a0d4fd --- /dev/null +++ b/src/services/notion-service.ts @@ -0,0 +1,166 @@ +import { Client } from '@notionhq/client'; +import { NotionSyncSettings } from '../types'; +import { Notice, requestUrl, RequestUrlParam } from 'obsidian'; + +export class NotionService { + private client: Client; + private readonly MAX_CONTENT_SIZE = 1024 * 1024; // 1MB + private readonly token: string; + + constructor(token: string) { + this.token = token; + this.client = new Client({ + auth: token, + notionVersion: '2022-06-28', + fetch: this.customFetch.bind(this) + }); + } + + private async customFetch(url: string, options: any): Promise { + try { + const headers = { + 'Authorization': `Bearer ${this.token}`, + 'Notion-Version': '2022-06-28', + 'Content-Type': 'application/json', + 'mode': 'no-cors', + 'credentials': 'omit' + }; + + const params: RequestUrlParam = { + url, + method: options.method, + headers, + body: options.body + }; + + const response = await requestUrl(params); + + if (response.status === 401) { + throw new Error('Authentication failed: Invalid token or insufficient permissions'); + } + + if (response.status >= 400) { + throw new Error(`API error (${response.status}): ${response.text}`); + } + + return new Response(response.text, { + status: response.status, + headers: new Headers(response.headers) + }); + } catch (error) { + if (error.message.includes('CORS')) { + throw new Error('CORS error: Unable to access Notion API. Try refreshing the token.'); + } + throw error; + } + } + + async createOrUpdatePage(databaseId: string, content: any, settings: NotionSyncSettings, fileName: string): Promise { + try { + const contentSize = new TextEncoder().encode(JSON.stringify(content)).length; + if (contentSize > this.MAX_CONTENT_SIZE) { + throw new Error('Content size exceeds maximum limit'); + } + + const blocks = this.formatBlocks(content); + + try { + const requestData = { + parent: { + type: 'database_id', + database_id: databaseId + }, + properties: { + title: { + type: 'title', + title: [ + { + type: 'text', + text: { + content: fileName + } + } + ] + } + }, + children: blocks.map(block => ({ + object: 'block', + ...block + })) + }; + + const response = await this.client.pages.create(requestData as any); + + if (!response || !response.id) { + throw new Error('Failed to create page in Notion'); + } + + } catch (apiError: any) { + throw new Error(`Failed to create page: ${apiError.message}`); + } + + } catch (error) { + throw new Error('Failed to sync with Notion'); + } + } + + private formatBlocks(blocks: any[]): any[] { + return blocks.map(block => { + if (typeof block === 'object' && block.type) { + const formattedBlock: any = { + type: block.type + }; + + switch (block.type) { + case 'paragraph': + formattedBlock[block.type] = { + rich_text: [ + { + type: 'text', + text: { + content: block.paragraph?.rich_text?.[0]?.text?.content || '' + } + } + ] + }; + break; + case 'heading_1': + case 'heading_2': + case 'heading_3': + formattedBlock[block.type] = { + rich_text: [ + { + type: 'text', + text: { + content: block[block.type]?.rich_text?.[0]?.text?.content || '' + } + } + ], + color: 'default' + }; + break; + case 'bulleted_list_item': + case 'numbered_list_item': + formattedBlock[block.type] = { + rich_text: [ + { + type: 'text', + text: { + content: block[block.type]?.rich_text?.[0]?.text?.content || '' + } + } + ], + color: 'default' + }; + break; + default: + console.warn('Unsupported block type:', block.type); + return null; + } + + return formattedBlock; + } + return null; + }).filter(block => block !== null); + } +} \ No newline at end of file diff --git a/src/services/storage-service.ts b/src/services/storage-service.ts new file mode 100644 index 0000000..d0e3479 --- /dev/null +++ b/src/services/storage-service.ts @@ -0,0 +1,122 @@ +import { DataAdapter, Notice } from 'obsidian'; +import { NotionSyncSettings } from '../types'; +import * as crypto from 'crypto'; + +export class StorageService { + private adapter: DataAdapter; + private readonly SETTINGS_PATH = '.obsidian/plugins/obsidian-notion-sync/settings.encrypted'; + private readonly LEGACY_SETTINGS_PATH = '.obsidian/plugins/obsidian-notion-sync/data.json'; + private readonly ALGORITHM = 'aes-256-cbc'; + private readonly KEY = crypto.scryptSync('obsidian-notion-sync', 'salt', 32); + private readonly IV_LENGTH = 16; + + constructor(adapter: DataAdapter) { + this.adapter = adapter; + } + + private encrypt(text: string): string { + if (!text) return ''; + try { + const iv = crypto.randomBytes(this.IV_LENGTH); + const cipher = crypto.createCipheriv(this.ALGORITHM, this.KEY, iv); + + let encrypted = cipher.update(text, 'utf8', 'hex'); + encrypted += cipher.final('hex'); + + return iv.toString('hex') + ':' + encrypted; + } catch (error) { + console.error('Encryption failed:', error); + return ''; + } + } + + private decrypt(encryptedData: string): string { + if (!encryptedData) return ''; + try { + if (!encryptedData.includes(':')) { + // 鏈姞瀵嗙殑鏁版嵁锛岀洿鎺ヨ繑鍥 + return encryptedData; + } + + const [ivHex, encryptedText] = encryptedData.split(':'); + if (!ivHex || !encryptedText) { + console.warn('Invalid encrypted data format'); + return encryptedData; + } + + const iv = Buffer.from(ivHex, 'hex'); + const decipher = crypto.createDecipheriv(this.ALGORITHM, this.KEY, iv); + + let decrypted = decipher.update(encryptedText, 'hex', 'utf8'); + decrypted += decipher.final('utf8'); + + return decrypted; + } catch (error) { + console.error('Decryption failed:', error); + return encryptedData; // 濡傛灉瑙e瘑澶辫触锛岃繑鍥炲師濮嬫暟鎹 + } + } + + async saveSettings(settings: NotionSyncSettings): Promise { + const encryptedSettings = { + ...settings, + notionToken: this.encrypt(settings.notionToken), + notionDatabaseId: this.encrypt(settings.databaseId) + }; + + await this.adapter.write( + this.SETTINGS_PATH, + JSON.stringify(encryptedSettings) + ); + + try { + if (await this.adapter.exists(this.LEGACY_SETTINGS_PATH)) { + await this.adapter.remove(this.LEGACY_SETTINGS_PATH); + } + } catch (error) { + console.warn('Failed to remove legacy settings file:', error); + } + } + + async loadSettings(): Promise { + try { + if (await this.adapter.exists(this.SETTINGS_PATH)) { + const data = await this.adapter.read(this.SETTINGS_PATH); + const encryptedSettings = JSON.parse(data); + const decryptedToken = this.decrypt(encryptedSettings.notionToken); + + return { + ...encryptedSettings, + notionToken: decryptedToken, + notionDatabaseId: this.decrypt(encryptedSettings.notionDatabaseId) + }; + } + + if (await this.adapter.exists(this.LEGACY_SETTINGS_PATH)) { + const legacyData = await this.adapter.read(this.LEGACY_SETTINGS_PATH); + const legacySettings = JSON.parse(legacyData); + await this.saveSettings(legacySettings); + return legacySettings; + } + + return null; + } catch (error) { + console.error('Failed to load settings:', error); + return null; + } + } + + async clearSettings(): Promise { + try { + if (await this.adapter.exists(this.SETTINGS_PATH)) { + await this.adapter.remove(this.SETTINGS_PATH); + } + if (await this.adapter.exists(this.LEGACY_SETTINGS_PATH)) { + await this.adapter.remove(this.LEGACY_SETTINGS_PATH); + } + } catch (error) { + console.error('Failed to clear settings:', error); + throw error; + } + } +} \ No newline at end of file diff --git a/src/services/sync-service.ts b/src/services/sync-service.ts new file mode 100644 index 0000000..17e8f13 --- /dev/null +++ b/src/services/sync-service.ts @@ -0,0 +1,73 @@ +import { App, TFile, Notice } from 'obsidian'; +import { NotionService } from './notion-service'; +import { MarkdownConverter } from '../utils/converter'; +import { NotionSyncSettings } from '../types'; +import { DataValidator } from '../utils/validator'; + +export class SyncService { + private app: App; + private notionService: NotionService; + private settings: NotionSyncSettings; + private readonly MAX_FILE_SIZE = 500 * 1024; // 500KB + + constructor(app: App, notionService: NotionService, settings: NotionSyncSettings) { + this.app = app; + this.notionService = notionService; + this.settings = settings; + } + + private validateSettings(): boolean { + if (!this.settings) { + new Notice('Configuration required'); + return false; + } + + + if (!this.settings.databaseId || this.settings.databaseId.length < 32) { + new Notice('Invalid database ID'); + return false; + } + + return true; + } + + async syncFile(file: TFile): Promise { + if (!this.validateSettings()) { + return; + } + + if (!file || !(file instanceof TFile)) { + new Notice('Invalid file'); + return; + } + + try { + // 妫鏌ユ枃浠跺ぇ灏 + if (file.stat.size > this.MAX_FILE_SIZE) { + new Notice('File size exceeds maximum limit'); + return; + } + + const content = await this.app.vault.read(file); + + if (!DataValidator.validateContent(content)) { + new Notice('Invalid file content'); + return; + } + + const blocks = MarkdownConverter.convertToNotionBlocks(content); + + await this.notionService.createOrUpdatePage( + this.settings.databaseId, + blocks, + this.settings, + file.basename + ); + + new Notice('Sync completed'); + } catch (error) { + console.error('Sync failed:', error); + new Notice('Sync failed. Check console for details.'); + } + } +} \ No newline at end of file diff --git a/src/settings.ts b/src/settings.ts new file mode 100644 index 0000000..7b0741b --- /dev/null +++ b/src/settings.ts @@ -0,0 +1,93 @@ +import { App, PluginSettingTab, Setting } from 'obsidian'; +import NotionSyncPlugin from './main'; +import { NotionSyncSettings } from './types'; + +export const DEFAULT_SETTINGS: NotionSyncSettings = { + notionToken: '', + databaseId: '', + handleDeepLists: 'keep', + autoSync: false, + syncOnSave: false +}; + +export class NotionSyncSettingTab extends PluginSettingTab { + plugin: NotionSyncPlugin; + + constructor(app: App, plugin: NotionSyncPlugin) { + super(app, plugin); + this.plugin = plugin; + } + + display(): void { + const { containerEl } = this; + containerEl.empty(); + + containerEl.createEl('h2', { text: '鍚屾璁剧疆' }); + + // Token 璁剧疆 + new Setting(containerEl) + .setName('Integration Token') + .setDesc(createFragment(fragment => { + fragment.appendText('鐢ㄤ簬杩炴帴 Notion 鐨勬巿鏉冨嚟璇侊紝'); + fragment.createEl('a', { + text: '鐐瑰嚮杩欓噷鍒涘缓', + href: 'https://www.notion.so/my-integrations' + }); + fragment.appendText('銆傚垱寤哄悗璇风‘淇濊祴浜堣鍐欐潈闄愩'); + })) + .addText(text => text + .setPlaceholder('secret_...') + .setValue(this.maskToken(this.plugin.settings.notionToken)) + .onChange(async (value) => { + if (value && !value.startsWith('鈥⑩⑩⑩')) { + this.plugin.settings.notionToken = value; + await this.plugin.saveSettings(); + } + })); + + // Database ID 璁剧疆 + new Setting(containerEl) + .setName('Database ID') + .setDesc(createFragment(fragment => { + fragment.appendText('鐩爣鏁版嵁搴撶殑鍞竴鏍囪瘑锛屽彲鍦ㄦ暟鎹簱椤甸潰鐨勭綉鍧涓壘鍒帮細'); + fragment.createEl('code', { text: 'notion.so/workspace/[database-id]' }); + fragment.createEl('br'); + fragment.appendText('璇峰厛纭繚鏁版嵁搴撳凡涓庝綘鐨 Integration 鍏变韩銆'); + })) + .addText(text => text + .setPlaceholder('Enter Database ID') + .setValue(this.plugin.settings.databaseId) + .onChange(async (value) => { + this.plugin.settings.databaseId = value; + await this.plugin.saveSettings(); + })); + + // 楂樼骇璁剧疆 + containerEl.createEl('h3', { text: '楂樼骇璁剧疆' }); + + new Setting(containerEl) + .setName('鍒楄〃澶勭悊鏂瑰紡') + .setDesc(createFragment(fragment => { + fragment.appendText('璁剧疆濡備綍澶勭悊澶氬眰绾х殑鍒楄〃缁撴瀯銆'); + fragment.createEl('br'); + fragment.createEl('a', { + text: '鏌ョ湅璇︾粏璇存槑鍜岀ず渚', + href: 'https://github.com/e6g2cyvryi/obsidian-notion-sync#澶氱骇鍒楄〃澶勭悊' + }); + })) + .addDropdown(dropdown => dropdown + .addOption('keep', '淇濇寔鍘熸湁灞傜骇') + .addOption('convert', '杞负骞崇骇缁撴瀯') + .addOption('skip', '蹇界暐瀛愮骇鍐呭') + .setValue(this.plugin.settings.handleDeepLists) + .onChange(async (value: any) => { + this.plugin.settings.handleDeepLists = value; + await this.plugin.saveSettings(); + })); + } + + private maskToken(token: string): string { + if (!token) return ''; + return '鈥⑩⑩⑩' + token.slice(-4); + } +} \ No newline at end of file diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..b683bbf --- /dev/null +++ b/src/types.ts @@ -0,0 +1,7 @@ +export interface NotionSyncSettings { + notionToken: string; + databaseId: string; + handleDeepLists: 'convert' | 'skip' | 'keep'; + autoSync: boolean; + syncOnSave: boolean; +} \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..573bdda --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,13 @@ +import { Plugin } from 'obsidian'; + +export interface NotionSyncSettings { + notionToken: string; + notionDatabaseId: string; + handleDeepLists: 'convert' | 'skip' | 'keep'; +} + +export interface NotionSyncPlugin extends Plugin { + settings: NotionSyncSettings; + loadSettings(): Promise; + saveSettings(): Promise; +} \ No newline at end of file diff --git a/src/utils/converter.ts b/src/utils/converter.ts new file mode 100644 index 0000000..aa87d5e --- /dev/null +++ b/src/utils/converter.ts @@ -0,0 +1,114 @@ +import { DataValidator } from './validator'; + +export class MarkdownConverter { + /** + * Convert Markdown to Notion blocks + */ + static convertToNotionBlocks(markdown: string, handleDeepLists: 'convert' | 'skip' | 'keep' = 'keep'): any[] { + const blocks: any[] = []; + const listStack: any[] = []; + let previousLevel = 0; + let inList = false; // 娣诲姞鏍囧織鏉ヨ窡韪槸鍚﹀湪鍒楄〃涓 + + const lines = markdown.split('\n'); + for (const line of lines) { + const listMatch = line.match(/^(\s*|\t*)([-*+])\s(.+)/); + if (listMatch) { + inList = true; + const [, indent, , content] = listMatch; + const level = indent.replace(/\t/g, ' ').length / 4; + + // 鍒涘缓鍒楄〃椤 + const listItem = { + type: "bulleted_list_item", + bulleted_list_item: { + rich_text: [{ + type: "text", + text: { + content: content.trim() + } + }] + } + }; + + if (level === 0) { + blocks.push(listItem); + listStack.length = 0; + listStack.push(listItem); + } else { + // 瀛愬垪琛ㄩ」澶勭悊 + if (level > previousLevel) { + const parent = listStack[listStack.length - 1]; + if (!parent.bulleted_list_item.children) { + parent.bulleted_list_item.children = []; + } + parent.bulleted_list_item.children.push(listItem); + listStack.push(listItem); + } else if (level === previousLevel && listStack.length > 1) { + const parent = listStack[listStack.length - 2]; + if (!parent.bulleted_list_item.children) { + parent.bulleted_list_item.children = []; + } + parent.bulleted_list_item.children.push(listItem); + listStack[listStack.length - 1] = listItem; + } else { + while (listStack.length > level) { + listStack.pop(); + } + const parent = listStack[listStack.length - 1]; + if (!parent.bulleted_list_item.children) { + parent.bulleted_list_item.children = []; + } + parent.bulleted_list_item.children.push(listItem); + listStack.push(listItem); + } + } + previousLevel = level; + } else { + // 闈炲垪琛ㄥ唴瀹 + if (inList) { + // 濡傛灉涔嬪墠鍦ㄥ垪琛ㄤ腑锛岄噸缃垪琛ㄧ浉鍏崇姸鎬 + listStack.length = 0; + previousLevel = 0; + inList = false; + } + + const trimmedLine = line.trim(); + if (trimmedLine) { // 鍙鐞嗛潪绌鸿 + // 澶勭悊鏍囬 + const headingMatch = line.match(/^(#{1,6})\s(.+)/); + if (headingMatch) { + const level = headingMatch[1].length; + const content = headingMatch[2]; + blocks.push({ + type: `heading_${level}`, + [`heading_${level}`]: { + rich_text: [{ + type: "text", + text: { + content: content.trim() + } + }] + } + }); + } else { + // 澶勭悊鏅氭钀 + blocks.push({ + type: 'paragraph', + paragraph: { + rich_text: [{ + type: "text", + text: { + content: trimmedLine + } + }] + } + }); + } + } + } + } + + return blocks; + } +} \ No newline at end of file diff --git a/src/utils/validator.ts b/src/utils/validator.ts new file mode 100644 index 0000000..7d8a4b5 --- /dev/null +++ b/src/utils/validator.ts @@ -0,0 +1,15 @@ +import { Notice } from 'obsidian'; + +export class DataValidator { + static validateToken(token: string): boolean { + return token.startsWith('secret_') && token.length > 50; + } + + static validateDatabaseId(id: string): boolean { + return /^[a-zA-Z0-9-]{32,}$/.test(id); + } + + static validateContent(content: string): boolean { + return Boolean(content) && content.length > 0; + } +} \ No newline at end of file diff --git a/test-steps.md b/test-steps.md new file mode 100644 index 0000000..0ed7484 --- /dev/null +++ b/test-steps.md @@ -0,0 +1,30 @@ +# Obsidian 娴嬭瘯姝ラ + +1. 鍚姩 Obsidian +- 鎵撳紑娴嬭瘯 vault锛圚:\Obsidian\helper-test锛 +- 杩涘叆璁剧疆 -> 绗笁鏂规彃浠 +- 鍏抽棴瀹夊叏妯″紡 +- 鍒锋柊鎻掍欢鍒楄〃 +- 纭 "Obsidian Notion Sync" 鍑虹幇鍦ㄦ彃浠跺垪琛ㄤ腑 +- 鍚敤鎻掍欢 + +2. 閰嶇疆鎻掍欢 +- 鎵撳紑鎻掍欢璁剧疆 +- 濉叆 Notion Token +- 濉叆鏁版嵁搴 ID +- 閫夋嫨娣卞眰鍒楄〃澶勭悊鏂瑰紡锛堝缓璁厛閫夋嫨 "convert"锛 + +3. 鍔熻兘娴嬭瘯 +- 鎵撳紑 test-cases/basic.md +- 浣跨敤鍛戒护闈㈡澘锛圕trl+P锛 +- 杈撳叆 "鍚屾鍒 Notion" +- 瑙傚療鍚屾缁撴灉鍜屾彁绀轰俊鎭 + +4. 閿欒娴嬭瘯 +- 灏濊瘯鍚屾绌烘枃妗 +- 灏濊瘯浣跨敤閿欒鐨 Token +- 灏濊瘯浣跨敤閿欒鐨勬暟鎹簱 ID + +5. 鏃ュ織妫鏌 +- 鎵撳紑寮鍙戣呭伐鍏凤紙Ctrl+Shift+I锛 +- 妫鏌 Console 闈㈡澘涓殑閿欒淇℃伅 \ No newline at end of file diff --git a/troubleshooting.md b/troubleshooting.md new file mode 100644 index 0000000..505bde1 --- /dev/null +++ b/troubleshooting.md @@ -0,0 +1,22 @@ +# 甯歌闂瑙e喅 + +## 1. 鍚屾澶辫触 +- 妫鏌 Notion Token 鏍煎紡 +- 纭鏁版嵁搴 ID 姝g‘ +- 楠岃瘉鏁版嵁搴撴潈闄愯缃 +- 妫鏌ョ綉缁滆繛鎺 + +## 2. 鏍煎紡闂 +- 纭娣卞眰鍒楄〃澶勭悊鏂瑰紡璁剧疆 +- 妫鏌 Markdown 璇硶鏄惁姝g‘ +- 楠岃瘉 Notion 鍧楄浆鎹㈢粨鏋 + +## 3. 鏉冮檺闂 +- 纭 Integration 鏉冮檺鑼冨洿 +- 妫鏌ユ暟鎹簱璁块棶鏉冮檺 +- 楠岃瘉 Token 鏈夋晥鎬 + +## 4. 鎬ц兘闂 +- 妫鏌ユ枃浠跺ぇ灏 +- 楠岃瘉缃戠粶鐘舵 +- 瑙傚療鍚屾鏃堕棿 \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index c44b729..95ef6ad 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,6 +11,7 @@ "importHelpers": true, "isolatedModules": true, "strictNullChecks": true, + "types": ["node", "obsidian", "@notionhq/client"], "lib": [ "DOM", "ES5", @@ -19,6 +20,6 @@ ] }, "include": [ - "**/*.ts" + "src/**/*.ts" ] -} +} \ No newline at end of file diff --git a/verification-steps.md b/verification-steps.md new file mode 100644 index 0000000..3b73889 --- /dev/null +++ b/verification-steps.md @@ -0,0 +1,29 @@ +# 楠岃瘉姝ラ + +1. 鎻掍欢瀹夎楠岃瘉 +- 鍚姩 Obsidian +- 妫鏌ユ彃浠舵槸鍚﹀嚭鐜板湪宸插畨瑁呮彃浠跺垪琛ㄤ腑 +- 妫鏌ユ槸鍚︽湁閿欒鎻愮ず + +2. 閰嶇疆楠岃瘉 +- 鎵撳紑鎻掍欢璁剧疆 +- 濉叆 Notion Token +- 濉叆鏁版嵁搴 ID +- 閫夋嫨娣卞眰鍒楄〃澶勭悊鏂瑰紡 + +3. 鍔熻兘楠岃瘉 +- 鎵撳紑娴嬭瘯鏂囨。 +- 浣跨敤鍛戒护闈㈡澘鎵ц鍚屾鍛戒护 +- 妫鏌 Notion 鏁版嵁搴撲腑鏄惁鍑虹幇鍚屾鐨勫唴瀹 +- 楠岃瘉娣卞眰鍒楄〃鏄惁鎸夌収璁剧疆姝g‘澶勭悊 + +4. 閿欒澶勭悊楠岃瘉 +- 灏濊瘯浣跨敤閿欒鐨 Token +- 灏濊瘯鍚屾绌烘枃妗 +- 灏濊瘯鍦ㄦ湭閰嶇疆鐨勬儏鍐典笅鍚屾 +- 妫鏌ラ敊璇彁绀烘槸鍚︽纭 + +5. 鎵归噺鍚屾楠岃瘉 +- 閫夋嫨澶氫釜鏂囦欢 +- 鎵ц鎵归噺鍚屾 +- 妫鏌ユ垚鍔熺巼鎻愮ず \ No newline at end of file diff --git a/version-bump.mjs b/version-bump.mjs index d409fa0..e5bed3a 100644 --- a/version-bump.mjs +++ b/version-bump.mjs @@ -11,4 +11,4 @@ writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t")); // update versions.json with target version and minAppVersion from manifest.json let versions = JSON.parse(readFileSync("versions.json", "utf8")); versions[targetVersion] = minAppVersion; -writeFileSync("versions.json", JSON.stringify(versions, null, "\t")); +writeFileSync("versions.json", JSON.stringify(versions, null, "\t")); \ No newline at end of file diff --git a/versions.json b/versions.json index 26382a1..ad90acc 100644 --- a/versions.json +++ b/versions.json @@ -1,3 +1,3 @@ { - "1.0.0": "0.15.0" -} + "1.0.0": "0.15.0" +} \ No newline at end of file