mirror of
https://github.com/jxpeng98/obsidian-to-NotionNext
synced 2026-07-30 00:48:36 +08:00
Compare commits
118 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ded01ce678 | ||
|
|
3535d9fc70 | ||
|
|
9c93e38aff | ||
|
|
bfde5dfe1a | ||
|
|
d8eb3c8984 | ||
|
|
1cee2b67a6 | ||
|
|
82529ce56a | ||
|
|
70f30f0713 | ||
|
|
24ebb0bf8a | ||
|
|
d7372c7c55 | ||
|
|
f7bcf71020 | ||
|
|
72146afe48 | ||
|
|
9bcdb42edb | ||
|
|
16e0827991 | ||
|
|
863a6fb0dc | ||
|
|
5799ffe79b | ||
|
|
a8ebab7fcb | ||
|
|
e0ec27ad07 | ||
|
|
85220105b0 | ||
|
|
9afdb24a82 | ||
|
|
3c7ab13995 | ||
|
|
31b72f3bd3 | ||
|
|
ee5aada1b9 | ||
|
|
9e15044f6a | ||
|
|
9d891dae55 | ||
|
|
bc665253ec | ||
|
|
125c75f7c0 | ||
|
|
8248640b5f | ||
|
|
0198898086 | ||
|
|
8c6aea8250 | ||
|
|
a179d3da52 | ||
|
|
569d8eb699 | ||
|
|
2a58dd3258 | ||
|
|
8c65e471eb | ||
|
|
388693d5b4 | ||
|
|
dda6710fe9 | ||
|
|
76ea9a4aee | ||
|
|
856f83e8a1 | ||
|
|
f670dd972a | ||
|
|
e6de2d036a | ||
|
|
dd131f5df1 | ||
|
|
a9fb81fc1b | ||
|
|
4f21e3ff91 | ||
|
|
4abbbecd37 | ||
|
|
35746f1c51 | ||
|
|
9ac64268fc | ||
|
|
3123d91e97 | ||
|
|
cf08703fea | ||
|
|
fb463ceaae | ||
|
|
306573d81e | ||
|
|
fb5f7b502d | ||
|
|
59c15f1206 | ||
|
|
370444fb94 | ||
|
|
11aaf14c0b | ||
|
|
f3fb251cf7 | ||
|
|
913fa4dd03 | ||
|
|
f638310f31 | ||
|
|
c2421f7a84 | ||
|
|
6c30d0c2b4 | ||
|
|
ccb5a20abe | ||
|
|
630e3a20c2 | ||
|
|
040b72b760 | ||
|
|
7df4b5e9c8 | ||
|
|
3f35a56b6e | ||
|
|
3c7e266a35 | ||
|
|
8201c706ea | ||
|
|
4433ea449d | ||
|
|
96126d1334 | ||
|
|
b3702a9cc1 | ||
|
|
ba9b477db7 | ||
|
|
fb6951cfdf | ||
|
|
6454c0417d | ||
|
|
35f19a9f43 | ||
|
|
8e32e8f0f5 | ||
|
|
880f256ae1 | ||
|
|
bf9c3f2933 | ||
|
|
2ac354299a | ||
|
|
8317abb667 | ||
|
|
d74634aca6 | ||
|
|
d400b92263 | ||
|
|
e7fa597e5a | ||
|
|
6ce689ba96 | ||
|
|
b978cf7e01 | ||
|
|
4e3c88f129 | ||
|
|
6550bca70f | ||
|
|
662edbb84c | ||
|
|
671936ee41 | ||
|
|
2b16ea76c6 | ||
|
|
399a02187f | ||
|
|
eee349509a | ||
|
|
57c0941fed | ||
|
|
8734af6dbf | ||
|
|
041a78fb52 | ||
|
|
afec5d8364 | ||
|
|
75926a38d4 | ||
|
|
793dd67e0a | ||
|
|
f235c80d18 | ||
|
|
f33315a4b6 | ||
|
|
b62bd4ba2e | ||
|
|
402d15a38b | ||
|
|
87f1ce0146 | ||
|
|
398b785e03 | ||
|
|
8617878582 | ||
|
|
ae9e0c14fb | ||
|
|
9be9b18efb | ||
|
|
588f57205b | ||
|
|
cfbedea91d | ||
|
|
4cd6557444 | ||
|
|
407688c303 | ||
|
|
84b998aa93 | ||
|
|
4b6f1a1f1d | ||
|
|
4a2c59d13b | ||
|
|
8f116956ce | ||
|
|
df138d9304 | ||
|
|
49b8d470fb | ||
|
|
c39323c3fa | ||
|
|
05dff7a389 | ||
|
|
24fcadbc8b |
26
.github/workflows/CI.yml
vendored
26
.github/workflows/CI.yml
vendored
@@ -1,26 +0,0 @@
|
|||||||
name: CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- "*"
|
|
||||||
|
|
||||||
env:
|
|
||||||
PLUGIN_NAME: share-to-notionnext
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Use Node.js
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 16
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
id: build
|
|
||||||
run: |
|
|
||||||
npm install
|
|
||||||
npm run build
|
|
||||||
24
.github/workflows/release.yml
vendored
24
.github/workflows/release.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: Release Obsidian plugin
|
name: Release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -7,17 +7,26 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
PLUGIN_NAME: share-to-notionnext # Change this to match the id of your plugin.
|
PLUGIN_NAME: share-to-notionnext # Change this to match the id of your plugin.
|
||||||
|
CHANGELOG_FILENAME: CHANGELOG.md
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
name: release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Use Node.js
|
- name: Use Node.js
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: "14.x"
|
node-version: "18"
|
||||||
|
|
||||||
|
# - name: Generate changelog
|
||||||
|
# id: changelog
|
||||||
|
# uses: saadmk11/changelog-ci@v1.1.2
|
||||||
|
# with:
|
||||||
|
# github_token: ${{ secrets.REPO_ACCESS_TOKEN }}
|
||||||
|
# release_version: ${{ github.ref }}
|
||||||
|
# changelog_filename: CHANGELOG.md
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
id: build
|
id: build
|
||||||
@@ -25,10 +34,10 @@ jobs:
|
|||||||
npm install
|
npm install
|
||||||
npm run build
|
npm run build
|
||||||
mkdir ${{ env.PLUGIN_NAME }}
|
mkdir ${{ env.PLUGIN_NAME }}
|
||||||
cp main.js manifest.json styles.css ${{ env.PLUGIN_NAME }}
|
cp main.js manifest.json ${{ env.PLUGIN_NAME }}
|
||||||
zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }}
|
zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }}
|
||||||
ls
|
ls
|
||||||
echo "::set-output name=tag_name::$(git tag --sort version:refname | tail -n 1)"
|
echo "tag_name=$(git tag --sort version:refname | tail -n 1)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
id: create_release
|
id: create_release
|
||||||
@@ -39,6 +48,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
tag_name: ${{ github.ref }}
|
tag_name: ${{ github.ref }}
|
||||||
release_name: ${{ github.ref }}
|
release_name: ${{ github.ref }}
|
||||||
|
body_path: ${{ env.CHANGELOG_FILENAME }}
|
||||||
draft: false
|
draft: false
|
||||||
prerelease: false
|
prerelease: false
|
||||||
|
|
||||||
|
|||||||
32
.github/workflows/test.yml
vendored
Normal file
32
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
name: Test and Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "*"
|
||||||
|
|
||||||
|
env:
|
||||||
|
PLUGIN_NAME: share-to-notionnext # Change this to match the id of your plugin.
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Use Node.js
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: "18"
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
id: build
|
||||||
|
run: |
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
mkdir ${{ env.PLUGIN_NAME }}
|
||||||
|
cp main.js manifest.json ${{ env.PLUGIN_NAME }}
|
||||||
|
zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }}
|
||||||
|
ls
|
||||||
|
echo "tag_name=$(git tag --sort version:refname | tail -n 1)" >> $GITHUB_OUTPUT
|
||||||
25
CHANGELOG.md
Normal file
25
CHANGELOG.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
## 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`。
|
||||||
|
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
- 改进了自定义数据库的预览模态框。
|
||||||
|

|
||||||
142
README-zh.md
142
README-zh.md
@@ -1,15 +1,109 @@
|
|||||||
# Obsidian share to NotionNext
|
# Obsidian share to NotionNext
|
||||||
|
|
||||||
感谢[原作者](https://github.com/EasyChris/obsidian-to-notion)开发出了这么好用的插件,能够将obsidian同步到notion。 但是原仓库只能同步Name和Tags信息,如果像我一样通过[NotionNext](https://github.com/tangly1024/NotionNext)来搭建自己的网页,就有一些局限性。每次导入之后都需要进行大量的修改。
|
感谢[原作者](https://github.com/EasyChris/obsidian-to-notion)开发出了这么好用的插件,能够将obsidian同步到notion。 但是原仓库只能同步Name和Tags信息,如果像我一样通过[NotionNext](https://github.com/tangly1024/NotionNext)来搭建自己的网页,就有一些局限性。每次导入之后都需要进行大量的修改。
|
||||||
|
|
||||||
所以我在[原作者](https://github.com/EasyChris/obsidian-to-notion)的基础之上,增加了匹配[NotionNext](https://github.com/tangly1024/NotionNext)模板的功能。这样可以直接在Obsidian编辑,整理好之后一键发布。
|
所以我在[原作者](https://github.com/EasyChris/obsidian-to-notion)的基础之上,增加了匹配[NotionNext](https://github.com/tangly1024/NotionNext)模板的功能。这样可以直接在Obsidian编辑,整理好之后一键发布。
|
||||||
|
|
||||||
|
## 更新说明
|
||||||
|
### 1.1.2
|
||||||
|
- 修复了一个拼写错误,导致无法同步`status`到NotionNext。现在你可以使用`stats`或者`status`来同步文章的状态到NotionNext。这个更新不会影响到同步到General数据库的功能。
|
||||||
|
- **`stats`和`status`都可以使用,但是你只能使用其中一个。**
|
||||||
|
|
||||||
|
```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
|
||||||
|
- 修复设置中自定义标题的显示问题。
|
||||||
|
- 增加了标签同步的控制按钮。以减少空标签集造成的上传失败。
|
||||||
|
|
||||||
|
如果你不想使用同步标签功能,你可以在设置中关闭。不论你在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](https://github.com/tangly1024/NotionNext)。如果你不是使用这个模板,你只会无限返回`error 400`.
|
||||||
|
|
||||||
所以没有NotionNext需求的,请使用原作者的[Obsidian-to-notion](https://github.com/EasyChris/obsidian-to-notion)
|
所以没有NotionNext需求的,请使用原作者的[Obsidian-to-notion](https://github.com/EasyChris/obsidian-to-notion)
|
||||||
|
|
||||||
### 准备安装
|
### 准备安装
|
||||||
|
|
||||||
在安装插件之前,你必须配置好了以下内容:
|
在安装插件之前,你必须配置好了以下内容:
|
||||||
|
|
||||||
1. 你的NotionNext数据库。
|
1. 你的NotionNext数据库。
|
||||||
@@ -28,25 +122,33 @@
|
|||||||
- icon
|
- icon
|
||||||
|
|
||||||
在最新的0.0.6版本,我还增加了以下内容:
|
在最新的0.0.6版本,我还增加了以下内容:
|
||||||
|
|
||||||
- titleicon: 标题的emoji图标,暂时只能支持emoji
|
- titleicon: 标题的emoji图标,暂时只能支持emoji
|
||||||
- coverurl: 封面图片的url, url可以是任何一种图片的url, 例如jpg,png,svg等等。
|
- coverurl: 封面图片的url, url可以是任何一种图片的url, 例如jpg,png,svg等等。
|
||||||
|
|
||||||
**如果你是直接复制的NotionNext的模板,这些内容应该已经有了。我在原作者的基础之上对本插件的内容进行了更改,所以你只需要保证你的数据库有如上内容,并且所有的字母都是小写!!!**
|
**如果你是直接复制的NotionNext的模板,这些内容应该已经有了。我在原作者的基础之上对本插件的内容进行了更改,所以你只需要保证你的数据库有如上内容,并且所有的字母都是小写!!!**
|
||||||
|
|
||||||
|
|
||||||
**⚠️⚠️⚠️:表头全部小写!!!顺序无所谓!**
|
**⚠️⚠️⚠️:表头全部小写!!!顺序无所谓!**
|
||||||
|
|
||||||
### 安装插件
|
### 安装插件
|
||||||
|
|
||||||
|
#### 社区插件市场下载
|
||||||
|
|
||||||
|
打开`Obsidian -> 设置 -> 社区插件 -> 浏览 -> 搜索Share to NotionNext`, 然后点击安装。
|
||||||
|
|
||||||
|
#### 手动安装
|
||||||
|
|
||||||
1. 关闭Obsidian
|
1. 关闭Obsidian
|
||||||
2. 从Release下载插件文件,解压到你的obsidian插件目录下。
|
2. 从Release下载插件文件,解压到你的obsidian插件目录下。
|
||||||
3. 重新打开Obsidian,进入设置,启用插件。
|
3. 重新打开Obsidian,进入设置,启用插件。
|
||||||
4. 在设置中,找到Obsidian to NotionNext,填入你的NotionNext Database ID 和 API token。
|
4. 在设置中,找到Obsidian to NotionNext,填入你的NotionNext Database ID 和 API token。
|
||||||
|
|
||||||
### 使用插件
|
### 使用插件
|
||||||
|
|
||||||
在仓库中我上传了一个模板,你可以直接复制到你的模板文件夹中。然后使用Obsidian的模板功能一键生成新笔记。
|
在仓库中我上传了一个模板,你可以直接复制到你的模板文件夹中。然后使用Obsidian的模板功能一键生成新笔记。
|
||||||
|
|
||||||
|
|
||||||
如果你不想使用模板,你也可以直接在Obsidian中创建一个新的文件,然后复制下边的内容。然后保存。
|
如果你不想使用模板,你也可以直接在Obsidian中创建一个新的文件,然后复制下边的内容。然后保存。
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
---
|
---
|
||||||
# default value has been set.
|
# default value has been set.
|
||||||
@@ -58,6 +160,7 @@
|
|||||||
# 现在阶段一定不要修改表头的名字, please do not change the name of the header in YAML front matter
|
# 现在阶段一定不要修改表头的名字, please do not change the name of the header in YAML front matter
|
||||||
# !!!!!!!!!!!!
|
# !!!!!!!!!!!!
|
||||||
titleicon: 📎 # emoji icon, default is 📜, 默认是📜
|
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, 默认是空
|
coverurl: https://img.jxpeng.dev/2023/08/843e27a210847f05a0f7cfb121fec100.jpg # default is empty, 默认是空
|
||||||
type: Post # Post or Page, default is Post, 默认是Post
|
type: Post # Post or Page, default is Post, 默认是Post
|
||||||
slug: test # slug for url, default is empty, 默认是空
|
slug: test # slug for url, default is empty, 默认是空
|
||||||
@@ -76,12 +179,12 @@ Contents Below
|
|||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
模板使用如下:
|
模板使用如下:
|
||||||

|

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

|

|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -90,6 +193,7 @@ Contents Below
|
|||||||
非常感谢原作者的开发,我只是在原作者的基础之上进行了修改,增加了一些功能。如果你觉得这个插件对你有帮助,可以给[原作者](https://github.com/EasyChris/obsidian-to-notion)一个star。
|
非常感谢原作者的开发,我只是在原作者的基础之上进行了修改,增加了一些功能。如果你觉得这个插件对你有帮助,可以给[原作者](https://github.com/EasyChris/obsidian-to-notion)一个star。
|
||||||
|
|
||||||
# Obsidian to Notion
|
# Obsidian to Notion
|
||||||
|
|
||||||
[](https://github.com/Easychris/obsidian-to-notion/actions/workflows/CI.yml)
|
[](https://github.com/Easychris/obsidian-to-notion/actions/workflows/CI.yml)
|
||||||
[](https://github.com/Easychris/obsidian-to-notion/actions/workflows/release.yml)
|
[](https://github.com/Easychris/obsidian-to-notion/actions/workflows/release.yml)
|
||||||
[](https://raw.githubusercontent.com/EasyChris/obsidian-to-notion/master/LICENSE)
|
[](https://raw.githubusercontent.com/EasyChris/obsidian-to-notion/master/LICENSE)
|
||||||
@@ -105,31 +209,40 @@ Obsidian share to Notion [English](README.md)
|
|||||||

|

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

|

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

|

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

|

|
||||||
@@ -144,9 +257,6 @@ git clone https://github.com/EasyChris/obsidian-to-notion.git
|
|||||||
|
|
||||||

|

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

|

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

|

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

|

|
||||||
|
|
||||||
|
|
||||||
## 页面 Banner 链接[可选]
|
## 页面 Banner 链接[可选]
|
||||||
|
|
||||||
默认可以不填写
|
默认可以不填写
|
||||||
横幅URL必须是图像URL,例如:https://i.imgur.com/xxx.jpg
|
横幅URL必须是图像URL,例如:https://i.imgur.com/xxx.jpg
|
||||||
|
|
||||||
## Notion ID [可选]
|
## Notion ID [可选]
|
||||||
|
|
||||||
Notion ID是你想分享文件的页面ID。
|
Notion ID是你想分享文件的页面ID。
|
||||||
如果你不写它,notion将分享到默认的链接,如:https://www.notion.so/myworkspace/a8aec43384f447ed84390,访问这个页面将重定向到你的网站页面。
|
如果你不写它,notion将分享到默认的链接,如:<https://www.notion.so/myworkspace/a8aec43384f447ed84390,访问这个页面将重定向到你的网站页面。>
|
||||||
如果你写了Notion ID,它将分享到页面链接如:https://your_user_name.notion.site/myworkspace/a8aec43384f447ed84390。不需要重定向网址。
|
如果你写了Notion ID,它将分享到页面链接如:<https://your_user_name.notion.site/myworkspace/a8aec43384f447ed84390。不需要重定向网址。>
|
||||||
|
|
||||||
## 同步图片
|
## 同步图片
|
||||||
|
|
||||||
@@ -186,6 +297,7 @@ Notion ID是你想分享文件的页面ID。
|
|||||||
[顿顿饭](https://dun.mianbaoduo.com/@easy)
|
[顿顿饭](https://dun.mianbaoduo.com/@easy)
|
||||||
|
|
||||||
# 感谢
|
# 感谢
|
||||||
|
|
||||||
[开发流程 | Obsidian 插件开发文档](https://luhaifeng666.github.io/obsidian-plugin-docs-zh/zh/getting-started/development-workflow.html)
|
[开发流程 | 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 - devbean/obsidian-wordpress: An obsidian plugin for publishing docs to WordPress.](https://github.com/devbean/obsidian-wordpress)
|
||||||
|
|||||||
184
README.md
184
README.md
@@ -1,18 +1,117 @@
|
|||||||
# Obsidian to NotionNext
|
# Obsidian to NotionNext
|
||||||
[中文文档](README-zh.md)
|
|
||||||
|
|
||||||
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 Tags 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.
|
[](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/jxpeng98/obsidian-to-NotionNext/releases/))
|
||||||
|
|
||||||
|
|
||||||
|
[//]: # ([中文文档](README-zh.md))
|
||||||
|
|
||||||
|
**Now, support both NotionNext and General databases with customised properties.**
|
||||||
|
|
||||||
|
**现在支持NotionNext和普通Notion数据库,可自定义数据库列表。**
|
||||||
|
|
||||||
|
## 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.
|
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.
|
||||||
|
|
||||||
## How to Use
|
---
|
||||||
|
|
||||||
|
<details> <summary> Previous How to Use </summary>
|
||||||
|
|
||||||
### Precautions
|
### 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 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).
|
For those without a NotionNext requirement, please use the original [Obsidian-to-notion](https://github.com/EasyChris/obsidian-to-notion).
|
||||||
|
|
||||||
### Pre-Installation Steps
|
### Pre-Installation Steps
|
||||||
|
|
||||||
Before installing the plugin, you must have set up the following:
|
Before installing the plugin, you must have set up the following:
|
||||||
|
|
||||||
1. Your NotionNext database.
|
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.
|
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
|
3. NotionNext Database ID
|
||||||
@@ -28,7 +127,7 @@ Before installing the plugin, you must have set up the following:
|
|||||||
- password
|
- password
|
||||||
- icon
|
- icon
|
||||||
|
|
||||||
In version 0.0.6, I also add the following contents:
|
From version 0.0.6, I also add the following contents:
|
||||||
- titleicon: the unique icon for each post, it can only support emoji currently.
|
- 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.
|
- cover url: the cover image for each post, it should be end with a type of image, such as .jpg, .png, .gif, etc.
|
||||||
|
|
||||||
@@ -37,16 +136,26 @@ In version 0.0.6, I also add the following contents:
|
|||||||
**⚠️⚠️⚠️: All headers are in lowercase!!! The order doesn't matter!**
|
**⚠️⚠️⚠️: All headers are in lowercase!!! The order doesn't matter!**
|
||||||
|
|
||||||
### Plugin Installation
|
### Plugin Installation
|
||||||
|
|
||||||
|
#### Install via Community Plugins
|
||||||
|
|
||||||
|
Open `Obsidian settings -> Community Plugins -> Browse -> NotionNext -> Share to NotionNext`
|
||||||
|
|
||||||
|
#### Mannually Install
|
||||||
|
|
||||||
1. Close Obsidian.
|
1. Close Obsidian.
|
||||||
2. Download the plugin file from Release and unzip it into your Obsidian plugin directory.
|
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.
|
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.
|
4. In the settings, find Obsidian to NotionNext, and enter your NotionNext Database ID and API token.
|
||||||
|
|
||||||
### How to Use
|
### How to Use
|
||||||
|
|
||||||
### Using the Plugin
|
### 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.
|
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.
|
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
|
```markdown
|
||||||
---
|
---
|
||||||
# default value has been set.
|
# default value has been set.
|
||||||
@@ -58,6 +167,7 @@ If you don't want to use the template, you can also directly create a new file i
|
|||||||
# 现在阶段一定不要修改表头的名字, please do not change the name of the header in YAML front matter
|
# 现在阶段一定不要修改表头的名字, please do not change the name of the header in YAML front matter
|
||||||
# !!!!!!!!!!!!
|
# !!!!!!!!!!!!
|
||||||
titleicon: 📎 # emoji icon, default is 📜, 默认是📜
|
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, 默认是空
|
coverurl: https://img.jxpeng.dev/2023/08/843e27a210847f05a0f7cfb121fec100.jpg # default is empty, 默认是空
|
||||||
type: Post # Post or Page, default is Post, 默认是Post
|
type: Post # Post or Page, default is Post, 默认是Post
|
||||||
slug: test # slug for url, default is empty, 默认是空
|
slug: test # slug for url, default is empty, 默认是空
|
||||||
@@ -75,26 +185,29 @@ tags:
|
|||||||
Contents Below
|
Contents Below
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Usage of the template is as follows:
|
Usage of the template is as follows:
|
||||||

|

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

|

|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Original README.md**
|
<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.
|
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
|
# Obsidian to Notion
|
||||||
|
|
||||||
[](https://github.com/Easychris/obsidian-to-notion/actions/workflows/CI.yml)
|
[](https://github.com/Easychris/obsidian-to-notion/actions/workflows/CI.yml)
|
||||||
[](https://github.com/Easychris/obsidian-to-notion/actions/workflows/release.yml)
|
[](https://github.com/Easychris/obsidian-to-notion/actions/workflows/release.yml)
|
||||||
[](https://raw.githubusercontent.com/EasyChris/obsidian-to-notion/master/LICENSE)
|
[](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/)
|
||||||
[](https://github.com/Easychris/obsidian-to-notion/releases)
|
[](https://github.com/Easychris/obsidian-to-notion/releases)
|
||||||
|
|
||||||
|
|
||||||
Share of obsidian to Notion [中文文档](README-zh.md)
|
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
|
Sharing files from Obsidian to Notion with a single click, and Obsidian will automatically add the Notion share link
|
||||||
@@ -104,7 +217,9 @@ You are welcome to offer it a star if it can benefit you.
|
|||||||

|

|
||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
### [TODO Board](https://github.com/users/EasyChris/projects/3/views/1)
|
### [TODO Board](https://github.com/users/EasyChris/projects/3/views/1)
|
||||||
|
|
||||||
- [x] support for custom page banner
|
- [x] support for custom page banner
|
||||||
- [x] update the exsit page
|
- [x] update the exsit page
|
||||||
- [x] support for mult language
|
- [x] support for mult language
|
||||||
@@ -113,41 +228,47 @@ You are welcome to offer it a star if it can benefit you.
|
|||||||
- [x] support tags thank for [@jannikbuscha](https://github.com/jannikbuscha)
|
- [x] support tags thank for [@jannikbuscha](https://github.com/jannikbuscha)
|
||||||
- [ ] transfer the bi-link format like [[]] into the format that Notion supports.
|
- [ ] transfer the bi-link format like [[]] into the format that Notion supports.
|
||||||
|
|
||||||
|
|
||||||
# How to use
|
# How to use
|
||||||
|
|
||||||
## Install the plugin
|
## Install the plugin
|
||||||
|
|
||||||
### Marketplace download
|
### Marketplace download
|
||||||
|
|
||||||
Open obsidian setting -> Add plugin -> Search -> notion
|
Open obsidian setting -> Add plugin -> Search -> notion
|
||||||
|
|
||||||

|

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

|

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

|

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

|

|
||||||
@@ -164,25 +285,25 @@ https://www.notion.so/myworkspace/a8aec43384f447ed84390e8e42c2e089?v=...
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Open the plugin configuration
|
## Open the plugin configuration
|
||||||
|
|
||||||
Fill the configuration with the `NOTION_API_KEY` and `DATABASE_ID` you got
|
Fill the configuration with the `NOTION_API_KEY` and `DATABASE_ID` you got
|
||||||

|

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

|

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

|

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

|

|
||||||
@@ -191,11 +312,11 @@ Add tags to your notion page
|
|||||||
|
|
||||||

|

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

|

|
||||||
|
|
||||||
* add tags in the head
|
- add tags in the head
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
---
|
---
|
||||||
@@ -206,7 +327,6 @@ this is test tags
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
```markdown
|
```markdown
|
||||||
---
|
---
|
||||||
tags:
|
tags:
|
||||||
@@ -221,24 +341,20 @@ this is test tags
|
|||||||
|
|
||||||
Thanks for [@jannikbuscha](https://github.com/jannikbuscha) contribution
|
Thanks for [@jannikbuscha](https://github.com/jannikbuscha) contribution
|
||||||
|
|
||||||
|
|
||||||
## Notion ID [option]
|
## Notion ID [option]
|
||||||
|
|
||||||
Notion ID is the your notion site ID that you want to share the file to.
|
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:
|
if you don't write it, notion will share to the default link like:
|
||||||
https://www.notion.so/myworkspace/a8aec43384f447ed84390
|
<https://www.notion.so/myworkspace/a8aec43384f447ed84390>
|
||||||
that visit this page need to redirect to your site url
|
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:
|
if you write the Notion ID, it will share to the page link like:
|
||||||
https://your_user_name.notion.site/myworkspace/a8aec43384f447ed84390.
|
<https://your_user_name.notion.site/myworkspace/a8aec43384f447ed84390>.
|
||||||
The visiter don't need to redirect url.
|
The visiter don't need to redirect url.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Sync image to Notion
|
## 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).
|
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
|
# Development
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -253,9 +369,6 @@ yarn dev
|
|||||||
node update-version.js
|
node update-version.js
|
||||||
./release.sh
|
./release.sh
|
||||||
```
|
```
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Thanks
|
# Thanks
|
||||||
@@ -273,3 +386,6 @@ node update-version.js
|
|||||||
|
|
||||||
# License
|
# License
|
||||||
GNU GPLv3
|
GNU GPLv3
|
||||||
|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|||||||
@@ -1,59 +1,60 @@
|
|||||||
import esbuild from "esbuild";
|
import esbuild from "esbuild";
|
||||||
import process from "process";
|
import process from "process";
|
||||||
import builtins from 'builtin-modules'
|
import builtins from "builtin-modules";
|
||||||
|
|
||||||
const banner =
|
const banner = `/*
|
||||||
`/*
|
|
||||||
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
|
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
|
||||||
if you want to view the source, please visit the github repository of this plugin
|
if you want to view the source, please visit the github repository of this plugin
|
||||||
*/
|
*/
|
||||||
`;
|
`;
|
||||||
const prod = (process.argv[2] === 'production');
|
const prod = process.argv[2] === "production";
|
||||||
|
|
||||||
(async () => { // Enclose everything in an async function for using await
|
|
||||||
const ctx = await esbuild.context({
|
const ctx = await esbuild.context({
|
||||||
banner: {
|
banner: {
|
||||||
js: banner,
|
js: banner,
|
||||||
},
|
},
|
||||||
entryPoints: ['src/main.ts'],
|
entryPoints: ["src/main.ts"],
|
||||||
bundle: true,
|
bundle: true,
|
||||||
external: [
|
external: [
|
||||||
'obsidian',
|
"obsidian",
|
||||||
'electron',
|
"electron",
|
||||||
'@codemirror/autocomplete',
|
"@codemirror/autocomplete",
|
||||||
'@codemirror/closebrackets',
|
"@codemirror/closebrackets",
|
||||||
'@codemirror/collab',
|
"@codemirror/collab",
|
||||||
'@codemirror/commands',
|
"@codemirror/commands",
|
||||||
'@codemirror/comment',
|
"@codemirror/comment",
|
||||||
'@codemirror/fold',
|
"@codemirror/fold",
|
||||||
'@codemirror/gutter',
|
"@codemirror/gutter",
|
||||||
'@codemirror/highlight',
|
"@codemirror/highlight",
|
||||||
'@codemirror/history',
|
"@codemirror/history",
|
||||||
'@codemirror/language',
|
"@codemirror/language",
|
||||||
'@codemirror/lint',
|
"@codemirror/lint",
|
||||||
'@codemirror/matchbrackets',
|
"@codemirror/matchbrackets",
|
||||||
'@codemirror/panel',
|
"@codemirror/panel",
|
||||||
'@codemirror/rangeset',
|
"@codemirror/rangeset",
|
||||||
'@codemirror/rectangular-selection',
|
"@codemirror/rectangular-selection",
|
||||||
'@codemirror/search',
|
"@codemirror/search",
|
||||||
'@codemirror/state',
|
"@codemirror/state",
|
||||||
'@codemirror/stream-parser',
|
"@codemirror/stream-parser",
|
||||||
'@codemirror/text',
|
"@codemirror/text",
|
||||||
'@codemirror/tooltip',
|
"@codemirror/tooltip",
|
||||||
'@codemirror/view',
|
"@codemirror/view",
|
||||||
...builtins
|
...builtins,
|
||||||
],
|
],
|
||||||
format: 'cjs',
|
format: "cjs",
|
||||||
target: 'es2016',
|
target: "es2016",
|
||||||
logLevel: "info",
|
logLevel: "info",
|
||||||
sourcemap: prod ? false : 'inline',
|
sourcemap: prod ? false : "inline",
|
||||||
treeShaking: true,
|
treeShaking: true,
|
||||||
outfile: 'main.js',
|
outfile: "main.js",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!prod) {
|
if (!prod) {
|
||||||
await ctx.watch();
|
await ctx.watch();
|
||||||
} else {
|
} else {
|
||||||
await ctx.dispose();
|
await ctx.rebuild();
|
||||||
|
ctx.dispose().then(
|
||||||
|
(r) => console.log("Build completed"),
|
||||||
|
(e) => console.error(e),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
})().catch(() => process.exit(1)); // Use .catch here to handle any errors
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"id": "share-to-notionnext",
|
"id": "share-to-notionnext",
|
||||||
"name": "Share to NotionNext",
|
"name": "Share to NotionNext",
|
||||||
"version": "0.1.1",
|
"version": "2.3.0",
|
||||||
"minAppVersion": "0.0.1",
|
"minAppVersion": "0.0.1",
|
||||||
"description": "Shares obsidian md file to notion with notion api for NotionNext web deploy, motivated by EasyChris/obsidian-to-notion.",
|
"description": "Shares obsidian md file to notion with notion api for NotionNext web deploy, originally created by EasyChris/obsidian-to-notion.",
|
||||||
"author": "EasyChris, jxpeng98",
|
"author": "EasyChris, jxpeng98",
|
||||||
"authorUrl": "https://github.com/jxpeng98/obsidian-to-NotionNext",
|
"authorUrl": "https://github.com/jxpeng98",
|
||||||
"isDesktopOnly": false
|
"isDesktopOnly": false
|
||||||
}
|
}
|
||||||
@@ -8,10 +8,11 @@
|
|||||||
# 现在阶段一定不要修改表头的名字, please do not change the name of the header in YAML front matter
|
# 现在阶段一定不要修改表头的名字, please do not change the name of the header in YAML front matter
|
||||||
# !!!!!!!!!!!!
|
# !!!!!!!!!!!!
|
||||||
titleicon: 📎 # default is 📜, 默认是📜
|
titleicon: 📎 # 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, 默认是空
|
coverurl: https://img.jxpeng.dev/2023/08/843e27a210847f05a0f7cfb121fec100.jpg # default is empty, 默认是空
|
||||||
type: Post # Post or Page, default is Post, 默认是Post
|
type: Post # Post or Page, default is Post, 默认是Post
|
||||||
slug: test # slug for url, default is empty, 默认是空
|
slug: test # slug for url, default is empty, 默认是空
|
||||||
stats: Draft # Draft, Invisible, Published, default is Draft, 默认是Draft
|
status: Draft # Draft, Invisible, Published, default is Draft, 默认是Draft
|
||||||
category: test # default is 'Obsidian', 默认是'Obsidian'
|
category: test # default is 'Obsidian', 默认是'Obsidian'
|
||||||
summary: this is a summary for test post # default is empty, 默认是空
|
summary: this is a summary for test post # default is empty, 默认是空
|
||||||
icon: fa-solid fa-camera # you can ignore this, default is empty, 默认是空,可直接删除
|
icon: fa-solid fa-camera # you can ignore this, default is empty, 默认是空,可直接删除
|
||||||
|
|||||||
21
package.json
21
package.json
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "share-to-notionnext",
|
"name": "share-to-notionnext",
|
||||||
"version": "0.1.1",
|
"version": "2.3.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Shares obsidian md file to notion with notion api for NotionNext web deploy, motivated by EasyChris/obsidian-to-notion.",
|
"description": "Shares obsidian md file to notion with notion api for NotionNext web deploy, originally created by EasyChris/obsidian-to-notion.",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node esbuild.config.mjs",
|
"dev": "node esbuild.config.mjs",
|
||||||
@@ -14,20 +14,21 @@
|
|||||||
"license": "GNU GPLv3",
|
"license": "GNU GPLv3",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.5.7",
|
"@types/node": "^20.5.7",
|
||||||
"@types/yaml-front-matter": "^4.1.0",
|
"@types/yaml-front-matter": "^4.1.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.5.0",
|
"@typescript-eslint/eslint-plugin": "^6.16.0",
|
||||||
"@typescript-eslint/parser": "^6.5.0",
|
"@typescript-eslint/parser": "^6.16.0",
|
||||||
"builtin-modules": "^3.2.0",
|
"builtin-modules": "^3.3.0",
|
||||||
"esbuild": "0.19.2",
|
"esbuild": "0.19.5",
|
||||||
"obsidian": "latest",
|
"obsidian": "latest",
|
||||||
"tslib": "2.6.2",
|
"tslib": "2.6.2",
|
||||||
"typescript": "5.2.2"
|
"typescript": "5.2.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tryfabric/martian": "^1.2.0",
|
"@tryfabric/martian": "^1.2.4",
|
||||||
"https-proxy-agent": "^7.0.1",
|
"https-proxy-agent": "^7.0.2",
|
||||||
"process": "^0.11.10",
|
"process": "^0.11.10",
|
||||||
"yaml": "^2.2.2",
|
"remark-math": "^6.0.0",
|
||||||
|
"yaml": "^2.3.4",
|
||||||
"yaml-front-matter": "^4.1.1"
|
"yaml-front-matter": "^4.1.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
export const NoticeMsg: {[key: string]:any} = {
|
|
||||||
"en": {
|
|
||||||
"notion-logo": "Share to NotionNext",
|
|
||||||
"sync-success": "Sync to NotionNext success: \n",
|
|
||||||
"sync-fail": "Sync to NotionNext fail: \n",
|
|
||||||
"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.",
|
|
||||||
"config-secrets-database-id": "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.",
|
|
||||||
},
|
|
||||||
"zh": {
|
|
||||||
"notion-logo": "分享到NotionNext",
|
|
||||||
"sync-success": "同步到NotionNext成功:\n",
|
|
||||||
"sync-fail": "同步到NotionNext失败: \n",
|
|
||||||
"open-file": "请打开需要同步的文件",
|
|
||||||
"set-tags-fail": "设置标签失败,请检查文件的frontmatter,或者在插件设置中关闭设置tags开关",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export const NoticeMConfig = (lang:any) :any => {
|
|
||||||
return NoticeMsg[lang]
|
|
||||||
}
|
|
||||||
@@ -1,239 +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";
|
|
||||||
|
|
||||||
export class Upload2Notion {
|
|
||||||
plugin: MyPlugin;
|
|
||||||
notion: Client;
|
|
||||||
agent: any;
|
|
||||||
constructor(plugin: MyPlugin) {
|
|
||||||
this.plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
async deletePage(notionID: string) {
|
|
||||||
return requestUrl({
|
|
||||||
url: `https://api.notion.com/v1/blocks/${notionID}`,
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': 'Bearer ' + this.plugin.settings.notionAPI,
|
|
||||||
'Notion-Version': '2022-06-28',
|
|
||||||
},
|
|
||||||
body: ''
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getDataBase(databaseID: string) {
|
|
||||||
const response = await requestUrl({
|
|
||||||
url: `https://api.notion.com/v1/databases/${databaseID}`,
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Authorization': 'Bearer ' + this.plugin.settings.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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 因为需要解析notion的block进行对比,非常的麻烦,
|
|
||||||
// 暂时就直接删除,新建一个page
|
|
||||||
async updatePage(notionID: string, title: string, allowTags: boolean, 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 databasecover = await this.getDataBase(this.plugin.settings.databaseID)
|
|
||||||
|
|
||||||
if (cover == null) {
|
|
||||||
cover = databasecover
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.createPage(title, allowTags, emoji, cover, tags, type, slug, stats, category, summary, paword, favicon, datetime, childArr)
|
|
||||||
}
|
|
||||||
|
|
||||||
async createPage(title: string, allowTags: boolean, 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 bodyString: any = {
|
|
||||||
parent: {
|
|
||||||
database_id: this.plugin.settings.databaseID
|
|
||||||
},
|
|
||||||
icon: {
|
|
||||||
emoji: emoji || '📜'
|
|
||||||
},
|
|
||||||
properties: {
|
|
||||||
title: {
|
|
||||||
title: [
|
|
||||||
{
|
|
||||||
text: {
|
|
||||||
content: title
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
tags: {
|
|
||||||
multi_select: allowTags && tags !== undefined ? tags.map(tag => {
|
|
||||||
return { "name": tag }
|
|
||||||
}) : [],
|
|
||||||
},
|
|
||||||
type: {
|
|
||||||
select: {
|
|
||||||
name: type || 'Post'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
slug: {
|
|
||||||
rich_text: [
|
|
||||||
{
|
|
||||||
text: {
|
|
||||||
content: slug || ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
select: {
|
|
||||||
name: stats || 'Draft'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
category: {
|
|
||||||
select: {
|
|
||||||
name: category || 'Obsidian'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
summary: {
|
|
||||||
rich_text: [
|
|
||||||
{
|
|
||||||
text: {
|
|
||||||
content: summary || ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
password: {
|
|
||||||
rich_text: [
|
|
||||||
{
|
|
||||||
text: {
|
|
||||||
content: pawrod || ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
icon: {
|
|
||||||
rich_text: [
|
|
||||||
{
|
|
||||||
text: {
|
|
||||||
content: favicon || ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
date: {
|
|
||||||
date: {
|
|
||||||
start: datetime || new Date().toISOString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
children: childArr,
|
|
||||||
}
|
|
||||||
if (cover) {
|
|
||||||
bodyString.cover = {
|
|
||||||
type: "external",
|
|
||||||
external: {
|
|
||||||
url: cover
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!bodyString.cover && this.plugin.settings.bannerUrl) {
|
|
||||||
bodyString.cover = {
|
|
||||||
type: "external",
|
|
||||||
external: {
|
|
||||||
url: this.plugin.settings.bannerUrl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return await requestUrl({
|
|
||||||
url: `https://api.notion.com/v1/pages`,
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
// 'User-Agent': 'obsidian.md',
|
|
||||||
'Authorization': 'Bearer ' + this.plugin.settings.notionAPI,
|
|
||||||
'Notion-Version': '2022-06-28',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(bodyString),
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
new Notice(`network error ${error}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async syncMarkdownToNotion(title: string, allowTags: boolean, 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, settings: any): Promise<any> {
|
|
||||||
let res: any
|
|
||||||
const yamlContent: any = yamlFrontMatter.loadFront(markdown);
|
|
||||||
const __content = yamlContent.__content
|
|
||||||
const file2Block = markdownToBlocks(__content);
|
|
||||||
const frontmasster = app.metadataCache.getFileCache(nowFile)?.frontmatter
|
|
||||||
const notionID = frontmasster ? frontmasster.notionID : null
|
|
||||||
|
|
||||||
if (notionID) {
|
|
||||||
res = await this.updatePage(notionID, title, allowTags, emoji, cover, tags, type, slug, stats, category, summary, paword, favicon, datetime, file2Block);
|
|
||||||
} else {
|
|
||||||
res = await this.createPage(title, allowTags, emoji, cover, tags, type, slug, stats, category, summary, paword, favicon, datetime, file2Block);
|
|
||||||
}
|
|
||||||
if (res.status === 200) {
|
|
||||||
await this.updateYamlInfo(markdown, nowFile, res, app, settings)
|
|
||||||
} else {
|
|
||||||
new Notice(`${res.text}`)
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateYamlInfo(yamlContent: string, nowFile: TFile, res: any, app: App, settings: any) {
|
|
||||||
let { url, id } = res.json
|
|
||||||
// replace www to notionID
|
|
||||||
const { notionID } = settings;
|
|
||||||
if (notionID !== "") {
|
|
||||||
// replace url str "www" to notionID
|
|
||||||
url = url.replace("www.notion.so", `${notionID}.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}`)
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
51
src/commands/FuzzySuggester.ts
Normal file
51
src/commands/FuzzySuggester.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { FuzzySuggestModal, FuzzyMatch } from 'obsidian';
|
||||||
|
import MyPlugin from "../main";
|
||||||
|
import { i18nConfig } from "../lang/I18n";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple interface for what should be displayed and stored for suggester
|
||||||
|
*/
|
||||||
|
export interface DatabaseList {
|
||||||
|
name: string, // specific database name
|
||||||
|
match: any //
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class FuzzySuggester extends FuzzySuggestModal<DatabaseList> {
|
||||||
|
private plugin: MyPlugin;
|
||||||
|
private data: DatabaseList[];
|
||||||
|
private callback: any;
|
||||||
|
|
||||||
|
constructor(plugin: MyPlugin) {
|
||||||
|
super(plugin.app);
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.setPlaceholder(i18nConfig.PlaceHolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
setSuggesterData(suggesterData: Array<DatabaseList>): void { this.data = suggesterData }
|
||||||
|
|
||||||
|
async display(callBack: (item: DatabaseList, evt: MouseEvent | KeyboardEvent) => void): Promise<any> {
|
||||||
|
this.callback = callBack;
|
||||||
|
this.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the data
|
||||||
|
getItems(): DatabaseList[] {
|
||||||
|
return this.data
|
||||||
|
}
|
||||||
|
|
||||||
|
getItemText(item: DatabaseList): string {
|
||||||
|
return item.name
|
||||||
|
}
|
||||||
|
|
||||||
|
onChooseItem(item: DatabaseList, evt: MouseEvent | KeyboardEvent): void { }
|
||||||
|
|
||||||
|
onChooseSuggestion(item: FuzzyMatch<DatabaseList>, evt: MouseEvent | KeyboardEvent): void {
|
||||||
|
this.callback(item.item, evt)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSuggestion(item: FuzzyMatch<DatabaseList>, el: HTMLElement): void {
|
||||||
|
el.createEl('div', { text: item.item.name })
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
107
src/commands/NotionCommands.ts
Normal file
107
src/commands/NotionCommands.ts
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import { i18nConfig } from "src/lang/I18n";
|
||||||
|
import { Editor, MarkdownView, setTooltip } from "obsidian";
|
||||||
|
import { FuzzySuggester, DatabaseList } from "./FuzzySuggester";
|
||||||
|
import { uploadCommandCustom, uploadCommandGeneral, uploadCommandNext } from "../upload/uploadCommand";
|
||||||
|
import ObsidianSyncNotionPlugin from "src/main";
|
||||||
|
import { DatabaseDetails } from "../ui/settingTabs";
|
||||||
|
|
||||||
|
|
||||||
|
interface Command {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
editorCallback: (editor: Editor, view: MarkdownView) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// create the commands list
|
||||||
|
export default class RibbonCommands {
|
||||||
|
plugin: ObsidianSyncNotionPlugin;
|
||||||
|
|
||||||
|
Ncommand: Command[] = [];
|
||||||
|
|
||||||
|
constructor(plugin: ObsidianSyncNotionPlugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
|
||||||
|
// iterate through the database detail
|
||||||
|
|
||||||
|
for (let key in this.plugin.settings.databaseDetails) {
|
||||||
|
let dbDetails = this.plugin.settings.databaseDetails[key];
|
||||||
|
this.addCommandForDatabase(dbDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register all the commands
|
||||||
|
this.Ncommand.forEach(command => {
|
||||||
|
this.plugin.addCommand(
|
||||||
|
{
|
||||||
|
id: command.id,
|
||||||
|
name: command.name,
|
||||||
|
editorCallback: command.editorCallback,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async ribbonDisplay() {
|
||||||
|
const NcommandList: DatabaseList[] = [];
|
||||||
|
|
||||||
|
this.Ncommand.map(command => NcommandList.push(
|
||||||
|
{
|
||||||
|
name: command.name,
|
||||||
|
match: command.editorCallback
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const fusg = new FuzzySuggester(this.plugin);
|
||||||
|
|
||||||
|
fusg.setSuggesterData(NcommandList);
|
||||||
|
await fusg.display(async (results) => { await results.match() })
|
||||||
|
};
|
||||||
|
|
||||||
|
// if the setting has been changed, try to rebuild the command list
|
||||||
|
async updateCommand() {
|
||||||
|
|
||||||
|
this.Ncommand = [];
|
||||||
|
|
||||||
|
for (let key in this.plugin.settings.databaseDetails) {
|
||||||
|
let dbDetails = this.plugin.settings.databaseDetails[key];
|
||||||
|
this.addCommandForDatabase(dbDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Ncommand.forEach(command => {
|
||||||
|
this.plugin.addCommand(
|
||||||
|
{
|
||||||
|
id: command.id,
|
||||||
|
name: command.name,
|
||||||
|
editorCallback: command.editorCallback,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private addCommandForDatabase(dbDetails: DatabaseDetails) {
|
||||||
|
// Example logic - adjust based on your specific requirements
|
||||||
|
let commandId = `share-to-${dbDetails.abName}`;
|
||||||
|
let commandName = `Share to ${dbDetails.fullName} (${dbDetails.abName})`; // or use a translated name
|
||||||
|
|
||||||
|
let editorCallback: (editor: Editor, view: MarkdownView) => Promise<void>;
|
||||||
|
if (dbDetails.format === 'next') {
|
||||||
|
editorCallback = async (editor, view) => {
|
||||||
|
await uploadCommandNext(this.plugin, this.plugin.settings, dbDetails, this.plugin.app);
|
||||||
|
};
|
||||||
|
} else if (dbDetails.format === 'general') {
|
||||||
|
editorCallback = async (editor, view) => {
|
||||||
|
await uploadCommandGeneral(this.plugin, this.plugin.settings, dbDetails, this.plugin.app);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (dbDetails.format === 'custom') {
|
||||||
|
editorCallback = async (editor, view) => {
|
||||||
|
await uploadCommandCustom(this.plugin, this.plugin.settings, dbDetails, this.plugin.app);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Ncommand.push({ id: commandId, name: commandName, editorCallback });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
17
src/lang/I18n.ts
Normal file
17
src/lang/I18n.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import {en} from "./locale/en";
|
||||||
|
import {zh} from "./locale/zh";
|
||||||
|
import {ja} from "./locale/ja";
|
||||||
|
|
||||||
|
export const I18n: {[key: string]:any} = {
|
||||||
|
en,
|
||||||
|
zh,
|
||||||
|
ja,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const I18nConfig = (lang: string): any => {
|
||||||
|
return I18n[lang];
|
||||||
|
};
|
||||||
|
|
||||||
|
const storedLanguage = window.localStorage.getItem("language");
|
||||||
|
|
||||||
|
export const i18nConfig = I18n[storedLanguage || window.navigator.language.split('-')[0] || "en"];
|
||||||
78
src/lang/locale/en.ts
Normal file
78
src/lang/locale/en.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
export const en = {
|
||||||
|
databaseFormat: "Database Format",
|
||||||
|
databaseFormatDesc: "Select the database format you want to sync to NotionNext or General",
|
||||||
|
databaseNext: "NotionNext",
|
||||||
|
databaseGeneral: "General",
|
||||||
|
databaseCustom: "Custom",
|
||||||
|
databaseFullName: "Database Full Name",
|
||||||
|
databaseFullNameDesc: "Please give a full name for your database",
|
||||||
|
databaseFullNameText: "Enter your database full name",
|
||||||
|
databaseAbbreviateName: "Database Abbreviate Name",
|
||||||
|
databaseAbbreviateNameDesc: "Please give a nick name for your database",
|
||||||
|
databaseAbbreviateNameText: "Enter your database nick name",
|
||||||
|
ribbonIcon: "Share to NotionNext",
|
||||||
|
GeneralSetting: "General information Settings",
|
||||||
|
CommandID: "share-to-notionnext",
|
||||||
|
CommandName: "Share to NotionNext Database",
|
||||||
|
CommandIDGeneral: "share-to-notion",
|
||||||
|
CommandNameGeneral: "Share to Notion General Database",
|
||||||
|
NotionNextButton: "NotionNext Sync",
|
||||||
|
NotionNextButtonDesc: "Open this option, Sync to NotionNext command will be displayed in the command palette (default: ON)",
|
||||||
|
NotionNextSettingHeader: "NotionNext Database Settings",
|
||||||
|
NotionAPI: "Notion API Token",
|
||||||
|
NotionAPIDesc: "Generate from https://www.notion.so/my-integrations",
|
||||||
|
NotionAPIText: "Enter your Notion API Token",
|
||||||
|
DatabaseID: "Database ID",
|
||||||
|
DatabaseIDDesc: "Collect from the top-right Share --> Publish",
|
||||||
|
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)",
|
||||||
|
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]",
|
||||||
|
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",
|
||||||
|
NotionGeneralSettingHeader: "General Notion Database Settings",
|
||||||
|
NotionGeneralButton: "Notion General Sync",
|
||||||
|
NotionGeneralButtonDesc: "Open this option, Sync to Notion General Database command will be displayed in the command palette (default: ON)",
|
||||||
|
NotionTagButton: "Notion Tags Sync",
|
||||||
|
NotionTagButtonDesc: "Sync Tags to Notion General Database (default: ON)",
|
||||||
|
NotionCustomTitle: "Customise title property",
|
||||||
|
NotionCustomTitleDesc: "Modify the column name of the Notion database (default: OFF)",
|
||||||
|
NotionCustomTitleName: "Preferred title name",
|
||||||
|
NotionCustomTitleNameDesc: "Enter the preferred title name for the first column of the Notion database (default: title)",
|
||||||
|
NotionCustomTitleText: "Enter the name",
|
||||||
|
NotionCustomValues: "Customise values property",
|
||||||
|
NotionCustomValuesDesc: "Modify the column name of the Notion database,one per line",
|
||||||
|
NotionCustomValuesText: "Enter all properties that you want to sync",
|
||||||
|
NotYetFinish:
|
||||||
|
"Not finished. This function will be available in the next version",
|
||||||
|
PlaceHolder: "Enter database Name",
|
||||||
|
"notion-logo": "Share to NotionNext",
|
||||||
|
"sync-success": "Sync to NotionNext success: \n",
|
||||||
|
"sync-fail": "Sync to NotionNext fail: \n",
|
||||||
|
"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.",
|
||||||
|
"config-secrets-database-id":
|
||||||
|
"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.",
|
||||||
|
NNonMissing:
|
||||||
|
"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.",
|
||||||
|
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",
|
||||||
|
CustomPropertyName: "Property Name",
|
||||||
|
CustomPropertyFirstColumn: "Title Column",
|
||||||
|
CustomPropertyFirstColumnDesc: "The title of the page, must be the first property",
|
||||||
|
CustomProperty: "Property",
|
||||||
|
AddCustomProperty: "Add Custom Property",
|
||||||
|
AddNewProperty: "Add New Property",
|
||||||
|
AddNewPropertyDesc: "Add new property match with your notion database",
|
||||||
|
}
|
||||||
70
src/lang/locale/ja.ts
Normal file
70
src/lang/locale/ja.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
export const ja = {
|
||||||
|
databaseFormat: "データベース形式",
|
||||||
|
databaseFormatDesc: "同期したいデータベース形式を選択してください",
|
||||||
|
databaseNext: "NotionNext",
|
||||||
|
databaseGeneral: "一般的なNotion",
|
||||||
|
databaseCustom: "カスタム",
|
||||||
|
databaseFullName: "データベースの全称",
|
||||||
|
databaseFullNameDesc: "データベースの全称を入力してください",
|
||||||
|
databaseFullNameText: "データベースの全称を入力",
|
||||||
|
databaseAbbreviateName: "データベースの略称",
|
||||||
|
databaseAbbreviateNameDesc: "データベースの略称を入力してください",
|
||||||
|
databaseAbbreviateNameText: "データベースの略称を入力",
|
||||||
|
ribbonIcon: "NotionNextで共有",
|
||||||
|
GeneralSetting: "一般設定",
|
||||||
|
CommandID: "share-to-notionnext",
|
||||||
|
CommandName: "NotionNextデータベースに共有",
|
||||||
|
CommandIDGeneral: "share-to-notion",
|
||||||
|
CommandNameGeneral: "一般的なNotionデータベースに共有",
|
||||||
|
NotionNextButton: "NotionNext同期",
|
||||||
|
NotionNextButtonDesc: "このオプションを開くと、NotionNext同期コマンドがコマンドパレットに表示されます(デフォルト:ON)",
|
||||||
|
NotionNextSettingHeader: "NotionNextデータベース設定",
|
||||||
|
NotionAPI: "Notion API トークン",
|
||||||
|
NotionAPIDesc: "https://www.notion.so/my-integrations から生成してください",
|
||||||
|
NotionAPIText: "Notion API トークンを入力",
|
||||||
|
DatabaseID: "データベースID",
|
||||||
|
DatabaseIDDesc: "右上の共有 --> 公開から取得してください",
|
||||||
|
DatabaseIDText: "データベースIDを入力",
|
||||||
|
BannerUrl: "バナーのURL(任意)",
|
||||||
|
BannerUrlDesc: "デフォルトは空白です。バナーを表示したい場合は、URLを入力してください(例:https://abc.com/b.png)",
|
||||||
|
BannerUrlText: "バナーのURLを入力",
|
||||||
|
NotionUser: "Notion ID(ユーザー名、任意)",
|
||||||
|
NotionUserDesc: "共有リンクから取得(例:https://username.notion.site)。Notion IDは[username]です",
|
||||||
|
NotionUserText: "Notion IDを入力",
|
||||||
|
NotionLinkDisplay: "Notionリンク表示",
|
||||||
|
NotionLinkDisplayDesc: "デフォルトはONです。front matterにリンクを非表示にしたい場合は、オフにしてください",
|
||||||
|
NotionGeneralSettingHeader: "一般的なNotionデータベース設定",
|
||||||
|
NotionGeneralButton: "一般的なNotion同期",
|
||||||
|
NotionGeneralButtonDesc: "このオプションを開くと、一般的なNotionデータベース同期コマンドがコマンドパレットに表示されます(デフォルト:ON)",
|
||||||
|
NotionTagButton: "Notionタグ同期",
|
||||||
|
NotionTagButtonDesc: "タグを一般的なNotionデータベースに同期(デフォルト:ON)",
|
||||||
|
NotionCustomTitle: "タイトルのカスタマイズ",
|
||||||
|
NotionCustomTitleDesc: "Notionデータベースの列名を変更(デフォルト:OFF)",
|
||||||
|
NotionCustomTitleName: "希望のタイトル名",
|
||||||
|
NotionCustomTitleNameDesc: "Notionデータベースの最初の列のための希望のタイトル名を入力(デフォルト:title)",
|
||||||
|
NotionCustomTitleText: "名前を入力",
|
||||||
|
NotionCustomValues: "値のカスタマイズ",
|
||||||
|
NotionCustomValuesDesc: "Notionデータベースの列名を変更、1行に1つ",
|
||||||
|
NotionCustomValuesText: "同期したいすべてのプロパティを入力",
|
||||||
|
NotYetFinish: "未完了。この機能は次のバージョンで利用可能になります",
|
||||||
|
PlaceHolder: "データベース名を入力",
|
||||||
|
"notion-logo": "NotionNextで共有",
|
||||||
|
"sync-success": "NotionNextへの同期に成功:\n",
|
||||||
|
"sync-fail": "NotionNextへの同期に失敗:\n",
|
||||||
|
"open-notion": "同期が必要なファイルを開いてください",
|
||||||
|
"config-secrets-notion-api": "設定タブでNotion APIを設定してください",
|
||||||
|
"config-secrets-database-id": "設定タブでデータベースIDを設定してください",
|
||||||
|
"set-tags-fail": "タグの設定に失敗。ファイルのfrontmatterを確認するか、設定タブでタグのスイッチをオフにしてください",
|
||||||
|
NNonMissing: "設定に 'NNon' プロパティがありません。設定してください",
|
||||||
|
"set-api-id": "設定タブでNotion APIおよびデータベースIDを設定してください",
|
||||||
|
NotionCustomSettingHeader: "Notionカスタムデータベース設定",
|
||||||
|
NotionCustomButton: "Notionカスタマイズコマンドの切り替え",
|
||||||
|
NotionCustomButtonDesc: "このオプションを開くと、Notionカスタムデータベース同期コマンドがコマンドパレットに表示されます",
|
||||||
|
CustomPropertyName: "カスタムプロパティ名",
|
||||||
|
CustomPropertyFirstColumn: "最初の列のカスタムプロパティ名",
|
||||||
|
CustomPropertyFirstColumnDesc: "最初の列のカスタムプロパティ名を入力してください",
|
||||||
|
CustomProperty: "カスタムプロパティ",
|
||||||
|
AddCustomProperty: "カスタムプロパティを追加",
|
||||||
|
AddNewProperty: "新しいプロパティを追加",
|
||||||
|
AddNewPropertyDesc: "新しいプロパティを追加してください",
|
||||||
|
};
|
||||||
73
src/lang/locale/zh.ts
Normal file
73
src/lang/locale/zh.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
export const zh = {
|
||||||
|
databaseFormat: "数据库格式",
|
||||||
|
databaseFormatDesc: "选择你想要同步的数据库格式Next 或者 普通",
|
||||||
|
databaseNext: "NotionNext",
|
||||||
|
databaseGeneral: "普通",
|
||||||
|
databaseCustom: "自定义",
|
||||||
|
databaseFullName: "数据库全称",
|
||||||
|
databaseFullNameDesc: "给你的数据库起一个全称",
|
||||||
|
databaseFullNameText: "输入你的数据库全称",
|
||||||
|
databaseAbbreviateName: "数据库简称",
|
||||||
|
databaseAbbreviateNameDesc: "给你的数据库起一个简称",
|
||||||
|
databaseAbbreviateNameText: "输入你的数据库简称",
|
||||||
|
ribbonIcon: "分享到 NotionNext",
|
||||||
|
GeneralSetting: "通用设置",
|
||||||
|
CommandID: "share-to-notionnext",
|
||||||
|
CommandName: "分享到 NotionNext",
|
||||||
|
CommandIDGeneral: "share-to-notion",
|
||||||
|
CommandNameGeneral: "分享到 Notion 普通数据库",
|
||||||
|
NotionNextButton: "NotionNext 同步",
|
||||||
|
NotionNextButtonDesc: "打开此选项,NotionNext 同步将显示在命令面板中(默认:开)",
|
||||||
|
NotionNextSettingHeader: "NotionNext 数据库参数设置",
|
||||||
|
NotionAPI: "Notion API 令牌",
|
||||||
|
NotionAPIDesc: "从 https://www.notion.so/my-integrations 生成",
|
||||||
|
NotionAPIText: "输入你的 Notion API 令牌",
|
||||||
|
DatabaseID: "数据库 ID",
|
||||||
|
DatabaseIDDesc: "从右上角的分享 --> 发布中获取",
|
||||||
|
DatabaseIDText: "输入你的数据库 ID",
|
||||||
|
BannerUrl: "封面图片地址(可选)",
|
||||||
|
BannerUrlDesc:
|
||||||
|
"默认为空,如果你想显示封面图片,请输入图片地址(例如:https://abc.com/b.png)",
|
||||||
|
BannerUrlText: "输入你的封面图片地址",
|
||||||
|
NotionUser: "Notion ID(用户名,可选)",
|
||||||
|
NotionUserDesc:
|
||||||
|
"数据库分享链接类似:https://username.notion.site/。你的 Notion ID 是 [username]",
|
||||||
|
NotionUserText: "输入你的 Notion ID",
|
||||||
|
NotionLinkDisplay: "Notion 链接显示",
|
||||||
|
NotionLinkDisplayDesc: "默认开启,如果你不想在front matter中显示链接,请关闭",
|
||||||
|
NotionGeneralSettingHeader: "普通 Notion 数据库设置",
|
||||||
|
NotionGeneralButton: "普通数据库同步",
|
||||||
|
NotionGeneralButtonDesc: "打开此选项,同步到普通数据库命令将显示在命令面板中(默认:开)",
|
||||||
|
NotionTagButton: "标签同步开关",
|
||||||
|
NotionTagButtonDesc: "将标签同步到普通数据库(默认:开)",
|
||||||
|
NotionCustomTitle: "修改 Notion 数据库表头开关",
|
||||||
|
NotionCustomTitleDesc: "自定义Notion 数据库第一列表头名(默认:关)",
|
||||||
|
NotionCustomTitleName: "想要修改的表头名",
|
||||||
|
NotionCustomTitleNameDesc: "输入你想要修改的notion数据库的表头名(默认:title)",
|
||||||
|
NotionCustomTitleText: "输入表头名",
|
||||||
|
NotionCustomValues: "自定义Notion 数据库表头",
|
||||||
|
NotionCustomValuesDesc: "自定义Notion 数据库表头,每行一个",
|
||||||
|
NotionCustomValuesText: "输入你想要同步的所有属性",
|
||||||
|
NotYetFinish: "未完成。此功能将在之后版本中提供",
|
||||||
|
PlaceHolder: "输入数据库名称",
|
||||||
|
"notion-logo": "分享到NotionNext",
|
||||||
|
"sync-success": "同步到NotionNext成功:\n",
|
||||||
|
"sync-fail": "同步到NotionNext失败: \n",
|
||||||
|
"open-file": "请打开需要同步的文件",
|
||||||
|
"config-secrets-notion-api": "请在插件设置中添加notion API",
|
||||||
|
"config-secrets-database-id": "请在插件设置中添加database id",
|
||||||
|
"set-tags-fail":
|
||||||
|
"设置标签失败,请检查文件的frontmatter,或者在插件设置中关闭设置tags开关",
|
||||||
|
NNonMissing: "未设置'NNon'属性,请在插件设置中选择NotionNext数据库。",
|
||||||
|
"set-api-id": "请在插件设置中设置notion API和database ID",
|
||||||
|
NotionCustomSettingHeader: "Notion 自定义数据库设置",
|
||||||
|
NotionCustomButton: "Notion 自定义数据库同步命令开关",
|
||||||
|
NotionCustomButtonDesc: "打开此选项,同步到自定义数据库命令将显示在命令面板中",
|
||||||
|
CustomPropertyName: "自定义属性名",
|
||||||
|
CustomPropertyFirstColumn: "第一列属性名",
|
||||||
|
CustomPropertyFirstColumnDesc: "第一列必须为标题属性名",
|
||||||
|
CustomProperty: "自定义属性",
|
||||||
|
AddCustomProperty: "添加自定义属性",
|
||||||
|
AddNewProperty: "添加新属性",
|
||||||
|
AddNewPropertyDesc: "添加一个和Notion数据库匹配的新属性",
|
||||||
|
}
|
||||||
232
src/main.ts
232
src/main.ts
@@ -1,142 +1,46 @@
|
|||||||
import { App, Editor, MarkdownView, Notice, Plugin, PluginSettingTab, Setting } from "obsidian";
|
import { App, Editor, MarkdownView, Notice, Plugin, PluginSettingTab, Setting } from "obsidian";
|
||||||
import {addIcons} from 'src/icon';
|
import { addIcons } from 'src/ui/icon';
|
||||||
import {Upload2Notion} from "src/Upload2Notion";
|
import { Upload2NotionGeneral } from "src/upload/upload_general/Upload2NotionGeneral";
|
||||||
import {NoticeMConfig} from "src/Message";
|
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";
|
||||||
|
|
||||||
// Remember to rename these classes and interfaces!
|
// Remember to rename these classes and interfaces!
|
||||||
|
|
||||||
interface PluginSettings {
|
|
||||||
notionAPI: string;
|
|
||||||
databaseID: string;
|
|
||||||
bannerUrl: string;
|
|
||||||
notionID: string;
|
|
||||||
proxy: string;
|
|
||||||
allowTags: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const langConfig = NoticeMConfig( window.localStorage.getItem('language') || 'en')
|
|
||||||
|
|
||||||
const DEFAULT_SETTINGS: PluginSettings = {
|
|
||||||
notionAPI: "",
|
|
||||||
databaseID: "",
|
|
||||||
bannerUrl: "",
|
|
||||||
notionID: "",
|
|
||||||
proxy: "",
|
|
||||||
allowTags: false
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class ObsidianSyncNotionPlugin extends Plugin {
|
export default class ObsidianSyncNotionPlugin extends Plugin {
|
||||||
settings: PluginSettings;
|
settings: PluginSettings;
|
||||||
|
commands: ribbonCommands;
|
||||||
|
app: App;
|
||||||
|
|
||||||
async onload() {
|
async onload() {
|
||||||
await this.loadSettings();
|
await this.loadSettings();
|
||||||
|
this.commands = new ribbonCommands(this);
|
||||||
|
|
||||||
addIcons();
|
addIcons();
|
||||||
// This creates an icon in the left ribbon.
|
// This creates an icon in the left ribbon.
|
||||||
const ribbonIconEl = this.addRibbonIcon(
|
const ribbonIconEl = this.addRibbonIcon(
|
||||||
"notion-logo",
|
"notion-logo",
|
||||||
"Share to NotionNext",
|
i18nConfig.ribbonIcon,
|
||||||
async (evt: MouseEvent) => {
|
async (evt: MouseEvent) => {
|
||||||
// Called when the user clicks the icon.
|
// Called when the user clicks the icon.
|
||||||
await this.upload();
|
// await this.uploadCommand();
|
||||||
|
await this.commands.ribbonDisplay();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// This adds a status bar item to the bottom of the app. Does not work on mobile apps.
|
// This adds a status bar item to the bottom of the app. Does not work on mobile apps.
|
||||||
const statusBarItemEl = this.addStatusBarItem();
|
|
||||||
// statusBarItemEl.setText("share to notion");
|
|
||||||
|
|
||||||
this.addCommand({
|
|
||||||
id: "share-to-notionnext",
|
|
||||||
name: "share to notionnext",
|
|
||||||
editorCallback: async (editor: Editor, view: MarkdownView) => {
|
|
||||||
await this.upload()
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// const statusBarItemEl = this.addStatusBarItem();
|
||||||
|
// // statusBarItemEl.setText("share to notion");
|
||||||
|
|
||||||
// This adds a settings tab so the user can configure various aspects of the plugin
|
// This adds a settings tab so the user can configure various aspects of the plugin
|
||||||
this.addSettingTab(new ObsidianSettingTab(this.app, this));
|
this.addSettingTab(new ObsidianSettingTab(this.app, this));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onunload() {}
|
onunload() {
|
||||||
|
|
||||||
async upload(){
|
|
||||||
const { notionAPI, databaseID, allowTags } = this.settings;
|
|
||||||
if (notionAPI === "" || databaseID === "") {
|
|
||||||
new Notice(
|
|
||||||
"Please set up the notion API and database ID in the settings tab."
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { markDownData, nowFile, emoji, cover, tags, type, slug, stats, category, summary, paword, favicon, datetime } =await this.getNowFileMarkdownContent(this.app);
|
|
||||||
|
|
||||||
if (markDownData) {
|
|
||||||
const { basename } = nowFile;
|
|
||||||
const upload = new Upload2Notion(this);
|
|
||||||
const res = await upload.syncMarkdownToNotion(basename, allowTags, emoji, cover, tags, type, slug, stats, category, summary, paword, favicon, datetime, markDownData, nowFile, this.app, this.settings)
|
|
||||||
if(res.status === 200){
|
|
||||||
new Notice(`${langConfig["sync-success"]}${basename}`)
|
|
||||||
}else {
|
|
||||||
new Notice(`${langConfig["sync-fail"]}${basename}`, 5000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getNowFileMarkdownContent(app: App) {
|
|
||||||
const nowFile = app.workspace.getActiveFile();
|
|
||||||
const { allowTags } = this.settings;
|
|
||||||
let emoji = ''
|
|
||||||
let cover = ''
|
|
||||||
let tags = []
|
|
||||||
let type = ''
|
|
||||||
let slug = ''
|
|
||||||
let stats = ''
|
|
||||||
let category = ''
|
|
||||||
let summary = ''
|
|
||||||
let paword = ''
|
|
||||||
let favicon = ''
|
|
||||||
let datetime = ''
|
|
||||||
|
|
||||||
const FileCache = app.metadataCache.getFileCache(nowFile)
|
|
||||||
try {
|
|
||||||
if(allowTags) {
|
|
||||||
emoji = FileCache.frontmatter.titleicon;
|
|
||||||
cover = FileCache.frontmatter.coverurl;
|
|
||||||
tags = FileCache.frontmatter.tags;
|
|
||||||
type = FileCache.frontmatter.type;
|
|
||||||
slug = FileCache.frontmatter.slug;
|
|
||||||
stats = FileCache.frontmatter.stats;
|
|
||||||
category = FileCache.frontmatter.category;
|
|
||||||
summary = FileCache.frontmatter.summary;
|
|
||||||
paword = FileCache.frontmatter.password;
|
|
||||||
favicon = FileCache.frontmatter.icon;
|
|
||||||
datetime = FileCache.frontmatter.date;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
new Notice(langConfig["set-tags-fail"]);
|
|
||||||
}
|
|
||||||
if (nowFile) {
|
|
||||||
const markDownData = await nowFile.vault.read(nowFile);
|
|
||||||
return {
|
|
||||||
markDownData,
|
|
||||||
nowFile,
|
|
||||||
emoji,
|
|
||||||
cover,
|
|
||||||
tags,
|
|
||||||
type,
|
|
||||||
slug,
|
|
||||||
stats,
|
|
||||||
category,
|
|
||||||
summary,
|
|
||||||
paword,
|
|
||||||
favicon,
|
|
||||||
datetime,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
new Notice(langConfig["open-file"]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadSettings() {
|
async loadSettings() {
|
||||||
@@ -150,92 +54,36 @@ export default class ObsidianSyncNotionPlugin extends Plugin {
|
|||||||
async saveSettings() {
|
async saveSettings() {
|
||||||
await this.saveData(this.settings);
|
await this.saveData(this.settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async addDatabaseDetails(dbDetails: DatabaseDetails) {
|
||||||
|
this.settings.databaseDetails = {
|
||||||
|
...this.settings.databaseDetails,
|
||||||
|
[dbDetails.abName]: dbDetails,
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
class ObsidianSettingTab extends PluginSettingTab {
|
async deleteDatabaseDetails(dbDetails: DatabaseDetails) {
|
||||||
plugin: ObsidianSyncNotionPlugin;
|
delete this.settings.databaseDetails[dbDetails.abName];
|
||||||
|
|
||||||
constructor(app: App, plugin: ObsidianSyncNotionPlugin) {
|
await this.saveSettings();
|
||||||
super(app, plugin);
|
|
||||||
this.plugin = plugin;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
display(): void {
|
async updateDatabaseDetails(dbDetails: DatabaseDetails) {
|
||||||
const { containerEl } = this;
|
// delete the old database details
|
||||||
|
delete this.settings.databaseDetails[dbDetails.abName];
|
||||||
|
|
||||||
containerEl.empty();
|
this.settings.databaseDetails = {
|
||||||
|
...this.settings.databaseDetails,
|
||||||
|
[dbDetails.abName]: dbDetails,
|
||||||
|
};
|
||||||
|
|
||||||
new Setting(containerEl)
|
await this.saveSettings();
|
||||||
.setName("Notion API Token")
|
|
||||||
.setDesc("It's a secret")
|
|
||||||
.addText((text) =>{
|
|
||||||
text.inputEl.type = 'password';
|
|
||||||
return text
|
|
||||||
.setPlaceholder("Enter your Notion API Token")
|
|
||||||
.setValue(this.plugin.settings.notionAPI)
|
|
||||||
.onChange(async (value) => {
|
|
||||||
this.plugin.settings.notionAPI = value;
|
|
||||||
await this.plugin.saveSettings();
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
const notionDatabaseID = new Setting(containerEl)
|
|
||||||
.setName("Database ID")
|
|
||||||
.setDesc("It's a secret")
|
|
||||||
.addText((text) => {
|
|
||||||
text.inputEl.type = 'password';
|
|
||||||
return text
|
|
||||||
.setPlaceholder("Enter your Database ID")
|
|
||||||
.setValue(this.plugin.settings.databaseID)
|
|
||||||
.onChange(async (value) => {
|
|
||||||
this.plugin.settings.databaseID = value;
|
|
||||||
await this.plugin.saveSettings();
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
// notionDatabaseID.controlEl.querySelector('input').type='password'
|
|
||||||
|
|
||||||
new Setting(containerEl)
|
|
||||||
.setName("Banner url(optional)")
|
|
||||||
.setDesc("page banner url(optional), default is empty, if you want to show a banner, please enter the url(like:https://raw.githubusercontent.com/EasyChris/obsidian-to-notion/ae7a9ac6cf427f3ca338a409ce6967ced9506f12/doc/2.png)")
|
|
||||||
.addText((text) =>
|
|
||||||
text
|
|
||||||
.setPlaceholder("Enter banner pic url: ")
|
|
||||||
.setValue(this.plugin.settings.bannerUrl)
|
|
||||||
.onChange(async (value) => {
|
|
||||||
this.plugin.settings.bannerUrl = value;
|
|
||||||
await this.plugin.saveSettings();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
new Setting(containerEl)
|
|
||||||
.setName("Notion ID(optional)")
|
|
||||||
.setDesc("Your notion ID(optional),share link likes:https://username.notion.site/,your notion id is [username]")
|
|
||||||
.addText((text) =>
|
|
||||||
text
|
|
||||||
.setPlaceholder("Enter notion ID(options) ")
|
|
||||||
.setValue(this.plugin.settings.notionID)
|
|
||||||
.onChange(async (value) => {
|
|
||||||
this.plugin.settings.notionID = value;
|
|
||||||
await this.plugin.saveSettings();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
new Setting(containerEl)
|
|
||||||
.setName("Convert tags(optional)")
|
|
||||||
.setDesc("Transfer the Obsidian tags to the Notion table. It requires the column with the name 'Tags'")
|
|
||||||
.addToggle((toggle) =>
|
|
||||||
toggle
|
|
||||||
.setValue(this.plugin.settings.allowTags)
|
|
||||||
.onChange(async (value) => {
|
|
||||||
this.plugin.settings.allowTags = value;
|
|
||||||
await this.plugin.saveSettings();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
64
src/ui/DeleteModal.ts
Normal file
64
src/ui/DeleteModal.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import { App, ButtonComponent, Modal, Setting } from "obsidian";
|
||||||
|
import { DatabaseDetails, ObsidianSettingTab } from "./settingTabs";
|
||||||
|
import ObsidianSyncNotionPlugin from "../main";
|
||||||
|
|
||||||
|
export class DeleteModal extends Modal {
|
||||||
|
data: Record<string, any> = {
|
||||||
|
deleted: false,
|
||||||
|
}
|
||||||
|
plugin: ObsidianSyncNotionPlugin;
|
||||||
|
settingTab: ObsidianSettingTab;
|
||||||
|
dbDetails: DatabaseDetails
|
||||||
|
|
||||||
|
constructor(app: App, plugin: ObsidianSyncNotionPlugin, settingTab: ObsidianSettingTab, dbDetails: DatabaseDetails) {
|
||||||
|
super(app);
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.settingTab = settingTab;
|
||||||
|
this.dbDetails = dbDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
display() {
|
||||||
|
this.containerEl.addClass("delete-modal");
|
||||||
|
this.titleEl.setText('Delete Database');
|
||||||
|
|
||||||
|
let { contentEl } = this;
|
||||||
|
contentEl.empty();
|
||||||
|
|
||||||
|
const deleteDiv = contentEl.createDiv('delete-div');
|
||||||
|
deleteDiv.createEl('h4', { text: 'Are you sure you want to delete the following database?' });
|
||||||
|
deleteDiv.createEl('h2', { text: this.dbDetails.fullName + ' (' + this.dbDetails.abName + ', ' + this.dbDetails.format + ')' });
|
||||||
|
|
||||||
|
|
||||||
|
// add delete button
|
||||||
|
let footerEl = contentEl.createDiv('save-button');
|
||||||
|
let deleteButton = new Setting(footerEl)
|
||||||
|
|
||||||
|
deleteButton
|
||||||
|
.addButton((button: ButtonComponent): ButtonComponent => {
|
||||||
|
return button
|
||||||
|
.setTooltip("Delete")
|
||||||
|
.setIcon("trash")
|
||||||
|
.onClick(async () => {
|
||||||
|
this.data.deleted = true;
|
||||||
|
this.close();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
deleteButton.addExtraButton((button) => {
|
||||||
|
return button
|
||||||
|
.setTooltip('Cancel')
|
||||||
|
.setIcon('cross')
|
||||||
|
.onClick(() => {
|
||||||
|
this.data.deleted = false;
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onOpen() {
|
||||||
|
this.display();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
349
src/ui/EditModal.ts
Normal file
349
src/ui/EditModal.ts
Normal file
@@ -0,0 +1,349 @@
|
|||||||
|
import { App, ButtonComponent, Setting } from "obsidian";
|
||||||
|
import { customProperty, SettingModal } from "./settingModal";
|
||||||
|
import ObsidianSyncNotionPlugin from "../main";
|
||||||
|
import { DatabaseDetails, ObsidianSettingTab } from "./settingTabs";
|
||||||
|
import { i18nConfig } from "../lang/I18n";
|
||||||
|
|
||||||
|
export class EditModal extends SettingModal {
|
||||||
|
propertyLines: Setting[] = []; // Store all property line settings
|
||||||
|
[key: string]: any; // Index signature
|
||||||
|
dataTemp: Record<string, any> = {
|
||||||
|
databaseFormatTemp: '',
|
||||||
|
// databaseFormatTempInd: false,
|
||||||
|
databaseFullNameTemp: '',
|
||||||
|
// databaseFullNameTempInd: false,
|
||||||
|
databaseAbbreviateNameTemp: '',
|
||||||
|
// databaseAbbreviateNameTempInd: false,
|
||||||
|
notionAPITemp: '',
|
||||||
|
// notionAPITempInd: false,
|
||||||
|
databaseIDTemp: '',
|
||||||
|
// databaseIDTempInd: false,
|
||||||
|
tagButtonTemp: false,
|
||||||
|
// tagButtonTempInd: false,
|
||||||
|
customTitleButtonTemp: false,
|
||||||
|
// customTitleButtonTempInd: false,
|
||||||
|
customTitleNameTemp: '',
|
||||||
|
customPropertiesTemp: [],
|
||||||
|
// customTitleNameTempInd: false,
|
||||||
|
// customValues: '',
|
||||||
|
savedTemp: false,
|
||||||
|
savedTempInd: false,
|
||||||
|
};
|
||||||
|
dataPrev: Record<string, any> = {
|
||||||
|
databaseFormatPrev: '',
|
||||||
|
// databaseFormatPrevInd: false,
|
||||||
|
databaseFullNamePrev: '',
|
||||||
|
// databaseFullNamePrevInd: false,
|
||||||
|
databaseAbbreviateNamePrev: '',
|
||||||
|
// databaseAbbreviateNamePrevInd: false,
|
||||||
|
notionAPIPrev: '',
|
||||||
|
// notionAPIPrevInd: false,
|
||||||
|
databaseIDPrev: '',
|
||||||
|
// databaseIDPrevInd: false,
|
||||||
|
tagButtonPrev: false,
|
||||||
|
// tagButtonPrevInd: false,
|
||||||
|
customTitleButtonPrev: false,
|
||||||
|
// customTitleButtonPrevInd: false,
|
||||||
|
customTitleNamePrev: '',
|
||||||
|
customPropertiesPrev: [],
|
||||||
|
// customTitleNamePrevInd: false,
|
||||||
|
// customValues: '',
|
||||||
|
savedPrev: false,
|
||||||
|
savedPrevInd: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
plugin: ObsidianSyncNotionPlugin;
|
||||||
|
settingTab: ObsidianSettingTab;
|
||||||
|
dbDetails: DatabaseDetails;
|
||||||
|
|
||||||
|
constructor(app: App, plugin: ObsidianSyncNotionPlugin, settingTab: ObsidianSettingTab, dbDetails: DatabaseDetails) {
|
||||||
|
super(app, plugin, settingTab);
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.settingTab = settingTab;
|
||||||
|
if (dbDetails) {
|
||||||
|
// Temp details
|
||||||
|
this.dataTemp.databaseFormatTemp = dbDetails.format;
|
||||||
|
this.dataTemp.databaseFullNameTemp = dbDetails.fullName;
|
||||||
|
this.dataTemp.databaseAbbreviateNameTemp = dbDetails.abName;
|
||||||
|
this.dataTemp.notionAPITemp = dbDetails.notionAPI;
|
||||||
|
this.dataTemp.databaseIDTemp = dbDetails.databaseID;
|
||||||
|
this.dataTemp.tagButtonTemp = dbDetails.tagButton;
|
||||||
|
this.dataTemp.customTitleButtonTemp = dbDetails.customTitleButton;
|
||||||
|
this.dataTemp.customTitleNameTemp = dbDetails.customTitleName;
|
||||||
|
this.dataTemp.customPropertiesTemp = dbDetails.customProperties.map(prop => ({ ...prop })); // Ensure deep copy
|
||||||
|
// this.dataTemp.customValues = dbDetails.customValues;
|
||||||
|
this.dataTemp.savedTemp = dbDetails.saved;
|
||||||
|
|
||||||
|
// Prev details
|
||||||
|
this.dataPrev.databaseFormatPrev = dbDetails.format;
|
||||||
|
this.dataPrev.databaseFullNamePrev = dbDetails.fullName;
|
||||||
|
this.dataPrev.databaseAbbreviateNamePrev = dbDetails.abName;
|
||||||
|
this.dataPrev.notionAPIPrev = dbDetails.notionAPI;
|
||||||
|
this.dataPrev.databaseIDPrev = dbDetails.databaseID;
|
||||||
|
this.dataPrev.tagButtonPrev = dbDetails.tagButton;
|
||||||
|
this.dataPrev.customTitleButtonPrev = dbDetails.customTitleButton;
|
||||||
|
this.dataPrev.customTitleNamePrev = dbDetails.customTitleName;
|
||||||
|
this.dataPrev.customPropertiesPrev = dbDetails.customProperties.map(prop => ({ ...prop })); // Ensure deep copy
|
||||||
|
// this.dataTemp.customValues = dbDetails.customValues;
|
||||||
|
this.dataPrev.savedPrev = dbDetails.saved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
display(): void {
|
||||||
|
this.containerEl.addClass("edit-modal");
|
||||||
|
this.titleEl.setText('Edit Database');
|
||||||
|
|
||||||
|
let { contentEl } = this;
|
||||||
|
contentEl.empty();
|
||||||
|
|
||||||
|
const editDiv = contentEl.createDiv('edit-div');
|
||||||
|
const nextTabs = contentEl.createDiv('next-tabs');
|
||||||
|
|
||||||
|
|
||||||
|
new Setting(editDiv)
|
||||||
|
.setName(i18nConfig.databaseFormat)
|
||||||
|
.setDesc(i18nConfig.databaseFormatDesc)
|
||||||
|
.addDropdown((component) => {
|
||||||
|
component
|
||||||
|
.addOption('none', '')
|
||||||
|
.addOption('general', i18nConfig.databaseGeneral)
|
||||||
|
.addOption('next', i18nConfig.databaseNext)
|
||||||
|
.addOption('custom', i18nConfig.databaseCustom)
|
||||||
|
.setValue(this.dataTemp.databaseFormatTemp)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.dataTemp.databaseFormatTemp = value;
|
||||||
|
nextTabs.empty();
|
||||||
|
this.updateContentBasedOnSelection(value, nextTabs);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize content based on the current dropdown value
|
||||||
|
this.updateContentBasedOnSelection(this.dataTemp.databaseFormatTemp, nextTabs);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// add save button
|
||||||
|
let footerEl = contentEl.createDiv('save-button');
|
||||||
|
let saveButton = new Setting(footerEl)
|
||||||
|
saveButton.addButton((button: ButtonComponent) => {
|
||||||
|
return button
|
||||||
|
.setTooltip('Save')
|
||||||
|
.setIcon('checkmark')
|
||||||
|
.onClick(async () => {
|
||||||
|
this.dataTemp.savedTempInd = true;
|
||||||
|
this.dataTemp.savedTemp = true;
|
||||||
|
// console.log(this.dataTemp);
|
||||||
|
// console.log(this.dataPrev);
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
saveButton.addExtraButton((button) => {
|
||||||
|
return button
|
||||||
|
.setTooltip('Cancel')
|
||||||
|
.setIcon('cross')
|
||||||
|
.onClick(() => {
|
||||||
|
// console.log(this.dataTemp);
|
||||||
|
// console.log(this.dataPrev);
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onOpen(): void {
|
||||||
|
this.display()
|
||||||
|
}
|
||||||
|
|
||||||
|
updateContentBasedOnSelection(value: string, nextTabs: HTMLElement): void {
|
||||||
|
// Clear existing content
|
||||||
|
nextTabs.empty();
|
||||||
|
|
||||||
|
// Generate content based on the selected value
|
||||||
|
if (value === 'general') {
|
||||||
|
nextTabs.createEl('h3', { text: i18nConfig.NotionGeneralSettingHeader });
|
||||||
|
|
||||||
|
// add full name
|
||||||
|
this.createSettingEl(nextTabs, i18nConfig.databaseFullName, i18nConfig.databaseFullNameDesc, 'text', i18nConfig.databaseFullNameText, this.dataTemp.databaseFullNameTemp, 'dataTemp', 'databaseFullNameTemp')
|
||||||
|
|
||||||
|
// add abbreviate name
|
||||||
|
this.createSettingEl(nextTabs, i18nConfig.databaseAbbreviateName, i18nConfig.databaseAbbreviateNameDesc, 'text', i18nConfig.databaseAbbreviateNameText, this.dataTemp.databaseAbbreviateNameTemp, 'dataTemp', 'databaseAbbreviateNameTemp')
|
||||||
|
|
||||||
|
// tag button
|
||||||
|
this.createSettingEl(nextTabs, i18nConfig.NotionTagButton, i18nConfig.NotionTagButtonDesc, 'toggle', i18nConfig.NotionCustomTitleText, this.dataTemp.tagButtonTemp, 'dataTemp', 'tagButtonTemp')
|
||||||
|
|
||||||
|
// add custom title button
|
||||||
|
|
||||||
|
new Setting(nextTabs)
|
||||||
|
.setName(i18nConfig.NotionCustomTitle)
|
||||||
|
.setDesc(i18nConfig.NotionCustomTitleDesc)
|
||||||
|
.addToggle((toggle) =>
|
||||||
|
toggle
|
||||||
|
.setValue(this.dataTemp.customTitleButtonTemp)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.dataTemp.customTitleButtonTemp = value;
|
||||||
|
this.updateSettingEl(CustomNameEl, value)
|
||||||
|
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
// add custom title name
|
||||||
|
const CustomNameEl = this.createStyleDiv('custom-name', (this.dataTemp.customTitleButtonTemp), nextTabs);
|
||||||
|
this.createSettingEl(CustomNameEl, i18nConfig.NotionCustomTitleName, i18nConfig.NotionCustomTitleNameDesc, 'text', i18nConfig.NotionCustomTitleText, this.dataTemp.customTitleNameTemp, 'dataTemp', 'customTitleNameTemp')
|
||||||
|
|
||||||
|
|
||||||
|
// add api key
|
||||||
|
this.createSettingEl(nextTabs, i18nConfig.NotionAPI, i18nConfig.NotionAPIDesc, 'password', i18nConfig.NotionAPIText, this.dataTemp.notionAPITemp, 'dataTemp', 'notionAPITemp')
|
||||||
|
|
||||||
|
// add database id
|
||||||
|
this.createSettingEl(nextTabs, i18nConfig.DatabaseID, i18nConfig.DatabaseIDDesc, 'password', i18nConfig.DatabaseIDText, this.dataTemp.databaseIDTemp, 'dataTemp', 'databaseIDTemp')
|
||||||
|
|
||||||
|
|
||||||
|
} else if (value === 'next') {
|
||||||
|
|
||||||
|
nextTabs.createEl('h3', { text: i18nConfig.NotionNextSettingHeader });
|
||||||
|
|
||||||
|
// add full name
|
||||||
|
this.createSettingEl(nextTabs, i18nConfig.databaseFullName, i18nConfig.databaseFullNameDesc, 'text', i18nConfig.databaseFullNameText, this.dataTemp.databaseFullNameTemp, 'dataTemp', 'databaseFullNameTemp')
|
||||||
|
|
||||||
|
// add abbreviate name
|
||||||
|
this.createSettingEl(nextTabs, i18nConfig.databaseAbbreviateName, i18nConfig.databaseAbbreviateNameDesc, 'text', i18nConfig.databaseAbbreviateNameText, this.dataTemp.databaseAbbreviateNameTemp, 'dataTemp', 'databaseAbbreviateNameTemp')
|
||||||
|
|
||||||
|
// add api key
|
||||||
|
this.createSettingEl(nextTabs, i18nConfig.NotionAPI, i18nConfig.NotionAPIDesc, 'password', i18nConfig.NotionAPIText, this.dataTemp.notionAPITemp, 'dataTemp', 'notionAPITemp')
|
||||||
|
|
||||||
|
// add database id
|
||||||
|
this.createSettingEl(nextTabs, i18nConfig.DatabaseID, i18nConfig.DatabaseIDDesc, 'password', i18nConfig.DatabaseIDText, this.dataTemp.databaseIDTemp, 'dataTemp', 'databaseIDTemp')
|
||||||
|
|
||||||
|
} else if (value === 'custom') {
|
||||||
|
|
||||||
|
nextTabs.createEl('h3', { text: i18nConfig.NotionCustomSettingHeader });
|
||||||
|
|
||||||
|
// add full name
|
||||||
|
this.createSettingEl(nextTabs, i18nConfig.databaseFullName, i18nConfig.databaseFullNameDesc, 'text', i18nConfig.databaseFullNameText, this.dataTemp.databaseFullNameTemp, 'dataTemp', 'databaseFullNameTemp')
|
||||||
|
|
||||||
|
// add abbreviate name
|
||||||
|
this.createSettingEl(nextTabs, i18nConfig.databaseAbbreviateName, i18nConfig.databaseAbbreviateNameDesc, 'text', i18nConfig.databaseAbbreviateNameText, this.dataTemp.databaseAbbreviateNameTemp, 'dataTemp', 'databaseAbbreviateNameTemp')
|
||||||
|
|
||||||
|
// add api key
|
||||||
|
this.createSettingEl(nextTabs, i18nConfig.NotionAPI, i18nConfig.NotionAPIDesc, 'password', i18nConfig.NotionAPIText, this.dataTemp.notionAPITemp, 'dataTemp', 'notionAPITemp')
|
||||||
|
|
||||||
|
// add database id
|
||||||
|
this.createSettingEl(nextTabs, i18nConfig.DatabaseID, i18nConfig.DatabaseIDDesc, 'password', i18nConfig.DatabaseIDText, this.dataTemp.databaseIDTemp, 'dataTemp', 'databaseIDTemp')
|
||||||
|
|
||||||
|
// add custom properties
|
||||||
|
this.initializePropertyLines(nextTabs, this.dataTemp.customPropertiesTemp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initializePropertyLines(containerEl: HTMLElement, properties: customProperty[]): void {
|
||||||
|
if (!containerEl) {
|
||||||
|
console.error('Failed to initialize property lines: containerEl is null');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName("Add New Property")
|
||||||
|
.setDesc("Click to add a new property")
|
||||||
|
.addButton(button => {
|
||||||
|
return button
|
||||||
|
.setButtonText('Add')
|
||||||
|
.setTooltip('Add one more property')
|
||||||
|
.onClick(() => {
|
||||||
|
this.createPropertyLine(containerEl, properties);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
properties.forEach(property => {
|
||||||
|
this.updatePropertyLine(containerEl, property, properties);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
updatePropertyLine(containerEl: HTMLElement, property: customProperty, properties: customProperty[]) {
|
||||||
|
let isExistingProperty = property !== null;
|
||||||
|
const propertyIndex = isExistingProperty ? property.index : properties.length;
|
||||||
|
|
||||||
|
const propertyLine = new Setting(containerEl)
|
||||||
|
.setName(propertyIndex === 0 ? i18nConfig.CustomPropertyFirstColumn : `${i18nConfig.CustomProperty} ${propertyIndex}`)
|
||||||
|
.setDesc(propertyIndex === 0 ? i18nConfig.CustomPropertyFirstColumnDesc : "");
|
||||||
|
|
||||||
|
propertyLine.addText(text => {
|
||||||
|
text.setPlaceholder(i18nConfig.CustomPropertyName)
|
||||||
|
.setValue(isExistingProperty ? property.customName : "")
|
||||||
|
.onChange(value => {
|
||||||
|
const actualIndex = properties.findIndex(p => p.index === propertyIndex);
|
||||||
|
if (actualIndex !== -1) {
|
||||||
|
properties[actualIndex].customName = value;
|
||||||
|
} else {
|
||||||
|
properties.push({ customName: value, customType: '', index: propertyIndex });
|
||||||
|
isExistingProperty = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
propertyLine.addDropdown((dropdown) => {
|
||||||
|
const options: Record<string, string> = {
|
||||||
|
'text': 'Text',
|
||||||
|
'number': 'Number',
|
||||||
|
'select': 'Select',
|
||||||
|
'multi_select': 'Multi-Select',
|
||||||
|
'date': 'Date',
|
||||||
|
'files': 'Files & Media',
|
||||||
|
'checkbox': 'Checkbox',
|
||||||
|
'url': 'URL',
|
||||||
|
'email': 'Email',
|
||||||
|
'phone_number': 'Phone Number'
|
||||||
|
};
|
||||||
|
|
||||||
|
if (propertyIndex === 0) {
|
||||||
|
dropdown.addOption('title', 'Title');
|
||||||
|
} else {
|
||||||
|
Object.keys(options).forEach(key => {
|
||||||
|
dropdown.addOption(key, options[key]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
dropdown.setValue(isExistingProperty ? property.customType : "")
|
||||||
|
.onChange(value => {
|
||||||
|
const actualIndex = properties.findIndex(p => p.index === propertyIndex);
|
||||||
|
if (actualIndex !== -1) {
|
||||||
|
properties[actualIndex].customType = value;
|
||||||
|
} else if (!isExistingProperty) {
|
||||||
|
properties.push({ customName: '', customType: value, index: propertyIndex });
|
||||||
|
isExistingProperty = true; // Update the flag to prevent re-adding
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (propertyIndex > 0) {
|
||||||
|
propertyLine.addButton(button => {
|
||||||
|
return button
|
||||||
|
.setTooltip("Delete")
|
||||||
|
.setIcon("trash")
|
||||||
|
.onClick(() => {
|
||||||
|
console.log('Deleting property', properties[propertyIndex]);
|
||||||
|
this.deleteProperty(propertyIndex, properties);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.propertyLines.push(propertyLine);
|
||||||
|
this.updatePropertyLines();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
createStyleDiv(className: string, commandValue: boolean = false, parentEl: HTMLElement): HTMLDivElement {
|
||||||
|
return super.createStyleDiv(className, commandValue, parentEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSettingEl(element: HTMLElement, commandValue: boolean) {
|
||||||
|
super.updateSettingEl(element, commandValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
createSettingEl(contentEl: HTMLElement, name: string, desc: string, type: string, placeholder: string, holderValue: any, dataRecord: string, settingsKey: string): Setting {
|
||||||
|
return super.createSettingEl(contentEl, name, desc, type, placeholder, holderValue, dataRecord, settingsKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
178
src/ui/PreviewModal.ts
Normal file
178
src/ui/PreviewModal.ts
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
import { App, ExtraButtonComponent, Modal, Notice, Setting } from "obsidian";
|
||||||
|
import ObsidianSyncNotionPlugin from "../main";
|
||||||
|
import { DatabaseDetails, ObsidianSettingTab } from "./settingTabs";
|
||||||
|
import { customProperty } from "./settingModal";
|
||||||
|
import { i18nConfig } from "../lang/I18n";
|
||||||
|
|
||||||
|
export class PreviewModal extends Modal {
|
||||||
|
plugin: ObsidianSyncNotionPlugin;
|
||||||
|
settingTab: ObsidianSettingTab;
|
||||||
|
dbDetails: DatabaseDetails;
|
||||||
|
|
||||||
|
constructor(app: App, plugin: ObsidianSyncNotionPlugin, settingTab: ObsidianSettingTab, dbDetails: DatabaseDetails) {
|
||||||
|
super(app);
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.settingTab = settingTab;
|
||||||
|
this.dbDetails = dbDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
display(): void {
|
||||||
|
this.containerEl.addClass('preview-modal')
|
||||||
|
this.titleEl.setText('Preview')
|
||||||
|
|
||||||
|
let { contentEl } = this;
|
||||||
|
|
||||||
|
const previewEl = contentEl.createDiv('preview-content')
|
||||||
|
|
||||||
|
const dbFormatEl = new Setting(previewEl)
|
||||||
|
dbFormatEl
|
||||||
|
.setName('Database Format')
|
||||||
|
.addText(text => text
|
||||||
|
.setValue(this.dbDetails.format)
|
||||||
|
.setDisabled(true));
|
||||||
|
|
||||||
|
const dbFullEl = new Setting(previewEl)
|
||||||
|
dbFullEl
|
||||||
|
.setName('Database Full Name')
|
||||||
|
.addText(text => text
|
||||||
|
.setValue(this.dbDetails.fullName)
|
||||||
|
.setDisabled(true));
|
||||||
|
|
||||||
|
const dbAbbrEl = new Setting(previewEl)
|
||||||
|
dbAbbrEl
|
||||||
|
.setName('Database Abbreviate Name')
|
||||||
|
.addText(text => text
|
||||||
|
.setValue(this.dbDetails.abName)
|
||||||
|
.setDisabled(true));
|
||||||
|
// .controlEl.createEl('p', { text: this.dbDetails.abName })
|
||||||
|
|
||||||
|
// Setting for toggle and copy buttons
|
||||||
|
new Setting(previewEl)
|
||||||
|
.setName('Notion API Key')
|
||||||
|
.addExtraButton((button: ExtraButtonComponent) => {
|
||||||
|
let isApiKeyVisible = false;
|
||||||
|
|
||||||
|
return button
|
||||||
|
.setTooltip('Toggle API Key Visibility')
|
||||||
|
.setIcon('eye')
|
||||||
|
.onClick(() => {
|
||||||
|
isApiKeyVisible = !isApiKeyVisible;
|
||||||
|
button.setIcon(isApiKeyVisible ? 'eye-off' : 'eye');
|
||||||
|
apiKeySetting.settingEl.style.display = isApiKeyVisible ? '' : 'none'; // Toggle visibility
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// Setting for displaying the API key
|
||||||
|
const apiKeySetting = new Setting(previewEl)
|
||||||
|
|
||||||
|
apiKeySetting.infoEl.createEl('p', { text: this.dbDetails.notionAPI })
|
||||||
|
|
||||||
|
|
||||||
|
apiKeySetting
|
||||||
|
.addExtraButton((button: ExtraButtonComponent) => {
|
||||||
|
return button
|
||||||
|
.setTooltip('Copy API Key')
|
||||||
|
.setIcon('clipboard')
|
||||||
|
.onClick(() => {
|
||||||
|
navigator.clipboard.writeText(this.dbDetails.notionAPI)
|
||||||
|
new Notice('API Key copied to clipboard');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
apiKeySetting.settingEl.style.display = 'none'; // Hide initially
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
new Setting(previewEl)
|
||||||
|
.setName('Database ID')
|
||||||
|
.addExtraButton((button: ExtraButtonComponent) => {
|
||||||
|
let isDbIdVisible = false;
|
||||||
|
|
||||||
|
return button
|
||||||
|
.setTooltip('Toggle Database ID Visibility')
|
||||||
|
.setIcon('eye')
|
||||||
|
.onClick(() => {
|
||||||
|
|
||||||
|
isDbIdVisible = !isDbIdVisible;
|
||||||
|
button.setIcon(isDbIdVisible ? 'eye-off' : 'eye');
|
||||||
|
dbIdSetting.settingEl.style.display = isDbIdVisible ? '' : 'none'; // Toggle visibility
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
const dbIdSetting = new Setting(previewEl)
|
||||||
|
|
||||||
|
dbIdSetting.infoEl.createEl('p', { text: this.dbDetails.databaseID })
|
||||||
|
|
||||||
|
dbIdSetting
|
||||||
|
.addExtraButton((button: ExtraButtonComponent) => {
|
||||||
|
return button
|
||||||
|
.setTooltip('Copy Database ID')
|
||||||
|
.setIcon('clipboard')
|
||||||
|
.onClick(() => {
|
||||||
|
navigator.clipboard.writeText(this.dbDetails.databaseID)
|
||||||
|
new Notice('Database ID copied to clipboard');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
dbIdSetting.settingEl.style.display = 'none'; // Hide initially
|
||||||
|
|
||||||
|
// Preview the custom properties
|
||||||
|
|
||||||
|
if (this.dbDetails.format === 'custom') {
|
||||||
|
|
||||||
|
const customPrv = previewEl.createDiv("custom-tabs");
|
||||||
|
|
||||||
|
this.previewPropertyLine(previewEl, this.dbDetails.customProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onOpen() {
|
||||||
|
this.display()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
previewPropertyLine(containerEl: HTMLElement, properties: customProperty[]): void {
|
||||||
|
|
||||||
|
properties.forEach((property, index) => {
|
||||||
|
const propertyLine = new Setting(containerEl)
|
||||||
|
.setName(index === 0 ? i18nConfig.CustomPropertyFirstColumn : `${i18nConfig.CustomProperty} ${index}`)
|
||||||
|
.setDesc(index === 0 ? i18nConfig.CustomPropertyFirstColumnDesc : "");
|
||||||
|
|
||||||
|
propertyLine.addText(text => {
|
||||||
|
text.setPlaceholder(i18nConfig.CustomPropertyName)
|
||||||
|
.setValue(property.customName)
|
||||||
|
.setDisabled(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
propertyLine.addDropdown((dropdown) => {
|
||||||
|
const options: Record<string, string> = {
|
||||||
|
'title': 'Title',
|
||||||
|
'text': 'Text',
|
||||||
|
'number': 'Number',
|
||||||
|
'select': 'Select',
|
||||||
|
'multi_select': 'Multi-Select',
|
||||||
|
'date': 'Date',
|
||||||
|
'files': 'Files & Media',
|
||||||
|
'checkbox': 'Checkbox',
|
||||||
|
'url': 'URL',
|
||||||
|
'email': 'Email',
|
||||||
|
'phone_number': 'Phone Number',
|
||||||
|
// Additional options can be added here
|
||||||
|
};
|
||||||
|
|
||||||
|
// Populate dropdown with options
|
||||||
|
Object.keys(options).forEach(key => {
|
||||||
|
dropdown.addOption(key, options[key]);
|
||||||
|
});
|
||||||
|
|
||||||
|
dropdown.setValue(property.customType)
|
||||||
|
.setDisabled(true); // Disable dropdown to prevent changes
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
377
src/ui/settingModal.ts
Normal file
377
src/ui/settingModal.ts
Normal file
@@ -0,0 +1,377 @@
|
|||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
Setting,
|
||||||
|
ButtonComponent, App
|
||||||
|
} from 'obsidian';
|
||||||
|
|
||||||
|
import { i18nConfig } from "../lang/I18n";
|
||||||
|
import ObsidianSyncNotionPlugin from "../main";
|
||||||
|
import { DatabaseDetails, ObsidianSettingTab } from "./settingTabs";
|
||||||
|
|
||||||
|
export interface customProperty {
|
||||||
|
customName: string;
|
||||||
|
customType: string;
|
||||||
|
index: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SettingModal extends Modal {
|
||||||
|
propertyLines: Setting[] = []; // Store all property line settings
|
||||||
|
properties: customProperty[] = []; // Array to store property values and types
|
||||||
|
[key: string]: any; // Index signature
|
||||||
|
data: Record<string, any> = {
|
||||||
|
databaseFormat: 'none',
|
||||||
|
databaseFullName: '',
|
||||||
|
databaseAbbreviateName: '',
|
||||||
|
notionAPI: '',
|
||||||
|
databaseID: '',
|
||||||
|
tagButton: true,
|
||||||
|
customTitleButton: false,
|
||||||
|
customTitleName: '',
|
||||||
|
customProperties: [],
|
||||||
|
// customValues: '',
|
||||||
|
saved: false,
|
||||||
|
};
|
||||||
|
plugin: ObsidianSyncNotionPlugin;
|
||||||
|
settingTab: ObsidianSettingTab;
|
||||||
|
|
||||||
|
constructor(app: App, plugin: ObsidianSyncNotionPlugin, settingTab: ObsidianSettingTab, dbDetails?: DatabaseDetails) {
|
||||||
|
super(app);
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.settingTab = settingTab;
|
||||||
|
this.properties = [];
|
||||||
|
if (dbDetails) {
|
||||||
|
this.data.databaseFormat = dbDetails.format;
|
||||||
|
this.data.databaseFullName = dbDetails.fullName;
|
||||||
|
this.data.databaseAbbreviateName = dbDetails.abName;
|
||||||
|
this.data.notionAPI = dbDetails.notionAPI;
|
||||||
|
this.data.databaseID = dbDetails.databaseID;
|
||||||
|
this.data.tagButton = dbDetails.tagButton;
|
||||||
|
this.data.customTitleButton = dbDetails.customTitleButton;
|
||||||
|
this.data.customTitleName = dbDetails.customTitleName;
|
||||||
|
this.data.customProperties = dbDetails.customProperties;
|
||||||
|
// this.data.customValues = dbDetails.customValues;
|
||||||
|
this.data.saved = dbDetails.saved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
display(): void {
|
||||||
|
this.containerEl.addClass("settings-modal");
|
||||||
|
this.titleEl.setText('Add new database');
|
||||||
|
|
||||||
|
// create the dropdown button to select the database format
|
||||||
|
let { contentEl } = this;
|
||||||
|
contentEl.empty();
|
||||||
|
|
||||||
|
const settingDiv = contentEl.createDiv('setting-div');
|
||||||
|
const nextTabs = contentEl.createDiv('next-tabs');
|
||||||
|
|
||||||
|
|
||||||
|
new Setting(settingDiv)
|
||||||
|
.setName(i18nConfig.databaseFormat)
|
||||||
|
.setDesc(i18nConfig.databaseFormatDesc)
|
||||||
|
.addDropdown((component) => {
|
||||||
|
component
|
||||||
|
.addOption('none', '')
|
||||||
|
.addOption('general', i18nConfig.databaseGeneral)
|
||||||
|
.addOption('next', i18nConfig.databaseNext)
|
||||||
|
.addOption('custom', i18nConfig.databaseCustom)
|
||||||
|
.setValue(this.data.databaseFormat)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.data.databaseFormat = value;
|
||||||
|
nextTabs.empty();
|
||||||
|
this.updateContentBasedOnSelection(value, nextTabs);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize content based on the current dropdown value
|
||||||
|
(this.data.saved) ? this.updateContentBasedOnSelection(this.data.databaseFormat, nextTabs) : this.updateContentBasedOnSelection(this.plugin.settings.databaseFormat, nextTabs);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// add save button
|
||||||
|
let footerEl = contentEl.createDiv('save-button');
|
||||||
|
let saveButton = new Setting(footerEl)
|
||||||
|
saveButton.addButton((button: ButtonComponent) => {
|
||||||
|
return button
|
||||||
|
.setTooltip('Save')
|
||||||
|
.setIcon('checkmark')
|
||||||
|
.onClick(async () => {
|
||||||
|
this.data.saved = true;
|
||||||
|
this.data.customProperties = this.properties;
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
saveButton.addExtraButton((button) => {
|
||||||
|
return button
|
||||||
|
.setTooltip('Cancel')
|
||||||
|
.setIcon('cross')
|
||||||
|
.onClick(() => {
|
||||||
|
this.data.saved = false;
|
||||||
|
// this.data.customProperties = this.properties;
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateContentBasedOnSelection(value: string, nextTabs: HTMLElement): void {
|
||||||
|
// Clear existing content
|
||||||
|
nextTabs.empty();
|
||||||
|
|
||||||
|
// Generate content based on the selected value
|
||||||
|
if (value === 'general') {
|
||||||
|
nextTabs.createEl('h3', { text: i18nConfig.NotionGeneralSettingHeader });
|
||||||
|
|
||||||
|
// add full name
|
||||||
|
this.createSettingEl(nextTabs, i18nConfig.databaseFullName, i18nConfig.databaseFullNameDesc, 'text', i18nConfig.databaseFullNameText, this.data.databaseFullName, 'data', 'databaseFullName')
|
||||||
|
|
||||||
|
// add abbreviate name
|
||||||
|
this.createSettingEl(nextTabs, i18nConfig.databaseAbbreviateName, i18nConfig.databaseAbbreviateNameDesc, 'text', i18nConfig.databaseAbbreviateNameText, this.data.databaseAbbreviateName, 'data', 'databaseAbbreviateName')
|
||||||
|
|
||||||
|
// tag button
|
||||||
|
this.createSettingEl(nextTabs, i18nConfig.NotionTagButton, i18nConfig.NotionTagButtonDesc, 'toggle', i18nConfig.NotionCustomTitleText, this.data.tagButton, 'data', 'tagButton')
|
||||||
|
|
||||||
|
// add custom title button
|
||||||
|
|
||||||
|
new Setting(nextTabs)
|
||||||
|
.setName(i18nConfig.NotionCustomTitle)
|
||||||
|
.setDesc(i18nConfig.NotionCustomTitleDesc)
|
||||||
|
.addToggle((toggle) =>
|
||||||
|
toggle
|
||||||
|
.setValue(this.data.customTitleButton)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.data.customTitleButton = value;
|
||||||
|
this.updateSettingEl(CustomNameEl, value)
|
||||||
|
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
// add custom title name
|
||||||
|
const CustomNameEl = this.createStyleDiv('custom-name', (this.data.customTitleButton), nextTabs);
|
||||||
|
this.createSettingEl(CustomNameEl, i18nConfig.NotionCustomTitleName, i18nConfig.NotionCustomTitleNameDesc, 'text', i18nConfig.NotionCustomTitleText, this.data.customTitleName, 'data', 'customTitleName')
|
||||||
|
|
||||||
|
|
||||||
|
// add api key
|
||||||
|
this.createSettingEl(nextTabs, i18nConfig.NotionAPI, i18nConfig.NotionAPIDesc, 'password', i18nConfig.NotionAPIText, this.data.notionAPI, 'data', 'notionAPI')
|
||||||
|
|
||||||
|
// add database id
|
||||||
|
this.createSettingEl(nextTabs, i18nConfig.DatabaseID, i18nConfig.DatabaseIDDesc, 'password', i18nConfig.DatabaseIDText, this.data.databaseID, 'data', 'databaseID')
|
||||||
|
|
||||||
|
|
||||||
|
} else if (value === 'next') {
|
||||||
|
|
||||||
|
nextTabs.createEl('h3', { text: i18nConfig.NotionNextSettingHeader });
|
||||||
|
|
||||||
|
// add full name
|
||||||
|
this.createSettingEl(nextTabs, i18nConfig.databaseFullName, i18nConfig.databaseFullNameDesc, 'text', i18nConfig.databaseFullNameText, this.data.databaseFullName, 'data', 'databaseFullName')
|
||||||
|
|
||||||
|
// add abbreviate name
|
||||||
|
this.createSettingEl(nextTabs, i18nConfig.databaseAbbreviateName, i18nConfig.databaseAbbreviateNameDesc, 'text', i18nConfig.databaseAbbreviateNameText, this.data.databaseAbbreviateName, 'data', 'databaseAbbreviateName')
|
||||||
|
|
||||||
|
// add api key
|
||||||
|
this.createSettingEl(nextTabs, i18nConfig.NotionAPI, i18nConfig.NotionAPIDesc, 'password', i18nConfig.NotionAPIText, this.data.notionAPI, 'data', 'notionAPI')
|
||||||
|
|
||||||
|
|
||||||
|
// add database id
|
||||||
|
this.createSettingEl(nextTabs, i18nConfig.DatabaseID, i18nConfig.DatabaseIDDesc, 'password', i18nConfig.DatabaseIDText, this.data.databaseID, 'data', 'databaseID')
|
||||||
|
|
||||||
|
} else if (value === 'custom') {
|
||||||
|
|
||||||
|
nextTabs.createEl('h3', { text: i18nConfig.NotionCustomSettingHeader });
|
||||||
|
|
||||||
|
// add full name
|
||||||
|
this.createSettingEl(nextTabs, i18nConfig.databaseFullName, i18nConfig.databaseFullNameDesc, 'text', i18nConfig.databaseFullNameText, this.data.databaseFullName, 'data', 'databaseFullName')
|
||||||
|
|
||||||
|
// add abbreviate name
|
||||||
|
this.createSettingEl(nextTabs, i18nConfig.databaseAbbreviateName, i18nConfig.databaseAbbreviateNameDesc, 'text', i18nConfig.databaseAbbreviateNameText, this.data.databaseAbbreviateName, 'data', 'databaseAbbreviateName')
|
||||||
|
|
||||||
|
// add api key
|
||||||
|
this.createSettingEl(nextTabs, i18nConfig.NotionAPI, i18nConfig.NotionAPIDesc, 'password', i18nConfig.NotionAPIText, this.data.notionAPI, 'data', 'notionAPI')
|
||||||
|
|
||||||
|
// add database id
|
||||||
|
this.createSettingEl(nextTabs, i18nConfig.DatabaseID, i18nConfig.DatabaseIDDesc, 'password', i18nConfig.DatabaseIDText, this.data.databaseID, 'data', 'databaseID')
|
||||||
|
|
||||||
|
// add new property button
|
||||||
|
new Setting(nextTabs)
|
||||||
|
.setName(i18nConfig.NotionCustomValues)
|
||||||
|
.setDesc(i18nConfig.NotionCustomValuesDesc)
|
||||||
|
.addButton((button: ButtonComponent) => {
|
||||||
|
return button
|
||||||
|
.setTooltip('Add one more property')
|
||||||
|
.setButtonText('Add New Property')
|
||||||
|
.onClick(async () => {
|
||||||
|
const customTabs = nextTabs.createDiv("custom-tabs");
|
||||||
|
this.createPropertyLine(customTabs, this.properties);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onOpen() {
|
||||||
|
// add console log to check if the modal is opened
|
||||||
|
this.display()
|
||||||
|
}
|
||||||
|
|
||||||
|
createPropertyLine(containerEl: HTMLElement, properties: customProperty[]): void {
|
||||||
|
const propertyIndex = properties.length;
|
||||||
|
|
||||||
|
properties.push({ customName: "", customType: "", index: propertyIndex });
|
||||||
|
|
||||||
|
const propertyLine = new Setting(containerEl)
|
||||||
|
.setName(propertyIndex === 0 ? i18nConfig.CustomPropertyFirstColumn : `${i18nConfig.CustomProperty} ${propertyIndex}`)
|
||||||
|
.setDesc(propertyIndex === 0 ? i18nConfig.CustomPropertyFirstColumnDesc : "");
|
||||||
|
|
||||||
|
propertyLine.addText(text => {
|
||||||
|
text.setPlaceholder(i18nConfig.CustomPropertyName)
|
||||||
|
.setValue("")
|
||||||
|
.onChange(value => {
|
||||||
|
let actualIndex = properties.findIndex(p => p.index === propertyIndex);
|
||||||
|
if (actualIndex !== -1) {
|
||||||
|
properties[actualIndex].customName = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
propertyLine.addDropdown((dropdown) => {
|
||||||
|
const options: Record<string, string> = {
|
||||||
|
'text': 'Text',
|
||||||
|
'number': 'Number',
|
||||||
|
'select': 'Select',
|
||||||
|
'multi_select': 'Multi-Select',
|
||||||
|
'date': 'Date',
|
||||||
|
'files': 'Files & Media',
|
||||||
|
'checkbox': 'Checkbox',
|
||||||
|
'url': 'URL',
|
||||||
|
'email': 'Email',
|
||||||
|
'phone_number': 'Phone Number',
|
||||||
|
// 'formula': 'Formula',
|
||||||
|
// 'relation': 'Relation',
|
||||||
|
// 'rollup': 'Rollup',
|
||||||
|
// 'created_time': 'Created time',
|
||||||
|
// 'created_by': 'Created by',
|
||||||
|
// 'last_edited_time': 'Last Edited Time',
|
||||||
|
// 'last_edited_by': 'Last Edited By',
|
||||||
|
};
|
||||||
|
|
||||||
|
const currentProperty = properties[propertyIndex];
|
||||||
|
|
||||||
|
if (propertyIndex === 0) {
|
||||||
|
dropdown.addOption("title", "Title");
|
||||||
|
} else {
|
||||||
|
Object.keys(options).forEach(key => {
|
||||||
|
dropdown.addOption(key, options[key]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
dropdown.setValue("")
|
||||||
|
.onChange(value => {
|
||||||
|
if (currentProperty) {
|
||||||
|
currentProperty.customType = value;
|
||||||
|
// Retrieve the index of currentProperty from the properties array
|
||||||
|
const updatedIndex = properties.findIndex(p => p === currentProperty);
|
||||||
|
console.log(`Updated value at index ${updatedIndex}: ${value}`);
|
||||||
|
} else {
|
||||||
|
console.log("Property not found, may have been deleted.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
if (propertyIndex > 0) {
|
||||||
|
propertyLine.addButton(button => {
|
||||||
|
return button
|
||||||
|
.setTooltip("Delete")
|
||||||
|
.setIcon("trash")
|
||||||
|
.onClick(() => {
|
||||||
|
this.deleteProperty(propertyIndex, properties);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.propertyLines.push(propertyLine);
|
||||||
|
this.updatePropertyLines(); // Ensure property lines are updated after creation
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteProperty(propertyIndex: number, properties: customProperty[]): void {
|
||||||
|
let actualIndex = properties.findIndex(p => p.index === propertyIndex);
|
||||||
|
if (actualIndex !== -1) {
|
||||||
|
properties.splice(actualIndex, 1);
|
||||||
|
if (this.propertyLines[actualIndex]) {
|
||||||
|
this.propertyLines[actualIndex].settingEl.remove();
|
||||||
|
this.propertyLines.splice(actualIndex, 1);
|
||||||
|
}
|
||||||
|
// Update indices in the properties array
|
||||||
|
properties.forEach((prop, idx) => {
|
||||||
|
prop.index = idx;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.updatePropertyLines();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePropertyLines() {
|
||||||
|
this.propertyLines.forEach((line, idx) => {
|
||||||
|
line.setName(idx === 0 ? i18nConfig.CustomPropertyFirstColumn : `${i18nConfig.CustomProperty} ${idx}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a function to create a div with a style for pop over elements
|
||||||
|
public createStyleDiv(className: string, commandValue: boolean = false, parentEl: HTMLElement) {
|
||||||
|
return parentEl.createDiv(className, (div) => {
|
||||||
|
this.updateSettingEl(div, commandValue);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the setting display style in the setting tab
|
||||||
|
public updateSettingEl(element: HTMLElement, commandValue: boolean) {
|
||||||
|
element.style.borderTop = commandValue ? "1px solid var(--background-modifier-border)" : "none";
|
||||||
|
element.style.paddingTop = commandValue ? "0.75em" : "0";
|
||||||
|
element.style.display = commandValue ? "block" : "none";
|
||||||
|
element.style.alignItems = "center";
|
||||||
|
}
|
||||||
|
|
||||||
|
// function to add one setting element in the setting tab.
|
||||||
|
public createSettingEl(contentEl: HTMLElement, name: string, desc: string, type: string, placeholder: string, holderValue: any, dataRecord: string, settingsKey: string) {
|
||||||
|
if (type === 'password') {
|
||||||
|
return new Setting(contentEl)
|
||||||
|
.setName(name)
|
||||||
|
.setDesc(desc)
|
||||||
|
.addText((text) => {
|
||||||
|
text.inputEl.type = type;
|
||||||
|
return text
|
||||||
|
.setPlaceholder(placeholder)
|
||||||
|
.setValue(holderValue)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this[dataRecord][settingsKey] = value; // Update the settings dictionary await this.plugin.saveSettings();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
} else if (type === 'toggle') {
|
||||||
|
return new Setting(contentEl)
|
||||||
|
.setName(name)
|
||||||
|
.setDesc(desc)
|
||||||
|
.addToggle((toggle) =>
|
||||||
|
toggle
|
||||||
|
.setValue(holderValue)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this[dataRecord][settingsKey] = value; // Update the settings dictionary await this.plugin.saveSettings();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else if (type === 'text') {
|
||||||
|
return new Setting(contentEl)
|
||||||
|
.setName(name)
|
||||||
|
.setDesc(desc)
|
||||||
|
.addText((text) =>
|
||||||
|
text
|
||||||
|
.setPlaceholder(placeholder)
|
||||||
|
.setValue(holderValue)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this[dataRecord][settingsKey] = value; // Update the settings dictionary await this.plugin.saveSettings();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
302
src/ui/settingTabs.ts
Normal file
302
src/ui/settingTabs.ts
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
import { App, ButtonComponent, PluginSettingTab, Setting } from "obsidian";
|
||||||
|
import { i18nConfig } from "../lang/I18n";
|
||||||
|
import ObsidianSyncNotionPlugin from "../main";
|
||||||
|
import { SettingModal } from "./settingModal";
|
||||||
|
import { PreviewModal } from "./PreviewModal";
|
||||||
|
import { EditModal } from "./EditModal";
|
||||||
|
import { DeleteModal } from "./DeleteModal";
|
||||||
|
|
||||||
|
export interface PluginSettings {
|
||||||
|
NextButton: boolean;
|
||||||
|
notionAPINext: string;
|
||||||
|
databaseIDNext: string;
|
||||||
|
bannerUrl: string;
|
||||||
|
notionUser: string;
|
||||||
|
proxy: string;
|
||||||
|
GeneralButton: boolean;
|
||||||
|
tagButton: boolean;
|
||||||
|
customTitleButton: boolean;
|
||||||
|
customTitleName: string;
|
||||||
|
notionAPIGeneral: string;
|
||||||
|
databaseIDGeneral: string;
|
||||||
|
CustomButton: boolean;
|
||||||
|
CustomValues: string;
|
||||||
|
notionAPICustom: string;
|
||||||
|
databaseIDCustom: string;
|
||||||
|
[key: string]: any;
|
||||||
|
databaseDetails: Record<string, DatabaseDetails>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DatabaseDetails {
|
||||||
|
format: string;
|
||||||
|
fullName: string;
|
||||||
|
abName: string;
|
||||||
|
notionAPI: string;
|
||||||
|
databaseID: string;
|
||||||
|
tagButton: boolean;
|
||||||
|
customTitleButton: boolean;
|
||||||
|
customTitleName: string;
|
||||||
|
customProperties: { customName: string, customType: string, index: number }[];
|
||||||
|
// customValues: string;
|
||||||
|
saved: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DEFAULT_SETTINGS: PluginSettings = {
|
||||||
|
NextButton: true,
|
||||||
|
notionAPINext: "",
|
||||||
|
databaseIDNext: "",
|
||||||
|
bannerUrl: "",
|
||||||
|
notionUser: "",
|
||||||
|
proxy: "",
|
||||||
|
GeneralButton: true,
|
||||||
|
tagButton: true,
|
||||||
|
customTitleButton: false,
|
||||||
|
customTitleName: "",
|
||||||
|
notionAPIGeneral: "",
|
||||||
|
databaseIDGeneral: "",
|
||||||
|
CustomButton: false,
|
||||||
|
CustomValues: "",
|
||||||
|
notionAPICustom: "",
|
||||||
|
databaseIDCustom: "",
|
||||||
|
databaseDetails: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export class ObsidianSettingTab extends PluginSettingTab {
|
||||||
|
plugin: ObsidianSyncNotionPlugin;
|
||||||
|
databaseEl: HTMLDivElement;
|
||||||
|
|
||||||
|
constructor(app: App, plugin: ObsidianSyncNotionPlugin) {
|
||||||
|
super(app, plugin);
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
display(): void {
|
||||||
|
const { containerEl } = this;
|
||||||
|
|
||||||
|
containerEl.empty();
|
||||||
|
|
||||||
|
// General Settings
|
||||||
|
containerEl.createEl('h2', { text: i18nConfig.GeneralSetting });
|
||||||
|
|
||||||
|
this.createSettingEl(containerEl, i18nConfig.BannerUrl, i18nConfig.BannerUrlDesc, 'text', i18nConfig.BannerUrlText, this.plugin.settings.bannerUrl, 'bannerUrl')
|
||||||
|
|
||||||
|
this.createSettingEl(containerEl, i18nConfig.NotionUser, i18nConfig.NotionUserDesc, 'text', i18nConfig.NotionUserText, this.plugin.settings.notionUser, 'notionUser')
|
||||||
|
|
||||||
|
// add new button
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName("Add New Database")
|
||||||
|
.setDesc("Add New Database")
|
||||||
|
.addButton((button: ButtonComponent): ButtonComponent => {
|
||||||
|
return button
|
||||||
|
.setTooltip("Add New Database")
|
||||||
|
.setIcon("plus")
|
||||||
|
.onClick(async () => {
|
||||||
|
let modal = new SettingModal(this.app, this.plugin, this);
|
||||||
|
|
||||||
|
modal.onClose = () => {
|
||||||
|
if (modal.data.saved) {
|
||||||
|
const dbDetails = {
|
||||||
|
format: modal.data.databaseFormat,
|
||||||
|
fullName: modal.data.databaseFullName,
|
||||||
|
abName: modal.data.databaseAbbreviateName,
|
||||||
|
notionAPI: modal.data.notionAPI,
|
||||||
|
databaseID: modal.data.databaseID,
|
||||||
|
tagButton: modal.data.tagButton,
|
||||||
|
customTitleButton: modal.data.customTitleButton,
|
||||||
|
customTitleName: modal.data.customTitleName,
|
||||||
|
customProperties: modal.data.customProperties,
|
||||||
|
// customValues: modal.data.customValues,
|
||||||
|
saved: modal.data.saved,
|
||||||
|
}
|
||||||
|
|
||||||
|
this.plugin.addDatabaseDetails(dbDetails);
|
||||||
|
|
||||||
|
this.plugin.commands.updateCommand();
|
||||||
|
|
||||||
|
this.display()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modal.open();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// new section to display all created database
|
||||||
|
containerEl.createEl('h2', { text: "Database List" });
|
||||||
|
|
||||||
|
this.databaseEl = containerEl.createDiv('database-list');
|
||||||
|
// list all created database
|
||||||
|
this.showDatabase();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a function to create a div with a style for pop over elements
|
||||||
|
// public createStyleDiv(className: string, commandValue: boolean = false) {
|
||||||
|
// return this.containerEl.createDiv(className, (div) => {
|
||||||
|
// this.updateSettingEl(div, commandValue);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// update the setting display style in the setting tab
|
||||||
|
public updateSettingEl(element: HTMLElement, commandValue: boolean) {
|
||||||
|
element.style.borderTop = commandValue ? "1px solid var(--background-modifier-border)" : "none";
|
||||||
|
element.style.paddingTop = commandValue ? "0.75em" : "0";
|
||||||
|
element.style.display = commandValue ? "block" : "none";
|
||||||
|
element.style.alignItems = "center";
|
||||||
|
}
|
||||||
|
|
||||||
|
// function to add one setting element in the setting tab.
|
||||||
|
public createSettingEl(containerEl: HTMLElement, name: string, desc: string, type: string, placeholder: string, holderValue: any, settingsKey: string) {
|
||||||
|
if (type === 'password') {
|
||||||
|
return new Setting(containerEl)
|
||||||
|
.setName(name)
|
||||||
|
.setDesc(desc)
|
||||||
|
.addText((text) => {
|
||||||
|
text.inputEl.type = type;
|
||||||
|
return text
|
||||||
|
.setPlaceholder(placeholder)
|
||||||
|
.setValue(holderValue)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.plugin.settings[settingsKey] = value; // Update the plugin settings directly
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
} else if (type === 'toggle') {
|
||||||
|
return new Setting(containerEl)
|
||||||
|
.setName(name)
|
||||||
|
.setDesc(desc)
|
||||||
|
.addToggle((toggle) =>
|
||||||
|
toggle
|
||||||
|
.setValue(holderValue)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.plugin.settings[settingsKey] = value; // Update the plugin settings directly
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
await this.plugin.commands.updateCommand();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else if (type === 'text') {
|
||||||
|
return new Setting(containerEl)
|
||||||
|
.setName(name)
|
||||||
|
.setDesc(desc)
|
||||||
|
.addText((text) =>
|
||||||
|
text
|
||||||
|
.setPlaceholder(placeholder)
|
||||||
|
.setValue(holderValue)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.plugin.settings[settingsKey] = value; // Update the plugin settings directly
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
await this.plugin.commands.updateCommand();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// function to show all the database details
|
||||||
|
showDatabase() {
|
||||||
|
this.databaseEl.empty();
|
||||||
|
|
||||||
|
for (let key in this.plugin.settings.databaseDetails) {
|
||||||
|
let dbDetails = this.plugin.settings.databaseDetails[key];
|
||||||
|
|
||||||
|
const databaseDiv = this.databaseEl.createDiv('database-div');
|
||||||
|
|
||||||
|
let settingEl = new Setting(databaseDiv)
|
||||||
|
.setName(`${dbDetails.fullName} (${dbDetails.abName})`)
|
||||||
|
.setDesc(dbDetails.format)
|
||||||
|
|
||||||
|
|
||||||
|
// add a button for preview data
|
||||||
|
settingEl
|
||||||
|
.addButton((button: ButtonComponent): ButtonComponent => {
|
||||||
|
return button
|
||||||
|
.setTooltip("Preview Database")
|
||||||
|
.setIcon("eye")
|
||||||
|
.onClick(async () => {
|
||||||
|
let modal = new PreviewModal(this.app, this.plugin, this, dbDetails);
|
||||||
|
|
||||||
|
modal.open();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// add a button for edit data
|
||||||
|
settingEl
|
||||||
|
.addButton((button: ButtonComponent): ButtonComponent => {
|
||||||
|
return button
|
||||||
|
.setTooltip("Edit Database")
|
||||||
|
.setIcon("pencil")
|
||||||
|
.onClick(async () => {
|
||||||
|
let modal = new EditModal(this.app, this.plugin, this, dbDetails);
|
||||||
|
|
||||||
|
modal.onClose = () => {
|
||||||
|
if (modal.dataTemp.savedTempInd) {
|
||||||
|
const dbDetailsNew: DatabaseDetails = {
|
||||||
|
format: modal.dataTemp.databaseFormatTemp,
|
||||||
|
fullName: modal.dataTemp.databaseFullNameTemp,
|
||||||
|
abName: modal.dataTemp.databaseAbbreviateNameTemp,
|
||||||
|
notionAPI: modal.dataTemp.notionAPITemp,
|
||||||
|
databaseID: modal.dataTemp.databaseIDTemp,
|
||||||
|
tagButton: modal.dataTemp.tagButtonTemp,
|
||||||
|
customTitleButton: modal.dataTemp.customTitleButtonTemp,
|
||||||
|
customTitleName: modal.dataTemp.customTitleNameTemp,
|
||||||
|
customProperties: modal.dataTemp.customPropertiesTemp,
|
||||||
|
// customValues: modal.data.customValues,
|
||||||
|
saved: modal.dataTemp.savedTemp,
|
||||||
|
}
|
||||||
|
|
||||||
|
const dbDetailsPrev: DatabaseDetails = {
|
||||||
|
format: modal.dataPrev.databaseFormatPrev,
|
||||||
|
fullName: modal.dataPrev.databaseFullNamePrev,
|
||||||
|
abName: modal.dataPrev.databaseAbbreviateNamePrev,
|
||||||
|
notionAPI: modal.dataPrev.notionAPIPrev,
|
||||||
|
databaseID: modal.dataPrev.databaseIDPrev,
|
||||||
|
tagButton: modal.dataPrev.tagButtonPrev,
|
||||||
|
customTitleButton: modal.dataPrev.customTitleButtonPrev,
|
||||||
|
customTitleName: modal.dataPrev.customTitleNamePrev,
|
||||||
|
customProperties: modal.dataPrev.customPropertiesPrev,
|
||||||
|
// customValues: modal.data.customValues,
|
||||||
|
saved: modal.dataPrev.savedPrev,
|
||||||
|
}
|
||||||
|
|
||||||
|
this.plugin.deleteDatabaseDetails(dbDetailsPrev);
|
||||||
|
this.plugin.updateDatabaseDetails(dbDetailsNew);
|
||||||
|
|
||||||
|
this.plugin.commands.updateCommand();
|
||||||
|
|
||||||
|
this.display()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modal.open();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
settingEl
|
||||||
|
.addButton((button: ButtonComponent): ButtonComponent => {
|
||||||
|
return button
|
||||||
|
.setTooltip("Delete Database")
|
||||||
|
.setIcon("trash")
|
||||||
|
.onClick(async () => {
|
||||||
|
let modal = new DeleteModal(this.app, this.plugin, this, dbDetails);
|
||||||
|
|
||||||
|
modal.onClose = () => {
|
||||||
|
if (modal.data.deleted) {
|
||||||
|
this.plugin.deleteDatabaseDetails(dbDetails);
|
||||||
|
|
||||||
|
console.log(dbDetails.fullName + " deleted");
|
||||||
|
|
||||||
|
this.plugin.commands.updateCommand();
|
||||||
|
|
||||||
|
this.display()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modal.open();
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
55
src/upload/updateYaml.ts
Normal file
55
src/upload/updateYaml.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { App, Notice, TFile } from "obsidian";
|
||||||
|
import ObsidianSyncNotionPlugin from "../main";
|
||||||
|
import { DatabaseDetails } from "../ui/settingTabs";
|
||||||
|
|
||||||
|
export async function updateYamlInfo(
|
||||||
|
yamlContent: string,
|
||||||
|
nowFile: TFile,
|
||||||
|
res: any,
|
||||||
|
app: App,
|
||||||
|
plugin: ObsidianSyncNotionPlugin,
|
||||||
|
dbDetails: DatabaseDetails,
|
||||||
|
) {
|
||||||
|
let { url, id } = res.json
|
||||||
|
// replace www to notionID
|
||||||
|
const { notionUser } = plugin.settings;
|
||||||
|
const { abName } = dbDetails
|
||||||
|
const notionIDKey = `NotionID-${abName}`;
|
||||||
|
const linkKey = `link-${abName}`;
|
||||||
|
|
||||||
|
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[notionIDKey]) {
|
||||||
|
delete yamlContent[notionIDKey]
|
||||||
|
}
|
||||||
|
if (yamlContent[linkKey]) {
|
||||||
|
delete yamlContent[linkKey]
|
||||||
|
}
|
||||||
|
// add new notionID and link
|
||||||
|
yamlContent[notionIDKey] = id;
|
||||||
|
yamlContent[linkKey] = 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}`)
|
||||||
|
// }
|
||||||
|
}
|
||||||
131
src/upload/uploadCommand.ts
Normal file
131
src/upload/uploadCommand.ts
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
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 ObsidianSyncNotionPlugin from "../main";
|
||||||
|
import { getNowFileMarkdownContentNext } from "./upload_next/getMarkdownNext";
|
||||||
|
import { getNowFileMarkdownContentGeneral } from "./upload_general/getMarkdownGeneral";
|
||||||
|
import { getNowFileMarkdownContentCustom } from "./upoload_custom/getMarkdownCustom";
|
||||||
|
|
||||||
|
export async function uploadCommandNext(
|
||||||
|
plugin: ObsidianSyncNotionPlugin,
|
||||||
|
settings: PluginSettings,
|
||||||
|
dbDetails: DatabaseDetails,
|
||||||
|
app: App,
|
||||||
|
) {
|
||||||
|
|
||||||
|
const { notionAPI, databaseID } = dbDetails;
|
||||||
|
|
||||||
|
// Check if NNon exists
|
||||||
|
// if (NNon === undefined) {
|
||||||
|
// const NNonmessage = i18nConfig.NNonMissing;
|
||||||
|
// new Notice(NNonmessage);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Check if the user has set up the Notion API and database ID
|
||||||
|
if (notionAPI === "" || databaseID === "") {
|
||||||
|
const setAPIMessage = i18nConfig["set-api-id"];
|
||||||
|
new Notice(setAPIMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
markDownData,
|
||||||
|
nowFile,
|
||||||
|
emoji,
|
||||||
|
cover,
|
||||||
|
tags,
|
||||||
|
type,
|
||||||
|
slug,
|
||||||
|
stats,
|
||||||
|
category,
|
||||||
|
summary,
|
||||||
|
paword,
|
||||||
|
favicon,
|
||||||
|
datetime
|
||||||
|
} = await getNowFileMarkdownContentNext(app, settings)
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (res.status === 200) {
|
||||||
|
new Notice(`${i18nConfig["sync-success"]}${basename}`);
|
||||||
|
} else {
|
||||||
|
new Notice(`${i18nConfig["sync-fail"]}${basename}`, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function uploadCommandGeneral(
|
||||||
|
plugin: ObsidianSyncNotionPlugin,
|
||||||
|
settings: PluginSettings,
|
||||||
|
dbDetails: DatabaseDetails,
|
||||||
|
app: App,
|
||||||
|
) {
|
||||||
|
|
||||||
|
const { notionAPI, databaseID } = dbDetails;
|
||||||
|
|
||||||
|
// Check if the user has set up the Notion API and database ID
|
||||||
|
if (notionAPI === "" || databaseID === "") {
|
||||||
|
const setAPIMessage = i18nConfig["set-api-id"];
|
||||||
|
new Notice(setAPIMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { markDownData, nowFile, cover, tags } = await getNowFileMarkdownContentGeneral(app, settings)
|
||||||
|
|
||||||
|
if (markDownData) {
|
||||||
|
const { basename } = nowFile;
|
||||||
|
|
||||||
|
const upload = new Upload2NotionGeneral(plugin, dbDetails);
|
||||||
|
const res = await upload.syncMarkdownToNotionGeneral(basename, cover, tags, markDownData, nowFile, this.app);
|
||||||
|
|
||||||
|
if (res.status === 200) {
|
||||||
|
new Notice(`${i18nConfig["sync-success"]}${basename}`);
|
||||||
|
} else {
|
||||||
|
new Notice(`${i18nConfig["sync-fail"]}${basename}`, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function uploadCommandCustom(
|
||||||
|
plugin: ObsidianSyncNotionPlugin,
|
||||||
|
settings: PluginSettings,
|
||||||
|
dbDetails: DatabaseDetails,
|
||||||
|
app: App,
|
||||||
|
) {
|
||||||
|
|
||||||
|
const { notionAPI, databaseID } = settings;
|
||||||
|
|
||||||
|
// Check if the user has set up the Notion API and database ID
|
||||||
|
if (notionAPI === "" || databaseID === "") {
|
||||||
|
const setAPIMessage = i18nConfig["set-api-id"];
|
||||||
|
new Notice(setAPIMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { markDownData, nowFile, cover, customValues } = await getNowFileMarkdownContentCustom(app, dbDetails)
|
||||||
|
|
||||||
|
if (markDownData) {
|
||||||
|
const { basename } = nowFile;
|
||||||
|
|
||||||
|
const upload = new Upload2NotionCustom(plugin, dbDetails);
|
||||||
|
const res = await upload.syncMarkdownToNotionCustom(cover, customValues, markDownData, nowFile, this.app);
|
||||||
|
|
||||||
|
if (res.status === 200) {
|
||||||
|
new Notice(`${i18nConfig["sync-success"]}${basename}`);
|
||||||
|
} else {
|
||||||
|
new Notice(`${i18nConfig["sync-fail"]}${basename}`, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
96
src/upload/upload_general/BaseUpload2NotionGeneral.ts
Normal file
96
src/upload/upload_general/BaseUpload2NotionGeneral.ts
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
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}`)
|
||||||
|
// // }
|
||||||
|
// }
|
||||||
|
}
|
||||||
164
src/upload/upload_general/Upload2NotionGeneral.ts
Normal file
164
src/upload/upload_general/Upload2NotionGeneral.ts
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
33
src/upload/upload_general/getMarkdownGeneral.ts
Normal file
33
src/upload/upload_general/getMarkdownGeneral.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { App, Notice } from "obsidian";
|
||||||
|
import { i18nConfig } from "../../lang/I18n";
|
||||||
|
import { PluginSettings } from "../../ui/settingTabs";
|
||||||
|
|
||||||
|
export async function getNowFileMarkdownContentGeneral(
|
||||||
|
app: App,
|
||||||
|
settings: PluginSettings,
|
||||||
|
) {
|
||||||
|
const nowFile = app.workspace.getActiveFile();
|
||||||
|
let cover = '';
|
||||||
|
let tags = [];
|
||||||
|
|
||||||
|
const FileCache = app.metadataCache.getFileCache(nowFile);
|
||||||
|
try {
|
||||||
|
cover = FileCache.frontmatter.coverurl;
|
||||||
|
tags = FileCache.frontmatter.tags;
|
||||||
|
} catch (error) {
|
||||||
|
new Notice(i18nConfig["set-tags-fail"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nowFile) {
|
||||||
|
const markDownData = await nowFile.vault.read(nowFile);
|
||||||
|
return {
|
||||||
|
markDownData,
|
||||||
|
nowFile,
|
||||||
|
cover,
|
||||||
|
tags,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
new Notice(i18nConfig["open-file"]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
96
src/upload/upload_next/BaseUpload2NotionNext.ts
Normal file
96
src/upload/upload_next/BaseUpload2NotionNext.ts
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
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}`)
|
||||||
|
// // }
|
||||||
|
// }
|
||||||
|
}
|
||||||
320
src/upload/upload_next/Upload2NotionNext.ts
Normal file
320
src/upload/upload_next/Upload2NotionNext.ts
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
60
src/upload/upload_next/getMarkdownNext.ts
Normal file
60
src/upload/upload_next/getMarkdownNext.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { App, Notice } from "obsidian";
|
||||||
|
import { i18nConfig } from "../../lang/I18n";
|
||||||
|
import { PluginSettings } from "../../ui/settingTabs";
|
||||||
|
|
||||||
|
export async function getNowFileMarkdownContentNext(
|
||||||
|
app: App,
|
||||||
|
settings: PluginSettings,
|
||||||
|
) {
|
||||||
|
const nowFile = app.workspace.getActiveFile();
|
||||||
|
let emoji = '';
|
||||||
|
let cover = '';
|
||||||
|
let tags = [];
|
||||||
|
let type = '';
|
||||||
|
let slug = '';
|
||||||
|
let stats = '';
|
||||||
|
let status = '';
|
||||||
|
let category = '';
|
||||||
|
let summary = '';
|
||||||
|
let paword = '';
|
||||||
|
let favicon = '';
|
||||||
|
let datetime = '';
|
||||||
|
|
||||||
|
const FileCache = app.metadataCache.getFileCache(nowFile);
|
||||||
|
try {
|
||||||
|
emoji = FileCache.frontmatter.titleicon;
|
||||||
|
cover = FileCache.frontmatter.coverurl;
|
||||||
|
tags = FileCache.frontmatter.tags;
|
||||||
|
type = FileCache.frontmatter.type;
|
||||||
|
slug = FileCache.frontmatter.slug;
|
||||||
|
stats = FileCache.frontmatter.stats || FileCache.frontmatter.status;
|
||||||
|
category = FileCache.frontmatter.category;
|
||||||
|
summary = FileCache.frontmatter.summary;
|
||||||
|
paword = FileCache.frontmatter.password;
|
||||||
|
favicon = FileCache.frontmatter.icon;
|
||||||
|
datetime = FileCache.frontmatter.date;
|
||||||
|
} catch (error) {
|
||||||
|
new Notice(i18nConfig["set-tags-fail"]);
|
||||||
|
}
|
||||||
|
if (nowFile) {
|
||||||
|
const markDownData = await nowFile.vault.read(nowFile);
|
||||||
|
return {
|
||||||
|
markDownData,
|
||||||
|
nowFile,
|
||||||
|
emoji,
|
||||||
|
cover,
|
||||||
|
tags,
|
||||||
|
type,
|
||||||
|
slug,
|
||||||
|
stats,
|
||||||
|
category,
|
||||||
|
summary,
|
||||||
|
paword,
|
||||||
|
favicon,
|
||||||
|
datetime,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
new Notice(i18nConfig["open-file"]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/upload/upoload_custom/BaseUpload2NotionCustom.ts
Normal file
53
src/upload/upoload_custom/BaseUpload2NotionCustom.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
242
src/upload/upoload_custom/Upload2NotionCustom.ts
Normal file
242
src/upload/upoload_custom/Upload2NotionCustom.ts
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
57
src/upload/upoload_custom/getMarkdownCustom.ts
Normal file
57
src/upload/upoload_custom/getMarkdownCustom.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { App, Notice } from "obsidian";
|
||||||
|
import { i18nConfig } from "../../lang/I18n";
|
||||||
|
import { DatabaseDetails } from "../../ui/settingTabs";
|
||||||
|
|
||||||
|
export async function getNowFileMarkdownContentCustom(
|
||||||
|
app: App,
|
||||||
|
dbDetails: DatabaseDetails,
|
||||||
|
) {
|
||||||
|
const nowFile = app.workspace.getActiveFile();
|
||||||
|
if (!nowFile) {
|
||||||
|
new Notice(i18nConfig["open-file"]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let cover = '';
|
||||||
|
let customValues: Record<string, any> = {};
|
||||||
|
|
||||||
|
const FileCache = app.metadataCache.getFileCache(nowFile);
|
||||||
|
try {
|
||||||
|
cover = FileCache.frontmatter.coverurl;
|
||||||
|
|
||||||
|
// Get custom property names from dbDetails excluding the title type property
|
||||||
|
const customPropertyNames = dbDetails.customProperties
|
||||||
|
.filter(property => property.customType !== 'title') // Exclude 'title' type property
|
||||||
|
.map(property => property.customName);
|
||||||
|
|
||||||
|
// Extract custom values from the front matter based on the names
|
||||||
|
customPropertyNames.forEach(propertyName => {
|
||||||
|
if (FileCache.frontmatter && FileCache.frontmatter[propertyName] !== undefined) {
|
||||||
|
customValues[propertyName] = FileCache.frontmatter[propertyName];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if any of the customProperties has a customType of '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
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
new Notice(i18nConfig["set-tags-fail"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nowFile) {
|
||||||
|
const markDownData = await nowFile.vault.read(nowFile);
|
||||||
|
return {
|
||||||
|
markDownData,
|
||||||
|
nowFile,
|
||||||
|
cover,
|
||||||
|
customValues,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
new Notice(i18nConfig["open-file"]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@
|
|||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"inlineSourceMap": true,
|
"inlineSourceMap": true,
|
||||||
"inlineSources": true,
|
"inlineSources": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"target": "ES6",
|
"target": "ES6",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
@@ -17,8 +19,8 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"**/*.ts"
|
"**/*.ts",
|
||||||
,
|
"src/Upload2Notion.ts",
|
||||||
"src/Upload2Notion.ts"
|
"src/*.ts"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
// update package.json version
|
// update package.json version
|
||||||
import fs from 'fs';
|
import fs from "fs";
|
||||||
|
|
||||||
const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
|
const packageJson = JSON.parse(fs.readFileSync("./package.json", "utf8"));
|
||||||
|
|
||||||
console.log(packageJson.version);
|
console.log(packageJson.version);
|
||||||
|
|
||||||
|
|
||||||
// get node args
|
// get node args
|
||||||
const args = process.argv.slice(2);
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
@@ -13,14 +12,13 @@ packageJson.version = args[0];
|
|||||||
|
|
||||||
console.log(packageJson.version);
|
console.log(packageJson.version);
|
||||||
|
|
||||||
|
|
||||||
// write package.json
|
// write package.json
|
||||||
fs.writeFileSync('./package.json', JSON.stringify(packageJson, null, 2));
|
fs.writeFileSync("./package.json", JSON.stringify(packageJson, null, 2));
|
||||||
|
|
||||||
// read manifest.json
|
// read manifest.json
|
||||||
const manifestJson = JSON.parse(fs.readFileSync('./manifest.json', 'utf8'));
|
const manifestJson = JSON.parse(fs.readFileSync("./manifest.json", "utf8"));
|
||||||
|
|
||||||
manifestJson.version = args[0];
|
manifestJson.version = args[0];
|
||||||
|
|
||||||
// write manifest.json
|
// write manifest.json
|
||||||
fs.writeFileSync('./manifest.json', JSON.stringify(manifestJson, null, 2));
|
fs.writeFileSync("./manifest.json", JSON.stringify(manifestJson, null, 2));
|
||||||
|
|||||||
Reference in New Issue
Block a user