mirror of
https://github.com/jxpeng98/obsidian-to-NotionNext
synced 2026-07-31 01:48:35 +08:00
Compare commits
73 Commits
2.3.0
...
33f0848b32
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33f0848b32 | ||
|
|
a33e63a219 | ||
|
|
99a40375f5 | ||
|
|
7282fc6955 | ||
|
|
770061e826 | ||
|
|
de7b7dd28d | ||
|
|
1c6c3fb0d9 | ||
|
|
ca0935bfe8 | ||
|
|
e944721415 | ||
|
|
db66ef137c | ||
|
|
a152201510 | ||
|
|
abb699fe4d | ||
|
|
cf6c80df0f | ||
|
|
cf884de554 | ||
|
|
8eb4694342 | ||
|
|
6c0f6cea30 | ||
|
|
8af4816f87 | ||
|
|
f6d24daa52 | ||
|
|
da72846903 | ||
|
|
8e08c4325d | ||
|
|
ebb7a290e8 | ||
|
|
24e68fc798 | ||
|
|
78d2a29c8a | ||
|
|
1156bd3809 | ||
|
|
b46e24f1ed | ||
|
|
a950e2e301 | ||
|
|
aac7bc154a | ||
|
|
a573ef728d | ||
|
|
1344c14933 | ||
|
|
c1f4ecd14b | ||
|
|
5942876c92 | ||
|
|
3218eb9fba | ||
|
|
48566eca20 | ||
|
|
8bd8346423 | ||
|
|
00411e9599 | ||
|
|
296125f6b4 | ||
|
|
1b385ccd0a | ||
|
|
a90e4871bb | ||
|
|
885ccba027 | ||
|
|
f282dde1b7 | ||
|
|
bc166d8db1 | ||
|
|
7612637951 | ||
|
|
e8b1617916 | ||
|
|
fd29ed820e | ||
|
|
1f58670906 | ||
|
|
fc47d36e8d | ||
|
|
cd4e610c46 | ||
|
|
b05c248c44 | ||
|
|
800e218dee | ||
|
|
3b774552af | ||
| b8a02c49f1 | |||
|
|
046a67f22c | ||
| 426f05dfbd | |||
|
|
0addaa0d7f | ||
| 1d5792c966 | |||
|
|
193d3f6f87 | ||
| e9c2b4aada | |||
| b5ac42e6e3 | |||
| 60d002677b | |||
| 3c3ec6881e | |||
|
|
a80155fafc | ||
| 3a814b8390 | |||
| bb522db9a2 | |||
| 201f72ae0e | |||
| be6f192dd8 | |||
| 4ce1019094 | |||
| 095c608511 | |||
| 2d8d7fb11a | |||
| 36725cb699 | |||
|
|
654e7e854d | ||
|
|
fa67f08512 | ||
|
|
7e42ef49a7 | ||
|
|
5a95aa0acc |
9
.gitignore
vendored
9
.gitignore
vendored
@@ -20,4 +20,11 @@ data.json
|
||||
|
||||
# Exclude macOS Finder (System Explorer) View States
|
||||
.DS_Store
|
||||
.history
|
||||
.history
|
||||
|
||||
# local data
|
||||
local-data
|
||||
|
||||
# vitepress
|
||||
docs/.vitepress/dist
|
||||
docs/.vitepress/cache
|
||||
|
||||
29
CHANGELOG.md
29
CHANGELOG.md
@@ -1,25 +1,6 @@
|
||||
## Fix
|
||||
- fixed the issue that you cannot have empty values in the front matter of the customised database.
|
||||
- 修复了自定义数据库的前置数据中不能有空值的问题。
|
||||
For example, you have the property `tag` in your customised database, and you do not want to sync `tag` to Notion. You can remove the `tag` from the front matter.
|
||||
- 例如,自定义数据库中有属性`tag`,但是你不想将`tag`同步到Notion。现在,你可以直接在frontmatter中删除`tag`。
|
||||
## Feature
|
||||
|
||||
|
||||
## Improvement
|
||||
**⚠️⚠️⚠️: The exist customised database should be recreated if you want to update to version 2.3.0. The new version has a new database structure, and the old database structure is not compatible with the new version to build the index properly.**
|
||||
|
||||
### 自定义数据库用户
|
||||
**⚠️⚠️⚠️: 如果你想要更新到2.3.0版本,你需要重新创建自定义数据库。新版本有一个新的数据库结构,旧的数据库结构无法构建索引。**
|
||||
|
||||
- redesigned the way to create new customised database. The modal has been removed with a more straightforward layout.
|
||||
- 重新设计了自定义数据库的创建方式。已删除模态框,采用更直观的布局。
|
||||

|
||||
|
||||
|
||||
- improved the edit modal for customised database. The layout has been simplified and the form has been reorganized.
|
||||
- 改进了自定义数据库的编辑模态框。简化了布局,重新组织了表单。
|
||||

|
||||
|
||||
- improved the preview modal for customised database.
|
||||
- 改进了自定义数据库的预览模态框。
|
||||

|
||||
- Better debugging information for upload failures.
|
||||
- Support synchronising Obsidian callouts as Notion callout blocks.
|
||||
- 优化控制台输出的调试信息。
|
||||
- 支持将 Obsidian Callout 同步为 Notion Callout 区块。
|
||||
|
||||
313
README-zh.md
313
README-zh.md
@@ -1,307 +1,20 @@
|
||||
# Obsidian share to NotionNext
|
||||
# Obsidian to NotionNext
|
||||
|
||||
感谢[原作者](https://github.com/EasyChris/obsidian-to-notion)开发出了这么好用的插件,能够将obsidian同步到notion。 但是原仓库只能同步Name和Tags信息,如果像我一样通过[NotionNext](https://github.com/tangly1024/NotionNext)来搭建自己的网页,就有一些局限性。每次导入之后都需要进行大量的修改。
|
||||
[](https://github.com/jxpeng98/obsidian-to-NotionNext/actions/workflows/test.yml)
|
||||
[](https://github.com/jxpeng98/obsidian-to-NotionNext/actions/workflows/release.yml)
|
||||
[](https://GitHub.com/jxpeng98/obsidian-to-NotionNext/releases/)
|
||||
[](https://github.com/jxpeng98/obsidian-to-NotionNext/releases/)
|
||||
|
||||
所以我在[原作者](https://github.com/EasyChris/obsidian-to-notion)的基础之上,增加了匹配[NotionNext](https://github.com/tangly1024/NotionNext)模板的功能。这样可以直接在Obsidian编辑,整理好之后一键发布。
|
||||
使用 Notion API 将文件从 Obsidian 分享到任何 Notion 数据库。此插件支持通用 Notion 数据库、NotionNext 博客模板和完全自定义的数据库模式。
|
||||
|
||||
## 更新说明
|
||||
### 1.1.2
|
||||
- 修复了一个拼写错误,导致无法同步`status`到NotionNext。现在你可以使用`stats`或者`status`来同步文章的状态到NotionNext。这个更新不会影响到同步到General数据库的功能。
|
||||
- **`stats`和`status`都可以使用,但是你只能使用其中一个。**
|
||||
**详细说明请访问[文档网站](https://obsidian-to-notionnext.pages.dev/)。**
|
||||
|
||||
```yaml
|
||||
stats: Draft # Draft, Invisible, Published, default is Draft, 默认是Draft
|
||||
# or
|
||||
status: Draft # Draft, Invisible, Published, default is Draft, 默认是Draft
|
||||
# both of them will work, but you can only use one of them.
|
||||
```
|
||||
## 功能特性
|
||||
|
||||
### 1.1.1
|
||||
- 修复了在日语系统下,无法显示设置按钮的错误。
|
||||
- 增加日语翻译。
|
||||
### 1.1.0
|
||||
- 修复设置中自定义标题的显示问题。
|
||||
- 增加了标签同步的控制按钮。以减少空标签集造成的上传失败。
|
||||
- **多种数据库类型**:支持通用、NotionNext 和自定义数据库。
|
||||
- **自定义属性**:在自定义数据库中,可将任何 frontmatter 键映射到任何 Notion 属性。
|
||||
- **灵活同步**:即时选择要同步到哪个数据库。
|
||||
|
||||
如果你不想使用同步标签功能,你可以在设置中关闭。不论你在md文件表头添加了tags与否,都不会将标签同步到Notion数据库中。
|
||||
## 致谢
|
||||
|
||||
如果你需要同步标签到Notion数据库中,**你需要通过如下格式添加表填到md表头中。**
|
||||
|
||||
```yaml
|
||||
tags: #空标签,选项1
|
||||
tags: [test,test1,test2] # 用中括号将所有的标签括起来,选项2
|
||||
tags:
|
||||
- test
|
||||
- test1
|
||||
- test2 # 每个标签单独一行,选项3
|
||||
```
|
||||
|
||||
### 1.0.1
|
||||
|
||||
- 修复了自定义标题在设置菜单中显示错误的问题。
|
||||
|
||||
### 1.0.0 (大更新)
|
||||
|
||||
- 这个版本开始,你可以**任意修改第一列表头的名字 (标题列,默认:'title')**。 (**注意:你必须保证你的Notion数据库中有'tags‘这一列的存在,并且在markdown文件中把`tags:`添加到你的YAML表头中。否则会返回`400 错误`。但是你可以`tags:`设置为空。**)
|
||||
|
||||

|
||||
例如这样:
|
||||

|
||||
- 增加了一个控制按钮来控制是否开启当前数据库同步命令的功能。如果你关闭了这个按钮,你在上传命令列表中就不会出现当前数据库的选项。
|
||||
|
||||

|
||||
|
||||
### 0.2.6
|
||||
|
||||
- 增加一个按钮用来控制是否在名列列表中显示如下数据库。
|
||||
|
||||

|
||||
如果你关闭普通数据库的按钮,你在上传命令列表中就不会出现普通数据库的选项。如果你关闭NotionNext数据库的按钮,你在上传命令列表中就不会出现NotionNext数据库的选项。
|
||||
|
||||

|
||||
|
||||
### 0.2.3
|
||||
|
||||
- 修复了一个bug,现在可以正常更新了。
|
||||
|
||||
### 0.2.2
|
||||
|
||||
- 支持NotionNext 和 普通Notion 数据库的同步。
|
||||
- 你可以有一个NotionNext和一个普通Notion数据库。
|
||||
- 普通Notion数据库的同步方式和原作者的一样,只同步`title`和`tags`。**如果使用普通数据库功能,你需要保证你的数据库中有`title`和`tags`这两个表头。必须使用小写。**
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### 0.2.1
|
||||
|
||||
- 重构代码。
|
||||
|
||||
### 0.2.0
|
||||
|
||||
- 从这个版本开始,重构了交互逻辑。现在当你点击左侧的按钮,会弹出可同步的数据库选项。你可以选择你想要同步的数据库,然后点击同步按钮。**但是,现在仅支持同步单个数据库,如果你的数据库不是NotionNext,你可以不用更新。**
|
||||
|
||||
### 0.1.10
|
||||
|
||||
- 修正了设置中的中文显示。
|
||||
|
||||
### 0.1.8
|
||||
|
||||
- 重新整理了代码,增加了一个仓库转换按钮为之后的多数据库支持作准备。**但是现在还不支持多数据库,如果你的数据库不是NotionNext,你可以不用更新。**
|
||||
|
||||
### 0.1.7
|
||||
|
||||
- [x] 删除了设置中`convert tags`的选项。 你现在可以直接添加tags,而不需要考虑是否开启tags选项。如果你不需要tags,可以直接删除tags选项或者留白。
|
||||
|
||||
## 使用方式
|
||||
|
||||
### 注意事项
|
||||
|
||||
本插件现在暂时只能用于匹配[NotionNext](https://github.com/tangly1024/NotionNext)。如果你不是使用这个模板,你只会无限返回`error 400`.
|
||||
|
||||
所以没有NotionNext需求的,请使用原作者的[Obsidian-to-notion](https://github.com/EasyChris/obsidian-to-notion)
|
||||
|
||||
### 准备安装
|
||||
|
||||
在安装插件之前,你必须配置好了以下内容:
|
||||
|
||||
1. 你的NotionNext数据库。
|
||||
2. 根据原作者的readme.md,配置好了Notion API,并且已经和你的NotionNext仓库关联。
|
||||
3. NotionNext Database ID
|
||||
4. 你的NotionNext数据库有如下内容:
|
||||
- type
|
||||
- title
|
||||
- slug
|
||||
- category
|
||||
- tags
|
||||
- date
|
||||
- status
|
||||
- summary
|
||||
- password
|
||||
- icon
|
||||
|
||||
在最新的0.0.6版本,我还增加了以下内容:
|
||||
|
||||
- titleicon: 标题的emoji图标,暂时只能支持emoji
|
||||
- coverurl: 封面图片的url, url可以是任何一种图片的url, 例如jpg,png,svg等等。
|
||||
|
||||
**如果你是直接复制的NotionNext的模板,这些内容应该已经有了。我在原作者的基础之上对本插件的内容进行了更改,所以你只需要保证你的数据库有如上内容,并且所有的字母都是小写!!!**
|
||||
|
||||
**⚠️⚠️⚠️:表头全部小写!!!顺序无所谓!**
|
||||
|
||||
### 安装插件
|
||||
|
||||
#### 社区插件市场下载
|
||||
|
||||
打开`Obsidian -> 设置 -> 社区插件 -> 浏览 -> 搜索Share to NotionNext`, 然后点击安装。
|
||||
|
||||
#### 手动安装
|
||||
|
||||
1. 关闭Obsidian
|
||||
2. 从Release下载插件文件,解压到你的obsidian插件目录下。
|
||||
3. 重新打开Obsidian,进入设置,启用插件。
|
||||
4. 在设置中,找到Obsidian to NotionNext,填入你的NotionNext Database ID 和 API token。
|
||||
|
||||
### 使用插件
|
||||
|
||||
在仓库中我上传了一个模板,你可以直接复制到你的模板文件夹中。然后使用Obsidian的模板功能一键生成新笔记。
|
||||
|
||||
如果你不想使用模板,你也可以直接在Obsidian中创建一个新的文件,然后复制下边的内容。然后保存。
|
||||
|
||||
```markdown
|
||||
---
|
||||
# default value has been set.
|
||||
# for any unwanted value, you can delete it or set it to empty.
|
||||
# for example, if you donot want to set password, you can delete password: "1234" or set it to password: ""
|
||||
# 我已经在插件中设置了默认值,如果有不需要的选项,可以直接删除。
|
||||
# 例如你不需要密码选项,你可以将password: "1234"删除,或者将它设置为空。
|
||||
# !!!!!!!!!!!!
|
||||
# 现在阶段一定不要修改表头的名字, please do not change the name of the header in YAML front matter
|
||||
# !!!!!!!!!!!!
|
||||
titleicon: 📎 # emoji icon, default is 📜, 默认是📜
|
||||
date: 2023-07-23 # default is today, 默认是今天。 Format is YYYY-MM-DD, 格式是YYYY-MM-DD
|
||||
coverurl: https://img.jxpeng.dev/2023/08/843e27a210847f05a0f7cfb121fec100.jpg # default is empty, 默认是空
|
||||
type: Post # Post or Page, default is Post, 默认是Post
|
||||
slug: test # slug for url, default is empty, 默认是空
|
||||
stats: Draft # Draft, Invisible, Published, default is Draft, 默认是Draft
|
||||
category: test # default is 'Obsidian', 默认是'Obsidian'
|
||||
summary: this is a summary for test post # default is empty, 默认是空
|
||||
icon: fa-solid fa-camera # you can ignore this, default is empty, 默认是空,可直接删除
|
||||
password: "1234" # if you donot want to set password, you can delete this line, default is empty, 默认是空,可直接删除
|
||||
# 现在必须开启tags选项,否则会报错
|
||||
tags:
|
||||
- test # tags for post
|
||||
- web # add more tags if you want
|
||||
---
|
||||
|
||||
Contents Below
|
||||
|
||||
|
||||
```
|
||||
|
||||
模板使用如下:
|
||||

|
||||
|
||||
**插件预览如下**
|
||||

|
||||
|
||||
---
|
||||
|
||||
**以下为原README.md**
|
||||
|
||||
非常感谢原作者的开发,我只是在原作者的基础之上进行了修改,增加了一些功能。如果你觉得这个插件对你有帮助,可以给[原作者](https://github.com/EasyChris/obsidian-to-notion)一个star。
|
||||
|
||||
# Obsidian to Notion
|
||||
|
||||
[](https://github.com/Easychris/obsidian-to-notion/actions/workflows/CI.yml)
|
||||
[](https://github.com/Easychris/obsidian-to-notion/actions/workflows/release.yml)
|
||||
[](https://raw.githubusercontent.com/EasyChris/obsidian-to-notion/master/LICENSE)
|
||||
[](https://GitHub.com/Easychris/obsidian-to-notion/releases/)
|
||||
[](https://github.com/Easychris/obsidian-to-notion/releases)
|
||||
|
||||
Obsidian share to Notion [English](README.md)
|
||||
|
||||
将obsidian文件一键分享到Notion,并在obsidian中添加Notion分享链接
|
||||
|
||||
如果能对你有所帮助,欢迎给一个star支持。
|
||||
|
||||

|
||||
|
||||
# 使用方式
|
||||
|
||||
## 安装插件
|
||||
|
||||
### 市场下载
|
||||
|
||||
插件市场搜索 noiton 即可下载
|
||||
|
||||

|
||||
|
||||
### BRAT
|
||||
|
||||
插件中中心搜索 BRAT
|
||||
添加 `EasyChris/obsidian-to-notion` 到 BRAT 插件安装列表中
|
||||
返回插件中心启用即可
|
||||
|
||||
### 手动安装
|
||||
|
||||
```
|
||||
cd YOUR_OBSIDIAN_FOLDER/.obsidian/plugins/
|
||||
git clone https://github.com/EasyChris/obsidian-to-notion.git
|
||||
```
|
||||
|
||||
## 申请 Notion API
|
||||
|
||||
官方参考文档:[https://developers.notion.com/docs](https://developers.notion.com/docs)
|
||||
|
||||
### 第 1 步:创建integration
|
||||
|
||||
转到 [https://www.notion.com/my-integrations](https://www.notion.com/my-integrations)
|
||||
创建完成之后,复制`secrets toekn`
|
||||

|
||||
|
||||
### 第2步:与你的集成共享一个数据库
|
||||
|
||||
新建一个的page(权限为公开)
|
||||
在page中新建一个数据库 -> 需要`full page database`
|
||||

|
||||
|
||||
将`integration`添加到你的新建的数据库中
|
||||
|
||||

|
||||
|
||||
#### 注意
|
||||
|
||||
数据库的第一个自定义名称必须是 "Name",否则同步会失败。
|
||||
|
||||

|
||||
|
||||
### 第三步:复制database ID
|
||||
|
||||
```
|
||||
https://www.notion.so/myworkspace/a8aec43384f447ed84390e8e42c2e089?v=...
|
||||
|--------- Database ID --------|
|
||||
|
||||
```
|
||||
|
||||
## 打开插件配置
|
||||
|
||||
将得到的 `NOTION_API_KEY` 和 `DATABASE_ID`填入配置当中
|
||||

|
||||
|
||||
## 上传文件内容到notion
|
||||
|
||||
点击上传notion的按钮
|
||||

|
||||
上传成功之后会自动生成一个分享链接
|
||||

|
||||
|
||||
## 页面 Banner 链接[可选]
|
||||
|
||||
默认可以不填写
|
||||
横幅URL必须是图像URL,例如:https://i.imgur.com/xxx.jpg
|
||||
|
||||
## Notion ID [可选]
|
||||
|
||||
Notion ID是你想分享文件的页面ID。
|
||||
如果你不写它,notion将分享到默认的链接,如:<https://www.notion.so/myworkspace/a8aec43384f447ed84390,访问这个页面将重定向到你的网站页面。>
|
||||
如果你写了Notion ID,它将分享到页面链接如:<https://your_user_name.notion.site/myworkspace/a8aec43384f447ed84390。不需要重定向网址。>
|
||||
|
||||
## 同步图片
|
||||
|
||||
使用 [Obsidian Image Auto Upload Plugin](https://github.com/renmu123/obsidian-image-auto-upload-plugin) 插件,配置你自己的 cos 或者 oss,将图片存储到你自己的云存储,然后在 obsidian 中使用图片链接即可。该插件会自动帮你上传图片,并替换成链接。
|
||||
|
||||
# 请我喝杯咖啡
|
||||
|
||||
[顿顿饭](https://dun.mianbaoduo.com/@easy)
|
||||
|
||||
# 感谢
|
||||
|
||||
[开发流程 | Obsidian 插件开发文档](https://luhaifeng666.github.io/obsidian-plugin-docs-zh/zh/getting-started/development-workflow.html)
|
||||
|
||||
[GitHub - devbean/obsidian-wordpress: An obsidian plugin for publishing docs to WordPress.](https://github.com/devbean/obsidian-wordpress)
|
||||
|
||||
[GitHub - obsidianmd/obsidian-api](https://github.com/obsidianmd/obsidian-api)
|
||||
|
||||
[GitHub - zhaohongxuan/obsidian-weread-plugin: Obsidian Weread Plugin is an plugin to sync Weread(微信读书) hightlights and annotations into your Obsidian Vault.](https://github.com/zhaohongxuan/obsidian-weread-plugin)
|
||||
此插件是 EasyChris 的原创作品 [obsidian-to-notion](https://github.com/EasyChris/obsidian-to-notion) 的一个分支,增加了新功能并支持更灵活的数据库配置。
|
||||
|
||||
388
README.md
388
README.md
@@ -5,387 +5,17 @@
|
||||
[](https://GitHub.com/jxpeng98/obsidian-to-NotionNext/releases/)
|
||||
[](https://github.com/jxpeng98/obsidian-to-NotionNext/releases/)
|
||||
|
||||
[//]: # ([](https://GitHub.com/jxpeng98/obsidian-to-NotionNext/releases/))
|
||||
[中文版](README-zh.md)
|
||||
Share files from Obsidian to any Notion database using the Notion API. This plugin supports general Notion databases, NotionNext blog templates, and fully custom database schemas.
|
||||
|
||||
**For detailed instructions, please visit the [documentation website](https://obsidian-to-notionnext.pages.dev/).**
|
||||
|
||||
[//]: # ([中文文档](README-zh.md))
|
||||
## Features
|
||||
|
||||
**Now, support both NotionNext and General databases with customised properties.**
|
||||
- **Multiple Database Types**: Supports General, NotionNext, and Custom databases.
|
||||
- **Custom Properties**: Map any frontmatter key to any Notion property in custom databases.
|
||||
- **Flexible Syncing**: Choose which database to sync to on-the-fly.
|
||||
|
||||
**现在支持NotionNext和普通Notion数据库,可自定义数据库列表。**
|
||||
## Acknowledgment
|
||||
|
||||
## TODO List
|
||||
|
||||
- [x] ~~Modify the Edit function for the custom properties. 改进自定义属性的编辑功能~~
|
||||
- [ ] Support group upload with one click 支持一键多数据库上传
|
||||
- [x] ~~Support custom properties for Notion General database. 支持自定义属性~~
|
||||
- [x] ~~Support preview for database details in plugin settings. 支持预览数据库详情~~
|
||||
- [x] ~~Support edit for database details in plugin settings. 支持编辑数据库详情~~
|
||||
|
||||
## Precautions
|
||||
### For customised database users
|
||||
**⚠️⚠️⚠️: The exist customised database should be recreated if you want to update to version 2.3.0. The new version has a new database structure, and the old database structure is not compatible with the new version to build the index properly.**
|
||||
|
||||
### 自定义数据库用户
|
||||
**⚠️⚠️⚠️: 如果你想要更新到2.3.0版本,你需要重新创建自定义数据库。新版本有一个新的数据库结构,旧的数据库结构无法构建索引。**
|
||||
|
||||
## How to use
|
||||
You need seven steps to use this plugin in your Obsidian.
|
||||
1. Create a database in your Notion workspace.
|
||||
Open Notion, clink the `+` button on the left side, and 'Add a page' -> select 'Table' -> 'New database'.
|
||||
2. On the right-top corner, click the 'Share' -> 'Publish' -> 'Publish' -> copy the database id from the URL.
|
||||
3. go to [Notion API](https://www.notion.com/my-integrations) to create a new integration, and copy the token.
|
||||
4. Go back to your created Notion database, click the right-top '...' -> connections -> connect to -> find the integration you created and connect it.
|
||||
5. Go to obsidian settings -> community plugins -> search 'NotionNext' -> install it.
|
||||
6. Create a database in the plugin settings, and fill in the database details and all properties you want to sync (**Note: the name of the properties is case-sensitive.**).
|
||||
- There are three type of database that you can use:
|
||||
- NotionNext: the database for the NotionNext template.(**All the properties are in lowercase**)
|
||||
- General: the database for the general Notion database (**Only have `title` and `tags` columns**)
|
||||
- Custom: the database for the custom properties (You can customise the properties you want to sync)
|
||||
7. Create a new note in Obsidian, and fill in the frontmatter with the properties you want to sync.
|
||||
|
||||
## 使用方法
|
||||
如果你想使用这个插件,你需要完成下边的七个步骤。
|
||||
1. 首先在你的Notion中创建一个数据库。
|
||||
2. 点击右上角的'分享' -> '发布' -> '发布' -> 然后复制URL中的数据库ID。(版本不同,可能id的形式也不同。只需要复制在?之前的纯数字部分应该就可以了)
|
||||
3. 去[Notion API](https://www.notion.com/my-integrations)创建一个新的API,然后复制token。
|
||||
4. 回到你创建的Notion数据库,点击右上角的'...' -> 连接 -> 连接到 -> 把你刚才创建的API绑定到你的数据库
|
||||
5. 回到Obsidian的设置 -> 社区插件 -> 搜索'NotionNext' -> 安装
|
||||
6. 在插件设置中创建一个数据库,然后填写数据库的详情和你想要同步的属性 (**注意:Notion会识别表头的大小写,一定要和notion中的表头保持大小写一致**)。
|
||||
- 你可以使用三种类型的数据库:
|
||||
- NotionNext: 用于NotionNext模板的数据库。(**默认全部小写**)
|
||||
- General: 用于普通的Notion数据库(**只有`title`和`tags`两个属性**)
|
||||
- Custom: 用于自定义属性的数据库(你可以自定义你想要同步的属性)
|
||||
7. 在Obsidian中创建一个新的笔记,然后在frontmatter中写入你想要的属性。
|
||||
|
||||
## Example
|
||||
```plain
|
||||
---
|
||||
# default value has been set.
|
||||
# for any unwanted value, you can delete it or set it to empty.
|
||||
# for example, if you donot want to set password, you can delete password: "1234" or set it to password: ""
|
||||
# 我已经在插件中设置了默认值,如果有不需要的选项,可以直接删除。
|
||||
# 例如你不需要密码选项,你可以将password: "1234"删除,或者将它设置为空。
|
||||
# !!!!!!!!!!!!
|
||||
# 现在阶段一定不要修改表头的名字, please do not change the name of the header in YAML front matter
|
||||
# !!!!!!!!!!!!
|
||||
titleicon: 📎 # emoji icon, default is 📜, 默认是📜
|
||||
date: 2023-07-23 # default is today, 默认是今天。 Format is YYYY-MM-DD, 格式是YYYY-MM-DD
|
||||
coverurl: https://img.jxpeng.dev/2023/08/843e27a210847f05a0f7cfb121fec100.jpg # default is empty, 默认是空
|
||||
type: Post # Post or Page, default is Post, 默认是Post
|
||||
slug: test # slug for url, default is empty, 默认是空
|
||||
status: Draft # Draft, Invisible, Published, default is Draft, 默认是Draft
|
||||
category: test # default is 'Obsidian', 默认是'Obsidian'
|
||||
summary: this is a summary for test post # default is empty, 默认是空
|
||||
icon: fa-solid fa-camera # you can ignore this, default is empty, 默认是空,可直接删除
|
||||
password: "1234" # if you donot want to set password, you can delete this line, default is empty, 默认是空,可直接删除
|
||||
# 现在必须开启tags选项,否则会报错
|
||||
tags:
|
||||
- test # tags for post
|
||||
- web # add more tags if you want
|
||||
---
|
||||
```
|
||||
|
||||

|
||||
- Once you create the properties, you can preview the database details in the plugin settings.
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Acknowledgement
|
||||
|
||||
Thanks to the [original author](https://github.com/EasyChris/obsidian-to-notion) for developing such a useful plugin that can synchronize Obsidian to Notion. However, the original repository can only sync Name and Tag information. For those like me who use [NotionNext](https://github.com/tangly1024/NotionNext) to set up their website, this presents some limitations. Every time I import, I need to make a lot of modifications.
|
||||
|
||||
Thus, based on the [original author's work](https://github.com/EasyChris/obsidian-to-notion), I've added a feature to match the [NotionNext](https://github.com/tangly1024/NotionNext) template. This way, you can edit directly in Obsidian and publish with a single click after organizing.
|
||||
|
||||
---
|
||||
|
||||
<details> <summary> Previous How to Use </summary>
|
||||
|
||||
### Precautions
|
||||
|
||||
For now, this plugin is exclusively for [NotionNext](https://github.com/tangly1024/NotionNext). If you're not using this template, you'll keep receiving `error 400`.
|
||||
|
||||
For those without a NotionNext requirement, please use the original [Obsidian-to-notion](https://github.com/EasyChris/obsidian-to-notion).
|
||||
|
||||
### Pre-Installation Steps
|
||||
|
||||
Before installing the plugin, you must have set up the following:
|
||||
|
||||
1. Your NotionNext database.
|
||||
2. According to the original author's readme.md, set up the Notion API, and it should already be associated with your NotionNext repository.
|
||||
3. NotionNext Database ID
|
||||
4. Your NotionNext database should have the following contents:
|
||||
- type
|
||||
- title
|
||||
- slug
|
||||
- category
|
||||
- tags
|
||||
- date
|
||||
- status
|
||||
- summary
|
||||
- password
|
||||
- icon
|
||||
|
||||
From version 0.0.6, I also add the following contents:
|
||||
- titleicon: the unique icon for each post, it can only support emoji currently.
|
||||
- cover url: the cover image for each post, it should be end with a type of image, such as .jpg, .png, .gif, etc.
|
||||
|
||||
**If you've directly copied the NotionNext template, these contents should already exist. I've made changes to this plugin based on the original author's work. All you need to ensure is that your database has the above content, and every letter is in lowercase!!!**
|
||||
|
||||
**⚠️⚠️⚠️: All headers are in lowercase!!! The order doesn't matter!**
|
||||
|
||||
### Plugin Installation
|
||||
|
||||
#### Install via Community Plugins
|
||||
|
||||
Open `Obsidian settings -> Community Plugins -> Browse -> NotionNext -> Share to NotionNext`
|
||||
|
||||
#### Mannually Install
|
||||
|
||||
1. Close Obsidian.
|
||||
2. Download the plugin file from Release and unzip it into your Obsidian plugin directory.
|
||||
3. Re-open Obsidian, go to settings, and enable the plugin.
|
||||
4. In the settings, find Obsidian to NotionNext, and enter your NotionNext Database ID and API token.
|
||||
|
||||
### How to Use
|
||||
|
||||
### Using the Plugin
|
||||
|
||||
In the repository, I have uploaded a template which you can directly copy into your template folder. After that, use Obsidian's template feature to create a new note with one click.
|
||||
|
||||
If you don't want to use the template, you can also directly create a new file in Obsidian and then copy the content below. Then save it.
|
||||
|
||||
```markdown
|
||||
---
|
||||
# default value has been set.
|
||||
# for any unwanted value, you can delete it or set it to empty.
|
||||
# for example, if you donot want to set password, you can delete password: "1234" or set it to password: ""
|
||||
# 我已经在插件中设置了默认值,如果有不需要的选项,可以直接删除。
|
||||
# 例如你不需要密码选项,你可以将password: "1234"删除,或者将它设置为空。
|
||||
# !!!!!!!!!!!!
|
||||
# 现在阶段一定不要修改表头的名字, please do not change the name of the header in YAML front matter
|
||||
# !!!!!!!!!!!!
|
||||
titleicon: 📎 # emoji icon, default is 📜, 默认是📜
|
||||
date: 2023-07-23 # default is today, 默认是今天。 Format is YYYY-MM-DD, 格式是YYYY-MM-DD
|
||||
coverurl: https://img.jxpeng.dev/2023/08/843e27a210847f05a0f7cfb121fec100.jpg # default is empty, 默认是空
|
||||
type: Post # Post or Page, default is Post, 默认是Post
|
||||
slug: test # slug for url, default is empty, 默认是空
|
||||
stats: Draft # Draft, Invisible, Published, default is Draft, 默认是Draft
|
||||
category: test # default is 'Obsidian', 默认是'Obsidian'
|
||||
summary: this is a summary for test post # default is empty, 默认是空
|
||||
icon: fa-solid fa-camera # you can ignore this, default is empty, 默认是空,可直接删除
|
||||
password: "1234" # if you donot want to set password, you can delete this line, default is empty, 默认是空,可直接删除
|
||||
# 现在必须开启tags选项,否则会报错
|
||||
tags:
|
||||
- test # tags for post
|
||||
- web # add more tags if you want
|
||||
---
|
||||
|
||||
Contents Below
|
||||
|
||||
```
|
||||
|
||||
Usage of the template is as follows:
|
||||

|
||||
|
||||
**Plugin preview is shown below**
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
<details> <summary> Original README.md </summary>
|
||||
|
||||
Many Thanks for the original author's work. I've only made some changes to the original author's work. If you find this plugin useful, please give the [original author](https://github.com/EasyChris/obsidian-to-notion) a star.
|
||||
|
||||
# Obsidian to Notion
|
||||
|
||||
[](https://github.com/Easychris/obsidian-to-notion/actions/workflows/CI.yml)
|
||||
[](https://github.com/Easychris/obsidian-to-notion/actions/workflows/release.yml)
|
||||
[](https://raw.githubusercontent.com/EasyChris/obsidian-to-notion/master/LICENSE)
|
||||
[](https://GitHub.com/Easychris/obsidian-to-notion/releases/)
|
||||
[](https://github.com/Easychris/obsidian-to-notion/releases)
|
||||
|
||||
Share of obsidian to Notion [中文文档](README-zh.md)
|
||||
|
||||
Sharing files from Obsidian to Notion with a single click, and Obsidian will automatically add the Notion share link
|
||||
|
||||
You are welcome to offer it a star if it can benefit you.
|
||||
|
||||

|
||||
|
||||
# TODO
|
||||
|
||||
### [TODO Board](https://github.com/users/EasyChris/projects/3/views/1)
|
||||
|
||||
- [x] support for custom page banner
|
||||
- [x] update the exsit page
|
||||
- [x] support for mult language
|
||||
- [x] support for auto copy the share link to clipboard
|
||||
- [x] support for mobile
|
||||
- [x] support tags thank for [@jannikbuscha](https://github.com/jannikbuscha)
|
||||
- [ ] transfer the bi-link format like [[]] into the format that Notion supports.
|
||||
|
||||
# How to use
|
||||
|
||||
## Install the plugin
|
||||
|
||||
### Marketplace download
|
||||
|
||||
Open obsidian setting -> Add plugin -> Search -> notion
|
||||
|
||||

|
||||
|
||||
### BRAT
|
||||
|
||||
Enter `BRAT` into the plugin market center to find it.
|
||||
Add `EasyChris/obsidian-to-notion` to the list of BRAT plugins that have been installed.
|
||||
Return to the plugin center and turn it on.
|
||||
|
||||
### Manual installation
|
||||
|
||||
```
|
||||
cd YOUR_OBSIDIAN_FOLDER/.obsidian/plugins/
|
||||
git clone https://github.com/EasyChris/obsidian-to-notion.git
|
||||
```
|
||||
|
||||
## Apply Notion API
|
||||
|
||||
Official reference documentation: [https://developers.notion.com/docs](https://developers.notion.com/docs)
|
||||
|
||||
### Step 1: Create integration
|
||||
|
||||
Go to [https://www.notion.com/my-integrations](https://www.notion.com/my-integrations)
|
||||
Once created, copy `secrets toekn`
|
||||

|
||||
|
||||
#### Note
|
||||
|
||||
database first custom name must be "Name", otherwise sync to notion will be failed
|
||||
|
||||

|
||||
|
||||
### Step 2: Share a database with your integration
|
||||
|
||||
Create a new page (with public permissions)
|
||||
Create a new database in the page -> you need `full page database`
|
||||

|
||||
|
||||
Add `integration` to your new database
|
||||
|
||||

|
||||
|
||||
### Step 3: Copy the database ID
|
||||
|
||||
```
|
||||
https://www.notion.so/myworkspace/a8aec43384f447ed84390e8e42c2e089?v=...
|
||||
| --------- Database ID --------|
|
||||
|
||||
```
|
||||
|
||||
## Open the plugin configuration
|
||||
|
||||
Fill the configuration with the `NOTION_API_KEY` and `DATABASE_ID` you got
|
||||

|
||||
|
||||
## Upload file content to notion
|
||||
|
||||
Click the uploadCommand notion button
|
||||

|
||||
A share link will be automatically generated after successful uploadCommand
|
||||

|
||||
|
||||
## Banner URL [option]
|
||||
|
||||
Banner url must be a image url like: <https://i.imgur.com/xxx.jpg>
|
||||
If you don't want to use banner, leave it blank
|
||||
|
||||
## Convert Tags [option]
|
||||
|
||||
Transfer the Obsidian tags to the Notion table.
|
||||
It requires the column with the name 'Tags'.
|
||||

|
||||
|
||||
Add tags to your notion page
|
||||
|
||||

|
||||
|
||||
- open plugin convert tags
|
||||
|
||||

|
||||
|
||||
- add tags in the head
|
||||
|
||||
```markdown
|
||||
---
|
||||
tags: [tag1,tag2]
|
||||
---
|
||||
|
||||
this is test tags
|
||||
|
||||
```
|
||||
|
||||
```markdown
|
||||
---
|
||||
tags:
|
||||
- tag4
|
||||
---
|
||||
|
||||
this is test tags
|
||||
|
||||
```
|
||||
|
||||

|
||||
|
||||
Thanks for [@jannikbuscha](https://github.com/jannikbuscha) contribution
|
||||
|
||||
## Notion ID [option]
|
||||
|
||||
Notion ID is the your notion site ID that you want to share the file to.
|
||||
if you don't write it, notion will share to the default link like:
|
||||
<https://www.notion.so/myworkspace/a8aec43384f447ed84390>
|
||||
that visit this page need to redirect to your site url
|
||||
if you write the Notion ID, it will share to the page link like:
|
||||
<https://your_user_name.notion.site/myworkspace/a8aec43384f447ed84390>.
|
||||
The visiter don't need to redirect url.
|
||||
|
||||
## Sync image to Notion
|
||||
|
||||
To sync images to your oss or cos bucket, use the [Obsidian Image Auto Upload Plugin](https://github.com/renmu123/obsidian-image-auto-upload-plugin).
|
||||
|
||||
# Development
|
||||
|
||||
```
|
||||
git clone https://github.com/EasyChris/obsidian-to-notion.git
|
||||
yarn install
|
||||
yarn dev
|
||||
```
|
||||
|
||||
## Release
|
||||
|
||||
```
|
||||
node update-version.js
|
||||
./release.sh
|
||||
```
|
||||
|
||||
|
||||
# Thanks
|
||||
[Development Process | Obsidian Plugin Development Documentation](https://luhaifeng666.github.io/obsidian-plugin-docs-zh/zh/getting-started/development-workflow.html)
|
||||
|
||||
[GitHub - devbean/obsidian-wordpress: An obsidian plugin for publishing docs to WordPress.](https://github.com/devbean/obsidian-wordpress)
|
||||
|
||||
[GitHub - obsidianmd/obsidian-api](https://github.com/obsidianmd/obsidian-api)
|
||||
|
||||
[GitHub - Easychris/obsidian-to-notion: Obsidian Weread Plugin is an plugin to sync Weread(微信读书) hightlights and annotations into your Obsidian Vault.](https://github.dev/zhaohongxuan/obsidian-weread-plugin)
|
||||
|
||||
[GitHub - Quorafind/Obsidian-Memos: A quick capture plugin for Obsidian, all data from your notes.](https://github.com/Quorafind/Obsidian-Memos)
|
||||
|
||||
[https://github.com/jannikbuscha/obsidian-to-notion](https://github.com/jannikbuscha)
|
||||
|
||||
# License
|
||||
GNU GPLv3
|
||||
|
||||
|
||||
</details>
|
||||
This plugin is a fork of the original [obsidian-to-notion](https://github.com/EasyChris/obsidian-to-notion) by EasyChris, with added features and support for more flexible database configurations.
|
||||
|
||||
BIN
doc/10.png
BIN
doc/10.png
Binary file not shown.
|
Before Width: | Height: | Size: 38 KiB |
69
docs/.vitepress/config.ts
Normal file
69
docs/.vitepress/config.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { defineConfig } from 'vitepress'
|
||||
import { withSidebar } from 'vitepress-sidebar';
|
||||
|
||||
// https://vitepress.dev/reference/site-config
|
||||
const vitePressOptions = {
|
||||
title: "Obsidian to NotionNext",
|
||||
description: "A plugin to share files to any Notion database using the Notion API.",
|
||||
|
||||
themeConfig: {
|
||||
socialLinks: [
|
||||
{ icon: 'github', link: 'https://github.com/jxpeng98/obsidian-to-NotionNext' }
|
||||
]
|
||||
},
|
||||
locales: {
|
||||
root: {
|
||||
label: 'English',
|
||||
lang: 'en',
|
||||
link: '/en/',
|
||||
themeConfig: {
|
||||
nav: [
|
||||
{ text: 'Home', link: '/en/' },
|
||||
{ text: 'Guide', link: '/en/01-getting-started' }
|
||||
],
|
||||
}
|
||||
},
|
||||
zh: {
|
||||
label: '简体中文',
|
||||
lang: 'zh-CN',
|
||||
link: '/zh/',
|
||||
themeConfig: {
|
||||
nav: [
|
||||
{ text: '首页', link: '/zh/' },
|
||||
{ text: '指南', link: '/zh/01-getting-started' }
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const commonSidebarOptions = {
|
||||
documentRootPath: '/',
|
||||
useTitleFromFrontmatter: true,
|
||||
frontmatterTitleFieldName: 'title',
|
||||
collapsed: true,
|
||||
removePrefixAfterOrdering: true,
|
||||
prefixSeparator: '-',
|
||||
hyphenToSpace: true,
|
||||
useFolderTitleFromIndexFile: true,
|
||||
useFolderLinkFromIndexFile: true,
|
||||
sortMenusByName: false,
|
||||
};
|
||||
|
||||
const vitePressSidebarOptions = [
|
||||
{
|
||||
...commonSidebarOptions,
|
||||
scanStartPath: '/en',
|
||||
basePath: '/',
|
||||
resolvePath: '/en/'
|
||||
},
|
||||
{
|
||||
...commonSidebarOptions,
|
||||
scanStartPath: '/zh',
|
||||
basePath: '/',
|
||||
resolvePath: '/zh/',
|
||||
}
|
||||
];
|
||||
|
||||
export default defineConfig(withSidebar(vitePressOptions, vitePressSidebarOptions));
|
||||
45
docs/en/01-getting-started.md
Normal file
45
docs/en/01-getting-started.md
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
title: Getting Started
|
||||
description: Guide to setting up the Obsidian to NotionNext plugin
|
||||
---
|
||||
|
||||
# Getting Started
|
||||
|
||||
## Get Notion API Token
|
||||
|
||||
Before using the plugin, you need to obtain an API Token from Notion to allow the plugin to access your Notion account.
|
||||
|
||||
1. Go to the [Notion API](https://www.notion.so/profile/integrations) page to create a new integration. You need to be logged into your Notion account.
|
||||
2. Click on `+ New integration`.
|
||||

|
||||
3. Fill in the required information:
|
||||

|
||||
4. Integration successfully created.
|
||||

|
||||
5. Enter the "Configure integration settings" page and **prepare to copy the API Token,** which you will need in the plugin settings.
|
||||

|
||||
|
||||
::: warning
|
||||
**⚠️ Note:** Keep your API Token secure and do not share it with others, as it grants access to your Notion data.
|
||||
:::
|
||||
|
||||
## Create a Notion Database and Connect Integration
|
||||
|
||||
Next, you need to create a Notion database to store the notes synced from Obsidian.
|
||||
|
||||
1. In your Notion workspace, click on `Create a new page` to create a new page.
|
||||

|
||||
|
||||
2. create a new page and select the `Database - Full page` option.
|
||||

|
||||
|
||||
3. Click the `...` menu in the top-right corner of the database page, go to `Connections`, and connect your newly created integration.
|
||||

|
||||
|
||||
4. Click `Share` -> `Publish` to publish the database to the web.
|
||||

|
||||
|
||||
5. Click and copy the **Database ID**.
|
||||

|
||||
|
||||
Next, you can proceed to install and configure the plugin in Obsidian by following the [Installation Guide](./02-installation.md).
|
||||
28
docs/en/02-installation.md
Normal file
28
docs/en/02-installation.md
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
title: Installation
|
||||
description: Guide to installing the Obsidian to NotionNext plugin
|
||||
---
|
||||
|
||||
# Installation
|
||||
|
||||
Once you have completed the previous section setup on the Notion side, you can install and configure the plugin in Obsidian.
|
||||
|
||||
1. Open Obsidian's `Settings`.
|
||||

|
||||
|
||||
2. Navigate to `Community plugins`.
|
||||

|
||||
::: tip
|
||||
If you haven't enabled community plugins before, you may need to turn off `Safe mode` first (`Turn on Community plugins`).
|
||||
:::
|
||||
|
||||
3. Go to `Community plugins` -> `Browse`.
|
||||

|
||||
|
||||
4. Search for "NotionNext" and click `Install`.
|
||||

|
||||
|
||||
5. After installation, enable the plugin.
|
||||

|
||||
|
||||
Now, once enabled, you can find the "Share to NotionNext" settings in the plugin settings tab to begin [configuring your databases](./03-configuration.md) in the next step.
|
||||
112
docs/en/03-configuration.md
Normal file
112
docs/en/03-configuration.md
Normal file
@@ -0,0 +1,112 @@
|
||||
---
|
||||
title: Configuration
|
||||
description: Guide to configuring Notion databases in the Obsidian to NotionNext plugin
|
||||
---
|
||||
|
||||
# Database Configuration
|
||||
|
||||
After installing the plugin and obtaining the Notion API token and Database ID, you can proceed to configure the databases you want to sync with.
|
||||
|
||||
First, open the plugin settings in Obsidian.
|
||||

|
||||
|
||||
Click "Add new database" to start creating a new database configuration.
|
||||
|
||||

|
||||
|
||||
In the plugin settings, you can add and configure the Notion databases you want to sync with. The plugin supports three types of database configurations.
|
||||
|
||||
- [Database Configuration](#database-configuration)
|
||||
- [1️⃣ General Database](#1️⃣-general-database)
|
||||
- [2️⃣ NotionNext Database](#2️⃣-notionnext-database)
|
||||
- [3️⃣ Custom Database](#3️⃣-custom-database)
|
||||
- [Finalizing Configuration](#finalizing-configuration)
|
||||
|
||||
## 1️⃣ General Database
|
||||
|
||||
This is the most basic database type and is suitable for most users.
|
||||
|
||||
**Configuration:**
|
||||
|
||||
- **Full Name**: A name for this database configuration.
|
||||
- **Notion API Token**: The API token you obtained in the first step.
|
||||
- **Database ID**: The database ID you obtained in the second step.
|
||||
|
||||
**Notion Database Requirements:**
|
||||
|
||||
- Must contain a `Title` property named `title`.
|
||||
- Must contain a `Multi-select` property named `tags`.
|
||||

|
||||
|
||||
Then, fill in the configuration fields as shown below:
|
||||
|
||||
- **Database Full Name**: the name you want to give this database to recognise it in the database list (e.g. "Learning Database").
|
||||
- **Database Abbreviate Name**: a short abbreviation for this database (e.g. "learning").
|
||||
- **Notion Tags Sync**: enable this option if you want to sync tags columns from Obsidian to Notion (**if you enable this option, make sure you add tags in your notes**).
|
||||
- **Customise title property**: the property in your Notion database that you want to use as the primary column (default is `title`).
|
||||
- **Notion API Token**: the API token you obtained in the first step.
|
||||
- **Database ID**: the database ID you obtained in the second step.
|
||||
|
||||

|
||||
|
||||
## 2️⃣ NotionNext Database
|
||||
|
||||
This type is specifically designed for users of the [NotionNext](https://github.com/tangly1024/NotionNext) blog system, with properties preset to match the NotionNext template.
|
||||
|
||||
Config the database similarly to the General Database type:
|
||||
|
||||
- **Database Full Name**: A name for this database configuration.
|
||||
- **Database Abbreviate Name**: A short abbreviation for this database.
|
||||
- **Notion API Token**: The API token you obtained in the first step.
|
||||
- **Database ID**: The database ID you obtained in the second step.
|
||||
|
||||

|
||||
|
||||
## 3️⃣ Custom Database
|
||||
|
||||
This type offers maximum flexibility for advanced users. It allows you to map any frontmatter metadata from your Obsidian notes to any property in your Notion database.
|
||||
|
||||
You should first config the database similarly to the General Database type:
|
||||
- **Database Full Name**: A name for this database configuration.
|
||||
- **Database Abbreviate Name**: A short abbreviation for this database.
|
||||
- **Notion API Token**: The API token you obtained in the first step.
|
||||
- **Database ID**: The database ID you obtained in the second step.
|
||||
|
||||

|
||||
|
||||
Once you have filled in the basic configuration, you can start to add new properties.
|
||||
|
||||
You can click the `Add New Property` button to create a new property mapping.
|
||||
|
||||
First, you need to add the title property mapping, which is required for the Notion database to function correctly.
|
||||
|
||||

|
||||
|
||||
::: tip
|
||||
Remember to set the "Notion Property" to the property name in your Notion database (case-sensitive) and the "Frontmatter Key" to the key in your Obsidian note's frontmatter.
|
||||
:::
|
||||
|
||||
After adding the title property, you can continue to add more properties as needed.
|
||||

|
||||
|
||||
For now, the plugin supports the following Notion property types:
|
||||
- Text
|
||||
- Number
|
||||
- Select
|
||||
- Multi-select
|
||||
- Date
|
||||
- Files & Media
|
||||
- Checkbox
|
||||
- URL
|
||||
- Email
|
||||
- Phone Number
|
||||
|
||||

|
||||
|
||||
::: info
|
||||
Relation and Rollup types are not supported yet.
|
||||
:::
|
||||
|
||||
## Finalizing Configuration
|
||||
|
||||
After configuring your database, make sure to save your settings. You can now start syncing your Obsidian notes to the configured Notion database by using the "Share to NotionNext" command from the command palette or the note context menu.
|
||||
12
docs/en/04-sync.md
Normal file
12
docs/en/04-sync.md
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
title: Syncing Notes
|
||||
description: How to sync your Obsidian notes to Notion using the NotionNext plugin
|
||||
---
|
||||
|
||||
# Syncing Notes
|
||||
|
||||
After configuring your Notion database in the plugin settings, you can start syncing your Obsidian notes to Notion.
|
||||
|
||||
To sync a note, open the note you want to sync and use the "Share to NotionNext" command from the command palette or the note context menu. This will create a new page in your Notion database with the content of your Obsidian note.
|
||||
|
||||
You can also set up automatic syncing for specific notes or folders by configuring the plugin settings. This way, any changes you make to your Obsidian notes will be automatically reflected in Notion.
|
||||
10
docs/en/05-troubleshooting.md
Normal file
10
docs/en/05-troubleshooting.md
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: Troubleshooting
|
||||
description: Common issues and solutions for the Obsidian to NotionNext plugin
|
||||
---
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
If the problem persists, you can [open an issue on GitHub](https://github.com/jxpeng98/obsidian-to-NotionNext/issues) with detailed error information and the steps you took.
|
||||
|
||||
You can find the error logs in Obsidian by going to developer tools (`Ctrl+Shift+I` or `Cmd+Option+I`) and checking the console for any error messages related to the NotionNext plugin.
|
||||
8
docs/en/06-changelog.md
Normal file
8
docs/en/06-changelog.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: Changelog
|
||||
description: Release notes and updates for Obsidian to NotionNext
|
||||
---
|
||||
|
||||
# Changelog
|
||||
|
||||
Welcome to the Changelog for Obsidian to NotionNext! Here you'll find a detailed list of all the updates, improvements, and bug fixes made to the plugin over time from the version `2.7.0` onwards.
|
||||
10
docs/en/07-roadmap.md
Normal file
10
docs/en/07-roadmap.md
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: Roadmap
|
||||
description: Planned features and improvements for Obsidian to NotionNext
|
||||
---
|
||||
|
||||
# Roadmap
|
||||
|
||||
- [ ] Automatic Syncing: Implement automatic syncing of notes at regular intervals or upon changes.
|
||||
- [ ] File Attachments: Support for syncing file attachments from Obsidian to Notion.
|
||||
- [ ] Relation Property: Enable mapping of Obsidian links to Notion relation properties.
|
||||
27
docs/en/index.md
Normal file
27
docs/en/index.md
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: Introduction
|
||||
description: Share files to any Notion database using the Notion API
|
||||
order: 0
|
||||
---
|
||||
|
||||
# Obsidian to NotionNext
|
||||
|
||||
In this documentation, you will find guides and instructions on how to set up and use the Obsidian to NotionNext plugin. This plugin allows you to share your Obsidian notes directly to Notion databases using the Notion API.
|
||||
|
||||
This guide will walk you through the process of setting up the Obsidian to NotionNext plugin.
|
||||
|
||||
[Getting Started](./01-getting-started.md)
|
||||
|
||||
[Installation](./02-installation.md)
|
||||
|
||||
[Configuration](./03-configuration.md)
|
||||
|
||||
[Syncing Notes](./04-sync.md)
|
||||
|
||||
[Troubleshooting](./05-troubleshooting.md)
|
||||
|
||||
---
|
||||
|
||||
[Changelog](./06-changelog.md)
|
||||
|
||||
[Roadmap](./07-roadmap.md)
|
||||
29
docs/index.md
Normal file
29
docs/index.md
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
# https://vitepress.dev/reference/default-theme-home-page
|
||||
layout: home
|
||||
|
||||
hero:
|
||||
name: "Obsidian to NotionNext"
|
||||
text: "Share files to any Notion database using the Notion API"
|
||||
tagline: Support All Notion databases, including NotionNext and General Notion databases, and customise the database list.
|
||||
actions:
|
||||
- theme: brand
|
||||
text: Get Started
|
||||
link: /en/
|
||||
- theme: brand
|
||||
text: 现在开始
|
||||
link: /zh/
|
||||
- theme: alt
|
||||
text: View on GitHub
|
||||
link: https://github.com/jxpeng98/obsidian-to-NotionNext
|
||||
|
||||
features:
|
||||
- title: All Notion Databases
|
||||
details: Support for both general and NotionNext-specific databases.
|
||||
- title: Custom Properties
|
||||
details: Customize database properties to fit your workflow.
|
||||
- title: Long Note Sync
|
||||
details: Sync long notes seamlessly.
|
||||
- title: Callout Support
|
||||
details: Obsidian callouts are converted to Notion callout blocks.
|
||||
---
|
||||
17
docs/package.json
Normal file
17
docs/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "obsidian-to-notionnext-docs",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vitepress dev",
|
||||
"build": "vitepress build",
|
||||
"preview": "vitepress preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitepress": "^2.0.0-alpha.12",
|
||||
"vitepress-sidebar": "^1.33.0",
|
||||
"vue": "^3.5.22"
|
||||
}
|
||||
}
|
||||
1756
docs/pnpm-lock.yaml
generated
Normal file
1756
docs/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
45
docs/zh/01-getting-started.md
Normal file
45
docs/zh/01-getting-started.md
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
title: 开始使用
|
||||
description: Obsidian to NotionNext 插件设置指南
|
||||
---
|
||||
|
||||
# 开始使用
|
||||
|
||||
## 获取 Notion API 令牌
|
||||
|
||||
在使用本插件前,你需要从 Notion 获取一个 API 令牌(Token),以授权插件访问你的 Notion 账户。
|
||||
|
||||
1. 前往 [Notion API](https://www.notion.so/profile/integrations) 页面来创建一个新的集成(integration)。你需要先登录你的 Notion 账户。
|
||||
2. 点击 `+ New integration`。
|
||||

|
||||
3. 填写一些基本信息:
|
||||

|
||||
4. 成功创建集成。
|
||||

|
||||
5. 进入“配置集成设置”页面,**准备好复制 API 令牌**,你很快就会在插件设置中用到它。
|
||||

|
||||
|
||||
::: warning
|
||||
**⚠️ 注意:** 请妥善保管你的 API 令牌,不要与他人分享,因为它授予了访问你 Notion 数据的权限。
|
||||
:::
|
||||
|
||||
## 创建 Notion 数据库并连接集成
|
||||
|
||||
接下来,你需要创建一个 Notion 数据库,用于存放从 Obsidian 同步过来的笔记。
|
||||
|
||||
1. 在你的 Notion 工作区中,点击 `Create a new page` 来创建一个新页面。
|
||||

|
||||
|
||||
2. 创建一个新页面后,选择 `Database - Full page` 选项。
|
||||

|
||||
|
||||
3. 点击数据库页面右上角的 `...` 菜单,进入 `Connections`,然后连接你刚刚创建的集成。
|
||||

|
||||
|
||||
4. 点击 `Share` -> `Publish`,将数据库发布到网络。
|
||||

|
||||
|
||||
5. 点击并复制 **数据库 ID**。
|
||||

|
||||
|
||||
接下来,你可以根据[安装指南](./02-installation.md)在 Obsidian 中安装和配置插件了。
|
||||
28
docs/zh/02-installation.md
Normal file
28
docs/zh/02-installation.md
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
title: 安装插件
|
||||
description: 如何安装 Obsidian to NotionNext 插件
|
||||
---
|
||||
|
||||
# 安装插件
|
||||
|
||||
当你在 Notion 端完成上一章节的设置后,就可以在 Obsidian 里安装和配置插件了。
|
||||
|
||||
1. 打开 Obsidian 的 `设置`。
|
||||

|
||||
|
||||
2. 找到 `第三方插件` 菜单。
|
||||

|
||||
::: tip
|
||||
如果你之前没有启用过第三方插件,可能需要先关闭 `安全模式`(点击 `打开社区插件`)。
|
||||
:::
|
||||
|
||||
3. 进入 `社区插件` -> `浏览`。
|
||||

|
||||
|
||||
4. 搜索 "NotionNext" 并点击 `安装`。
|
||||

|
||||
|
||||
5. 安装完成后,记得 `启用` 插件。
|
||||

|
||||
|
||||
启用后,你就可以在插件设置列表中找到 "Share to NotionNext",并在下一步开始[配置你的数据库](./03-configuration.md)了。
|
||||
112
docs/zh/03-configuration.md
Normal file
112
docs/zh/03-configuration.md
Normal file
@@ -0,0 +1,112 @@
|
||||
---
|
||||
title: 配置数据库
|
||||
description: 在 Obsidian to NotionNext 插件中配置 Notion 数据库的指南
|
||||
---
|
||||
|
||||
# 配置数据库
|
||||
|
||||
在安装好插件并获取了 Notion API 令牌和数据库 ID 之后,你就可以开始配置要同步的数据库了。
|
||||
|
||||
首先,在 Obsidian 中打开插件设置。
|
||||

|
||||
|
||||
点击 “添加新数据库” 来创建一个新的数据库配置。
|
||||
|
||||

|
||||
|
||||
插件支持三种类型的数据库配置,你可以根据自己的需求选择。
|
||||
|
||||
- [数据库配置](#数据库配置)
|
||||
- [1️⃣ 通用数据库](#1️⃣-通用数据库)
|
||||
- [2️⃣ NotionNext 数据库](#2️⃣-notionnext-数据库)
|
||||
- [3️⃣ 自定义数据库](#3️⃣-自定义数据库)
|
||||
- [完成配置](#完成配置)
|
||||
|
||||
## 1️⃣ 通用数据库
|
||||
|
||||
这是最基础的数据库类型,适合大多数用户。
|
||||
|
||||
**配置项:**
|
||||
|
||||
- **数据库全名**: 给这个数据库配置起一个容易识别的名字。
|
||||
- **Notion API 令牌**: 你在第一步中获取的 API 令牌。
|
||||
- **数据库 ID**: 你在第二步中获取的数据库 ID。
|
||||
|
||||
**Notion 数据库要求:**
|
||||
|
||||
- 必须包含一个名为 `title` 的 `Title` 属性。
|
||||
- 必须包含一个名为 `tags` 的 `Multi-select` 属性。
|
||||

|
||||
|
||||
然后,像下面这样填写配置字段:
|
||||
|
||||
- **数据库全名 (Database Full Name)**: 你希望这个数据库在列表中显示的名字(例如:“学习资料库”)。
|
||||
- **数据库简称 (Database Abbreviate Name)**: 一个简短的别名(例如:“learning”)。
|
||||
- **同步 Notion 标签 (Notion Tags Sync)**: 如果希望将 Obsidian 的标签同步到 Notion,请启用此选项(**启用后,请确保你的笔记中包含 `tags`**)。
|
||||
- **自定义标题属性 (Customise title property)**: 你想用作主列的 Notion 数据库属性(默认为 `title`)。
|
||||
- **Notion API 令牌 (Notion API Token)**: 你在第一步中获取的 API 令牌。
|
||||
- **数据库 ID (Database ID)**: 你在第二步中获取的数据库 ID。
|
||||
|
||||

|
||||
|
||||
## 2️⃣ NotionNext 数据库
|
||||
|
||||
这个类型是为 [NotionNext](https://github.com/tangly1024/NotionNext) 博客系统的用户量身定制的,其属性已预设好以匹配 NotionNext 模板。
|
||||
|
||||
配置方式与通用数据库类似:
|
||||
|
||||
- **数据库全名**: 为这个数据库配置起一个名字。
|
||||
- **数据库简称**: 一个简短的别名。
|
||||
- **Notion API 令牌**: 你在第一步中获取的 API 令牌。
|
||||
- **数据库 ID**: 你在第二步中获取的数据库 ID。
|
||||
|
||||

|
||||
|
||||
## 3️⃣ 自定义数据库
|
||||
|
||||
这个类型为高级用户提供了极大的灵活性,允许你将 Obsidian 笔记中的任何 frontmatter 元数据映射到 Notion 数据库的任意属性。
|
||||
|
||||
首先,你需要像配置通用数据库一样填写基本信息:
|
||||
- **数据库全名**: 为这个数据库配置起一个名字。
|
||||
- **数据库简称**: 一个简短的别名。
|
||||
- **Notion API 令牌**: 你在第一步中获取的 API 令牌。
|
||||
- **数据库 ID**: 你在第二步中获取的数据库 ID。
|
||||
|
||||

|
||||
|
||||
填写完基本配置后,你就可以开始添加新的属性映射了。
|
||||
|
||||
你可以点击 `Add New Property` 按钮来创建一个新的属性映射。
|
||||
|
||||
首先,你需要添加标题属性的映射,这是 Notion 数据库正常工作所必需的。
|
||||
|
||||

|
||||
|
||||
::: tip
|
||||
请记住,"Notion 属性 (Notion Property)" 需要填写你在 Notion 数据库中的属性名称(区分大小写),而 "Frontmatter 键 (Frontmatter Key)" 则是你 Obsidian 笔记 frontmatter 中的键。
|
||||
:::
|
||||
|
||||
添加完标题属性后,你可以根据需要继续添加更多属性。
|
||||

|
||||
|
||||
目前,插件支持以下 Notion 属性类型:
|
||||
- Text
|
||||
- Number
|
||||
- Select
|
||||
- Multi-select
|
||||
- Date
|
||||
- Files & Media
|
||||
- Checkbox
|
||||
- URL
|
||||
- Email
|
||||
- Phone Number
|
||||
|
||||

|
||||
|
||||
::: info
|
||||
关联(Relation)和汇总(Rollup)类型暂时还不支持。
|
||||
:::
|
||||
|
||||
## 完成配置
|
||||
|
||||
配置完数据库后,请确保保存你的设置。现在,你可以通过命令面板或笔记右键菜单中的 “Share to NotionNext” 命令,开始将你的 Obsidian 笔记同步到配置好的 Notion 数据库了。
|
||||
12
docs/zh/04-sync.md
Normal file
12
docs/zh/04-sync.md
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
title: 同步笔记
|
||||
description: 如何使用 NotionNext 插件将你的 Obsidian 笔记同步到 Notion
|
||||
---
|
||||
|
||||
# 同步笔记
|
||||
|
||||
在插件设置中配置好你的 Notion 数据库后,你就可以开始将 Obsidian 笔记同步到 Notion 了。
|
||||
|
||||
要同步一篇笔记,只需打开你想要同步的笔记,然后从命令面板(`Ctrl/Cmd + P`)或笔记的右键菜单中选择 “Share to NotionNext” 命令。这会在你的 Notion 数据库中创建一个新页面,内容与你的 Obsidian 笔记完全一致。
|
||||
|
||||
你还可以为特定的笔记或文件夹设置自动同步。这样,你在 Obsidian 中对这些笔记所做的任何更改,都会自动反映到 Notion 中,非常方便。
|
||||
10
docs/zh/05-troubleshooting.md
Normal file
10
docs/zh/05-troubleshooting.md
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
title: 问题排查
|
||||
description: Obsidian to NotionNext 插件的常见问题与解决方案
|
||||
---
|
||||
|
||||
# 问题排查
|
||||
|
||||
如果问题依然存在,你可以在 GitHub 上[提交一个 Issue](https://github.com/jxpeng98/obsidian-to-NotionNext/issues),并附上详细的错误信息和你的操作步骤,我会尽快帮助你。
|
||||
|
||||
你也可以通过 `Ctrl+Shift+I` (Windows/Linux) 或 `Cmd+Option+I` (Mac) 打开 Obsidian 的开发者工具,在控制台(Console)中查看是否有与 NotionNext 插件相关的错误日志,这对于定位问题非常有帮助。
|
||||
6
docs/zh/06-changelog.md
Normal file
6
docs/zh/06-changelog.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
title: 更新日志
|
||||
description: Obsidian to NotionNext 的版本更新与变更记录
|
||||
---
|
||||
|
||||
欢迎来到 Obsidian to NotionNext 的更新日志!这里会记录自 `2.7.0` 版本起的所有更新、改进以及问题修复,方便你快速了解插件的演进情况。
|
||||
8
docs/zh/07-roadmap.md
Normal file
8
docs/zh/07-roadmap.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: 路线图
|
||||
description: Obsidian to NotionNext 的规划功能与未来改进
|
||||
---
|
||||
|
||||
- [ ] 自动同步:支持按时间间隔或内容变更自动同步笔记。
|
||||
- [ ] 附件支持:允许将 Obsidian 笔记中的附件一同上传到 Notion。
|
||||
- [ ] 关联属性:实现将 Obsidian 链接映射到 Notion 的 relation 属性。
|
||||
27
docs/zh/index.md
Normal file
27
docs/zh/index.md
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
title: 介绍
|
||||
description: 使用 Notion API 将文件分享到任何 Notion 数据库
|
||||
order: 0
|
||||
---
|
||||
|
||||
# Obsidian to NotionNext
|
||||
|
||||
在这份文档中,你将找到关于如何设置和使用 Obsidian to NotionNext 插件的指南和说明。这款插件可以让你使用 Notion API 将你的 Obsidian 笔记直接分享到 Notion 数据库。
|
||||
|
||||
本指南将引导你完成设置 Obsidian to NotionNext 插件的全过程。
|
||||
|
||||
[开始使用](./01-getting-started.md)
|
||||
|
||||
[安装插件](./02-installation.md)
|
||||
|
||||
[配置数据库](./03-configuration.md)
|
||||
|
||||
[同步笔记](./04-sync.md)
|
||||
|
||||
[问题排查](./05-troubleshooting.md)
|
||||
|
||||
---
|
||||
|
||||
[更新日志](./06-changelog.md)
|
||||
|
||||
[路线图](./07-roadmap.md)
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "share-to-notionnext",
|
||||
"name": "Share to NotionNext",
|
||||
"version": "2.3.0",
|
||||
"version": "2.7.0",
|
||||
"minAppVersion": "0.0.1",
|
||||
"description": "Shares obsidian md file to notion with notion api for NotionNext web deploy, originally created by EasyChris/obsidian-to-notion.",
|
||||
"author": "EasyChris, jxpeng98",
|
||||
|
||||
13
package.json
13
package.json
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "share-to-notionnext",
|
||||
"version": "2.3.0",
|
||||
"version": "2.7.0",
|
||||
"type": "module",
|
||||
"description": "Shares obsidian md file to notion with notion api for NotionNext web deploy, originally created by EasyChris/obsidian-to-notion.",
|
||||
"description": "Share files to any Notion database using the Notion API, originally created by EasyChris/obsidian-to-notion.",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
@@ -10,7 +10,7 @@
|
||||
"version": "node version-bump.mjs && git add manifest.json versions.json"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"author": "Jiaxin PENG",
|
||||
"license": "GNU GPLv3",
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.5.7",
|
||||
@@ -19,16 +19,15 @@
|
||||
"@typescript-eslint/parser": "^6.16.0",
|
||||
"builtin-modules": "^3.3.0",
|
||||
"esbuild": "0.19.5",
|
||||
"obsidian": "latest",
|
||||
"obsidian": "^1.6.6",
|
||||
"tslib": "2.6.2",
|
||||
"typescript": "5.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tryfabric/martian": "^1.2.4",
|
||||
"@jxpeng98/martian": "^1.3.1",
|
||||
"https-proxy-agent": "^7.0.2",
|
||||
"process": "^0.11.10",
|
||||
"remark-math": "^6.0.0",
|
||||
"yaml": "^2.3.4",
|
||||
"yaml-front-matter": "^4.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2213
pnpm-lock.yaml
generated
Normal file
2213
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -8,10 +8,42 @@ export const I18n: {[key: string]:any} = {
|
||||
ja,
|
||||
};
|
||||
|
||||
export const I18nConfig = (lang: string): any => {
|
||||
return I18n[lang];
|
||||
};
|
||||
class I18nManager {
|
||||
private currentLanguage: string;
|
||||
|
||||
const storedLanguage = window.localStorage.getItem("language");
|
||||
constructor() {
|
||||
this.currentLanguage = this.detectLanguage();
|
||||
}
|
||||
|
||||
export const i18nConfig = I18n[storedLanguage || window.navigator.language.split('-')[0] || "en"];
|
||||
// return the language to use
|
||||
private detectLanguage(): string {
|
||||
const storedLanguage = window.localStorage.getItem("language");
|
||||
if (storedLanguage && this.isLanguageSupported(storedLanguage)) {
|
||||
console.log(`Using stored language: ${storedLanguage}`);
|
||||
return storedLanguage;
|
||||
}
|
||||
|
||||
const browserLanguage = window.navigator.language.split("-")[0];
|
||||
if (this.isLanguageSupported(browserLanguage)) {
|
||||
console.log(`Using browser language: ${browserLanguage}`);
|
||||
return browserLanguage;
|
||||
}
|
||||
|
||||
// Default to English if no match is found
|
||||
console.log("Using default language: en");
|
||||
return "en";
|
||||
}
|
||||
|
||||
private isLanguageSupported(lang: string): boolean {
|
||||
return Object.prototype.hasOwnProperty.call(I18n, lang);
|
||||
}
|
||||
|
||||
// Get the i18n configuration for the current language
|
||||
public getConfig(): any {
|
||||
return I18n[this.currentLanguage];
|
||||
}
|
||||
}
|
||||
|
||||
export const i18nManager = new I18nManager();
|
||||
|
||||
export const i18nConfig = i18nManager.getConfig();
|
||||
|
||||
@@ -27,11 +27,11 @@ export const en = {
|
||||
DatabaseIDText: "Enter your Database ID",
|
||||
BannerUrl: "Banner url (optional)",
|
||||
BannerUrlDesc:
|
||||
"Default is empty, if you want to show a banner, please enter the url (like: https://abc.com/b.png)",
|
||||
"Default is empty, if you want to show a banner, please enter the url (like: https://abc.com/b.png)",
|
||||
BannerUrlText: "Enter your banner url",
|
||||
NotionUser: "Notion ID (username, optional)",
|
||||
NotionUserDesc:
|
||||
"Collect from share link likes:https://username.notion.site. Your notion id is [username]",
|
||||
"Collect from share link likes:https://username.notion.site. Your notion id is [username]",
|
||||
NotionUserText: "Enter your notion ID",
|
||||
NotionLinkDisplay: "Notion Link Display",
|
||||
NotionLinkDisplayDesc: "Default is ON, if you want to hide the link in the front matter, please turn it off",
|
||||
@@ -48,23 +48,23 @@ export const en = {
|
||||
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",
|
||||
NotYetFinish: "Not finished. This function will be available in the next version",
|
||||
PlaceHolder: "Enter database Name",
|
||||
"notion-logo": "Share to NotionNext",
|
||||
"sync-success": "Sync to NotionNext success: \n",
|
||||
"sync-fail": "Sync to NotionNext fail: \n",
|
||||
"sync-preffix": "Sync to ",
|
||||
"sync-success": "success",
|
||||
"sync-fail": "failed",
|
||||
"open-notion": "Please open the file that needs to be synchronized",
|
||||
"config-secrets-notion-api":
|
||||
"Please set up the notion API in the settings tab.",
|
||||
"Please set up the notion API in the settings tab.",
|
||||
"config-secrets-database-id":
|
||||
"Please set up the database id in the settings tab.",
|
||||
"Please set up the database id in the settings tab.",
|
||||
"set-tags-fail":
|
||||
"Set tags fail,please check the frontmatter of the file or close the tag switch in the settings tab.",
|
||||
"Set tags fail,please check the frontmatter of the file or close the tag switch in the settings tab.",
|
||||
NNonMissing:
|
||||
"The 'NNon' property is missing in the settings. Please set it up.",
|
||||
"The 'NNon' property is missing in the settings. Please set it up.",
|
||||
"set-api-id":
|
||||
"Please set up the notion API and database ID in the settings tab.",
|
||||
"Please set up the notion API and database ID in the settings tab.",
|
||||
NotionCustomSettingHeader: "Notion Custom Database Settings",
|
||||
NotionCustomButton: "Notion Customised command switch",
|
||||
NotionCustomButtonDesc: "Open this option, Sync to Notion Customised Database command will be displayed in the command palette",
|
||||
@@ -75,4 +75,9 @@ export const en = {
|
||||
AddCustomProperty: "Add Custom Property",
|
||||
AddNewProperty: "Add New Property",
|
||||
AddNewPropertyDesc: "Add new property match with your notion database",
|
||||
CopyErrorMessage: "Auto copy failed, please copy it manually",
|
||||
BlockUploaded: "All blocks uploaded",
|
||||
ExtraBlockUploaded: "Extra blocks uploaded",
|
||||
CheckConsole: "Check the console for more information \n opt+cmd+i/ctrl+shift+i",
|
||||
"reach-mobile-limit": "The number of blocks exceeds the limit of 100, please use the desktop plugin",
|
||||
}
|
||||
|
||||
@@ -67,4 +67,8 @@ export const ja = {
|
||||
AddCustomProperty: "カスタムプロパティを追加",
|
||||
AddNewProperty: "新しいプロパティを追加",
|
||||
AddNewPropertyDesc: "新しいプロパティを追加してください",
|
||||
CopyErrorMessage: "自動コピーに失敗しました",
|
||||
BlockUploaded: "ブロックがアップロードされました",
|
||||
ExtraBlockUploaded: "追加ブロックがアップロードされました",
|
||||
CheckConsole: "詳細情報を確認するには、コンソールを開いてください \n opt+cmd+i/ctrl+shift+i",
|
||||
};
|
||||
|
||||
@@ -70,4 +70,8 @@ export const zh = {
|
||||
AddCustomProperty: "添加自定义属性",
|
||||
AddNewProperty: "添加新属性",
|
||||
AddNewPropertyDesc: "添加一个和Notion数据库匹配的新属性",
|
||||
CopyErrorMessage: "复制链接失败,请手动复制",
|
||||
BlockUploaded: "所有内容已成功上传",
|
||||
ExtraBlockUploaded: "额外内容已成功上传",
|
||||
CheckConsole: "opt+cmd+i/ctrl+shift+i,\n打开控制台查看更多信息",
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { App, Editor, MarkdownView, Notice, Plugin, PluginSettingTab, Setting } from "obsidian";
|
||||
import { addIcons } from 'src/ui/icon';
|
||||
import { Upload2NotionGeneral } from "src/upload/upload_general/Upload2NotionGeneral";
|
||||
import { Upload2NotionNext } from "src/upload/upload_next/Upload2NotionNext";
|
||||
import { i18nConfig } from "src/lang/I18n";
|
||||
import ribbonCommands from "src/commands/NotionCommands";
|
||||
import { ObsidianSettingTab, PluginSettings, DEFAULT_SETTINGS, DatabaseDetails } from "src/ui/settingTabs";
|
||||
@@ -20,7 +18,7 @@ export default class ObsidianSyncNotionPlugin extends Plugin {
|
||||
|
||||
addIcons();
|
||||
// This creates an icon in the left ribbon.
|
||||
const ribbonIconEl = this.addRibbonIcon(
|
||||
this.addRibbonIcon(
|
||||
"notion-logo",
|
||||
i18nConfig.ribbonIcon,
|
||||
async (evt: MouseEvent) => {
|
||||
@@ -86,4 +84,3 @@ export default class ObsidianSyncNotionPlugin extends Plugin {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -286,7 +286,7 @@ export class EditModal extends SettingModal {
|
||||
|
||||
propertyLine.addDropdown((dropdown) => {
|
||||
const options: Record<string, string> = {
|
||||
'text': 'Text',
|
||||
'rich_text': 'Text',
|
||||
'number': 'Number',
|
||||
'select': 'Select',
|
||||
'multi_select': 'Multi-Select',
|
||||
|
||||
@@ -151,7 +151,7 @@ export class PreviewModal extends Modal {
|
||||
propertyLine.addDropdown((dropdown) => {
|
||||
const options: Record<string, string> = {
|
||||
'title': 'Title',
|
||||
'text': 'Text',
|
||||
'rich_text': 'Text',
|
||||
'number': 'Number',
|
||||
'select': 'Select',
|
||||
'multi_select': 'Multi-Select',
|
||||
|
||||
@@ -239,7 +239,7 @@ export class SettingModal extends Modal {
|
||||
|
||||
propertyLine.addDropdown((dropdown) => {
|
||||
const options: Record<string, string> = {
|
||||
'text': 'Text',
|
||||
'rich_text': 'Text',
|
||||
'number': 'Number',
|
||||
'select': 'Select',
|
||||
'multi_select': 'Multi-Select',
|
||||
|
||||
@@ -12,6 +12,7 @@ export interface PluginSettings {
|
||||
databaseIDNext: string;
|
||||
bannerUrl: string;
|
||||
notionUser: string;
|
||||
NotionLinkDisplay: boolean;
|
||||
proxy: string;
|
||||
GeneralButton: boolean;
|
||||
tagButton: boolean;
|
||||
@@ -47,6 +48,7 @@ export const DEFAULT_SETTINGS: PluginSettings = {
|
||||
databaseIDNext: "",
|
||||
bannerUrl: "",
|
||||
notionUser: "",
|
||||
NotionLinkDisplay: true,
|
||||
proxy: "",
|
||||
GeneralButton: true,
|
||||
tagButton: true,
|
||||
@@ -83,6 +85,9 @@ export class ObsidianSettingTab extends PluginSettingTab {
|
||||
|
||||
this.createSettingEl(containerEl, i18nConfig.NotionUser, i18nConfig.NotionUserDesc, 'text', i18nConfig.NotionUserText, this.plugin.settings.notionUser, 'notionUser')
|
||||
|
||||
this.createSettingEl(containerEl, i18nConfig.NotionLinkDisplay, i18nConfig.NotionLinkDisplayDesc, 'toggle', i18nConfig.NotionLinkDisplay, this.plugin.settings.NotionLinkDisplay, 'NotionLinkDisplay')
|
||||
|
||||
|
||||
// add new button
|
||||
new Setting(containerEl)
|
||||
.setName("Add New Database")
|
||||
|
||||
628
src/upload/Upload2Notion.ts
Normal file
628
src/upload/Upload2Notion.ts
Normal file
@@ -0,0 +1,628 @@
|
||||
import { App, TFile } from "obsidian";
|
||||
import { markdownToBlocks } from "@jxpeng98/martian";
|
||||
import * as yamlFrontMatter from "yaml-front-matter";
|
||||
import { LIMITS, paragraph } from "@jxpeng98/martian";
|
||||
import MyPlugin from "src/main";
|
||||
import { DatabaseDetails } from "../ui/settingTabs";
|
||||
import { updateYamlInfo } from "./updateYaml";
|
||||
import { UploadBase, NotionPageResponse } from "./common/UploadBase";
|
||||
|
||||
export type DatasetType = "general" | "next" | "custom";
|
||||
|
||||
interface BaseSyncRequest {
|
||||
dataset: DatasetType;
|
||||
markdown: string;
|
||||
nowFile: TFile;
|
||||
app: App;
|
||||
}
|
||||
|
||||
interface GeneralSyncRequest extends BaseSyncRequest {
|
||||
dataset: "general";
|
||||
title: string;
|
||||
cover: string;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
interface NextSyncRequest extends BaseSyncRequest {
|
||||
dataset: "next";
|
||||
title: string;
|
||||
emoji: string;
|
||||
cover: string;
|
||||
tags: string[];
|
||||
type: string;
|
||||
slug: string;
|
||||
stats: string;
|
||||
category: string;
|
||||
summary: string;
|
||||
password: string;
|
||||
favicon: string;
|
||||
datetime: string;
|
||||
}
|
||||
|
||||
interface CustomSyncRequest extends BaseSyncRequest {
|
||||
dataset: "custom";
|
||||
cover: string;
|
||||
customValues: Record<string, string>;
|
||||
}
|
||||
|
||||
export type SyncRequest =
|
||||
| GeneralSyncRequest
|
||||
| NextSyncRequest
|
||||
| CustomSyncRequest;
|
||||
|
||||
interface GeneralParams {
|
||||
title: string;
|
||||
cover?: string;
|
||||
tags: string[];
|
||||
childArr: any[];
|
||||
notionId?: string;
|
||||
}
|
||||
|
||||
interface NextParams {
|
||||
title: string;
|
||||
emoji: string;
|
||||
cover?: string;
|
||||
tags: string[];
|
||||
type: string;
|
||||
slug: string;
|
||||
stats: string;
|
||||
category: string;
|
||||
summary: string;
|
||||
password: string;
|
||||
favicon: string;
|
||||
datetime: string;
|
||||
childArr: any[];
|
||||
notionId?: string;
|
||||
}
|
||||
|
||||
interface CustomParams {
|
||||
cover?: string;
|
||||
customValues: Record<string, string>;
|
||||
childArr: any[];
|
||||
notionId?: string;
|
||||
}
|
||||
|
||||
export class Upload2Notion extends UploadBase {
|
||||
|
||||
constructor(plugin: MyPlugin, dbDetails: DatabaseDetails) {
|
||||
super(plugin, dbDetails);
|
||||
}
|
||||
|
||||
async sync(request: SyncRequest): Promise<NotionPageResponse> {
|
||||
const startedAt = Date.now();
|
||||
|
||||
let response: NotionPageResponse;
|
||||
|
||||
switch (request.dataset) {
|
||||
case "general":
|
||||
response = await this.handleGeneral(request);
|
||||
break;
|
||||
case "next":
|
||||
response = await this.handleNext(request);
|
||||
break;
|
||||
case "custom":
|
||||
response = await this.handleCustom(request);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported dataset type: ${(request as any).dataset}`);
|
||||
}
|
||||
|
||||
if (response.response && response.response.status === 200) {
|
||||
await updateYamlInfo(
|
||||
request.markdown,
|
||||
request.nowFile,
|
||||
response.data,
|
||||
request.app,
|
||||
this.plugin,
|
||||
this.dbDetails,
|
||||
);
|
||||
} else {
|
||||
console.log(`[Upload2Notion] Sync failed`, response.data);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private async handleGeneral(request: GeneralSyncRequest): Promise<NotionPageResponse> {
|
||||
console.log(`[Upload2Notion] Handling general dataset`, {
|
||||
cover: request.cover,
|
||||
tags: request.tags,
|
||||
});
|
||||
const blocks = this.buildBlocks(request.markdown, {
|
||||
notionLimits: {truncate: false},
|
||||
});
|
||||
const notionId = this.getNotionId(request.app, request.nowFile);
|
||||
this.debugLog("Upload2Notion", "General dataset payload prepared", {
|
||||
blockCount: blocks.length,
|
||||
notionId: notionId ?? null,
|
||||
tagCount: request.tags?.length ?? 0,
|
||||
});
|
||||
|
||||
return this.upsertGeneral({
|
||||
title: request.title,
|
||||
cover: request.cover,
|
||||
tags: request.tags || [],
|
||||
childArr: blocks,
|
||||
notionId,
|
||||
});
|
||||
}
|
||||
|
||||
private async handleNext(request: NextSyncRequest): Promise<NotionPageResponse> {
|
||||
console.log(`[Upload2Notion] Handling next dataset`, {
|
||||
type: request.type,
|
||||
slug: request.slug,
|
||||
category: request.category,
|
||||
});
|
||||
const blocks = this.buildBlocks(request.markdown, {
|
||||
notionLimits: {truncate: false},
|
||||
});
|
||||
this.splitRichTextParagraphs(blocks);
|
||||
const notionId = this.getNotionId(request.app, request.nowFile);
|
||||
this.debugLog("Upload2Notion", "Next dataset payload prepared", {
|
||||
blockCount: blocks.length,
|
||||
notionId: notionId ?? null,
|
||||
hasEmoji: !!request.emoji,
|
||||
tags: request.tags || [],
|
||||
});
|
||||
|
||||
return this.upsertNext({
|
||||
title: request.title,
|
||||
emoji: request.emoji,
|
||||
cover: request.cover,
|
||||
tags: request.tags || [],
|
||||
type: request.type,
|
||||
slug: request.slug,
|
||||
stats: request.stats,
|
||||
category: request.category,
|
||||
summary: request.summary,
|
||||
password: request.password,
|
||||
favicon: request.favicon,
|
||||
datetime: request.datetime,
|
||||
childArr: blocks,
|
||||
notionId,
|
||||
});
|
||||
}
|
||||
|
||||
private async handleCustom(request: CustomSyncRequest): Promise<NotionPageResponse> {
|
||||
console.log(`[Upload2Notion] Handling custom dataset`, {
|
||||
customKeys: Object.keys(request.customValues || {}),
|
||||
});
|
||||
const blocks = this.buildBlocks(request.markdown, {
|
||||
strictImageUrls: true,
|
||||
notionLimits: {truncate: false},
|
||||
});
|
||||
const notionId = this.getNotionId(request.app, request.nowFile);
|
||||
this.debugLog("Upload2Notion", "Custom dataset payload prepared", {
|
||||
blockCount: blocks.length,
|
||||
notionId: notionId ?? null,
|
||||
customValueKeys: Object.keys(request.customValues || {}),
|
||||
});
|
||||
|
||||
return this.upsertCustom({
|
||||
cover: request.cover,
|
||||
customValues: request.customValues,
|
||||
childArr: blocks,
|
||||
notionId,
|
||||
});
|
||||
}
|
||||
|
||||
private buildBlocks(markdown: string, options: Record<string, unknown>): any[] {
|
||||
const yamlContent: any = yamlFrontMatter.loadFront(markdown);
|
||||
const content = yamlContent.__content;
|
||||
const blocks = markdownToBlocks(content, options);
|
||||
this.debugLog("Upload2Notion", "Converted markdown to blocks", {
|
||||
blockCount: blocks.length,
|
||||
firstBlockTypes: blocks.slice(0, 5).map((block: any) => block?.type),
|
||||
options,
|
||||
});
|
||||
return blocks;
|
||||
}
|
||||
|
||||
private getNotionId(app: App, nowFile: TFile): string | undefined {
|
||||
const frontMatter = app.metadataCache.getFileCache(nowFile)?.frontmatter;
|
||||
if (!frontMatter) {
|
||||
this.debugLog("Upload2Notion", "No frontmatter found when resolving Notion ID", {
|
||||
filePath: nowFile.path,
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
const {abName} = this.dbDetails;
|
||||
const notionIDKey = `NotionID-${abName}`;
|
||||
const notionId = frontMatter[notionIDKey];
|
||||
return notionId ? String(notionId) : undefined;
|
||||
}
|
||||
|
||||
private splitRichTextParagraphs(blocks: any[]): void {
|
||||
blocks.forEach((block, index) => {
|
||||
if (
|
||||
block.type === "paragraph" &&
|
||||
block.paragraph.rich_text.length > LIMITS.RICH_TEXT_ARRAYS
|
||||
) {
|
||||
console.log(`[Upload2Notion] Splitting rich text paragraph`, {
|
||||
index,
|
||||
length: block.paragraph.rich_text.length,
|
||||
});
|
||||
const paragraphChunks = this.chunkArray<any>(block.paragraph.rich_text, 100);
|
||||
const newParagraphBlocks = paragraphChunks.map((chunk) => paragraph(chunk));
|
||||
blocks.splice(index, 1, ...newParagraphBlocks);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private chunkArray<T>(items: T[], size: number): T[][] {
|
||||
const result: T[][] = [];
|
||||
for (let i = 0; i < items.length; i += size) {
|
||||
result.push(items.slice(i, i + size));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private async upsertGeneral(params: GeneralParams): Promise<NotionPageResponse> {
|
||||
const {firstChunk, extraChunks} = this.prepareBlocks(params.childArr);
|
||||
const cover = params.notionId
|
||||
? await this.resolveCoverForUpdate(params.cover)
|
||||
: params.cover;
|
||||
this.debugLog("Upload2Notion", "Upserting general page", {
|
||||
title: params.title,
|
||||
blockCount: params.childArr.length,
|
||||
firstChunkSize: firstChunk.length,
|
||||
extraChunkCount: extraChunks.length,
|
||||
cover: cover ?? null,
|
||||
notionIdExisting: params.notionId ?? null,
|
||||
tagList: params.tags,
|
||||
});
|
||||
|
||||
if (params.notionId) {
|
||||
console.log(`[Upload2Notion] Deleting existing Notion page`, {
|
||||
notionId: params.notionId,
|
||||
});
|
||||
await this.deletePage(params.notionId);
|
||||
}
|
||||
|
||||
const body = this.buildGeneralBody({
|
||||
title: params.title,
|
||||
tags: params.tags,
|
||||
firstChunk,
|
||||
});
|
||||
this.applyCover(body, cover);
|
||||
console.log(body);
|
||||
return this.submitPage(body, extraChunks);
|
||||
}
|
||||
|
||||
private async upsertNext(params: NextParams): Promise<NotionPageResponse> {
|
||||
const {firstChunk, extraChunks} = this.prepareBlocks(params.childArr);
|
||||
const cover = params.notionId
|
||||
? await this.resolveCoverForUpdate(params.cover)
|
||||
: params.cover;
|
||||
this.debugLog("Upload2Notion", "Upserting NotionNext page", {
|
||||
title: params.title,
|
||||
type: params.type,
|
||||
slug: params.slug,
|
||||
blockCount: params.childArr.length,
|
||||
firstChunkSize: firstChunk.length,
|
||||
extraChunkCount: extraChunks.length,
|
||||
cover: cover ?? null,
|
||||
hasEmoji: !!params.emoji,
|
||||
notionIdExisting: params.notionId ?? null,
|
||||
});
|
||||
|
||||
if (params.notionId) {
|
||||
await this.deletePage(params.notionId);
|
||||
}
|
||||
|
||||
const body = this.buildNextBody({
|
||||
firstChunk,
|
||||
title: params.title,
|
||||
emoji: params.emoji,
|
||||
tags: params.tags,
|
||||
type: params.type,
|
||||
slug: params.slug,
|
||||
stats: params.stats,
|
||||
category: params.category,
|
||||
summary: params.summary,
|
||||
password: params.password,
|
||||
favicon: params.favicon,
|
||||
datetime: params.datetime,
|
||||
});
|
||||
this.applyCover(body, cover);
|
||||
console.log(body);
|
||||
return this.submitPage(body, extraChunks);
|
||||
}
|
||||
|
||||
private async upsertCustom(params: CustomParams): Promise<NotionPageResponse> {
|
||||
const {firstChunk, extraChunks} = this.prepareBlocks(params.childArr);
|
||||
const cover = params.notionId
|
||||
? await this.resolveCoverForUpdate(params.cover)
|
||||
: params.cover;
|
||||
this.debugLog("Upload2Notion", "Upserting custom page", {
|
||||
blockCount: params.childArr.length,
|
||||
firstChunkSize: firstChunk.length,
|
||||
extraChunkCount: extraChunks.length,
|
||||
cover: cover ?? null,
|
||||
notionIdExisting: params.notionId ?? null,
|
||||
customValueKeys: Object.keys(params.customValues || {}),
|
||||
});
|
||||
|
||||
if (params.notionId) {
|
||||
await this.deletePage(params.notionId);
|
||||
}
|
||||
|
||||
const body = this.buildCustomBody({
|
||||
customValues: params.customValues,
|
||||
firstChunk,
|
||||
});
|
||||
this.applyCover(body, cover);
|
||||
console.log(body);
|
||||
return this.submitPage(body, extraChunks);
|
||||
}
|
||||
|
||||
private buildGeneralBody(params: {title: string; tags: string[]; firstChunk: any[]}): any {
|
||||
const {
|
||||
databaseID,
|
||||
customTitleButton,
|
||||
customTitleName,
|
||||
tagButton,
|
||||
} = this.dbDetails;
|
||||
|
||||
return {
|
||||
parent: {
|
||||
database_id: databaseID,
|
||||
},
|
||||
properties: {
|
||||
[customTitleButton ? customTitleName : "title"]: {
|
||||
title: [
|
||||
{
|
||||
text: {
|
||||
content: params.title,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
...(tagButton
|
||||
? {
|
||||
tags: {
|
||||
multi_select:
|
||||
params.tags && params.tags.length > 0
|
||||
? params.tags.map((tag) => ({name: tag}))
|
||||
: [],
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
children: params.firstChunk,
|
||||
};
|
||||
}
|
||||
|
||||
private buildNextBody(params: {
|
||||
firstChunk: any[];
|
||||
title: string;
|
||||
emoji: string;
|
||||
tags: string[];
|
||||
type: string;
|
||||
slug: string;
|
||||
stats: string;
|
||||
category: string;
|
||||
summary: string;
|
||||
password: string;
|
||||
favicon: string;
|
||||
datetime: string;
|
||||
}): any {
|
||||
const {databaseID} = this.dbDetails;
|
||||
|
||||
const pageProperties: any = {
|
||||
parent: {
|
||||
database_id: databaseID,
|
||||
},
|
||||
properties: {
|
||||
title: {
|
||||
title: [
|
||||
{
|
||||
text: {
|
||||
content: params.title,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
type: {
|
||||
select: {
|
||||
name: params.type || "Post",
|
||||
},
|
||||
},
|
||||
status: {
|
||||
select: {
|
||||
name: params.stats || "Draft",
|
||||
},
|
||||
},
|
||||
category: {
|
||||
select: {
|
||||
name: params.category || "Obsidian",
|
||||
},
|
||||
},
|
||||
password: {
|
||||
rich_text: [
|
||||
{
|
||||
text: {
|
||||
content: params.password || "",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
icon: {
|
||||
rich_text: [
|
||||
{
|
||||
text: {
|
||||
content: params.favicon || "",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
date: {
|
||||
date: {
|
||||
start: params.datetime || new Date().toISOString(),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (params.tags && params.tags.length > 0) {
|
||||
pageProperties.properties.tags = {
|
||||
multi_select: params.tags.map((tag) => ({name: tag})),
|
||||
};
|
||||
}
|
||||
|
||||
if (params.emoji) {
|
||||
pageProperties.icon = {
|
||||
emoji: params.emoji,
|
||||
};
|
||||
}
|
||||
|
||||
if (params.slug) {
|
||||
pageProperties.properties.slug = {
|
||||
rich_text: [
|
||||
{
|
||||
text: {
|
||||
content: params.slug,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
if (params.summary) {
|
||||
pageProperties.properties.summary = {
|
||||
rich_text: [
|
||||
{
|
||||
text: {
|
||||
content: params.summary,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...pageProperties,
|
||||
children: params.firstChunk,
|
||||
};
|
||||
}
|
||||
|
||||
private buildCustomBody(params: {
|
||||
customValues: Record<string, string>;
|
||||
firstChunk: any[];
|
||||
}): any {
|
||||
const {customProperties, databaseID} = this.dbDetails;
|
||||
const properties: { [key: string]: any } = {};
|
||||
|
||||
if (customProperties) {
|
||||
customProperties.forEach(({customName, customType}) => {
|
||||
if (params.customValues[customName] !== undefined) {
|
||||
const propertyValue = this.buildCustomProperty(customName, customType, params.customValues);
|
||||
if (propertyValue !== undefined) {
|
||||
properties[customName] = propertyValue;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
parent: {
|
||||
database_id: databaseID,
|
||||
},
|
||||
properties,
|
||||
children: params.firstChunk,
|
||||
};
|
||||
}
|
||||
|
||||
private buildCustomProperty(customName: string, customType: string, customValues: Record<string, any>) {
|
||||
const value = customValues[customName] || "";
|
||||
|
||||
switch (customType) {
|
||||
case "title":
|
||||
return {
|
||||
title: [
|
||||
{
|
||||
text: {
|
||||
content: value,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
case "rich_text":
|
||||
return {
|
||||
rich_text: [
|
||||
{
|
||||
text: {
|
||||
content: value || "",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
case "date":
|
||||
return {
|
||||
date: {
|
||||
start: value || new Date().toISOString(),
|
||||
},
|
||||
};
|
||||
case "number":
|
||||
return {
|
||||
number: Number(value),
|
||||
};
|
||||
case "phone_number":
|
||||
return {
|
||||
phone_number: value,
|
||||
};
|
||||
case "email":
|
||||
return {
|
||||
email: value,
|
||||
};
|
||||
case "url":
|
||||
return {
|
||||
url: value,
|
||||
};
|
||||
case "files":
|
||||
return {
|
||||
files: Array.isArray(value)
|
||||
? value.map((url: string) => ({
|
||||
name: url,
|
||||
type: "external",
|
||||
external: {
|
||||
url,
|
||||
},
|
||||
}))
|
||||
: [
|
||||
{
|
||||
name: value,
|
||||
type: "external",
|
||||
external: {
|
||||
url: value,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
case "checkbox":
|
||||
return {
|
||||
checkbox: Boolean(value) || false,
|
||||
};
|
||||
case "select":
|
||||
return {
|
||||
select: {
|
||||
name: value,
|
||||
},
|
||||
};
|
||||
case "multi_select":
|
||||
return {
|
||||
multi_select: Array.isArray(value)
|
||||
? value.map((item: string) => ({name: item}))
|
||||
: [{name: value}],
|
||||
};
|
||||
case "relation":
|
||||
return {
|
||||
relation: Array.isArray(value)
|
||||
? value.map((item: string) => ({id: item}))
|
||||
: [{id: value}],
|
||||
};
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
248
src/upload/common/UploadBase.ts
Normal file
248
src/upload/common/UploadBase.ts
Normal file
@@ -0,0 +1,248 @@
|
||||
import { Notice, requestUrl } from "obsidian";
|
||||
import MyPlugin from "src/main";
|
||||
import { DatabaseDetails } from "../../ui/settingTabs";
|
||||
import { i18nConfig } from "../../lang/I18n";
|
||||
|
||||
export interface NotionPageResponse {
|
||||
response: any;
|
||||
data: any;
|
||||
}
|
||||
|
||||
interface PreparedBlocks {
|
||||
firstChunk: any[];
|
||||
extraChunks: any[][];
|
||||
}
|
||||
|
||||
export abstract class UploadBase {
|
||||
protected plugin: MyPlugin;
|
||||
protected dbDetails: DatabaseDetails;
|
||||
|
||||
protected constructor(plugin: MyPlugin, dbDetails: DatabaseDetails) {
|
||||
this.plugin = plugin;
|
||||
this.dbDetails = dbDetails;
|
||||
}
|
||||
|
||||
async deletePage(notionID: string) {
|
||||
const {notionAPI} = this.dbDetails;
|
||||
return requestUrl({
|
||||
url: `https://api.notion.com/v1/blocks/${notionID}`,
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + notionAPI,
|
||||
"Notion-Version": "2022-06-28",
|
||||
},
|
||||
body: "",
|
||||
throw: false,
|
||||
}).catch((error) =>
|
||||
this.handleRequestError(error, `Deleting Notion page ${notionID}`),
|
||||
);
|
||||
}
|
||||
|
||||
protected prepareBlocks(childArr: any[]): PreparedBlocks {
|
||||
this.stripCodeAnnotations(childArr);
|
||||
|
||||
const childArrLength = childArr.length;
|
||||
|
||||
if (childArrLength <= 100) {
|
||||
this.debugLog("UploadBase", "Blocks fit into a single request", {
|
||||
totalBlocks: childArrLength,
|
||||
});
|
||||
return {
|
||||
firstChunk: childArr,
|
||||
extraChunks: [],
|
||||
};
|
||||
}
|
||||
|
||||
const extraChunks: any[][] = [];
|
||||
for (let i = 100; i < childArr.length; i += 100) {
|
||||
extraChunks.push(childArr.slice(i, i + 100));
|
||||
}
|
||||
|
||||
this.debugLog("UploadBase", "Blocks split into multiple chunks", {
|
||||
totalBlocks: childArrLength,
|
||||
firstChunkSize: 100,
|
||||
extraChunkCount: extraChunks.length,
|
||||
lastChunkSize: extraChunks[extraChunks.length - 1].length,
|
||||
});
|
||||
|
||||
return {
|
||||
firstChunk: childArr.slice(0, 100),
|
||||
extraChunks,
|
||||
};
|
||||
}
|
||||
|
||||
protected applyCover(body: any, cover?: string) {
|
||||
if (cover) {
|
||||
body.cover = {
|
||||
type: "external",
|
||||
external: {
|
||||
url: cover,
|
||||
},
|
||||
};
|
||||
} else if (!body.cover && this.plugin.settings.bannerUrl) {
|
||||
body.cover = {
|
||||
type: "external",
|
||||
external: {
|
||||
url: this.plugin.settings.bannerUrl,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
this.debugLog("UploadBase", "Cover applied to payload", {
|
||||
chosenCover: body.cover?.external?.url ?? null,
|
||||
defaultBannerUsed: !cover && !!this.plugin.settings.bannerUrl,
|
||||
});
|
||||
}
|
||||
|
||||
protected async resolveCoverForUpdate(cover?: string): Promise<string | undefined> {
|
||||
if (cover) {
|
||||
this.debugLog("UploadBase", "Existing cover retained for update", {
|
||||
cover,
|
||||
});
|
||||
return cover;
|
||||
}
|
||||
const databaseCover = await this.fetchDatabaseCover();
|
||||
return databaseCover ?? undefined;
|
||||
}
|
||||
|
||||
protected async submitPage(body: any, extraChunks: any[][]): Promise<NotionPageResponse> {
|
||||
const {notionAPI} = this.dbDetails;
|
||||
const startedAt = Date.now();
|
||||
|
||||
const response = await requestUrl({
|
||||
url: `https://api.notion.com/v1/pages`,
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + notionAPI,
|
||||
"Notion-Version": "2022-06-28",
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
throw: false,
|
||||
}).catch((error) =>
|
||||
this.handleRequestError(error, "Creating Notion page"),
|
||||
);
|
||||
|
||||
const data = await response.json;
|
||||
this.debugLog("UploadBase", "Page creation response received", {
|
||||
status: response.status,
|
||||
durationMs: Date.now() - startedAt,
|
||||
notionUrl: data?.url ?? null,
|
||||
pageId: data?.id ?? null,
|
||||
});
|
||||
|
||||
if (response.status !== 200) {
|
||||
new Notice(`Error ${data.status}: ${data.code} \n ${i18nConfig["CheckConsole"]}`, 5000);
|
||||
console.log(`Error message: \n ${data.message}`);
|
||||
} else {
|
||||
if (extraChunks.length > 0) {
|
||||
await this.appendExtraBlocks(data.id, extraChunks);
|
||||
}
|
||||
}
|
||||
|
||||
return {response, data};
|
||||
}
|
||||
|
||||
private stripCodeAnnotations(childArr: any[]) {
|
||||
childArr.forEach((block: any) => {
|
||||
if (block.type === "code") {
|
||||
block.code.rich_text.forEach((item: any) => {
|
||||
if (item.type === "text" && item.annotations) {
|
||||
delete item.annotations;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async appendExtraBlocks(pageId: string, extraChunks: any[][]) {
|
||||
const {notionAPI} = this.dbDetails;
|
||||
|
||||
for (let i = 0; i < extraChunks.length; i++) {
|
||||
const chunk = extraChunks[i];
|
||||
const extraBlocks = {
|
||||
children: chunk,
|
||||
};
|
||||
|
||||
const extraResponse = await requestUrl({
|
||||
url: `https://api.notion.com/v1/blocks/${pageId}/children`,
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + notionAPI,
|
||||
"Notion-Version": "2022-06-28",
|
||||
},
|
||||
body: JSON.stringify(extraBlocks),
|
||||
throw: false,
|
||||
}).catch((error) =>
|
||||
this.handleRequestError(error, `Appending blocks to page ${pageId}`),
|
||||
);
|
||||
|
||||
const extraData: any = await extraResponse.json;
|
||||
|
||||
if (extraResponse.status !== 200) {
|
||||
new Notice(`Error ${extraData.status}: ${extraData.code} \n ${i18nConfig["CheckConsole"]}`, 5000);
|
||||
console.log(`Error message: \n ${extraData.message}`);
|
||||
} else {
|
||||
console.log(`${i18nConfig["ExtraBlockUploaded"]} to page: ${pageId}`);
|
||||
if (i === extraChunks.length - 1) {
|
||||
console.log(`${i18nConfig["BlockUploaded"]} to page: ${pageId}`);
|
||||
new Notice(`${i18nConfig["BlockUploaded"]} page: ${pageId}`, 5000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async fetchDatabaseCover(): Promise<string | null> {
|
||||
const {notionAPI, databaseID} = this.dbDetails;
|
||||
const response = await requestUrl({
|
||||
url: `https://api.notion.com/v1/databases/${databaseID}`,
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: "Bearer " + notionAPI,
|
||||
"Notion-Version": "2022-06-28",
|
||||
},
|
||||
throw: false,
|
||||
}).catch((error) =>
|
||||
this.handleRequestError(error, `Fetching database ${databaseID}`),
|
||||
);
|
||||
|
||||
if (response.json.cover && response.json.cover.external) {
|
||||
return response.json.cover.external.url;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected debugLog(context: string, message: string, payload?: Record<string, unknown>): void {
|
||||
if (payload) {
|
||||
console.log(`[${context}] ${message}`, payload);
|
||||
} else {
|
||||
console.log(`[${context}] ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
protected maskValue(value?: string, visibleChars = 4): string | undefined {
|
||||
if (!value) {
|
||||
return value;
|
||||
}
|
||||
if (value.length <= visibleChars * 2) {
|
||||
return `${value.slice(0, visibleChars)}***`;
|
||||
}
|
||||
return `${value.slice(0, visibleChars)}***${value.slice(-visibleChars)}`;
|
||||
}
|
||||
|
||||
private handleRequestError(error: unknown, context: string): never {
|
||||
const message = error instanceof Error && error.message
|
||||
? error.message
|
||||
: String(error);
|
||||
console.error(`[UploadBase] ${context} failed`, {
|
||||
message,
|
||||
error,
|
||||
});
|
||||
throw new Error(
|
||||
`${context} failed: ${message}. Please check your network connection or proxy settings.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { App, Notice } from "obsidian";
|
||||
import { i18nConfig } from "../../lang/I18n";
|
||||
import { DatabaseDetails } from "../../ui/settingTabs";
|
||||
import {App, Notice} from "obsidian";
|
||||
import {i18nConfig} from "../../lang/I18n";
|
||||
import {DatabaseDetails} from "../../ui/settingTabs";
|
||||
|
||||
export async function getNowFileMarkdownContentCustom(
|
||||
app: App,
|
||||
@@ -24,6 +24,7 @@ export async function getNowFileMarkdownContentCustom(
|
||||
.map(property => property.customName);
|
||||
|
||||
// Extract custom values from the front matter based on the names
|
||||
// Only collect data 'Relation' should be handled separately in the function buildBodyString
|
||||
customPropertyNames.forEach(propertyName => {
|
||||
if (FileCache.frontmatter && FileCache.frontmatter[propertyName] !== undefined) {
|
||||
customValues[propertyName] = FileCache.frontmatter[propertyName];
|
||||
@@ -31,11 +32,15 @@ export async function getNowFileMarkdownContentCustom(
|
||||
});
|
||||
|
||||
// Check if any of the customProperties has a customType of 'title'
|
||||
const titleProperty = dbDetails.customProperties.find(property => property.customType === 'title');
|
||||
const titleProperty = dbDetails.customProperties
|
||||
.find(property => property.customType === 'title');
|
||||
|
||||
// If a 'title' type property exists, use the file's basename as its value
|
||||
if (titleProperty) {
|
||||
customValues[titleProperty.customName] = nowFile.basename; // Use 'basename' for the file name without extension
|
||||
customValues[titleProperty.customName] =
|
||||
(FileCache.frontmatter && FileCache.frontmatter[titleProperty.customName]) ?
|
||||
FileCache.frontmatter[titleProperty.customName] : // use the front matter value if it exists
|
||||
nowFile.basename; // Use 'basename' for the file name without extension
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
@@ -1,6 +1,7 @@
|
||||
import { App, Notice, TFile } from "obsidian";
|
||||
import ObsidianSyncNotionPlugin from "../main";
|
||||
import { DatabaseDetails } from "../ui/settingTabs";
|
||||
import { i18nConfig } from "src/lang/I18n";
|
||||
|
||||
export async function updateYamlInfo(
|
||||
yamlContent: string,
|
||||
@@ -10,9 +11,9 @@ export async function updateYamlInfo(
|
||||
plugin: ObsidianSyncNotionPlugin,
|
||||
dbDetails: DatabaseDetails,
|
||||
) {
|
||||
let { url, id } = res.json
|
||||
let { url, id } = res;
|
||||
// replace www to notionID
|
||||
const { notionUser } = plugin.settings;
|
||||
const { notionUser, NotionLinkDisplay } = plugin.settings;
|
||||
const { abName } = dbDetails
|
||||
const notionIDKey = `NotionID-${abName}`;
|
||||
const linkKey = `link-${abName}`;
|
||||
@@ -31,25 +32,13 @@ export async function updateYamlInfo(
|
||||
}
|
||||
// add new notionID and link
|
||||
yamlContent[notionIDKey] = id;
|
||||
yamlContent[linkKey] = url;
|
||||
(NotionLinkDisplay) ? yamlContent[linkKey] = url : null;
|
||||
});
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(url)
|
||||
} catch (error) {
|
||||
new Notice(`复制链接失败,请手动复制${error}`)
|
||||
console.log(error)
|
||||
new Notice(`${i18nConfig.CopyErrorMessage}`);
|
||||
}
|
||||
// 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}`)
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -1,13 +1,38 @@
|
||||
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 { DatabaseDetails, PluginSettings } from "../ui/settingTabs";
|
||||
import {i18nConfig} from "../lang/I18n";
|
||||
import {App, Notice} from "obsidian";
|
||||
import {Upload2Notion} from "./Upload2Notion";
|
||||
import type {NotionPageResponse} from "./common/UploadBase";
|
||||
import {DatabaseDetails, 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";
|
||||
import {getNowFileMarkdownContentNext} from "./common/getMarkdownNext";
|
||||
import {getNowFileMarkdownContentGeneral} from "./common/getMarkdownGeneral";
|
||||
import {getNowFileMarkdownContentCustom} from "./common/getMarkdownCustom";
|
||||
|
||||
const SYNC_ERROR_NOTICE_DURATION = 8000;
|
||||
|
||||
function extractErrorMessage(error: unknown): string {
|
||||
if (error instanceof Error && error.message) {
|
||||
return error.message;
|
||||
}
|
||||
return String(error);
|
||||
}
|
||||
|
||||
function notifySyncError(prefix: string, basename: string, error: unknown): void {
|
||||
const errorMessage = extractErrorMessage(error);
|
||||
console.error(`${prefix} Sync failed`, error);
|
||||
new Notice(
|
||||
`${i18nConfig["sync-fail"]} ${basename}. ${errorMessage}\n${i18nConfig["CheckConsole"]}`,
|
||||
SYNC_ERROR_NOTICE_DURATION,
|
||||
);
|
||||
}
|
||||
|
||||
function logCommandDebug(context: string, message: string, payload?: Record<string, unknown>): void {
|
||||
if (payload) {
|
||||
console.log(`[${context}] ${message}`, payload);
|
||||
} else {
|
||||
console.log(`[${context}] ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function uploadCommandNext(
|
||||
plugin: ObsidianSyncNotionPlugin,
|
||||
@@ -16,14 +41,8 @@ export async function uploadCommandNext(
|
||||
app: App,
|
||||
) {
|
||||
|
||||
const { notionAPI, databaseID } = dbDetails;
|
||||
|
||||
// Check if NNon exists
|
||||
// if (NNon === undefined) {
|
||||
// const NNonmessage = i18nConfig.NNonMissing;
|
||||
// new Notice(NNonmessage);
|
||||
// return;
|
||||
// }
|
||||
const {notionAPI, databaseID} = dbDetails;
|
||||
console.log(`[uploadCommandNext] ${new Date().toISOString()} Triggered for file`, app.workspace.getActiveFile()?.path);
|
||||
|
||||
// Check if the user has set up the Notion API and database ID
|
||||
if (notionAPI === "" || databaseID === "") {
|
||||
@@ -46,17 +65,76 @@ export async function uploadCommandNext(
|
||||
paword,
|
||||
favicon,
|
||||
datetime
|
||||
} = await getNowFileMarkdownContentNext(app, settings)
|
||||
} = await getNowFileMarkdownContentNext(app, settings);
|
||||
|
||||
logCommandDebug("uploadCommandNext", "Metadata extracted from markdown", {
|
||||
hasMarkdown: !!markDownData,
|
||||
markdownLength: markDownData?.length ?? 0,
|
||||
tagCount: tags?.length ?? 0,
|
||||
coverIncluded: !!cover,
|
||||
hasEmoji: !!emoji,
|
||||
type,
|
||||
slug,
|
||||
hasPassword: !!paword,
|
||||
datetime,
|
||||
});
|
||||
|
||||
if (markDownData) {
|
||||
const { basename } = nowFile;
|
||||
const upload = new Upload2NotionNext(plugin, dbDetails);
|
||||
const res = await upload.syncMarkdownToNotionNext(basename, emoji, cover, tags, type, slug, stats, category, summary, paword, favicon, datetime, markDownData, nowFile, this.app);
|
||||
const {basename} = nowFile;
|
||||
logCommandDebug("uploadCommandNext", "Preparing to upload", {
|
||||
filename: basename,
|
||||
filePath: nowFile.path,
|
||||
});
|
||||
|
||||
if (res.status === 200) {
|
||||
new Notice(`${i18nConfig["sync-success"]}${basename}`);
|
||||
const upload = new Upload2Notion(plugin, dbDetails);
|
||||
let res: NotionPageResponse;
|
||||
try {
|
||||
res = await upload.sync({
|
||||
dataset: "next",
|
||||
title: basename,
|
||||
emoji: emoji || "",
|
||||
cover: cover || "",
|
||||
tags: tags || [],
|
||||
type: type || "",
|
||||
slug: slug || "",
|
||||
stats: stats || "",
|
||||
category: category || "",
|
||||
summary: summary || "",
|
||||
password: paword || "",
|
||||
favicon: favicon || "",
|
||||
datetime: datetime || "",
|
||||
markdown: markDownData,
|
||||
nowFile,
|
||||
app,
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
notifySyncError("[uploadCommandNext]", basename, error);
|
||||
logCommandDebug("uploadCommandNext", "Sync threw error", {
|
||||
filename: basename,
|
||||
error: extractErrorMessage(error),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const {response} = res;
|
||||
if (response.status === 200) {
|
||||
new Notice(`${i18nConfig["sync-preffix"]} ${basename} ${i18nConfig["sync-success"]}`).noticeEl.style.color = "green";
|
||||
|
||||
logCommandDebug("uploadCommandNext", "Sync succeeded", {
|
||||
filename: basename,
|
||||
status: response.status,
|
||||
pageId: res.data?.id ?? null,
|
||||
pageUrl: res.data?.url ?? null,
|
||||
});
|
||||
} else {
|
||||
new Notice(`${i18nConfig["sync-fail"]}${basename}`, 5000);
|
||||
new Notice(`${i18nConfig["sync-fail"]} ${basename}`, 5000);
|
||||
console.log(`${i18nConfig["sync-fail"]} ${basename}`);
|
||||
logCommandDebug("uploadCommandNext", "Sync failed with status", {
|
||||
filename: basename,
|
||||
status: response.status,
|
||||
errorCode: res.data?.code ?? null,
|
||||
errorStatus: res.data?.status ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -70,7 +148,8 @@ export async function uploadCommandGeneral(
|
||||
app: App,
|
||||
) {
|
||||
|
||||
const { notionAPI, databaseID } = dbDetails;
|
||||
const {notionAPI, databaseID} = dbDetails;
|
||||
console.log(`[uploadCommandGeneral] ${new Date().toISOString()} Triggered for file`, app.workspace.getActiveFile()?.path);
|
||||
|
||||
// Check if the user has set up the Notion API and database ID
|
||||
if (notionAPI === "" || databaseID === "") {
|
||||
@@ -79,18 +158,60 @@ export async function uploadCommandGeneral(
|
||||
return;
|
||||
}
|
||||
|
||||
const { markDownData, nowFile, cover, tags } = await getNowFileMarkdownContentGeneral(app, settings)
|
||||
const {markDownData, nowFile, cover, tags} = await getNowFileMarkdownContentGeneral(app, settings)
|
||||
|
||||
new Notice(`Start upload ${nowFile.basename}`);
|
||||
console.log(`Start upload ${nowFile.basename}`);
|
||||
|
||||
if (markDownData) {
|
||||
const { basename } = nowFile;
|
||||
const {basename} = nowFile;
|
||||
logCommandDebug("uploadCommandGeneral", "Preparing to upload", {
|
||||
filename: basename,
|
||||
markdownLength: markDownData.length,
|
||||
tagCount: tags?.length ?? 0,
|
||||
hasCover: !!cover,
|
||||
});
|
||||
|
||||
const upload = new Upload2NotionGeneral(plugin, dbDetails);
|
||||
const res = await upload.syncMarkdownToNotionGeneral(basename, cover, tags, markDownData, nowFile, this.app);
|
||||
const upload = new Upload2Notion(plugin, dbDetails);
|
||||
let res: NotionPageResponse;
|
||||
try {
|
||||
res = await upload.sync({
|
||||
dataset: "general",
|
||||
title: basename,
|
||||
cover: cover || "",
|
||||
tags: tags || [],
|
||||
markdown: markDownData,
|
||||
nowFile,
|
||||
app,
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
notifySyncError("[uploadCommandGeneral]", basename, error);
|
||||
logCommandDebug("uploadCommandGeneral", "Sync threw error", {
|
||||
filename: basename,
|
||||
error: extractErrorMessage(error),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.status === 200) {
|
||||
new Notice(`${i18nConfig["sync-success"]}${basename}`);
|
||||
const {response} = res;
|
||||
if (response.status === 200) {
|
||||
new Notice(`${i18nConfig["sync-preffix"]} ${basename} ${i18nConfig["sync-success"]}`).noticeEl.style.color = "green";
|
||||
|
||||
logCommandDebug("uploadCommandGeneral", "Sync succeeded", {
|
||||
filename: basename,
|
||||
status: response.status,
|
||||
pageId: res.data?.id ?? null,
|
||||
pageUrl: res.data?.url ?? null,
|
||||
});
|
||||
} else {
|
||||
new Notice(`${i18nConfig["sync-fail"]}${basename}`, 5000);
|
||||
new Notice(`${i18nConfig["sync-fail"]} ${basename}`, 5000);
|
||||
console.log(`${i18nConfig["sync-fail"]} ${basename}`);
|
||||
logCommandDebug("uploadCommandGeneral", "Sync failed with status", {
|
||||
filename: basename,
|
||||
status: response.status,
|
||||
errorCode: res.data?.code ?? null,
|
||||
errorStatus: res.data?.status ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -104,7 +225,8 @@ export async function uploadCommandCustom(
|
||||
app: App,
|
||||
) {
|
||||
|
||||
const { notionAPI, databaseID } = settings;
|
||||
const {notionAPI, databaseID} = dbDetails;
|
||||
console.log(`[uploadCommandCustom] ${new Date().toISOString()} Triggered for file`, app.workspace.getActiveFile()?.path);
|
||||
|
||||
// Check if the user has set up the Notion API and database ID
|
||||
if (notionAPI === "" || databaseID === "") {
|
||||
@@ -113,18 +235,60 @@ export async function uploadCommandCustom(
|
||||
return;
|
||||
}
|
||||
|
||||
const { markDownData, nowFile, cover, customValues } = await getNowFileMarkdownContentCustom(app, dbDetails)
|
||||
const {markDownData, nowFile, cover, customValues} = await getNowFileMarkdownContentCustom(app, dbDetails)
|
||||
|
||||
new Notice(`Start upload ${nowFile.basename}`);
|
||||
console.log(`Start upload ${nowFile.basename}`);
|
||||
|
||||
if (markDownData) {
|
||||
const { basename } = nowFile;
|
||||
const {basename} = nowFile;
|
||||
logCommandDebug("uploadCommandCustom", "Preparing to upload", {
|
||||
filename: basename,
|
||||
markdownLength: markDownData.length,
|
||||
hasCover: !!cover,
|
||||
customValueKeys: Object.keys(customValues || {}),
|
||||
});
|
||||
|
||||
const upload = new Upload2NotionCustom(plugin, dbDetails);
|
||||
const res = await upload.syncMarkdownToNotionCustom(cover, customValues, markDownData, nowFile, this.app);
|
||||
const upload = new Upload2Notion(plugin, dbDetails);
|
||||
let res: NotionPageResponse;
|
||||
try {
|
||||
res = await upload.sync({
|
||||
dataset: "custom",
|
||||
cover: cover || "",
|
||||
customValues,
|
||||
markdown: markDownData,
|
||||
nowFile,
|
||||
app,
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
notifySyncError("[uploadCommandCustom]", basename, error);
|
||||
logCommandDebug("uploadCommandCustom", "Sync threw error", {
|
||||
filename: basename,
|
||||
error: extractErrorMessage(error),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.status === 200) {
|
||||
new Notice(`${i18nConfig["sync-success"]}${basename}`);
|
||||
const {response} = res;
|
||||
|
||||
if (response.status === 200) {
|
||||
new Notice(`${i18nConfig["sync-preffix"]} ${basename} ${i18nConfig["sync-success"]}`).noticeEl.style.color = "green";
|
||||
|
||||
logCommandDebug("uploadCommandCustom", "Sync succeeded", {
|
||||
filename: basename,
|
||||
status: response.status,
|
||||
pageId: res.data?.id ?? null,
|
||||
pageUrl: res.data?.url ?? null,
|
||||
});
|
||||
} else {
|
||||
new Notice(`${i18nConfig["sync-fail"]}${basename}`, 5000);
|
||||
new Notice(`${i18nConfig["sync-fail"]} ${basename}`, 5000);
|
||||
console.log(`${i18nConfig["sync-fail"]} ${basename}`);
|
||||
logCommandDebug("uploadCommandCustom", "Sync failed with status", {
|
||||
filename: basename,
|
||||
status: response.status,
|
||||
errorCode: res.data?.code ?? null,
|
||||
errorStatus: res.data?.status ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
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 { DatabaseDetails } from "../../ui/settingTabs";
|
||||
|
||||
export class UploadBaseGeneral {
|
||||
plugin: MyPlugin;
|
||||
notion: Client;
|
||||
agent: any;
|
||||
dbDetails: DatabaseDetails
|
||||
|
||||
constructor(plugin: MyPlugin, dbDetails: DatabaseDetails) {
|
||||
this.plugin = plugin;
|
||||
this.dbDetails = dbDetails
|
||||
}
|
||||
|
||||
async deletePage(notionID: string) {
|
||||
|
||||
const { notionAPI } = this.dbDetails
|
||||
return requestUrl({
|
||||
url: `https://api.notion.com/v1/blocks/${notionID}`,
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer ' + notionAPI,
|
||||
'Notion-Version': '2022-06-28',
|
||||
},
|
||||
body: ''
|
||||
});
|
||||
}
|
||||
|
||||
async getDataBase(databaseID: string) {
|
||||
const { notionAPI } = this.dbDetails
|
||||
const response = await requestUrl({
|
||||
url: `https://api.notion.com/v1/databases/${databaseID}`,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + notionAPI,
|
||||
'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}`)
|
||||
// // }
|
||||
// }
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
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 { DatabaseDetails, PluginSettings } from "../../ui/settingTabs";
|
||||
import { UploadBaseGeneral } from "./BaseUpload2NotionGeneral";
|
||||
import { updateYamlInfo } from "../updateYaml";
|
||||
|
||||
export class Upload2NotionGeneral extends UploadBaseGeneral {
|
||||
settings: PluginSettings;
|
||||
dbDetails: DatabaseDetails;
|
||||
|
||||
constructor(plugin: MyPlugin, dbDetails: DatabaseDetails) {
|
||||
super(plugin, dbDetails);
|
||||
this.dbDetails = dbDetails;
|
||||
}
|
||||
|
||||
// 因为需要解析notion的block进行对比,非常的麻烦,
|
||||
// 暂时就直接删除,新建一个page
|
||||
async updatePage(
|
||||
notionID: string,
|
||||
title: string,
|
||||
cover: string,
|
||||
tags: string[],
|
||||
childArr: any,
|
||||
) {
|
||||
await this.deletePage(notionID);
|
||||
|
||||
const { databaseID } = this.dbDetails;
|
||||
|
||||
const databaseCover = await this.getDataBase(
|
||||
databaseID,
|
||||
);
|
||||
|
||||
if (cover == null) {
|
||||
cover = databaseCover;
|
||||
}
|
||||
|
||||
return await this.createPage(title, cover, tags, childArr);
|
||||
}
|
||||
|
||||
async createPage(
|
||||
title: string,
|
||||
cover: string,
|
||||
tags: string[],
|
||||
childArr: any,
|
||||
) {
|
||||
|
||||
const {
|
||||
databaseID,
|
||||
customTitleButton,
|
||||
customTitleName,
|
||||
tagButton,
|
||||
notionAPI
|
||||
} = this.dbDetails;
|
||||
|
||||
const bodyString: any = {
|
||||
parent: {
|
||||
database_id: databaseID,
|
||||
},
|
||||
properties: {
|
||||
[customTitleButton
|
||||
? customTitleName
|
||||
: "title"]: {
|
||||
title: [
|
||||
{
|
||||
text: {
|
||||
content: title,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
...(tagButton
|
||||
? {
|
||||
tags: {
|
||||
multi_select: tags && true ? tags.map((tag) => ({ name: tag })) : [],
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
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 " + notionAPI,
|
||||
"Notion-Version": "2022-06-28",
|
||||
},
|
||||
body: JSON.stringify(bodyString),
|
||||
});
|
||||
} catch (error) {
|
||||
new Notice(`network error ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
async syncMarkdownToNotionGeneral(
|
||||
title: string,
|
||||
cover: string,
|
||||
tags: string[],
|
||||
markdown: string,
|
||||
nowFile: TFile,
|
||||
app: App,
|
||||
): Promise<any> {
|
||||
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 { abName } = this.dbDetails
|
||||
const notionIDKey = `NotionID-${abName}`;
|
||||
const notionID = frontmasster ? frontmasster[notionIDKey] : null;
|
||||
|
||||
|
||||
if (notionID) {
|
||||
res = await this.updatePage(
|
||||
notionID,
|
||||
title,
|
||||
cover,
|
||||
tags,
|
||||
file2Block,
|
||||
);
|
||||
} else {
|
||||
res = await this.createPage(title, cover, tags, file2Block);
|
||||
}
|
||||
if (res.status === 200) {
|
||||
await updateYamlInfo(markdown, nowFile, res, app, this.plugin, this.dbDetails);
|
||||
} else {
|
||||
new Notice(`${res.text}`);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
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 { DatabaseDetails } from "../../ui/settingTabs";
|
||||
|
||||
export class UploadBaseNext {
|
||||
plugin: MyPlugin;
|
||||
notion: Client;
|
||||
agent: any;
|
||||
dbDetails: DatabaseDetails
|
||||
|
||||
constructor(plugin: MyPlugin, dbDetails: DatabaseDetails) {
|
||||
this.plugin = plugin;
|
||||
this.dbDetails = dbDetails
|
||||
}
|
||||
|
||||
async deletePage(notionID: string) {
|
||||
const { notionAPI } = this.dbDetails
|
||||
return requestUrl({
|
||||
url: `https://api.notion.com/v1/blocks/${notionID}`,
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer ' + notionAPI,
|
||||
'Notion-Version': '2022-06-28',
|
||||
},
|
||||
body: ''
|
||||
});
|
||||
}
|
||||
|
||||
async getDataBase(databaseID: string) {
|
||||
const { notionAPI } = this.dbDetails
|
||||
|
||||
const response = await requestUrl({
|
||||
url: `https://api.notion.com/v1/databases/${databaseID}`,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + notionAPI,
|
||||
'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}`)
|
||||
// // }
|
||||
// }
|
||||
}
|
||||
@@ -1,320 +0,0 @@
|
||||
import { UploadBaseNext } from "./BaseUpload2NotionNext";
|
||||
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 { DatabaseDetails, PluginSettings } from "../../ui/settingTabs";
|
||||
import { updateYamlInfo } from "../updateYaml";
|
||||
import { LIMITS, paragraph } from "@tryfabric/martian/src/notion";
|
||||
|
||||
export class Upload2NotionNext extends UploadBaseNext {
|
||||
settings: PluginSettings;
|
||||
dbDetails: DatabaseDetails
|
||||
|
||||
constructor(plugin: MyPlugin, dbDetails: DatabaseDetails) {
|
||||
super(plugin, dbDetails);
|
||||
this.dbDetails = dbDetails
|
||||
}
|
||||
|
||||
// 因为需要解析notion的block进行对比,非常的麻烦,
|
||||
// 暂时就直接删除,新建一个page
|
||||
async updatePage(
|
||||
notionID: string,
|
||||
title: string,
|
||||
emoji: string,
|
||||
cover: string,
|
||||
tags: string[],
|
||||
type: string,
|
||||
slug: string,
|
||||
stats: string,
|
||||
category: string,
|
||||
summary: string,
|
||||
paword: string,
|
||||
favicon: string,
|
||||
datetime: string,
|
||||
childArr: any
|
||||
) {
|
||||
await this.deletePage(notionID)
|
||||
|
||||
const { databaseID } = this.dbDetails
|
||||
|
||||
const databaseCover = await this.getDataBase(databaseID)
|
||||
|
||||
if (cover == null) {
|
||||
cover = databaseCover
|
||||
}
|
||||
|
||||
return await this.createPage(
|
||||
title,
|
||||
emoji,
|
||||
cover,
|
||||
tags,
|
||||
type,
|
||||
slug,
|
||||
stats,
|
||||
category,
|
||||
summary,
|
||||
paword,
|
||||
favicon,
|
||||
datetime,
|
||||
childArr)
|
||||
}
|
||||
|
||||
async createPage(
|
||||
title: string,
|
||||
emoji: string,
|
||||
cover: string,
|
||||
tags: string[],
|
||||
type: string,
|
||||
slug: string,
|
||||
stats: string,
|
||||
category: string,
|
||||
summary: string,
|
||||
pawrod: string,
|
||||
favicon: string,
|
||||
datetime: string,
|
||||
childArr: any
|
||||
) {
|
||||
|
||||
const {
|
||||
databaseID,
|
||||
notionAPI
|
||||
} = this.dbDetails
|
||||
|
||||
|
||||
const bodyString: any = {
|
||||
parent: {
|
||||
database_id: databaseID,
|
||||
},
|
||||
properties: {
|
||||
title: {
|
||||
title: [
|
||||
{
|
||||
text: {
|
||||
content: title
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
type: {
|
||||
select: {
|
||||
name: type || 'Post'
|
||||
}
|
||||
},
|
||||
status: {
|
||||
select: {
|
||||
name: stats || 'Draft'
|
||||
}
|
||||
},
|
||||
category: {
|
||||
select: {
|
||||
name: category || 'Obsidian'
|
||||
}
|
||||
},
|
||||
|
||||
password: {
|
||||
rich_text: [
|
||||
{
|
||||
text: {
|
||||
content: pawrod || ''
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
icon: {
|
||||
rich_text: [
|
||||
{
|
||||
text: {
|
||||
content: favicon || ''
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
date: {
|
||||
date: {
|
||||
start: datetime || new Date().toISOString()
|
||||
}
|
||||
}
|
||||
},
|
||||
children: childArr,
|
||||
}
|
||||
|
||||
// add tags
|
||||
if (tags) {
|
||||
bodyString.properties.tags = {
|
||||
multi_select: tags.map(tag => {
|
||||
return { "name": tag }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// add title icon
|
||||
if (emoji) {
|
||||
bodyString.icon = {
|
||||
emoji: emoji
|
||||
}
|
||||
}
|
||||
|
||||
// add slug
|
||||
if (slug) {
|
||||
bodyString.properties.slug = {
|
||||
rich_text: [
|
||||
{
|
||||
text: {
|
||||
content: slug
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// check if summary is available
|
||||
if (summary) {
|
||||
bodyString.properties.summary = {
|
||||
rich_text: [
|
||||
{
|
||||
text: {
|
||||
content: summary
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
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 ' + notionAPI,
|
||||
'Notion-Version': '2022-06-28',
|
||||
},
|
||||
body: JSON.stringify(bodyString),
|
||||
})
|
||||
} catch (error) {
|
||||
new Notice(`network error ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
async syncMarkdownToNotionNext(
|
||||
title: string,
|
||||
emoji: string,
|
||||
cover: string,
|
||||
tags: string[],
|
||||
type: string,
|
||||
slug: string,
|
||||
stats: string,
|
||||
category: string,
|
||||
summary: string,
|
||||
paword: string,
|
||||
favicon: string,
|
||||
datetime: string,
|
||||
markdown: string,
|
||||
nowFile: TFile,
|
||||
app: App,
|
||||
): Promise<any> {
|
||||
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 { abName } = this.dbDetails
|
||||
const notionIDKey = `NotionID-${abName}`;
|
||||
const notionID = frontmasster ? frontmasster[notionIDKey] : null;
|
||||
|
||||
|
||||
// increase the limits
|
||||
// Motivated by https://github.com/tryfabric/martian/issues/51
|
||||
file2Block.forEach((block, index) => {
|
||||
if (
|
||||
block.type === 'paragraph' &&
|
||||
block.paragraph.rich_text.length > LIMITS.RICH_TEXT_ARRAYS
|
||||
) {
|
||||
|
||||
const newParagraphBlocks: any[] = []
|
||||
const chunk: any = []
|
||||
const richTextChunks = chunk(block.paragraph.rich_text, 100)
|
||||
|
||||
richTextChunks.forEach((chunk: any) => {
|
||||
newParagraphBlocks.push(paragraph(chunk))
|
||||
})
|
||||
|
||||
file2Block.splice(index, 1, ...newParagraphBlocks)
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
if (notionID) {
|
||||
res = await this.updatePage(
|
||||
notionID,
|
||||
title,
|
||||
emoji,
|
||||
cover,
|
||||
tags,
|
||||
type,
|
||||
slug,
|
||||
stats,
|
||||
category,
|
||||
summary,
|
||||
paword,
|
||||
favicon,
|
||||
datetime,
|
||||
file2Block
|
||||
);
|
||||
} else {
|
||||
res = await this.createPage(
|
||||
title,
|
||||
emoji,
|
||||
cover,
|
||||
tags,
|
||||
type,
|
||||
slug,
|
||||
stats,
|
||||
category,
|
||||
summary,
|
||||
paword,
|
||||
favicon,
|
||||
datetime,
|
||||
file2Block
|
||||
);
|
||||
}
|
||||
|
||||
if (res.status === 200) {
|
||||
await updateYamlInfo(markdown, nowFile, res, app, this.plugin, this.dbDetails)
|
||||
} else {
|
||||
new Notice(`${res.text}`)
|
||||
}
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
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 { DatabaseDetails } from "../../ui/settingTabs";
|
||||
|
||||
export class UploadBaseCustom {
|
||||
plugin: MyPlugin;
|
||||
notion: Client;
|
||||
agent: any;
|
||||
dbDetails: DatabaseDetails
|
||||
|
||||
constructor(plugin: MyPlugin, dbDetails: DatabaseDetails) {
|
||||
this.plugin = plugin;
|
||||
this.dbDetails = dbDetails
|
||||
}
|
||||
|
||||
async deletePage(notionID: string) {
|
||||
const { notionAPI } = this.dbDetails
|
||||
return requestUrl({
|
||||
url: `https://api.notion.com/v1/blocks/${notionID}`,
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer ' + notionAPI,
|
||||
'Notion-Version': '2022-06-28',
|
||||
},
|
||||
body: ''
|
||||
});
|
||||
}
|
||||
|
||||
async getDataBase(databaseID: string) {
|
||||
const { notionAPI } = this.dbDetails
|
||||
const response = await requestUrl({
|
||||
url: `https://api.notion.com/v1/databases/${databaseID}`,
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + notionAPI,
|
||||
'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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,242 +0,0 @@
|
||||
import { App, Notice, requestUrl, TFile } from "obsidian";
|
||||
import { markdownToBlocks } from "@tryfabric/martian";
|
||||
import * as yamlFrontMatter from "yaml-front-matter";
|
||||
// import * as yaml from "yaml"
|
||||
import MyPlugin from "src/main";
|
||||
import { DatabaseDetails, PluginSettings } from "../../ui/settingTabs";
|
||||
import { updateYamlInfo } from "../updateYaml";
|
||||
import { UploadBaseCustom } from "./BaseUpload2NotionCustom";
|
||||
|
||||
export class Upload2NotionCustom extends UploadBaseCustom {
|
||||
settings: PluginSettings;
|
||||
dbDetails: DatabaseDetails;
|
||||
|
||||
constructor(plugin: MyPlugin, dbDetails: DatabaseDetails) {
|
||||
super(plugin, dbDetails);
|
||||
this.dbDetails = dbDetails;
|
||||
}
|
||||
|
||||
// 因为需要解析notion的block进行对比,非常的麻烦,
|
||||
// 暂时就直接删除,新建一个page
|
||||
async updatePage(
|
||||
notionID: string,
|
||||
cover: string,
|
||||
customValues: Record<string, string>,
|
||||
childArr: any,
|
||||
) {
|
||||
await this.deletePage(notionID);
|
||||
|
||||
const { databaseID } = this.dbDetails;
|
||||
|
||||
const databaseCover = await this.getDataBase(
|
||||
databaseID
|
||||
);
|
||||
|
||||
if (cover == null) {
|
||||
cover = databaseCover;
|
||||
}
|
||||
|
||||
return await this.createPage(cover, customValues, childArr);
|
||||
}
|
||||
|
||||
async createPage(
|
||||
cover: string,
|
||||
customValues: Record<string, string>,
|
||||
childArr: any,
|
||||
) {
|
||||
|
||||
const {
|
||||
databaseID,
|
||||
customProperties,
|
||||
notionAPI
|
||||
} = this.dbDetails;
|
||||
|
||||
const bodyString: any = this.buildBodyString(customProperties, customValues, 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,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
console.log(bodyString)
|
||||
|
||||
try {
|
||||
return await requestUrl({
|
||||
url: `https://api.notion.com/v1/pages`,
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
// 'User-Agent': 'obsidian.md',
|
||||
Authorization:
|
||||
"Bearer " + notionAPI,
|
||||
"Notion-Version": "2022-06-28",
|
||||
},
|
||||
body: JSON.stringify(bodyString),
|
||||
});
|
||||
} catch (error) {
|
||||
new Notice(`network error ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
async syncMarkdownToNotionCustom(
|
||||
cover: string,
|
||||
customValues: Record<string, string>,
|
||||
markdown: string,
|
||||
nowFile: TFile,
|
||||
app: App,
|
||||
): Promise<any> {
|
||||
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 { abName } = this.dbDetails
|
||||
const notionIDKey = `NotionID-${abName}`;
|
||||
const notionID = frontmasster ? frontmasster[notionIDKey] : null;
|
||||
|
||||
if (notionID) {
|
||||
res = await this.updatePage(
|
||||
notionID,
|
||||
cover,
|
||||
customValues,
|
||||
file2Block,
|
||||
);
|
||||
} else {
|
||||
res = await this.createPage(cover, customValues, file2Block);
|
||||
}
|
||||
if (res.status === 200) {
|
||||
await updateYamlInfo(markdown, nowFile, res, app, this.plugin, this.dbDetails);
|
||||
} else {
|
||||
new Notice(`${res.text}`);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private buildPropertyObject(customName: string, customType: string, customValues: Record<string, any>) {
|
||||
const value = customValues[customName] || '';
|
||||
|
||||
switch (customType) {
|
||||
case "title":
|
||||
return {
|
||||
title: [
|
||||
{
|
||||
text: {
|
||||
content: value,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
case "rich_text":
|
||||
return {
|
||||
rich_text: [
|
||||
{
|
||||
text: {
|
||||
content: value || '',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
case "date":
|
||||
return {
|
||||
date: {
|
||||
start: value || new Date().toISOString(),
|
||||
},
|
||||
};
|
||||
case "number":
|
||||
return {
|
||||
number: Number(value),
|
||||
};
|
||||
case "phone_number":
|
||||
return {
|
||||
phone_number: value,
|
||||
};
|
||||
case "email":
|
||||
return {
|
||||
email: value,
|
||||
};
|
||||
case "url":
|
||||
return {
|
||||
url: value,
|
||||
};
|
||||
case "files":
|
||||
return {
|
||||
files: Array.isArray(value) ? value.map(url => ({
|
||||
name: url,
|
||||
type: "external",
|
||||
external: {
|
||||
url: url,
|
||||
},
|
||||
})) : [
|
||||
{
|
||||
name: value,
|
||||
type: "external",
|
||||
external: {
|
||||
url: value,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
case "checkbox":
|
||||
return {
|
||||
checkbox: Boolean(value) || false,
|
||||
};
|
||||
case "select":
|
||||
return {
|
||||
select: {
|
||||
name: value,
|
||||
},
|
||||
};
|
||||
case "multi_select":
|
||||
return {
|
||||
multi_select: Array.isArray(value) ? value.map(item => ({ name: item })) : [{ name: value }],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private buildBodyString(
|
||||
customProperties: { customName: string; customType: string }[],
|
||||
customValues: Record<string, string>,
|
||||
childArr: any,
|
||||
) {
|
||||
|
||||
const properties: { [key: string]: any } = {};
|
||||
|
||||
// Only include custom properties that have values
|
||||
customProperties.forEach(({ customName, customType }) => {
|
||||
if (customValues[customName] !== undefined) {
|
||||
properties[customName] = this.buildPropertyObject(customName, customType, customValues);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
console.log(properties)
|
||||
|
||||
return {
|
||||
parent: {
|
||||
database_id: this.dbDetails.databaseID,
|
||||
},
|
||||
properties,
|
||||
children: childArr,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user