mirror of
https://github.com/jxpeng98/obsidian-to-NotionNext
synced 2026-07-30 00:48:36 +08:00
Compare commits
124 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db66ef137c | ||
|
|
a152201510 | ||
|
|
abb699fe4d | ||
|
|
cf6c80df0f | ||
|
|
cf884de554 | ||
|
|
8eb4694342 | ||
|
|
6c0f6cea30 | ||
|
|
8af4816f87 | ||
|
|
f6d24daa52 | ||
|
|
da72846903 | ||
|
|
8e08c4325d | ||
|
|
ebb7a290e8 | ||
|
|
24e68fc798 | ||
|
|
78d2a29c8a | ||
|
|
1156bd3809 | ||
|
|
b46e24f1ed | ||
|
|
a950e2e301 | ||
|
|
aac7bc154a | ||
|
|
a573ef728d | ||
|
|
1344c14933 | ||
|
|
c1f4ecd14b | ||
|
|
5942876c92 | ||
|
|
3218eb9fba | ||
|
|
48566eca20 | ||
|
|
8bd8346423 | ||
|
|
00411e9599 | ||
|
|
296125f6b4 | ||
|
|
1b385ccd0a | ||
|
|
a90e4871bb | ||
|
|
885ccba027 | ||
|
|
f282dde1b7 | ||
|
|
bc166d8db1 | ||
|
|
7612637951 | ||
|
|
e8b1617916 | ||
|
|
fd29ed820e | ||
|
|
1f58670906 | ||
|
|
fc47d36e8d | ||
|
|
cd4e610c46 | ||
|
|
b05c248c44 | ||
|
|
800e218dee | ||
|
|
3b774552af | ||
| b8a02c49f1 | |||
|
|
046a67f22c | ||
| 426f05dfbd | |||
|
|
0addaa0d7f | ||
| 1d5792c966 | |||
|
|
193d3f6f87 | ||
| e9c2b4aada | |||
| b5ac42e6e3 | |||
| 60d002677b | |||
| 3c3ec6881e | |||
|
|
a80155fafc | ||
| 3a814b8390 | |||
| bb522db9a2 | |||
| 201f72ae0e | |||
| be6f192dd8 | |||
| 4ce1019094 | |||
| 095c608511 | |||
| 2d8d7fb11a | |||
| 36725cb699 | |||
|
|
654e7e854d | ||
|
|
fa67f08512 | ||
|
|
7e42ef49a7 | ||
|
|
5a95aa0acc | ||
|
|
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 |
11
.github/workflows/release.yml
vendored
11
.github/workflows/release.yml
vendored
@@ -7,7 +7,7 @@ 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
|
name: release
|
||||||
@@ -20,6 +20,14 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: "18"
|
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
|
||||||
run: |
|
run: |
|
||||||
@@ -40,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
|
||||||
|
|
||||||
|
|||||||
1
.github/workflows/test.yml
vendored
1
.github/workflows/test.yml
vendored
@@ -4,7 +4,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- "*"
|
- "*"
|
||||||
- "!master"
|
|
||||||
|
|
||||||
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.
|
||||||
|
|||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -21,3 +21,10 @@ data.json
|
|||||||
# Exclude macOS Finder (System Explorer) View States
|
# Exclude macOS Finder (System Explorer) View States
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.history
|
.history
|
||||||
|
|
||||||
|
# local data
|
||||||
|
local-data
|
||||||
|
|
||||||
|
# vitepress
|
||||||
|
doc/.vitepress/dist
|
||||||
|
doc/.vitepress/cache
|
||||||
|
|||||||
6
CHANGELOG.md
Normal file
6
CHANGELOG.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
## Feature
|
||||||
|
|
||||||
|
- Better debugging information for upload failures.
|
||||||
|
- Support synchronising Obsidian callouts as Notion callout blocks.
|
||||||
|
- 优化控制台输出的调试信息。
|
||||||
|
- 支持将 Obsidian Callout 同步为 Notion Callout 区块。
|
||||||
29
README-zh.md
29
README-zh.md
@@ -5,6 +5,17 @@
|
|||||||
所以我在[原作者](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.1
|
||||||
- 修复了在日语系统下,无法显示设置按钮的错误。
|
- 修复了在日语系统下,无法显示设置按钮的错误。
|
||||||
- 增加日语翻译。
|
- 增加日语翻译。
|
||||||
@@ -33,21 +44,21 @@ tags:
|
|||||||
|
|
||||||
- 这个版本开始,你可以**任意修改第一列表头的名字 (标题列,默认:'title')**。 (**注意:你必须保证你的Notion数据库中有'tags‘这一列的存在,并且在markdown文件中把`tags:`添加到你的YAML表头中。否则会返回`400 错误`。但是你可以`tags:`设置为空。**)
|
- 这个版本开始,你可以**任意修改第一列表头的名字 (标题列,默认:'title')**。 (**注意:你必须保证你的Notion数据库中有'tags‘这一列的存在,并且在markdown文件中把`tags:`添加到你的YAML表头中。否则会返回`400 错误`。但是你可以`tags:`设置为空。**)
|
||||||
|
|
||||||

|

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

|

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

|

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

|

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

|

|
||||||
|
|
||||||
### 0.2.3
|
### 0.2.3
|
||||||
|
|
||||||
@@ -59,9 +70,9 @@ tags:
|
|||||||
- 你可以有一个NotionNext和一个普通Notion数据库。
|
- 你可以有一个NotionNext和一个普通Notion数据库。
|
||||||
- 普通Notion数据库的同步方式和原作者的一样,只同步`title`和`tags`。**如果使用普通数据库功能,你需要保证你的数据库中有`title`和`tags`这两个表头。必须使用小写。**
|
- 普通Notion数据库的同步方式和原作者的一样,只同步`title`和`tags`。**如果使用普通数据库功能,你需要保证你的数据库中有`title`和`tags`这两个表头。必须使用小写。**
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### 0.2.1
|
### 0.2.1
|
||||||
|
|
||||||
@@ -170,10 +181,10 @@ Contents Below
|
|||||||
```
|
```
|
||||||
|
|
||||||
模板使用如下:
|
模板使用如下:
|
||||||

|

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

|

|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
632
README.html
632
README.html
@@ -1,632 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"><head>
|
|
||||||
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="generator" content="quarto-1.3.450">
|
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
|
|
||||||
|
|
||||||
|
|
||||||
<title>readme</title>
|
|
||||||
<style>
|
|
||||||
code{white-space: pre-wrap;}
|
|
||||||
span.smallcaps{font-variant: small-caps;}
|
|
||||||
div.columns{display: flex; gap: min(4vw, 1.5em);}
|
|
||||||
div.column{flex: auto; overflow-x: auto;}
|
|
||||||
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
|
|
||||||
ul.task-list{list-style: none;}
|
|
||||||
ul.task-list li input[type="checkbox"] {
|
|
||||||
width: 0.8em;
|
|
||||||
margin: 0 0.8em 0.2em -1em; /* quarto-specific, see https://github.com/quarto-dev/quarto-cli/issues/4556 */
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
/* CSS for syntax highlighting */
|
|
||||||
pre > code.sourceCode { white-space: pre; position: relative; }
|
|
||||||
pre > code.sourceCode > span { display: inline-block; line-height: 1.25; }
|
|
||||||
pre > code.sourceCode > span:empty { height: 1.2em; }
|
|
||||||
.sourceCode { overflow: visible; }
|
|
||||||
code.sourceCode > span { color: inherit; text-decoration: inherit; }
|
|
||||||
div.sourceCode { margin: 1em 0; }
|
|
||||||
pre.sourceCode { margin: 0; }
|
|
||||||
@media screen {
|
|
||||||
div.sourceCode { overflow: auto; }
|
|
||||||
}
|
|
||||||
@media print {
|
|
||||||
pre > code.sourceCode { white-space: pre-wrap; }
|
|
||||||
pre > code.sourceCode > span { text-indent: -5em; padding-left: 5em; }
|
|
||||||
}
|
|
||||||
pre.numberSource code
|
|
||||||
{ counter-reset: source-line 0; }
|
|
||||||
pre.numberSource code > span
|
|
||||||
{ position: relative; left: -4em; counter-increment: source-line; }
|
|
||||||
pre.numberSource code > span > a:first-child::before
|
|
||||||
{ content: counter(source-line);
|
|
||||||
position: relative; left: -1em; text-align: right; vertical-align: baseline;
|
|
||||||
border: none; display: inline-block;
|
|
||||||
-webkit-touch-callout: none; -webkit-user-select: none;
|
|
||||||
-khtml-user-select: none; -moz-user-select: none;
|
|
||||||
-ms-user-select: none; user-select: none;
|
|
||||||
padding: 0 4px; width: 4em;
|
|
||||||
}
|
|
||||||
pre.numberSource { margin-left: 3em; padding-left: 4px; }
|
|
||||||
div.sourceCode
|
|
||||||
{ }
|
|
||||||
@media screen {
|
|
||||||
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
|
|
||||||
<script src="README_files/libs/clipboard/clipboard.min.js"></script>
|
|
||||||
<script src="README_files/libs/quarto-html/quarto.js"></script>
|
|
||||||
<script src="README_files/libs/quarto-html/popper.min.js"></script>
|
|
||||||
<script src="README_files/libs/quarto-html/tippy.umd.min.js"></script>
|
|
||||||
<script src="README_files/libs/quarto-html/anchor.min.js"></script>
|
|
||||||
<link href="README_files/libs/quarto-html/tippy.css" rel="stylesheet">
|
|
||||||
<link href="README_files/libs/quarto-html/quarto-syntax-highlighting.css" rel="stylesheet" id="quarto-text-highlighting-styles">
|
|
||||||
<script src="README_files/libs/bootstrap/bootstrap.min.js"></script>
|
|
||||||
<link href="README_files/libs/bootstrap/bootstrap-icons.css" rel="stylesheet">
|
|
||||||
<link href="README_files/libs/bootstrap/bootstrap.min.css" rel="stylesheet" id="quarto-bootstrap" data-mode="light">
|
|
||||||
|
|
||||||
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body class="fullcontent">
|
|
||||||
|
|
||||||
<div id="quarto-content" class="page-columns page-rows-contents page-layout-article">
|
|
||||||
|
|
||||||
<main class="content" id="quarto-document-content">
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<section id="obsidian-to-notionnext" class="level1">
|
|
||||||
<h1>Obsidian to NotionNext</h1>
|
|
||||||
<p><a href="https://github.com/jxpeng98/obsidian-to-NotionNext/actions/workflows/test.yml"><img src="https://github.com/jxpeng98/obsidian-to-NotionNext/actions/workflows/test.yml/badge.svg" class="img-fluid" alt="test.yml"></a> <a href="https://github.com/jxpeng98/obsidian-to-NotionNext/actions/workflows/release.yml"><img src="https://github.com/jxpeng98/obsidian-to-NotionNext/actions/workflows/release.yml/badge.svg" class="img-fluid" alt="Release Obsidian plugin"></a> <a href="https://GitHub.com/jxpeng98/obsidian-to-NotionNext/releases/"><img src="https://img.shields.io/github/downloads/jxpeng98/obsidian-to-NotionNext/total.svg" class="img-fluid" alt="Github all releases"></a> <a href="https://github.com/jxpeng98/obsidian-to-NotionNext/releases"><img src="https://badgen.net/github/release/jxpeng98/obsidian-to-NotionNext.png" class="img-fluid" alt="GitLab latest release"></a></p>
|
|
||||||
<p><a href="README-zh.md">中文文档</a></p>
|
|
||||||
<p>Thanks to the <a href="https://github.com/EasyChris/obsidian-to-notion">original author</a> 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 <a href="https://github.com/tangly1024/NotionNext">NotionNext</a> to set up their website, this presents some limitations. Every time I import, I need to make a lot of modifications.</p>
|
|
||||||
<p>Thus, based on the <a href="https://github.com/EasyChris/obsidian-to-notion">original author’s work</a>, I’ve added a feature to match the <a href="https://github.com/tangly1024/NotionNext">NotionNext</a> template. This way, you can edit directly in Obsidian and publish with a single click after organizing.</p>
|
|
||||||
<p><strong>Now, support both NotionNext and General databases.</strong></p>
|
|
||||||
<section id="update" class="level2">
|
|
||||||
<h2 class="anchored" data-anchor-id="update">Update</h2>
|
|
||||||
<section id="section" class="level3">
|
|
||||||
<h3 class="anchored" data-anchor-id="section">1.0.1</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Fix the custom name element display bug in the settings.</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
<section id="big-update" class="level3">
|
|
||||||
<h3 class="anchored" data-anchor-id="big-update">1.0.0 (Big Update)</h3>
|
|
||||||
<ul>
|
|
||||||
<li>From this version, you can <strong>modify the first column name (title column, default: ‘title’)</strong> as you want. (<strong>Note: You need to have the ‘tags’ column in your Notion General database, and add <code>tags:</code> in your markdown frontmatter. If not, you will receive <code>network error 400</code>. But you can leave the <code>tags:</code> blank.</strong>)</li>
|
|
||||||
</ul>
|
|
||||||
<p><img src="https://minioapi.pjx.ac.cn/img1/2023/11/4a298b9be3990e9d2201bf2f50ca5a0a.png" class="img-fluid"> Like this: <img src="https://minioapi.pjx.ac.cn/img1/2023/11/4cd8d79cd9dd9dde299e39c666cb3473.gif" class="img-fluid"></p>
|
|
||||||
<ul>
|
|
||||||
<li>Add a switch button to control whether display the setting tabs in the plugin settings for both NotionNext and Notion General databases.</li>
|
|
||||||
</ul>
|
|
||||||
<p><img src="https://minioapi.pjx.ac.cn/img1/2023/11/becb60fc44783842da4b3cf4c322f363.gif" class="img-fluid"></p>
|
|
||||||
</section>
|
|
||||||
<section id="section-1" class="level3">
|
|
||||||
<h3 class="anchored" data-anchor-id="section-1">0.2.6</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Add a switch button to control whether to show the upload command in the command palette.</li>
|
|
||||||
</ul>
|
|
||||||
<p><img src="https://minioapi.pjx.ac.cn/img1/2023/11/147c6a4eaa34da41a6f102558ed77106.png" class="img-fluid"> If you turn off the button for the General database, you won’t see the option for the General database in the upload command list.</p>
|
|
||||||
<p>If you turn off the button for the NotionNext database, you won’t see the option for the NotionNext database in the upload command list.</p>
|
|
||||||
<p><img src="https://minioapi.pjx.ac.cn/img1/2023/11/70b8e4fc2148688ccbd6cfc53ce339a2.png" class="img-fluid"></p>
|
|
||||||
</section>
|
|
||||||
<section id="section-2" class="level3">
|
|
||||||
<h3 class="anchored" data-anchor-id="section-2">0.2.3</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Fix the bug, now you can update normally.</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
<section id="section-3" class="level3">
|
|
||||||
<h3 class="anchored" data-anchor-id="section-3">0.2.2</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Support both NotionNext and General Notion database.</li>
|
|
||||||
<li>You can have one NotionNext and one General Notion database.</li>
|
|
||||||
<li>General Notion database can only have <code>title</code> and <code>tags</code> columns, and <code>tags</code> columns should be the multi-selected property. <strong>the name of the columns is case sensitive. You should use small letter.</strong></li>
|
|
||||||
</ul>
|
|
||||||
<p><img src="https://minioapi.pjx.ac.cn/img1/2023/11/712a12081d855aa60f82a7b46913ab7e.gif" class="img-fluid"></p>
|
|
||||||
<p><img src="https://minioapi.pjx.ac.cn/img1/2023/11/9de76cecceef74c78884ddfc1c221659.gif" class="img-fluid"></p>
|
|
||||||
</section>
|
|
||||||
<section id="section-4" class="level3">
|
|
||||||
<h3 class="anchored" data-anchor-id="section-4">0.2.1</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Restructure the code</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
<section id="section-5" class="level3">
|
|
||||||
<h3 class="anchored" data-anchor-id="section-5">0.2.0</h3>
|
|
||||||
<ul>
|
|
||||||
<li>From this version, the interactive logic has been rewritten. When you click the ribbon icon, it will display the sync command for all presetting NotionNext databases. You can choose the database you want to sync to. <strong>However, only NotionNext database is supported for now.</strong></li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
<section id="section-6" class="level3">
|
|
||||||
<h3 class="anchored" data-anchor-id="section-6">0.1.10</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Fix the Chinese support in the settings.</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
<section id="section-7" class="level3">
|
|
||||||
<h3 class="anchored" data-anchor-id="section-7">0.1.8</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Rebuild the uploadCommand function, and add one button to select the different databases. <strong>However, only NotionNext database is supported for now.</strong></li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
<section id="section-8" class="level3">
|
|
||||||
<h3 class="anchored" data-anchor-id="section-8">0.1.7</h3>
|
|
||||||
<ul class="task-list">
|
|
||||||
<li><input type="checkbox" checked="">Removed the <code>convert tag</code> option. Now, you can directly add tags in the YAML front matter. If you don’t want to add tags, you can delete the tags in the YAML front matter or leave the tags blank.</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
<section id="how-to-use" class="level2">
|
|
||||||
<h2 class="anchored" data-anchor-id="how-to-use">How to Use</h2>
|
|
||||||
<section id="precautions" class="level3">
|
|
||||||
<h3 class="anchored" data-anchor-id="precautions">Precautions</h3>
|
|
||||||
<p>For now, this plugin is exclusively for <a href="https://github.com/tangly1024/NotionNext">NotionNext</a>. If you’re not using this template, you’ll keep receiving <code>error 400</code>.</p>
|
|
||||||
<p>For those without a NotionNext requirement, please use the original <a href="https://github.com/EasyChris/obsidian-to-notion">Obsidian-to-notion</a>.</p>
|
|
||||||
</section>
|
|
||||||
<section id="pre-installation-steps" class="level3">
|
|
||||||
<h3 class="anchored" data-anchor-id="pre-installation-steps">Pre-Installation Steps</h3>
|
|
||||||
<p>Before installing the plugin, you must have set up the following:</p>
|
|
||||||
<ol type="1">
|
|
||||||
<li>Your NotionNext database.</li>
|
|
||||||
<li>According to the original author’s readme.md, set up the Notion API, and it should already be associated with your NotionNext repository.</li>
|
|
||||||
<li>NotionNext Database ID</li>
|
|
||||||
<li>Your NotionNext database should have the following contents:
|
|
||||||
<ul>
|
|
||||||
<li>type</li>
|
|
||||||
<li>title</li>
|
|
||||||
<li>slug</li>
|
|
||||||
<li>category</li>
|
|
||||||
<li>tags</li>
|
|
||||||
<li>date</li>
|
|
||||||
<li>status</li>
|
|
||||||
<li>summary</li>
|
|
||||||
<li>password</li>
|
|
||||||
<li>icon</li>
|
|
||||||
</ul></li>
|
|
||||||
</ol>
|
|
||||||
<p>From version 0.0.6, I also add the following contents: - titleicon: the unique icon for each post, it can only support emoji currently. - cover url: the cover image for each post, it should be end with a type of image, such as .jpg, .png, .gif, etc.</p>
|
|
||||||
<p><strong>If you’ve directly copied the NotionNext template, these contents should already exist. I’ve made changes to this plugin based on the original author’s work. All you need to ensure is that your database has the above content, and every letter is in lowercase!!!</strong></p>
|
|
||||||
<p><strong>⚠️⚠️⚠️: All headers are in lowercase!!! The order doesn’t matter!</strong></p>
|
|
||||||
</section>
|
|
||||||
<section id="plugin-installation" class="level3">
|
|
||||||
<h3 class="anchored" data-anchor-id="plugin-installation">Plugin Installation</h3>
|
|
||||||
<section id="install-via-community-plugins" class="level4">
|
|
||||||
<h4 class="anchored" data-anchor-id="install-via-community-plugins">Install via Community Plugins</h4>
|
|
||||||
<p>Open <code>Obsidian settings -> Community Plugins -> Browse -> NotionNext -> Share to NotionNext</code></p>
|
|
||||||
</section>
|
|
||||||
<section id="mannually-install" class="level4">
|
|
||||||
<h4 class="anchored" data-anchor-id="mannually-install">Mannually Install</h4>
|
|
||||||
<ol type="1">
|
|
||||||
<li>Close Obsidian.</li>
|
|
||||||
<li>Download the plugin file from Release and unzip it into your Obsidian plugin directory.</li>
|
|
||||||
<li>Re-open Obsidian, go to settings, and enable the plugin.</li>
|
|
||||||
<li>In the settings, find Obsidian to NotionNext, and enter your NotionNext Database ID and API token.</li>
|
|
||||||
</ol>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
<section id="how-to-use-1" class="level3">
|
|
||||||
<h3 class="anchored" data-anchor-id="how-to-use-1">How to Use</h3>
|
|
||||||
</section>
|
|
||||||
<section id="using-the-plugin" class="level3">
|
|
||||||
<h3 class="anchored" data-anchor-id="using-the-plugin">Using the Plugin</h3>
|
|
||||||
<p>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.</p>
|
|
||||||
<p>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.</p>
|
|
||||||
<div class="sourceCode" id="cb1"><pre class="sourceCode markdown code-with-copy"><code class="sourceCode markdown"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="co">---</span></span>
|
|
||||||
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="co"># default value has been set.</span></span>
|
|
||||||
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="co"># for any unwanted value, you can delete it or set it to empty.</span></span>
|
|
||||||
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="co"># for example, if you donot want to set password, you can delete password: "1234" or set it to password: ""</span></span>
|
|
||||||
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a><span class="co"># 我已经在插件中设置了默认值,如果有不需要的选项,可以直接删除。</span></span>
|
|
||||||
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a><span class="co"># 例如你不需要密码选项,你可以将password: "1234"删除,或者将它设置为空。</span></span>
|
|
||||||
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a><span class="co"># !!!!!!!!!!!!</span></span>
|
|
||||||
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a><span class="co"># 现在阶段一定不要修改表头的名字, please do not change the name of the header in YAML front matter</span></span>
|
|
||||||
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a><span class="co"># !!!!!!!!!!!!</span></span>
|
|
||||||
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a><span class="an">titleicon:</span><span class="co"> 📎 # emoji icon, default is 📜, 默认是📜</span></span>
|
|
||||||
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a><span class="an">date:</span><span class="co"> 2023-07-23 # default is today, 默认是今天。 Format is YYYY-MM-DD, 格式是YYYY-MM-DD</span></span>
|
|
||||||
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a><span class="an">coverurl:</span><span class="co"> https://img.jxpeng.dev/2023/08/843e27a210847f05a0f7cfb121fec100.jpg # default is empty, 默认是空</span></span>
|
|
||||||
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a><span class="an">type:</span><span class="co"> Post # Post or Page, default is Post, 默认是Post</span></span>
|
|
||||||
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a><span class="an">slug:</span><span class="co"> test # slug for url, default is empty, 默认是空</span></span>
|
|
||||||
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a><span class="an">stats:</span><span class="co"> Draft # Draft, Invisible, Published, default is Draft, 默认是Draft</span></span>
|
|
||||||
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a><span class="an">category:</span><span class="co"> test # default is 'Obsidian', 默认是'Obsidian'</span></span>
|
|
||||||
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a><span class="an">summary:</span><span class="co"> this is a summary for test post # default is empty, 默认是空</span></span>
|
|
||||||
<span id="cb1-18"><a href="#cb1-18" aria-hidden="true" tabindex="-1"></a><span class="an">icon:</span><span class="co"> fa-solid fa-camera # you can ignore this, default is empty, 默认是空,可直接删除</span></span>
|
|
||||||
<span id="cb1-19"><a href="#cb1-19" aria-hidden="true" tabindex="-1"></a><span class="an">password:</span><span class="co"> "1234" # if you donot want to set password, you can delete this line, default is empty, 默认是空,可直接删除</span></span>
|
|
||||||
<span id="cb1-20"><a href="#cb1-20" aria-hidden="true" tabindex="-1"></a><span class="co"># 现在必须开启tags选项,否则会报错</span></span>
|
|
||||||
<span id="cb1-21"><a href="#cb1-21" aria-hidden="true" tabindex="-1"></a><span class="an">tags:</span></span>
|
|
||||||
<span id="cb1-22"><a href="#cb1-22" aria-hidden="true" tabindex="-1"></a><span class="co"> - test # tags for post</span></span>
|
|
||||||
<span id="cb1-23"><a href="#cb1-23" aria-hidden="true" tabindex="-1"></a><span class="co"> - web # add more tags if you want</span></span>
|
|
||||||
<span id="cb1-24"><a href="#cb1-24" aria-hidden="true" tabindex="-1"></a><span class="co">---</span></span>
|
|
||||||
<span id="cb1-25"><a href="#cb1-25" aria-hidden="true" tabindex="-1"></a></span>
|
|
||||||
<span id="cb1-26"><a href="#cb1-26" aria-hidden="true" tabindex="-1"></a>Contents Below</span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
|
|
||||||
<p>Usage of the template is as follows: <img src="https://minioapi.pjx.ac.cn/img1/2023/09/354b950e5777b48832c2475e4a31f2cc.gif" class="img-fluid"></p>
|
|
||||||
<p><strong>Plugin preview is shown below</strong> <img src="https://minioapi.pjx.ac.cn/img1/2023/09/d6199619b68fab218fca8ae0cebece78.gif" class="img-fluid"></p>
|
|
||||||
<hr>
|
|
||||||
<p><strong>Original README.md</strong></p>
|
|
||||||
<p>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 <a href="https://github.com/EasyChris/obsidian-to-notion">original author</a> a star.</p>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
<section id="obsidian-to-notion" class="level1">
|
|
||||||
<h1>Obsidian to Notion</h1>
|
|
||||||
<p><a href="https://github.com/Easychris/obsidian-to-notion/actions/workflows/CI.yml"><img src="https://github.com/Easychris/obsidian-to-notion/actions/workflows/CI.yml/badge.svg" class="img-fluid"></a> <a href="https://github.com/Easychris/obsidian-to-notion/actions/workflows/release.yml"><img src="https://github.com/Easychris/obsidian-to-notion/actions/workflows/release.yml/badge.svg" class="img-fluid" alt="Release Obsidian plugin"></a> <a href="https://raw.githubusercontent.com/EasyChris/obsidian-to-notion/master/LICENSE"><img src="https://img.shields.io/github/license/EasyChris/obsidian-to-notion.png" class="img-fluid" alt="GitHub license"></a> <a href="https://GitHub.com/Easychris/obsidian-to-notion/releases/"><img src="https://img.shields.io/github/downloads/Easychris/obsidian-to-notion/total.svg" class="img-fluid" alt="Github all releases"></a> <a href="https://github.com/Easychris/obsidian-to-notion/releases"><img src="https://badgen.net/github/release/Easychris/obsidian-to-notion/.png" class="img-fluid" alt="GitLab latest release"></a></p>
|
|
||||||
<p>Share of obsidian to Notion <a href="README-zh.md">中文文档</a></p>
|
|
||||||
<p>Sharing files from Obsidian to Notion with a single click, and Obsidian will automatically add the Notion share link</p>
|
|
||||||
<p>You are welcome to offer it a star if it can benefit you.</p>
|
|
||||||
<p><img src="./doc/1.gif" class="img-fluid"></p>
|
|
||||||
</section>
|
|
||||||
<section id="todo" class="level1">
|
|
||||||
<h1>TODO</h1>
|
|
||||||
<section id="todo-board" class="level3">
|
|
||||||
<h3 class="anchored" data-anchor-id="todo-board"><a href="https://github.com/users/EasyChris/projects/3/views/1">TODO Board</a></h3>
|
|
||||||
<ul class="task-list">
|
|
||||||
<li><input type="checkbox" checked="">support for custom page banner</li>
|
|
||||||
<li><input type="checkbox" checked="">update the exsit page</li>
|
|
||||||
<li><input type="checkbox" checked="">support for mult language</li>
|
|
||||||
<li><input type="checkbox" checked="">support for auto copy the share link to clipboard</li>
|
|
||||||
<li><input type="checkbox" checked="">support for mobile</li>
|
|
||||||
<li><input type="checkbox" checked="">support tags thank for <a href="https://github.com/jannikbuscha"><span class="citation" data-cites="jannikbuscha">@jannikbuscha</span></a></li>
|
|
||||||
<li><input type="checkbox">transfer the bi-link format like [[]] into the format that Notion supports.</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
<section id="how-to-use-2" class="level1">
|
|
||||||
<h1>How to use</h1>
|
|
||||||
<section id="install-the-plugin" class="level2">
|
|
||||||
<h2 class="anchored" data-anchor-id="install-the-plugin">Install the plugin</h2>
|
|
||||||
<section id="marketplace-download" class="level3">
|
|
||||||
<h3 class="anchored" data-anchor-id="marketplace-download">Marketplace download</h3>
|
|
||||||
<p>Open obsidian setting -> Add plugin -> Search -> notion</p>
|
|
||||||
<p><img src="https://afox-1256168983.cos.ap-shanghai.myqcloud.com/20220628214145.png" class="img-fluid"></p>
|
|
||||||
</section>
|
|
||||||
<section id="brat" class="level3">
|
|
||||||
<h3 class="anchored" data-anchor-id="brat">BRAT</h3>
|
|
||||||
<p>Enter <code>BRAT</code> into the plugin market center to find it. Add <code>EasyChris/obsidian-to-notion</code> to the list of BRAT plugins that have been installed. Return to the plugin center and turn it on.</p>
|
|
||||||
</section>
|
|
||||||
<section id="manual-installation" class="level3">
|
|
||||||
<h3 class="anchored" data-anchor-id="manual-installation">Manual installation</h3>
|
|
||||||
<pre><code>cd YOUR_OBSIDIAN_FOLDER/.obsidian/plugins/
|
|
||||||
git clone https://github.com/EasyChris/obsidian-to-notion.git</code></pre>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
<section id="apply-notion-api" class="level2">
|
|
||||||
<h2 class="anchored" data-anchor-id="apply-notion-api">Apply Notion API</h2>
|
|
||||||
<p>Official reference documentation: <a href="https://developers.notion.com/docs">https://developers.notion.com/docs</a></p>
|
|
||||||
<section id="step-1-create-integration" class="level3">
|
|
||||||
<h3 class="anchored" data-anchor-id="step-1-create-integration">Step 1: Create integration</h3>
|
|
||||||
<p>Go to <a href="https://www.notion.com/my-integrations">https://www.notion.com/my-integrations</a> Once created, copy <code>secrets toekn</code> <img src="https://files.readme.io/2ec137d-093ad49-create-integration.gif" class="img-fluid"></p>
|
|
||||||
<section id="note" class="level4">
|
|
||||||
<h4 class="anchored" data-anchor-id="note">Note</h4>
|
|
||||||
<p>database first custom name must be “Name”, otherwise sync to notion will be failed</p>
|
|
||||||
<p><img src="https://afox-1256168983.cos.ap-shanghai.myqcloud.com/20220618102029.png" class="img-fluid"></p>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
<section id="step-2-share-a-database-with-your-integration" class="level3">
|
|
||||||
<h3 class="anchored" data-anchor-id="step-2-share-a-database-with-your-integration">Step 2: Share a database with your integration</h3>
|
|
||||||
<p>Create a new page (with public permissions) Create a new database in the page -> you need <code>full page database</code> <img src="./doc/3.gif" class="img-fluid"></p>
|
|
||||||
<p>Add <code>integration</code> to your new database</p>
|
|
||||||
<p><img src="./doc/6.gif" class="img-fluid"></p>
|
|
||||||
</section>
|
|
||||||
<section id="step-3-copy-the-database-id" class="level3">
|
|
||||||
<h3 class="anchored" data-anchor-id="step-3-copy-the-database-id">Step 3: Copy the database ID</h3>
|
|
||||||
<pre><code>https://www.notion.so/myworkspace/a8aec43384f447ed84390e8e42c2e089?v=...
|
|
||||||
| --------- Database ID --------|
|
|
||||||
</code></pre>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
<section id="open-the-plugin-configuration" class="level2">
|
|
||||||
<h2 class="anchored" data-anchor-id="open-the-plugin-configuration">Open the plugin configuration</h2>
|
|
||||||
<p>Fill the configuration with the <code>NOTION_API_KEY</code> and <code>DATABASE_ID</code> you got <img src="./doc/2.png" class="img-fluid"></p>
|
|
||||||
</section>
|
|
||||||
<section id="upload-file-content-to-notion" class="level2">
|
|
||||||
<h2 class="anchored" data-anchor-id="upload-file-content-to-notion">Upload file content to notion</h2>
|
|
||||||
<p>Click the uploadCommand notion button <img src="./doc/4.png" class="img-fluid"> A share link will be automatically generated after successful uploadCommand <img src="./doc/5.png" class="img-fluid"></p>
|
|
||||||
</section>
|
|
||||||
<section id="banner-url-option" class="level2">
|
|
||||||
<h2 class="anchored" data-anchor-id="banner-url-option">Banner URL [option]</h2>
|
|
||||||
<p>Banner url must be a image url like: <a href="https://i.imgur.com/xxx.jpg" class="uri">https://i.imgur.com/xxx.jpg</a> If you don’t want to use banner, leave it blank</p>
|
|
||||||
</section>
|
|
||||||
<section id="convert-tags-option" class="level2">
|
|
||||||
<h2 class="anchored" data-anchor-id="convert-tags-option">Convert Tags [option]</h2>
|
|
||||||
<p>Transfer the Obsidian tags to the Notion table. It requires the column with the name ‘Tags’. <img src="./doc/7.png" class="img-fluid"></p>
|
|
||||||
<p>Add tags to your notion page</p>
|
|
||||||
<p><img src="./doc/10.png" class="img-fluid"></p>
|
|
||||||
<ul>
|
|
||||||
<li>open plugin convert tags</li>
|
|
||||||
</ul>
|
|
||||||
<p><img src="./doc/8.png" class="img-fluid"></p>
|
|
||||||
<ul>
|
|
||||||
<li>add tags in the head</li>
|
|
||||||
</ul>
|
|
||||||
<div class="sourceCode" id="cb4"><pre class="sourceCode markdown code-with-copy"><code class="sourceCode markdown"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="co">---</span></span>
|
|
||||||
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a><span class="an">tags:</span><span class="co"> [tag1,tag2]</span></span>
|
|
||||||
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="co">---</span></span>
|
|
||||||
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a></span>
|
|
||||||
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>this is test tags</span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
|
|
||||||
<div class="sourceCode" id="cb5"><pre class="sourceCode markdown code-with-copy"><code class="sourceCode markdown"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="co">---</span></span>
|
|
||||||
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="an">tags:</span></span>
|
|
||||||
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="co"> - tag4</span></span>
|
|
||||||
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="co">---</span></span>
|
|
||||||
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a></span>
|
|
||||||
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a>this is test tags</span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
|
|
||||||
<p><img src="./doc/9.png" class="img-fluid"></p>
|
|
||||||
<p>Thanks for <a href="https://github.com/jannikbuscha"><span class="citation" data-cites="jannikbuscha">@jannikbuscha</span></a> contribution</p>
|
|
||||||
</section>
|
|
||||||
<section id="notion-id-option" class="level2">
|
|
||||||
<h2 class="anchored" data-anchor-id="notion-id-option">Notion ID [option]</h2>
|
|
||||||
<p>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: <a href="https://www.notion.so/myworkspace/a8aec43384f447ed84390" class="uri">https://www.notion.so/myworkspace/a8aec43384f447ed84390</a> 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: <a href="https://your_user_name.notion.site/myworkspace/a8aec43384f447ed84390" class="uri">https://your_user_name.notion.site/myworkspace/a8aec43384f447ed84390</a>. The visiter don’t need to redirect url.</p>
|
|
||||||
</section>
|
|
||||||
<section id="sync-image-to-notion" class="level2">
|
|
||||||
<h2 class="anchored" data-anchor-id="sync-image-to-notion">Sync image to Notion</h2>
|
|
||||||
<p>To sync images to your oss or cos bucket, use the <a href="https://github.com/renmu123/obsidian-image-auto-upload-plugin">Obsidian Image Auto Upload Plugin</a>.</p>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
<section id="development" class="level1">
|
|
||||||
<h1>Development</h1>
|
|
||||||
<pre><code>git clone https://github.com/EasyChris/obsidian-to-notion.git
|
|
||||||
yarn install
|
|
||||||
yarn dev</code></pre>
|
|
||||||
<section id="release" class="level2">
|
|
||||||
<h2 class="anchored" data-anchor-id="release">Release</h2>
|
|
||||||
<pre><code>node update-version.js
|
|
||||||
./release.sh</code></pre>
|
|
||||||
<p>```</p>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
<section id="thanks" class="level1">
|
|
||||||
<h1>Thanks</h1>
|
|
||||||
<p><a href="https://luhaifeng666.github.io/obsidian-plugin-docs-zh/zh/getting-started/development-workflow.html">Development Process | Obsidian Plugin Development Documentation</a></p>
|
|
||||||
<p><a href="https://github.com/devbean/obsidian-wordpress">GitHub - devbean/obsidian-wordpress: An obsidian plugin for publishing docs to WordPress.</a></p>
|
|
||||||
<p><a href="https://github.com/obsidianmd/obsidian-api">GitHub - obsidianmd/obsidian-api</a></p>
|
|
||||||
<p><a href="https://github.dev/zhaohongxuan/obsidian-weread-plugin">GitHub - Easychris/obsidian-to-notion: Obsidian Weread Plugin is an plugin to sync Weread(微信读书) hightlights and annotations into your Obsidian Vault.</a></p>
|
|
||||||
<p><a href="https://github.com/Quorafind/Obsidian-Memos">GitHub - Quorafind/Obsidian-Memos: A quick capture plugin for Obsidian, all data from your notes.</a></p>
|
|
||||||
<p><a href="https://github.com/jannikbuscha">https://github.com/jannikbuscha/obsidian-to-notion</a></p>
|
|
||||||
</section>
|
|
||||||
<section id="license" class="level1">
|
|
||||||
<h1>License</h1>
|
|
||||||
<p>GNU GPLv3</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
</main>
|
|
||||||
<!-- /main column -->
|
|
||||||
<script id="quarto-html-after-body" type="application/javascript">
|
|
||||||
window.document.addEventListener("DOMContentLoaded", function (event) {
|
|
||||||
const toggleBodyColorMode = (bsSheetEl) => {
|
|
||||||
const mode = bsSheetEl.getAttribute("data-mode");
|
|
||||||
const bodyEl = window.document.querySelector("body");
|
|
||||||
if (mode === "dark") {
|
|
||||||
bodyEl.classList.add("quarto-dark");
|
|
||||||
bodyEl.classList.remove("quarto-light");
|
|
||||||
} else {
|
|
||||||
bodyEl.classList.add("quarto-light");
|
|
||||||
bodyEl.classList.remove("quarto-dark");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const toggleBodyColorPrimary = () => {
|
|
||||||
const bsSheetEl = window.document.querySelector("link#quarto-bootstrap");
|
|
||||||
if (bsSheetEl) {
|
|
||||||
toggleBodyColorMode(bsSheetEl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
toggleBodyColorPrimary();
|
|
||||||
const icon = "";
|
|
||||||
const anchorJS = new window.AnchorJS();
|
|
||||||
anchorJS.options = {
|
|
||||||
placement: 'right',
|
|
||||||
icon: icon
|
|
||||||
};
|
|
||||||
anchorJS.add('.anchored');
|
|
||||||
const isCodeAnnotation = (el) => {
|
|
||||||
for (const clz of el.classList) {
|
|
||||||
if (clz.startsWith('code-annotation-')) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const clipboard = new window.ClipboardJS('.code-copy-button', {
|
|
||||||
text: function(trigger) {
|
|
||||||
const codeEl = trigger.previousElementSibling.cloneNode(true);
|
|
||||||
for (const childEl of codeEl.children) {
|
|
||||||
if (isCodeAnnotation(childEl)) {
|
|
||||||
childEl.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return codeEl.innerText;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
clipboard.on('success', function(e) {
|
|
||||||
// button target
|
|
||||||
const button = e.trigger;
|
|
||||||
// don't keep focus
|
|
||||||
button.blur();
|
|
||||||
// flash "checked"
|
|
||||||
button.classList.add('code-copy-button-checked');
|
|
||||||
var currentTitle = button.getAttribute("title");
|
|
||||||
button.setAttribute("title", "Copied!");
|
|
||||||
let tooltip;
|
|
||||||
if (window.bootstrap) {
|
|
||||||
button.setAttribute("data-bs-toggle", "tooltip");
|
|
||||||
button.setAttribute("data-bs-placement", "left");
|
|
||||||
button.setAttribute("data-bs-title", "Copied!");
|
|
||||||
tooltip = new bootstrap.Tooltip(button,
|
|
||||||
{ trigger: "manual",
|
|
||||||
customClass: "code-copy-button-tooltip",
|
|
||||||
offset: [0, -8]});
|
|
||||||
tooltip.show();
|
|
||||||
}
|
|
||||||
setTimeout(function() {
|
|
||||||
if (tooltip) {
|
|
||||||
tooltip.hide();
|
|
||||||
button.removeAttribute("data-bs-title");
|
|
||||||
button.removeAttribute("data-bs-toggle");
|
|
||||||
button.removeAttribute("data-bs-placement");
|
|
||||||
}
|
|
||||||
button.setAttribute("title", currentTitle);
|
|
||||||
button.classList.remove('code-copy-button-checked');
|
|
||||||
}, 1000);
|
|
||||||
// clear code selection
|
|
||||||
e.clearSelection();
|
|
||||||
});
|
|
||||||
function tippyHover(el, contentFn) {
|
|
||||||
const config = {
|
|
||||||
allowHTML: true,
|
|
||||||
content: contentFn,
|
|
||||||
maxWidth: 500,
|
|
||||||
delay: 100,
|
|
||||||
arrow: false,
|
|
||||||
appendTo: function(el) {
|
|
||||||
return el.parentElement;
|
|
||||||
},
|
|
||||||
interactive: true,
|
|
||||||
interactiveBorder: 10,
|
|
||||||
theme: 'quarto',
|
|
||||||
placement: 'bottom-start'
|
|
||||||
};
|
|
||||||
window.tippy(el, config);
|
|
||||||
}
|
|
||||||
const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]');
|
|
||||||
for (var i=0; i<noterefs.length; i++) {
|
|
||||||
const ref = noterefs[i];
|
|
||||||
tippyHover(ref, function() {
|
|
||||||
// use id or data attribute instead here
|
|
||||||
let href = ref.getAttribute('data-footnote-href') || ref.getAttribute('href');
|
|
||||||
try { href = new URL(href).hash; } catch {}
|
|
||||||
const id = href.replace(/^#\/?/, "");
|
|
||||||
const note = window.document.getElementById(id);
|
|
||||||
return note.innerHTML;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
let selectedAnnoteEl;
|
|
||||||
const selectorForAnnotation = ( cell, annotation) => {
|
|
||||||
let cellAttr = 'data-code-cell="' + cell + '"';
|
|
||||||
let lineAttr = 'data-code-annotation="' + annotation + '"';
|
|
||||||
const selector = 'span[' + cellAttr + '][' + lineAttr + ']';
|
|
||||||
return selector;
|
|
||||||
}
|
|
||||||
const selectCodeLines = (annoteEl) => {
|
|
||||||
const doc = window.document;
|
|
||||||
const targetCell = annoteEl.getAttribute("data-target-cell");
|
|
||||||
const targetAnnotation = annoteEl.getAttribute("data-target-annotation");
|
|
||||||
const annoteSpan = window.document.querySelector(selectorForAnnotation(targetCell, targetAnnotation));
|
|
||||||
const lines = annoteSpan.getAttribute("data-code-lines").split(",");
|
|
||||||
const lineIds = lines.map((line) => {
|
|
||||||
return targetCell + "-" + line;
|
|
||||||
})
|
|
||||||
let top = null;
|
|
||||||
let height = null;
|
|
||||||
let parent = null;
|
|
||||||
if (lineIds.length > 0) {
|
|
||||||
//compute the position of the single el (top and bottom and make a div)
|
|
||||||
const el = window.document.getElementById(lineIds[0]);
|
|
||||||
top = el.offsetTop;
|
|
||||||
height = el.offsetHeight;
|
|
||||||
parent = el.parentElement.parentElement;
|
|
||||||
if (lineIds.length > 1) {
|
|
||||||
const lastEl = window.document.getElementById(lineIds[lineIds.length - 1]);
|
|
||||||
const bottom = lastEl.offsetTop + lastEl.offsetHeight;
|
|
||||||
height = bottom - top;
|
|
||||||
}
|
|
||||||
if (top !== null && height !== null && parent !== null) {
|
|
||||||
// cook up a div (if necessary) and position it
|
|
||||||
let div = window.document.getElementById("code-annotation-line-highlight");
|
|
||||||
if (div === null) {
|
|
||||||
div = window.document.createElement("div");
|
|
||||||
div.setAttribute("id", "code-annotation-line-highlight");
|
|
||||||
div.style.position = 'absolute';
|
|
||||||
parent.appendChild(div);
|
|
||||||
}
|
|
||||||
div.style.top = top - 2 + "px";
|
|
||||||
div.style.height = height + 4 + "px";
|
|
||||||
let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter");
|
|
||||||
if (gutterDiv === null) {
|
|
||||||
gutterDiv = window.document.createElement("div");
|
|
||||||
gutterDiv.setAttribute("id", "code-annotation-line-highlight-gutter");
|
|
||||||
gutterDiv.style.position = 'absolute';
|
|
||||||
const codeCell = window.document.getElementById(targetCell);
|
|
||||||
const gutter = codeCell.querySelector('.code-annotation-gutter');
|
|
||||||
gutter.appendChild(gutterDiv);
|
|
||||||
}
|
|
||||||
gutterDiv.style.top = top - 2 + "px";
|
|
||||||
gutterDiv.style.height = height + 4 + "px";
|
|
||||||
}
|
|
||||||
selectedAnnoteEl = annoteEl;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const unselectCodeLines = () => {
|
|
||||||
const elementsIds = ["code-annotation-line-highlight", "code-annotation-line-highlight-gutter"];
|
|
||||||
elementsIds.forEach((elId) => {
|
|
||||||
const div = window.document.getElementById(elId);
|
|
||||||
if (div) {
|
|
||||||
div.remove();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
selectedAnnoteEl = undefined;
|
|
||||||
};
|
|
||||||
// Attach click handler to the DT
|
|
||||||
const annoteDls = window.document.querySelectorAll('dt[data-target-cell]');
|
|
||||||
for (const annoteDlNode of annoteDls) {
|
|
||||||
annoteDlNode.addEventListener('click', (event) => {
|
|
||||||
const clickedEl = event.target;
|
|
||||||
if (clickedEl !== selectedAnnoteEl) {
|
|
||||||
unselectCodeLines();
|
|
||||||
const activeEl = window.document.querySelector('dt[data-target-cell].code-annotation-active');
|
|
||||||
if (activeEl) {
|
|
||||||
activeEl.classList.remove('code-annotation-active');
|
|
||||||
}
|
|
||||||
selectCodeLines(clickedEl);
|
|
||||||
clickedEl.classList.add('code-annotation-active');
|
|
||||||
} else {
|
|
||||||
// Unselect the line
|
|
||||||
unselectCodeLines();
|
|
||||||
clickedEl.classList.remove('code-annotation-active');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const findCites = (el) => {
|
|
||||||
const parentEl = el.parentElement;
|
|
||||||
if (parentEl) {
|
|
||||||
const cites = parentEl.dataset.cites;
|
|
||||||
if (cites) {
|
|
||||||
return {
|
|
||||||
el,
|
|
||||||
cites: cites.split(' ')
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return findCites(el.parentElement)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var bibliorefs = window.document.querySelectorAll('a[role="doc-biblioref"]');
|
|
||||||
for (var i=0; i<bibliorefs.length; i++) {
|
|
||||||
const ref = bibliorefs[i];
|
|
||||||
const citeInfo = findCites(ref);
|
|
||||||
if (citeInfo) {
|
|
||||||
tippyHover(citeInfo.el, function() {
|
|
||||||
var popup = window.document.createElement('div');
|
|
||||||
citeInfo.cites.forEach(function(cite) {
|
|
||||||
var citeDiv = window.document.createElement('div');
|
|
||||||
citeDiv.classList.add('hanging-indent');
|
|
||||||
citeDiv.classList.add('csl-entry');
|
|
||||||
var biblioDiv = window.document.getElementById('ref-' + cite);
|
|
||||||
if (biblioDiv) {
|
|
||||||
citeDiv.innerHTML = biblioDiv.innerHTML;
|
|
||||||
}
|
|
||||||
popup.appendChild(citeDiv);
|
|
||||||
});
|
|
||||||
return popup.innerHTML;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</div> <!-- /content -->
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</body></html>
|
|
||||||
449
README.md
449
README.md
@@ -2,195 +2,275 @@
|
|||||||
|
|
||||||
[](https://github.com/jxpeng98/obsidian-to-NotionNext/actions/workflows/test.yml)
|
[](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/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)
|
[](https://github.com/jxpeng98/obsidian-to-NotionNext/releases/)
|
||||||
|
|
||||||
[中文文档](README-zh.md)
|
[//]: # ([](https://GitHub.com/jxpeng98/obsidian-to-NotionNext/releases/))
|
||||||
|
|
||||||
|
[//]: # ([中文文档](README-zh.md))
|
||||||
|
|
||||||
|
**Now, support All Notion databases, including NotionNext and General Notion databases, and customise the database list.**
|
||||||
|
|
||||||
|
**现在支持所有Notion数据库,包括NotionNext和一般Notion数据库,以及自定义数据库列表。**
|
||||||
|
|
||||||
|
## Precautions
|
||||||
|
|
||||||
|
### For customised database users before version 2.3.0
|
||||||
|
|
||||||
|
**⚠️⚠️⚠️: The existing 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版本之前的自定义数据库用户
|
||||||
|
|
||||||
|
**⚠️⚠️⚠️: 如果你想要更新到2.3.0版本,你需要重新创建自定义数据库。新版本有一个新的数据库结构,旧的数据库结构无法构建索引。**
|
||||||
|
|
||||||
|
## TODO List
|
||||||
|
|
||||||
|
- [x] Modify the Edit function for the custom properties. 改进自定义属性的编辑功能
|
||||||
|
- [x] Support sync with long notes (From `v2.4.0`). 从`v2.4.0`开始支持长笔记同步
|
||||||
|
- [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. 支持编辑数据库详情
|
||||||
|
- [x] Convert Obsidian callouts into Notion callout blocks so your `[!info]` style notes stay consistent after syncing.
|
||||||
|
- [ ] Support group upload with one click 支持一键多数据库上传
|
||||||
|
|
||||||
|
## Callout support
|
||||||
|
|
||||||
|
Obsidian callouts written with the standard syntax (for example `> [!warning]` or `> [!quote]`) are automatically rendered as Notion callout blocks during upload. The plugin keeps the chosen callout type, title, and body content so your callout styling survives the trip from Obsidian to Notion without extra configuration.
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
> [!info] Tips
|
||||||
|
> Remember to update your front matter before syncing.
|
||||||
|
```
|
||||||
|
|
||||||
|
After syncing, the note will contain an equivalent Notion callout block with the same icon and text.
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
If you want to use this plugin, you need to follow the following steps to set up the plugin. The steps can be divided into two parts: setting up the Notion API and setting up the plugin in Obsidian.
|
||||||
|
|
||||||
|
### 1. Setting up the Notion API
|
||||||
|
|
||||||
|
1). Go to [Notion API](https://www.notion.com/my-integrations) to create a new integration, and **copy the token**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
2). Create a database in your Notion workspace.
|
||||||
|
|
||||||
|
Open Notion, click top-left `Create` a new page` -> You can give a title for the page and press "/" Select 'Database - Full page'.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Rename the database properties to `title` and `tags`.
|
||||||
|
**⚠️⚠️`title` is the title column! `tags` is lower case⚠️⚠️**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
3). Add the Notion API integration.
|
||||||
|
|
||||||
|
Click the right-top '...' -> connections -> connect to -> find the integration you created and connect it.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
4). Publish the database to the public.
|
||||||
|
Click the 'Share' -> 'Publish' -> 'Publish' -> **copy the database id from the URL**.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 2. Setting up the plugin in Obsidian
|
||||||
|
|
||||||
|
1). Go to Obsidian settings -> community plugins -> search 'NotionNext' -> install it.
|
||||||
|

|
||||||
|
|
||||||
|
2). Open the plugin settings and create a new database.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 3. Create a database
|
||||||
|
|
||||||
|
**You can create three types of databases:**
|
||||||
|
|
||||||
|
#### 1️⃣ General database
|
||||||
|
|
||||||
|
General database is the default database, which only has the `title` and `tags` columns. **⚠️⚠️`title` and `tags` are lowercase⚠️⚠️**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The general is as follows:
|
||||||
|
|
||||||
|
- Full Name: the full name that you would like to give to the database.
|
||||||
|
- Abbreviate Name: the abbreviation that you would like to give to the database.
|
||||||
|
- Notion tags sync: whether you want to sync the tags column to Notion.
|
||||||
|
- Customise title property: whether you want to change the `title` to another name (e.g., `name`).
|
||||||
|
- Notion API token: the token you copied from the Notion API page.
|
||||||
|
- Notion Database ID: the database ID you copied from the Notion database URL. (e.g., only copy `7158cd3f70ce4f60afd9da5bdf74fd0c` between `site/` and `?`. `https://jxpeng.notion.site/7158cd3f70ce4f60afd9da5bdf74fd0c?v=380317ac5373sadasdas3c13e618f&pvs=4**`)
|
||||||
|
|
||||||
|
After setting, it should look like this:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Once you click `save`, the new database will be created in the plugin settings and display in the detabase list.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 2️⃣ NotionNext database
|
||||||
|
|
||||||
|
If you use [NotionNext](https://github.com/tangly1024/NotionNext) to set up your website, you can use the NotionNext database. (**All the properties are in lowercase, and you do not need to change the properties**)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Like the general database, you need to fill in the following information:
|
||||||
|
|
||||||
|
- Full Name: the full name that you would like to give to the database.
|
||||||
|
- Abbreviate Name: the abbreviation that you would like to give to the database.
|
||||||
|
- Notion API token: the token you copied from the Notion API page.
|
||||||
|
- Notion Database ID: the database ID you copied from the Notion database URL.
|
||||||
|
|
||||||
|
#### 3️⃣ Custom database
|
||||||
|
|
||||||
|
The custom database is purely customised, and you can customise the properties you want to sync. (**All the properties are in lowercase, and you do not need to change the properties**)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The basic information is identical to the general database and NotionNext database. You need to fill in the following information:
|
||||||
|
|
||||||
|
- Full Name: the full name that you would like to give to the database.
|
||||||
|
- Abbreviate Name: the abbreviation that you would like to give to the database.
|
||||||
|
- Notion API token: the token you copied from the Notion API page.
|
||||||
|
- Notion Database ID: the database ID you copied from the Notion database URL.
|
||||||
|
|
||||||
|
After adding the information, You can click `Add New Property` to add the properties you want to sync. There is no limit to the number of properties you can add.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**⚠️⚠️⚠️**: **`Title column` is the main property in the Notion database, which has the open button to open the underlying page.** You can select the type of all other properties. **⚠️⚠️⚠️**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
According to the previously shown database, you can use `title` as the `Title column`, and `Tags` as the property 1.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
**Until now, you have set up the plugin in Obsidian. You can start syncing your notes to Notion.**
|
||||||
|
|
||||||
|
### 4. Start syncing
|
||||||
|
|
||||||
|
Create a new note in Obsidian, and fill in the front matter with the properties you want to sync.
|
||||||
|
|
||||||
|
#### 1️⃣ note for general database
|
||||||
|
|
||||||
|
For example, we can create a test note with the following front matter:
|
||||||
|
|
||||||
|
``` markdown
|
||||||
|
---
|
||||||
|
title: test
|
||||||
|
tags: [test, web]
|
||||||
|
---
|
||||||
|
|
||||||
|
This is a test file.
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
After creating the note, you can click the `Share to NotionNext` button on the left sidebar to sync the note to Notion.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 2️⃣ note for NotionNext database
|
||||||
|
|
||||||
|
We use the following front matter for the NotionNext database:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
title: test-notionnext
|
||||||
|
titleicon: 📎
|
||||||
|
date: 2023-07-23
|
||||||
|
coverurl: https://img.jxpeng.dev/2023/08/843e27a210847f05a0f7cfb121fec100.jpg
|
||||||
|
type: Post
|
||||||
|
slug: test
|
||||||
|
status: Draft
|
||||||
|
category: test
|
||||||
|
summary: this is a summary for test post
|
||||||
|
icon: fa-solid fa-camera
|
||||||
|
password: "1234"
|
||||||
|
tags:
|
||||||
|
- test
|
||||||
|
- web
|
||||||
|
NotionID-pengjiaxin: 8ba573de-8fdf-4681-b063-c39d26e7860e
|
||||||
|
---
|
||||||
|
|
||||||
|
this is a test file
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
#### 3️⃣ note for custom database
|
||||||
|
|
||||||
|
##### sync title and tags
|
||||||
|
|
||||||
|
Use the previously created general database and rename `title` to `Name` and `tags` to `Tags` in Notion.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The front matter for the custom database is as follows:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
Name: test-custom
|
||||||
|
Tags:
|
||||||
|
- test
|
||||||
|
- web
|
||||||
|
---
|
||||||
|
|
||||||
|
This is a test file.
|
||||||
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
##### sync more properties
|
||||||
|
|
||||||
|
Add more property to the custom database. For example, we can add a `summary` and `date` property to the custom database.
|
||||||
|
|
||||||
|
1). Click `Edit` in the plugin settings.
|
||||||
|

|
||||||
|
2). Click `Add` and add the `summary` and `date` property.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
3). After adding the properties, you can see the properties in the plugin settings.
|
||||||
|

|
||||||
|
4). Add the `summary` and `date` in Notion
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
5). Create a new note with the `summary` and `date` property.
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
Name: test-custom-more
|
||||||
|
Tags:
|
||||||
|
- test
|
||||||
|
- web
|
||||||
|
summary: this is a summary for test custom
|
||||||
|
date: 2022-05-03
|
||||||
|
---
|
||||||
|
|
||||||
|
This is a test file.
|
||||||
|
```
|
||||||
|
|
||||||
|
6). Sync the note to Notion.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Acknowledgment
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
**Now, support both NotionNext and General databases.**
|
|
||||||
|
|
||||||
## Update
|
|
||||||
### 1.1.1
|
|
||||||
- Fix the setting display bug in Japanese.
|
|
||||||
- Add Japanese translation.
|
|
||||||
|
|
||||||
### 1.1.0
|
|
||||||
- Fix the custom name setting tab display bug.
|
|
||||||
- Add a toggle to control whether to sync `tags` since the empty tags may cause the syncing error.
|
|
||||||
|
|
||||||
If you switch off the `tags` function in the plugin settings, it will ignore the `tags` in your frontmatter.
|
|
||||||
|
|
||||||
If you prefer to sync tags to Notion database, you can switch on the `tags` function in the plugin settings. **You can only use the following format for tags:**
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
tags: #empty tags, option 1
|
|
||||||
tags: [test,test1,test2] # use the square brackets, option 2
|
|
||||||
tags:
|
|
||||||
- test
|
|
||||||
- test1
|
|
||||||
- test2 # use the dash option 3
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1.0.1
|
|
||||||
|
|
||||||
- Fix the custom name element display bug in the settings.
|
|
||||||
|
|
||||||
### 1.0.0 (Big Update)
|
|
||||||
|
|
||||||
- From this version, you can **modify the first column name (title column, default: 'title')** as you want. (**Note: You need to have the 'tags' column in your Notion General database, and add `tags:` in your markdown frontmatter. If not, you will receive `network error 400`. But you can leave the `tags:` blank.**)
|
|
||||||
|
|
||||||

|
|
||||||
Like this:
|
|
||||||

|
|
||||||
|
|
||||||
- Add a switch button to control whether display the setting tabs in the plugin settings for both NotionNext and Notion General databases.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### 0.2.6
|
|
||||||
|
|
||||||
- Add a switch button to control whether to show the upload command in the command palette.
|
|
||||||
|
|
||||||
<!-- 
|
|
||||||
If you turn off the button for the General database, you won't see the option for the General database in the upload command list.
|
|
||||||
|
|
||||||
If you turn off the button for the NotionNext database, you won't see the option for the NotionNext database in the upload command list.
|
|
||||||
|
|
||||||
 -->
|
|
||||||
|
|
||||||
### 0.2.3
|
|
||||||
|
|
||||||
- Fix the bug, now you can update normally.
|
|
||||||
|
|
||||||
### 0.2.2
|
|
||||||
|
|
||||||
- Support both NotionNext and General Notion database.
|
|
||||||
- You can have one NotionNext and one General Notion database.
|
|
||||||
- General Notion database can only have `title` and `tags` columns, and `tags` columns should be the multi-selected property. **the name of the columns is case sensitive. You should use small letter.**
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

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

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

|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Original README.md**
|
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
@@ -364,10 +444,6 @@ node update-version.js
|
|||||||
./release.sh
|
./release.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Thanks
|
# Thanks
|
||||||
[Development Process | Obsidian Plugin Development Documentation](https://luhaifeng666.github.io/obsidian-plugin-docs-zh/zh/getting-started/development-workflow.html)
|
[Development Process | Obsidian Plugin Development Documentation](https://luhaifeng666.github.io/obsidian-plugin-docs-zh/zh/getting-started/development-workflow.html)
|
||||||
@@ -384,3 +460,6 @@ node update-version.js
|
|||||||
|
|
||||||
# License
|
# License
|
||||||
GNU GPLv3
|
GNU GPLv3
|
||||||
|
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "share-to-notionnext",
|
"id": "share-to-notionnext",
|
||||||
"name": "Share to NotionNext",
|
"name": "Share to NotionNext",
|
||||||
"version": "1.1.1",
|
"version": "2.7.0",
|
||||||
"minAppVersion": "0.0.1",
|
"minAppVersion": "0.0.1",
|
||||||
"description": "Shares obsidian md file to notion with notion api for NotionNext web deploy, originally created by EasyChris/obsidian-to-notion.",
|
"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",
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ date: 2023-07-23 # default is today, 默认是今天。 Format is 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, 默认是空,可直接删除
|
||||||
|
|||||||
17
package.json
17
package.json
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "share-to-notionnext",
|
"name": "share-to-notionnext",
|
||||||
"version": "1.1.1",
|
"version": "2.7.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Shares obsidian md file to notion with notion api for NotionNext web deploy, originally created by EasyChris/obsidian-to-notion.",
|
"description": "Share files to any Notion database using the Notion API, originally created by EasyChris/obsidian-to-notion.",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node esbuild.config.mjs",
|
"dev": "node esbuild.config.mjs",
|
||||||
@@ -10,23 +10,22 @@
|
|||||||
"version": "node version-bump.mjs && git add manifest.json versions.json"
|
"version": "node version-bump.mjs && git add manifest.json versions.json"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "Jiaxin PENG",
|
||||||
"license": "GNU GPLv3",
|
"license": "GNU GPLv3",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.5.7",
|
"@types/node": "^20.5.7",
|
||||||
"@types/yaml-front-matter": "^4.1.3",
|
"@types/yaml-front-matter": "^4.1.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
"@typescript-eslint/eslint-plugin": "^6.16.0",
|
||||||
"@typescript-eslint/parser": "^6.11.0",
|
"@typescript-eslint/parser": "^6.16.0",
|
||||||
"builtin-modules": "^3.2.0",
|
"builtin-modules": "^3.3.0",
|
||||||
"esbuild": "0.19.5",
|
"esbuild": "0.19.5",
|
||||||
"obsidian": "latest",
|
"obsidian": "^1.6.6",
|
||||||
"tslib": "2.6.2",
|
"tslib": "2.6.2",
|
||||||
"typescript": "5.2.2"
|
"typescript": "5.2.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tryfabric/martian": "^1.2.4",
|
"@jxpeng98/martian": "^1.3.1",
|
||||||
"https-proxy-agent": "^7.0.2",
|
"https-proxy-agent": "^7.0.2",
|
||||||
"process": "^0.11.10",
|
|
||||||
"remark-math": "^6.0.0",
|
"remark-math": "^6.0.0",
|
||||||
"yaml": "^2.3.4",
|
"yaml": "^2.3.4",
|
||||||
"yaml-front-matter": "^4.1.1"
|
"yaml-front-matter": "^4.1.1"
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { i18nConfig } from "src/lang/I18n";
|
import { i18nConfig } from "src/lang/I18n";
|
||||||
import { Editor, MarkdownView } from "obsidian";
|
import { Editor, MarkdownView, setTooltip } from "obsidian";
|
||||||
import { FuzzySuggester, DatabaseList } from "./FuzzySuggester";
|
import { FuzzySuggester, DatabaseList } from "./FuzzySuggester";
|
||||||
import { uploadCommandGeneral, uploadCommandNext } from "../upload/uploadCommand";
|
import { uploadCommandCustom, uploadCommandGeneral, uploadCommandNext } from "../upload/uploadCommand";
|
||||||
import ObsidianSyncNotionPlugin from "src/main";
|
import ObsidianSyncNotionPlugin from "src/main";
|
||||||
|
import { DatabaseDetails } from "../ui/settingTabs";
|
||||||
|
|
||||||
|
|
||||||
interface Command {
|
interface Command {
|
||||||
@@ -21,26 +22,11 @@ export default class RibbonCommands {
|
|||||||
constructor(plugin: ObsidianSyncNotionPlugin) {
|
constructor(plugin: ObsidianSyncNotionPlugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
|
|
||||||
// Check if NextButton is true, then include the corresponding command
|
// iterate through the database detail
|
||||||
if (this.plugin.settings.NextButton) {
|
|
||||||
this.Ncommand.push({
|
|
||||||
id: "share-to-notionnext",
|
|
||||||
name: i18nConfig.CommandName, // Use the translated text from i18nConfig
|
|
||||||
editorCallback: async (editor: Editor, view: MarkdownView) => {
|
|
||||||
await uploadCommandNext(this.plugin, this.plugin.settings, this.plugin.app);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if GeneralButton is true, then include the corresponding command
|
for (let key in this.plugin.settings.databaseDetails) {
|
||||||
if (this.plugin.settings.GeneralButton) {
|
let dbDetails = this.plugin.settings.databaseDetails[key];
|
||||||
this.Ncommand.push({
|
this.addCommandForDatabase(dbDetails);
|
||||||
id: "share-to-notion",
|
|
||||||
name: i18nConfig.CommandNameGeneral, // Use the translated text from i18nConfig
|
|
||||||
editorCallback: async (editor: Editor, view: MarkdownView) => {
|
|
||||||
await uploadCommandGeneral(this.plugin, this.plugin.settings, this.plugin.app);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register all the commands
|
// Register all the commands
|
||||||
@@ -77,24 +63,9 @@ export default class RibbonCommands {
|
|||||||
|
|
||||||
this.Ncommand = [];
|
this.Ncommand = [];
|
||||||
|
|
||||||
if (this.plugin.settings.NextButton) {
|
for (let key in this.plugin.settings.databaseDetails) {
|
||||||
this.Ncommand.push({
|
let dbDetails = this.plugin.settings.databaseDetails[key];
|
||||||
id: "share-to-notionnext",
|
this.addCommandForDatabase(dbDetails);
|
||||||
name: i18nConfig.CommandName, // Use the translated text from i18nConfig
|
|
||||||
editorCallback: async (editor: Editor, view: MarkdownView) => {
|
|
||||||
await uploadCommandNext(this.plugin, this.plugin.settings, this.plugin.app);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.plugin.settings.GeneralButton) {
|
|
||||||
this.Ncommand.push({
|
|
||||||
id: "share-to-notion",
|
|
||||||
name: i18nConfig.CommandNameGeneral, // Use the translated text from i18nConfig
|
|
||||||
editorCallback: async (editor: Editor, view: MarkdownView) => {
|
|
||||||
await uploadCommandGeneral(this.plugin, this.plugin.settings, this.plugin.app);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.Ncommand.forEach(command => {
|
this.Ncommand.forEach(command => {
|
||||||
@@ -107,4 +78,30 @@ export default class RibbonCommands {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,42 @@ export const I18n: {[key: string]:any} = {
|
|||||||
ja,
|
ja,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const I18nConfig = (lang: string): any => {
|
class I18nManager {
|
||||||
return I18n[lang];
|
private currentLanguage: string;
|
||||||
};
|
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.currentLanguage = this.detectLanguage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the language to use
|
||||||
|
private detectLanguage(): string {
|
||||||
const storedLanguage = window.localStorage.getItem("language");
|
const storedLanguage = window.localStorage.getItem("language");
|
||||||
|
if (storedLanguage && this.isLanguageSupported(storedLanguage)) {
|
||||||
|
console.log(`Using stored language: ${storedLanguage}`);
|
||||||
|
return storedLanguage;
|
||||||
|
}
|
||||||
|
|
||||||
export const i18nConfig = I18n[storedLanguage || window.navigator.language.split('-')[0] || "en"];
|
const browserLanguage = window.navigator.language.split("-")[0];
|
||||||
|
if (this.isLanguageSupported(browserLanguage)) {
|
||||||
|
console.log(`Using browser language: ${browserLanguage}`);
|
||||||
|
return browserLanguage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default to English if no match is found
|
||||||
|
console.log("Using default language: en");
|
||||||
|
return "en";
|
||||||
|
}
|
||||||
|
|
||||||
|
private isLanguageSupported(lang: string): boolean {
|
||||||
|
return Object.prototype.hasOwnProperty.call(I18n, lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the i18n configuration for the current language
|
||||||
|
public getConfig(): any {
|
||||||
|
return I18n[this.currentLanguage];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const i18nManager = new I18nManager();
|
||||||
|
|
||||||
|
export const i18nConfig = i18nManager.getConfig();
|
||||||
|
|||||||
@@ -1,4 +1,15 @@
|
|||||||
export const en = {
|
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",
|
ribbonIcon: "Share to NotionNext",
|
||||||
GeneralSetting: "General information Settings",
|
GeneralSetting: "General information Settings",
|
||||||
CommandID: "share-to-notionnext",
|
CommandID: "share-to-notionnext",
|
||||||
@@ -12,15 +23,18 @@ export const en = {
|
|||||||
NotionAPIDesc: "Generate from https://www.notion.so/my-integrations",
|
NotionAPIDesc: "Generate from https://www.notion.so/my-integrations",
|
||||||
NotionAPIText: "Enter your Notion API Token",
|
NotionAPIText: "Enter your Notion API Token",
|
||||||
DatabaseID: "Database ID",
|
DatabaseID: "Database ID",
|
||||||
|
DatabaseIDDesc: "Collect from the top-right Share --> Publish",
|
||||||
DatabaseIDText: "Enter your Database ID",
|
DatabaseIDText: "Enter your Database ID",
|
||||||
BannerUrl: "Banner url (optional)",
|
BannerUrl: "Banner url (optional)",
|
||||||
BannerUrlDesc:
|
BannerUrlDesc:
|
||||||
"Default is empty, if you want to show a banner, please enter the url (like: https://minioapi.pjx.ac.cn/img1/2023/11/b7b40a0724e93b7d7ab494bb3b8a2da8.png)",
|
"Default is empty, if you want to show a banner, please enter the url (like: https://abc.com/b.png)",
|
||||||
BannerUrlText: "Enter your banner url",
|
BannerUrlText: "Enter your banner url",
|
||||||
NotionUser: "Notion ID (username, optional)",
|
NotionUser: "Notion ID (username, optional)",
|
||||||
NotionUserDesc:
|
NotionUserDesc:
|
||||||
"Collect from share link likes:https://username.notion.site. Your notion id is [username]",
|
"Collect from share link likes:https://username.notion.site. Your notion id is [username]",
|
||||||
NotionUserText: "Enter your notion ID",
|
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",
|
NotionGeneralSettingHeader: "General Notion Database Settings",
|
||||||
NotionGeneralButton: "Notion General Sync",
|
NotionGeneralButton: "Notion General Sync",
|
||||||
NotionGeneralButtonDesc: "Open this option, Sync to Notion General Database command will be displayed in the command palette (default: ON)",
|
NotionGeneralButtonDesc: "Open this option, Sync to Notion General Database command will be displayed in the command palette (default: ON)",
|
||||||
@@ -31,12 +45,15 @@ export const en = {
|
|||||||
NotionCustomTitleName: "Preferred title name",
|
NotionCustomTitleName: "Preferred title name",
|
||||||
NotionCustomTitleNameDesc: "Enter the preferred title name for the first column of the Notion database (default: title)",
|
NotionCustomTitleNameDesc: "Enter the preferred title name for the first column of the Notion database (default: title)",
|
||||||
NotionCustomTitleText: "Enter the name",
|
NotionCustomTitleText: "Enter the name",
|
||||||
NotYetFinish:
|
NotionCustomValues: "Customise values property",
|
||||||
"Not finished. This function will be available in the next version",
|
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",
|
PlaceHolder: "Enter database Name",
|
||||||
"notion-logo": "Share to NotionNext",
|
"notion-logo": "Share to NotionNext",
|
||||||
"sync-success": "Sync to NotionNext success: \n",
|
"sync-preffix": "Sync to ",
|
||||||
"sync-fail": "Sync to NotionNext fail: \n",
|
"sync-success": "success",
|
||||||
|
"sync-fail": "failed",
|
||||||
"open-notion": "Please open the file that needs to be synchronized",
|
"open-notion": "Please open the file that needs to be synchronized",
|
||||||
"config-secrets-notion-api":
|
"config-secrets-notion-api":
|
||||||
"Please set up the notion API in the settings tab.",
|
"Please set up the notion API in the settings tab.",
|
||||||
@@ -51,4 +68,16 @@ export const en = {
|
|||||||
NotionCustomSettingHeader: "Notion Custom Database Settings",
|
NotionCustomSettingHeader: "Notion Custom Database Settings",
|
||||||
NotionCustomButton: "Notion Customised command switch",
|
NotionCustomButton: "Notion Customised command switch",
|
||||||
NotionCustomButtonDesc: "Open this option, Sync to Notion Customised Database command will be displayed in the command palette",
|
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",
|
||||||
|
CopyErrorMessage: "Auto copy failed, please copy it manually",
|
||||||
|
BlockUploaded: "All blocks uploaded",
|
||||||
|
ExtraBlockUploaded: "Extra blocks uploaded",
|
||||||
|
CheckConsole: "Check the console for more information \n opt+cmd+i/ctrl+shift+i",
|
||||||
|
"reach-mobile-limit": "The number of blocks exceeds the limit of 100, please use the desktop plugin",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,15 @@
|
|||||||
export const ja = {
|
export const ja = {
|
||||||
|
databaseFormat: "データベース形式",
|
||||||
|
databaseFormatDesc: "同期したいデータベース形式を選択してください",
|
||||||
|
databaseNext: "NotionNext",
|
||||||
|
databaseGeneral: "一般的なNotion",
|
||||||
|
databaseCustom: "カスタム",
|
||||||
|
databaseFullName: "データベースの全称",
|
||||||
|
databaseFullNameDesc: "データベースの全称を入力してください",
|
||||||
|
databaseFullNameText: "データベースの全称を入力",
|
||||||
|
databaseAbbreviateName: "データベースの略称",
|
||||||
|
databaseAbbreviateNameDesc: "データベースの略称を入力してください",
|
||||||
|
databaseAbbreviateNameText: "データベースの略称を入力",
|
||||||
ribbonIcon: "NotionNextで共有",
|
ribbonIcon: "NotionNextで共有",
|
||||||
GeneralSetting: "一般設定",
|
GeneralSetting: "一般設定",
|
||||||
CommandID: "share-to-notionnext",
|
CommandID: "share-to-notionnext",
|
||||||
@@ -12,13 +23,16 @@ export const ja = {
|
|||||||
NotionAPIDesc: "https://www.notion.so/my-integrations から生成してください",
|
NotionAPIDesc: "https://www.notion.so/my-integrations から生成してください",
|
||||||
NotionAPIText: "Notion API トークンを入力",
|
NotionAPIText: "Notion API トークンを入力",
|
||||||
DatabaseID: "データベースID",
|
DatabaseID: "データベースID",
|
||||||
|
DatabaseIDDesc: "右上の共有 --> 公開から取得してください",
|
||||||
DatabaseIDText: "データベースIDを入力",
|
DatabaseIDText: "データベースIDを入力",
|
||||||
BannerUrl: "バナーのURL(任意)",
|
BannerUrl: "バナーのURL(任意)",
|
||||||
BannerUrlDesc: "デフォルトは空白です。バナーを表示したい場合は、URLを入力してください(例:https://minioapi.pjx.ac.cn/img1/2023/11/b7b40a0724e93b7d7ab494bb3b8a2da8.png)",
|
BannerUrlDesc: "デフォルトは空白です。バナーを表示したい場合は、URLを入力してください(例:https://abc.com/b.png)",
|
||||||
BannerUrlText: "バナーのURLを入力",
|
BannerUrlText: "バナーのURLを入力",
|
||||||
NotionUser: "Notion ID(ユーザー名、任意)",
|
NotionUser: "Notion ID(ユーザー名、任意)",
|
||||||
NotionUserDesc: "共有リンクから取得(例:https://username.notion.site)。Notion IDは[username]です",
|
NotionUserDesc: "共有リンクから取得(例:https://username.notion.site)。Notion IDは[username]です",
|
||||||
NotionUserText: "Notion IDを入力",
|
NotionUserText: "Notion IDを入力",
|
||||||
|
NotionLinkDisplay: "Notionリンク表示",
|
||||||
|
NotionLinkDisplayDesc: "デフォルトはONです。front matterにリンクを非表示にしたい場合は、オフにしてください",
|
||||||
NotionGeneralSettingHeader: "一般的なNotionデータベース設定",
|
NotionGeneralSettingHeader: "一般的なNotionデータベース設定",
|
||||||
NotionGeneralButton: "一般的なNotion同期",
|
NotionGeneralButton: "一般的なNotion同期",
|
||||||
NotionGeneralButtonDesc: "このオプションを開くと、一般的なNotionデータベース同期コマンドがコマンドパレットに表示されます(デフォルト:ON)",
|
NotionGeneralButtonDesc: "このオプションを開くと、一般的なNotionデータベース同期コマンドがコマンドパレットに表示されます(デフォルト:ON)",
|
||||||
@@ -29,6 +43,9 @@ export const ja = {
|
|||||||
NotionCustomTitleName: "希望のタイトル名",
|
NotionCustomTitleName: "希望のタイトル名",
|
||||||
NotionCustomTitleNameDesc: "Notionデータベースの最初の列のための希望のタイトル名を入力(デフォルト:title)",
|
NotionCustomTitleNameDesc: "Notionデータベースの最初の列のための希望のタイトル名を入力(デフォルト:title)",
|
||||||
NotionCustomTitleText: "名前を入力",
|
NotionCustomTitleText: "名前を入力",
|
||||||
|
NotionCustomValues: "値のカスタマイズ",
|
||||||
|
NotionCustomValuesDesc: "Notionデータベースの列名を変更、1行に1つ",
|
||||||
|
NotionCustomValuesText: "同期したいすべてのプロパティを入力",
|
||||||
NotYetFinish: "未完了。この機能は次のバージョンで利用可能になります",
|
NotYetFinish: "未完了。この機能は次のバージョンで利用可能になります",
|
||||||
PlaceHolder: "データベース名を入力",
|
PlaceHolder: "データベース名を入力",
|
||||||
"notion-logo": "NotionNextで共有",
|
"notion-logo": "NotionNextで共有",
|
||||||
@@ -43,4 +60,15 @@ export const ja = {
|
|||||||
NotionCustomSettingHeader: "Notionカスタムデータベース設定",
|
NotionCustomSettingHeader: "Notionカスタムデータベース設定",
|
||||||
NotionCustomButton: "Notionカスタマイズコマンドの切り替え",
|
NotionCustomButton: "Notionカスタマイズコマンドの切り替え",
|
||||||
NotionCustomButtonDesc: "このオプションを開くと、Notionカスタムデータベース同期コマンドがコマンドパレットに表示されます",
|
NotionCustomButtonDesc: "このオプションを開くと、Notionカスタムデータベース同期コマンドがコマンドパレットに表示されます",
|
||||||
|
CustomPropertyName: "カスタムプロパティ名",
|
||||||
|
CustomPropertyFirstColumn: "最初の列のカスタムプロパティ名",
|
||||||
|
CustomPropertyFirstColumnDesc: "最初の列のカスタムプロパティ名を入力してください",
|
||||||
|
CustomProperty: "カスタムプロパティ",
|
||||||
|
AddCustomProperty: "カスタムプロパティを追加",
|
||||||
|
AddNewProperty: "新しいプロパティを追加",
|
||||||
|
AddNewPropertyDesc: "新しいプロパティを追加してください",
|
||||||
|
CopyErrorMessage: "自動コピーに失敗しました",
|
||||||
|
BlockUploaded: "ブロックがアップロードされました",
|
||||||
|
ExtraBlockUploaded: "追加ブロックがアップロードされました",
|
||||||
|
CheckConsole: "詳細情報を確認するには、コンソールを開いてください \n opt+cmd+i/ctrl+shift+i",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,15 @@
|
|||||||
export const zh = {
|
export const zh = {
|
||||||
|
databaseFormat: "数据库格式",
|
||||||
|
databaseFormatDesc: "选择你想要同步的数据库格式Next 或者 普通",
|
||||||
|
databaseNext: "NotionNext",
|
||||||
|
databaseGeneral: "普通",
|
||||||
|
databaseCustom: "自定义",
|
||||||
|
databaseFullName: "数据库全称",
|
||||||
|
databaseFullNameDesc: "给你的数据库起一个全称",
|
||||||
|
databaseFullNameText: "输入你的数据库全称",
|
||||||
|
databaseAbbreviateName: "数据库简称",
|
||||||
|
databaseAbbreviateNameDesc: "给你的数据库起一个简称",
|
||||||
|
databaseAbbreviateNameText: "输入你的数据库简称",
|
||||||
ribbonIcon: "分享到 NotionNext",
|
ribbonIcon: "分享到 NotionNext",
|
||||||
GeneralSetting: "通用设置",
|
GeneralSetting: "通用设置",
|
||||||
CommandID: "share-to-notionnext",
|
CommandID: "share-to-notionnext",
|
||||||
@@ -12,15 +23,18 @@ export const zh = {
|
|||||||
NotionAPIDesc: "从 https://www.notion.so/my-integrations 生成",
|
NotionAPIDesc: "从 https://www.notion.so/my-integrations 生成",
|
||||||
NotionAPIText: "输入你的 Notion API 令牌",
|
NotionAPIText: "输入你的 Notion API 令牌",
|
||||||
DatabaseID: "数据库 ID",
|
DatabaseID: "数据库 ID",
|
||||||
|
DatabaseIDDesc: "从右上角的分享 --> 发布中获取",
|
||||||
DatabaseIDText: "输入你的数据库 ID",
|
DatabaseIDText: "输入你的数据库 ID",
|
||||||
BannerUrl: "封面图片地址(可选)",
|
BannerUrl: "封面图片地址(可选)",
|
||||||
BannerUrlDesc:
|
BannerUrlDesc:
|
||||||
"默认为空,如果你想显示封面图片,请输入图片地址(例如:https://minioapi.pjx.ac.cn/img1/2023/11/b7b40a0724e93b7d7ab494bb3b8a2da8.png)",
|
"默认为空,如果你想显示封面图片,请输入图片地址(例如:https://abc.com/b.png)",
|
||||||
BannerUrlText: "输入你的封面图片地址",
|
BannerUrlText: "输入你的封面图片地址",
|
||||||
NotionUser: "Notion ID(用户名,可选)",
|
NotionUser: "Notion ID(用户名,可选)",
|
||||||
NotionUserDesc:
|
NotionUserDesc:
|
||||||
"数据库分享链接类似:https://username.notion.site/。你的 Notion ID 是 [username]",
|
"数据库分享链接类似:https://username.notion.site/。你的 Notion ID 是 [username]",
|
||||||
NotionUserText: "输入你的 Notion ID",
|
NotionUserText: "输入你的 Notion ID",
|
||||||
|
NotionLinkDisplay: "Notion 链接显示",
|
||||||
|
NotionLinkDisplayDesc: "默认开启,如果你不想在front matter中显示链接,请关闭",
|
||||||
NotionGeneralSettingHeader: "普通 Notion 数据库设置",
|
NotionGeneralSettingHeader: "普通 Notion 数据库设置",
|
||||||
NotionGeneralButton: "普通数据库同步",
|
NotionGeneralButton: "普通数据库同步",
|
||||||
NotionGeneralButtonDesc: "打开此选项,同步到普通数据库命令将显示在命令面板中(默认:开)",
|
NotionGeneralButtonDesc: "打开此选项,同步到普通数据库命令将显示在命令面板中(默认:开)",
|
||||||
@@ -31,6 +45,9 @@ export const zh = {
|
|||||||
NotionCustomTitleName: "想要修改的表头名",
|
NotionCustomTitleName: "想要修改的表头名",
|
||||||
NotionCustomTitleNameDesc: "输入你想要修改的notion数据库的表头名(默认:title)",
|
NotionCustomTitleNameDesc: "输入你想要修改的notion数据库的表头名(默认:title)",
|
||||||
NotionCustomTitleText: "输入表头名",
|
NotionCustomTitleText: "输入表头名",
|
||||||
|
NotionCustomValues: "自定义Notion 数据库表头",
|
||||||
|
NotionCustomValuesDesc: "自定义Notion 数据库表头,每行一个",
|
||||||
|
NotionCustomValuesText: "输入你想要同步的所有属性",
|
||||||
NotYetFinish: "未完成。此功能将在之后版本中提供",
|
NotYetFinish: "未完成。此功能将在之后版本中提供",
|
||||||
PlaceHolder: "输入数据库名称",
|
PlaceHolder: "输入数据库名称",
|
||||||
"notion-logo": "分享到NotionNext",
|
"notion-logo": "分享到NotionNext",
|
||||||
@@ -46,4 +63,15 @@ export const zh = {
|
|||||||
NotionCustomSettingHeader: "Notion 自定义数据库设置",
|
NotionCustomSettingHeader: "Notion 自定义数据库设置",
|
||||||
NotionCustomButton: "Notion 自定义数据库同步命令开关",
|
NotionCustomButton: "Notion 自定义数据库同步命令开关",
|
||||||
NotionCustomButtonDesc: "打开此选项,同步到自定义数据库命令将显示在命令面板中",
|
NotionCustomButtonDesc: "打开此选项,同步到自定义数据库命令将显示在命令面板中",
|
||||||
|
CustomPropertyName: "自定义属性名",
|
||||||
|
CustomPropertyFirstColumn: "第一列属性名",
|
||||||
|
CustomPropertyFirstColumnDesc: "第一列必须为标题属性名",
|
||||||
|
CustomProperty: "自定义属性",
|
||||||
|
AddCustomProperty: "添加自定义属性",
|
||||||
|
AddNewProperty: "添加新属性",
|
||||||
|
AddNewPropertyDesc: "添加一个和Notion数据库匹配的新属性",
|
||||||
|
CopyErrorMessage: "复制链接失败,请手动复制",
|
||||||
|
BlockUploaded: "所有内容已成功上传",
|
||||||
|
ExtraBlockUploaded: "额外内容已成功上传",
|
||||||
|
CheckConsole: "opt+cmd+i/ctrl+shift+i,\n打开控制台查看更多信息",
|
||||||
}
|
}
|
||||||
|
|||||||
34
src/main.ts
34
src/main.ts
@@ -1,10 +1,8 @@
|
|||||||
import { App, Editor, MarkdownView, Notice, Plugin, PluginSettingTab, Setting } from "obsidian";
|
import { App, Editor, MarkdownView, Notice, Plugin, PluginSettingTab, Setting } from "obsidian";
|
||||||
import { addIcons } from 'src/ui/icon';
|
import { addIcons } from 'src/ui/icon';
|
||||||
import { Upload2NotionGeneral } from "src/upload/upload_general/Upload2NotionGeneral";
|
|
||||||
import { Upload2NotionNext } from "src/upload/upload_next/Upload2NotionNext";
|
|
||||||
import { i18nConfig } from "src/lang/I18n";
|
import { i18nConfig } from "src/lang/I18n";
|
||||||
import ribbonCommands from "src/commands/NotionCommands";
|
import ribbonCommands from "src/commands/NotionCommands";
|
||||||
import { ObsidianSettingTab, PluginSettings, DEFAULT_SETTINGS } from "src/ui/settingTabs";
|
import { ObsidianSettingTab, PluginSettings, DEFAULT_SETTINGS, DatabaseDetails } from "src/ui/settingTabs";
|
||||||
|
|
||||||
// Remember to rename these classes and interfaces!
|
// Remember to rename these classes and interfaces!
|
||||||
|
|
||||||
@@ -20,7 +18,7 @@ export default class ObsidianSyncNotionPlugin extends Plugin {
|
|||||||
|
|
||||||
addIcons();
|
addIcons();
|
||||||
// This creates an icon in the left ribbon.
|
// This creates an icon in the left ribbon.
|
||||||
const ribbonIconEl = this.addRibbonIcon(
|
this.addRibbonIcon(
|
||||||
"notion-logo",
|
"notion-logo",
|
||||||
i18nConfig.ribbonIcon,
|
i18nConfig.ribbonIcon,
|
||||||
async (evt: MouseEvent) => {
|
async (evt: MouseEvent) => {
|
||||||
@@ -55,8 +53,34 @@ export default class ObsidianSyncNotionPlugin extends Plugin {
|
|||||||
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteDatabaseDetails(dbDetails: DatabaseDetails) {
|
||||||
|
delete this.settings.databaseDetails[dbDetails.abName];
|
||||||
|
|
||||||
|
await this.saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateDatabaseDetails(dbDetails: DatabaseDetails) {
|
||||||
|
// delete the old database details
|
||||||
|
delete this.settings.databaseDetails[dbDetails.abName];
|
||||||
|
|
||||||
|
this.settings.databaseDetails = {
|
||||||
|
...this.settings.databaseDetails,
|
||||||
|
[dbDetails.abName]: dbDetails,
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.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> = {
|
||||||
|
'rich_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',
|
||||||
|
'rich_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> = {
|
||||||
|
'rich_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();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
import { App, PluginSettingTab, Setting } from "obsidian";
|
import { App, ButtonComponent, PluginSettingTab, Setting } from "obsidian";
|
||||||
import { i18nConfig } from "../lang/I18n";
|
import { i18nConfig } from "../lang/I18n";
|
||||||
import ObsidianSyncNotionPlugin from "../main";
|
import ObsidianSyncNotionPlugin from "../main";
|
||||||
|
import { SettingModal } from "./settingModal";
|
||||||
|
import { PreviewModal } from "./PreviewModal";
|
||||||
|
import { EditModal } from "./EditModal";
|
||||||
|
import { DeleteModal } from "./DeleteModal";
|
||||||
|
|
||||||
export interface PluginSettings {
|
export interface PluginSettings {
|
||||||
NextButton: boolean;
|
NextButton: boolean;
|
||||||
@@ -8,17 +12,34 @@ export interface PluginSettings {
|
|||||||
databaseIDNext: string;
|
databaseIDNext: string;
|
||||||
bannerUrl: string;
|
bannerUrl: string;
|
||||||
notionUser: string;
|
notionUser: string;
|
||||||
|
NotionLinkDisplay: boolean;
|
||||||
proxy: string;
|
proxy: string;
|
||||||
GeneralButton: boolean;
|
GeneralButton: boolean;
|
||||||
tagButton: boolean;
|
tagButton: boolean;
|
||||||
CustomTitleButton: boolean;
|
customTitleButton: boolean;
|
||||||
CustomTitleName: string;
|
customTitleName: string;
|
||||||
notionAPIGeneral: string;
|
notionAPIGeneral: string;
|
||||||
databaseIDGeneral: string;
|
databaseIDGeneral: string;
|
||||||
CustomButton: boolean;
|
CustomButton: boolean;
|
||||||
|
CustomValues: string;
|
||||||
notionAPICustom: string;
|
notionAPICustom: string;
|
||||||
databaseIDCustom: string;
|
databaseIDCustom: string;
|
||||||
[key: string]: any;
|
[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 = {
|
export const DEFAULT_SETTINGS: PluginSettings = {
|
||||||
@@ -27,21 +48,25 @@ export const DEFAULT_SETTINGS: PluginSettings = {
|
|||||||
databaseIDNext: "",
|
databaseIDNext: "",
|
||||||
bannerUrl: "",
|
bannerUrl: "",
|
||||||
notionUser: "",
|
notionUser: "",
|
||||||
|
NotionLinkDisplay: true,
|
||||||
proxy: "",
|
proxy: "",
|
||||||
GeneralButton: true,
|
GeneralButton: true,
|
||||||
tagButton: true,
|
tagButton: true,
|
||||||
CustomTitleButton: false,
|
customTitleButton: false,
|
||||||
CustomTitleName: "",
|
customTitleName: "",
|
||||||
notionAPIGeneral: "",
|
notionAPIGeneral: "",
|
||||||
databaseIDGeneral: "",
|
databaseIDGeneral: "",
|
||||||
CustomButton: false,
|
CustomButton: false,
|
||||||
|
CustomValues: "",
|
||||||
notionAPICustom: "",
|
notionAPICustom: "",
|
||||||
databaseIDCustom: "",
|
databaseIDCustom: "",
|
||||||
|
databaseDetails: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export class ObsidianSettingTab extends PluginSettingTab {
|
export class ObsidianSettingTab extends PluginSettingTab {
|
||||||
plugin: ObsidianSyncNotionPlugin;
|
plugin: ObsidianSyncNotionPlugin;
|
||||||
|
databaseEl: HTMLDivElement;
|
||||||
|
|
||||||
constructor(app: App, plugin: ObsidianSyncNotionPlugin) {
|
constructor(app: App, plugin: ObsidianSyncNotionPlugin) {
|
||||||
super(app, plugin);
|
super(app, plugin);
|
||||||
@@ -60,138 +85,66 @@ export class ObsidianSettingTab extends PluginSettingTab {
|
|||||||
|
|
||||||
this.createSettingEl(containerEl, i18nConfig.NotionUser, i18nConfig.NotionUserDesc, 'text', i18nConfig.NotionUserText, this.plugin.settings.notionUser, 'notionUser')
|
this.createSettingEl(containerEl, i18nConfig.NotionUser, i18nConfig.NotionUserDesc, 'text', i18nConfig.NotionUserText, this.plugin.settings.notionUser, 'notionUser')
|
||||||
|
|
||||||
containerEl.createEl('h2', { text: i18nConfig.NotionNextSettingHeader })
|
this.createSettingEl(containerEl, i18nConfig.NotionLinkDisplay, i18nConfig.NotionLinkDisplayDesc, 'toggle', i18nConfig.NotionLinkDisplay, this.plugin.settings.NotionLinkDisplay, 'NotionLinkDisplay')
|
||||||
|
|
||||||
|
|
||||||
|
// add new button
|
||||||
new Setting(containerEl)
|
new Setting(containerEl)
|
||||||
.setName(i18nConfig.NotionNextButton)
|
.setName("Add New Database")
|
||||||
.setDesc(i18nConfig.NotionNextButtonDesc)
|
.setDesc("Add New Database")
|
||||||
.addToggle((toggle) =>
|
.addButton((button: ButtonComponent): ButtonComponent => {
|
||||||
toggle
|
return button
|
||||||
.setValue(this.plugin.settings.NextButton)
|
.setTooltip("Add New Database")
|
||||||
.onChange(async (value) => {
|
.setIcon("plus")
|
||||||
this.plugin.settings.NextButton = value;
|
.onClick(async () => {
|
||||||
|
let modal = new SettingModal(this.app, this.plugin, this);
|
||||||
|
|
||||||
this.updateSettingEl(notionAPINextEl, value)
|
modal.onClose = () => {
|
||||||
|
if (modal.data.saved) {
|
||||||
this.updateSettingEl(databaseIDNextEl, value)
|
const dbDetails = {
|
||||||
|
format: modal.data.databaseFormat,
|
||||||
await this.plugin.saveSettings();
|
fullName: modal.data.databaseFullName,
|
||||||
await this.plugin.commands.updateCommand();
|
abName: modal.data.databaseAbbreviateName,
|
||||||
})
|
notionAPI: modal.data.notionAPI,
|
||||||
);
|
databaseID: modal.data.databaseID,
|
||||||
|
tagButton: modal.data.tagButton,
|
||||||
|
customTitleButton: modal.data.customTitleButton,
|
||||||
const notionAPINextEl = this.createStyleDiv('api-next', this.plugin.settings.NextButton)
|
customTitleName: modal.data.customTitleName,
|
||||||
this.createSettingEl(notionAPINextEl, i18nConfig.NotionAPI, i18nConfig.NotionAPIDesc, 'password', i18nConfig.NotionAPIText, this.plugin.settings.notionAPINext, 'notionAPINext')
|
customProperties: modal.data.customProperties,
|
||||||
|
// customValues: modal.data.customValues,
|
||||||
const databaseIDNextEl = this.createStyleDiv('databaseID-next', this.plugin.settings.NextButton)
|
saved: modal.data.saved,
|
||||||
this.createSettingEl(databaseIDNextEl, i18nConfig.DatabaseID, i18nConfig.NotionAPIDesc, 'password', i18nConfig.DatabaseIDText, this.plugin.settings.databaseIDNext, 'databaseIDNext')
|
|
||||||
|
|
||||||
|
|
||||||
// General Database Settings
|
|
||||||
containerEl.createEl('h2', { text: i18nConfig.NotionGeneralSettingHeader });
|
|
||||||
|
|
||||||
// new Setting(containerEl)
|
|
||||||
// .setName(i18nConfig.NotYetFinish)
|
|
||||||
new Setting(containerEl)
|
|
||||||
.setName(i18nConfig.NotionGeneralButton)
|
|
||||||
.setDesc(i18nConfig.NotionGeneralButtonDesc)
|
|
||||||
.addToggle((toggle) =>
|
|
||||||
toggle
|
|
||||||
.setValue(this.plugin.settings.GeneralButton)
|
|
||||||
.onChange(async (value) => {
|
|
||||||
this.plugin.settings.GeneralButton = value;
|
|
||||||
|
|
||||||
this.updateSettingEl(tagButtonEl, value)
|
|
||||||
this.updateSettingEl(CustomTitleEl, value)
|
|
||||||
// name should follow the result of the title button
|
|
||||||
if (value) {
|
|
||||||
this.updateSettingEl(CustomNameEl, this.plugin.settings.CustomTitleButton)
|
|
||||||
} else {
|
|
||||||
this.updateSettingEl(CustomNameEl, value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateSettingEl(notionAPIGeneralEl, value)
|
this.plugin.addDatabaseDetails(dbDetails);
|
||||||
this.updateSettingEl(databaseIDGeneralEl, value)
|
|
||||||
|
|
||||||
|
this.plugin.commands.updateCommand();
|
||||||
|
|
||||||
await this.plugin.saveSettings();
|
this.display()
|
||||||
await this.plugin.commands.updateCommand();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
})
|
modal.open();
|
||||||
);
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// add the tagButton to control whether to add tags to the general database
|
// new section to display all created database
|
||||||
const tagButtonEl = this.createStyleDiv('tag-button', (this.plugin.settings.GeneralButton && this.plugin.settings.CustomTitleButton));
|
containerEl.createEl('h2', { text: "Database List" });
|
||||||
this.createSettingEl(tagButtonEl, i18nConfig.NotionTagButton, i18nConfig.NotionTagButtonDesc, 'toggle', i18nConfig.NotionCustomTitleText, this.plugin.settings.tagButton, 'tagButton')
|
|
||||||
|
|
||||||
// Custom Title Button
|
this.databaseEl = containerEl.createDiv('database-list');
|
||||||
const CustomTitleEl = this.createStyleDiv('custom-title', this.plugin.settings.GeneralButton);
|
// list all created database
|
||||||
new Setting(CustomTitleEl)
|
this.showDatabase();
|
||||||
.setName(i18nConfig.NotionCustomTitle)
|
|
||||||
.setDesc(i18nConfig.NotionCustomTitleDesc)
|
|
||||||
.addToggle((toggle) =>
|
|
||||||
toggle
|
|
||||||
.setValue(this.plugin.settings.CustomTitleButton)
|
|
||||||
.onChange(async (value) => {
|
|
||||||
this.plugin.settings.CustomTitleButton = value;
|
|
||||||
|
|
||||||
this.updateSettingEl(CustomNameEl, value)
|
|
||||||
|
|
||||||
await this.plugin.saveSettings();
|
|
||||||
await this.plugin.commands.updateCommand();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Custom Title Name
|
|
||||||
const CustomNameEl = this.createStyleDiv('custom-name', (this.plugin.settings.CustomTitleButton && this.plugin.settings.GeneralButton));
|
|
||||||
this.createSettingEl(CustomNameEl, i18nConfig.NotionCustomTitleName, i18nConfig.NotionCustomTitleNameDesc, 'text', i18nConfig.NotionCustomTitleText, this.plugin.settings.CustomTitleName, 'CustomTitleName')
|
|
||||||
|
|
||||||
// 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();
|
|
||||||
// })
|
|
||||||
// );
|
|
||||||
|
|
||||||
const notionAPIGeneralEl = this.createStyleDiv('api-general', this.plugin.settings.GeneralButton);
|
|
||||||
this.createSettingEl(notionAPIGeneralEl, i18nConfig.NotionAPI, i18nConfig.NotionAPIDesc, 'password', i18nConfig.NotionAPIText, this.plugin.settings.notionAPIGeneral, 'notionAPIGeneral')
|
|
||||||
|
|
||||||
|
|
||||||
const databaseIDGeneralEl = this.createStyleDiv('databaseID-general', this.plugin.settings.GeneralButton);
|
|
||||||
this.createSettingEl(databaseIDGeneralEl, i18nConfig.DatabaseID, i18nConfig.NotionAPIDesc, 'password', i18nConfig.DatabaseIDText, this.plugin.settings.databaseIDGeneral, 'databaseIDGeneral')
|
|
||||||
|
|
||||||
// Custom Database Settings
|
|
||||||
|
|
||||||
// containerEl.createEl('h2', {text: i18nConfig.NotionCustomSettingHeader});
|
|
||||||
//
|
|
||||||
// new Setting(containerEl)
|
|
||||||
// .setName(i18nConfig.NotionCustomButton)
|
|
||||||
// .setDesc(i18nConfig.NotionCustomButtonDesc)
|
|
||||||
// .addToggle((toggle) =>
|
|
||||||
// toggle
|
|
||||||
// .setValue(this.plugin.settings.CustomButton)
|
|
||||||
// .onChange(async (value) => {
|
|
||||||
// this.plugin.settings.CustomButton = value;
|
|
||||||
// await this.plugin.saveSettings();
|
|
||||||
// })
|
|
||||||
// );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a function to create a div with a style for pop over elements
|
// create a function to create a div with a style for pop over elements
|
||||||
private createStyleDiv(className: string, commandValue: boolean = false) {
|
// public createStyleDiv(className: string, commandValue: boolean = false) {
|
||||||
return this.containerEl.createDiv(className, (div) => {
|
// return this.containerEl.createDiv(className, (div) => {
|
||||||
this.updateSettingEl(div, commandValue);
|
// this.updateSettingEl(div, commandValue);
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
// update the setting display style in the setting tab
|
// update the setting display style in the setting tab
|
||||||
private updateSettingEl(element: HTMLElement, commandValue: boolean) {
|
public updateSettingEl(element: HTMLElement, commandValue: boolean) {
|
||||||
element.style.borderTop = commandValue ? "1px solid var(--background-modifier-border)" : "none";
|
element.style.borderTop = commandValue ? "1px solid var(--background-modifier-border)" : "none";
|
||||||
element.style.paddingTop = commandValue ? "0.75em" : "0";
|
element.style.paddingTop = commandValue ? "0.75em" : "0";
|
||||||
element.style.display = commandValue ? "block" : "none";
|
element.style.display = commandValue ? "block" : "none";
|
||||||
@@ -199,7 +152,7 @@ export class ObsidianSettingTab extends PluginSettingTab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// function to add one setting element in the setting tab.
|
// function to add one setting element in the setting tab.
|
||||||
private createSettingEl(containerEl: HTMLElement, name: string, desc: string, type: string, placeholder: string, holderValue: any, settingsKey: string) {
|
public createSettingEl(containerEl: HTMLElement, name: string, desc: string, type: string, placeholder: string, holderValue: any, settingsKey: string) {
|
||||||
if (type === 'password') {
|
if (type === 'password') {
|
||||||
return new Setting(containerEl)
|
return new Setting(containerEl)
|
||||||
.setName(name)
|
.setName(name)
|
||||||
@@ -244,4 +197,111 @@ export class ObsidianSettingTab extends PluginSettingTab {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
628
src/upload/Upload2Notion.ts
Normal file
628
src/upload/Upload2Notion.ts
Normal file
@@ -0,0 +1,628 @@
|
|||||||
|
import { App, TFile } from "obsidian";
|
||||||
|
import { markdownToBlocks } from "@jxpeng98/martian";
|
||||||
|
import * as yamlFrontMatter from "yaml-front-matter";
|
||||||
|
import { LIMITS, paragraph } from "@jxpeng98/martian";
|
||||||
|
import MyPlugin from "src/main";
|
||||||
|
import { DatabaseDetails } from "../ui/settingTabs";
|
||||||
|
import { updateYamlInfo } from "./updateYaml";
|
||||||
|
import { UploadBase, NotionPageResponse } from "./common/UploadBase";
|
||||||
|
|
||||||
|
export type DatasetType = "general" | "next" | "custom";
|
||||||
|
|
||||||
|
interface BaseSyncRequest {
|
||||||
|
dataset: DatasetType;
|
||||||
|
markdown: string;
|
||||||
|
nowFile: TFile;
|
||||||
|
app: App;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GeneralSyncRequest extends BaseSyncRequest {
|
||||||
|
dataset: "general";
|
||||||
|
title: string;
|
||||||
|
cover: string;
|
||||||
|
tags: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NextSyncRequest extends BaseSyncRequest {
|
||||||
|
dataset: "next";
|
||||||
|
title: string;
|
||||||
|
emoji: string;
|
||||||
|
cover: string;
|
||||||
|
tags: string[];
|
||||||
|
type: string;
|
||||||
|
slug: string;
|
||||||
|
stats: string;
|
||||||
|
category: string;
|
||||||
|
summary: string;
|
||||||
|
password: string;
|
||||||
|
favicon: string;
|
||||||
|
datetime: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CustomSyncRequest extends BaseSyncRequest {
|
||||||
|
dataset: "custom";
|
||||||
|
cover: string;
|
||||||
|
customValues: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SyncRequest =
|
||||||
|
| GeneralSyncRequest
|
||||||
|
| NextSyncRequest
|
||||||
|
| CustomSyncRequest;
|
||||||
|
|
||||||
|
interface GeneralParams {
|
||||||
|
title: string;
|
||||||
|
cover?: string;
|
||||||
|
tags: string[];
|
||||||
|
childArr: any[];
|
||||||
|
notionId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NextParams {
|
||||||
|
title: string;
|
||||||
|
emoji: string;
|
||||||
|
cover?: string;
|
||||||
|
tags: string[];
|
||||||
|
type: string;
|
||||||
|
slug: string;
|
||||||
|
stats: string;
|
||||||
|
category: string;
|
||||||
|
summary: string;
|
||||||
|
password: string;
|
||||||
|
favicon: string;
|
||||||
|
datetime: string;
|
||||||
|
childArr: any[];
|
||||||
|
notionId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CustomParams {
|
||||||
|
cover?: string;
|
||||||
|
customValues: Record<string, string>;
|
||||||
|
childArr: any[];
|
||||||
|
notionId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Upload2Notion extends UploadBase {
|
||||||
|
|
||||||
|
constructor(plugin: MyPlugin, dbDetails: DatabaseDetails) {
|
||||||
|
super(plugin, dbDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
async sync(request: SyncRequest): Promise<NotionPageResponse> {
|
||||||
|
const startedAt = Date.now();
|
||||||
|
|
||||||
|
let response: NotionPageResponse;
|
||||||
|
|
||||||
|
switch (request.dataset) {
|
||||||
|
case "general":
|
||||||
|
response = await this.handleGeneral(request);
|
||||||
|
break;
|
||||||
|
case "next":
|
||||||
|
response = await this.handleNext(request);
|
||||||
|
break;
|
||||||
|
case "custom":
|
||||||
|
response = await this.handleCustom(request);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported dataset type: ${(request as any).dataset}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.response && response.response.status === 200) {
|
||||||
|
await updateYamlInfo(
|
||||||
|
request.markdown,
|
||||||
|
request.nowFile,
|
||||||
|
response.data,
|
||||||
|
request.app,
|
||||||
|
this.plugin,
|
||||||
|
this.dbDetails,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log(`[Upload2Notion] Sync failed`, response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleGeneral(request: GeneralSyncRequest): Promise<NotionPageResponse> {
|
||||||
|
console.log(`[Upload2Notion] Handling general dataset`, {
|
||||||
|
cover: request.cover,
|
||||||
|
tags: request.tags,
|
||||||
|
});
|
||||||
|
const blocks = this.buildBlocks(request.markdown, {
|
||||||
|
notionLimits: {truncate: false},
|
||||||
|
});
|
||||||
|
const notionId = this.getNotionId(request.app, request.nowFile);
|
||||||
|
this.debugLog("Upload2Notion", "General dataset payload prepared", {
|
||||||
|
blockCount: blocks.length,
|
||||||
|
notionId: notionId ?? null,
|
||||||
|
tagCount: request.tags?.length ?? 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.upsertGeneral({
|
||||||
|
title: request.title,
|
||||||
|
cover: request.cover,
|
||||||
|
tags: request.tags || [],
|
||||||
|
childArr: blocks,
|
||||||
|
notionId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleNext(request: NextSyncRequest): Promise<NotionPageResponse> {
|
||||||
|
console.log(`[Upload2Notion] Handling next dataset`, {
|
||||||
|
type: request.type,
|
||||||
|
slug: request.slug,
|
||||||
|
category: request.category,
|
||||||
|
});
|
||||||
|
const blocks = this.buildBlocks(request.markdown, {
|
||||||
|
notionLimits: {truncate: false},
|
||||||
|
});
|
||||||
|
this.splitRichTextParagraphs(blocks);
|
||||||
|
const notionId = this.getNotionId(request.app, request.nowFile);
|
||||||
|
this.debugLog("Upload2Notion", "Next dataset payload prepared", {
|
||||||
|
blockCount: blocks.length,
|
||||||
|
notionId: notionId ?? null,
|
||||||
|
hasEmoji: !!request.emoji,
|
||||||
|
tags: request.tags || [],
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.upsertNext({
|
||||||
|
title: request.title,
|
||||||
|
emoji: request.emoji,
|
||||||
|
cover: request.cover,
|
||||||
|
tags: request.tags || [],
|
||||||
|
type: request.type,
|
||||||
|
slug: request.slug,
|
||||||
|
stats: request.stats,
|
||||||
|
category: request.category,
|
||||||
|
summary: request.summary,
|
||||||
|
password: request.password,
|
||||||
|
favicon: request.favicon,
|
||||||
|
datetime: request.datetime,
|
||||||
|
childArr: blocks,
|
||||||
|
notionId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleCustom(request: CustomSyncRequest): Promise<NotionPageResponse> {
|
||||||
|
console.log(`[Upload2Notion] Handling custom dataset`, {
|
||||||
|
customKeys: Object.keys(request.customValues || {}),
|
||||||
|
});
|
||||||
|
const blocks = this.buildBlocks(request.markdown, {
|
||||||
|
strictImageUrls: true,
|
||||||
|
notionLimits: {truncate: false},
|
||||||
|
});
|
||||||
|
const notionId = this.getNotionId(request.app, request.nowFile);
|
||||||
|
this.debugLog("Upload2Notion", "Custom dataset payload prepared", {
|
||||||
|
blockCount: blocks.length,
|
||||||
|
notionId: notionId ?? null,
|
||||||
|
customValueKeys: Object.keys(request.customValues || {}),
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.upsertCustom({
|
||||||
|
cover: request.cover,
|
||||||
|
customValues: request.customValues,
|
||||||
|
childArr: blocks,
|
||||||
|
notionId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildBlocks(markdown: string, options: Record<string, unknown>): any[] {
|
||||||
|
const yamlContent: any = yamlFrontMatter.loadFront(markdown);
|
||||||
|
const content = yamlContent.__content;
|
||||||
|
const blocks = markdownToBlocks(content, options);
|
||||||
|
this.debugLog("Upload2Notion", "Converted markdown to blocks", {
|
||||||
|
blockCount: blocks.length,
|
||||||
|
firstBlockTypes: blocks.slice(0, 5).map((block: any) => block?.type),
|
||||||
|
options,
|
||||||
|
});
|
||||||
|
return blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getNotionId(app: App, nowFile: TFile): string | undefined {
|
||||||
|
const frontMatter = app.metadataCache.getFileCache(nowFile)?.frontmatter;
|
||||||
|
if (!frontMatter) {
|
||||||
|
this.debugLog("Upload2Notion", "No frontmatter found when resolving Notion ID", {
|
||||||
|
filePath: nowFile.path,
|
||||||
|
});
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const {abName} = this.dbDetails;
|
||||||
|
const notionIDKey = `NotionID-${abName}`;
|
||||||
|
const notionId = frontMatter[notionIDKey];
|
||||||
|
return notionId ? String(notionId) : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private splitRichTextParagraphs(blocks: any[]): void {
|
||||||
|
blocks.forEach((block, index) => {
|
||||||
|
if (
|
||||||
|
block.type === "paragraph" &&
|
||||||
|
block.paragraph.rich_text.length > LIMITS.RICH_TEXT_ARRAYS
|
||||||
|
) {
|
||||||
|
console.log(`[Upload2Notion] Splitting rich text paragraph`, {
|
||||||
|
index,
|
||||||
|
length: block.paragraph.rich_text.length,
|
||||||
|
});
|
||||||
|
const paragraphChunks = this.chunkArray<any>(block.paragraph.rich_text, 100);
|
||||||
|
const newParagraphBlocks = paragraphChunks.map((chunk) => paragraph(chunk));
|
||||||
|
blocks.splice(index, 1, ...newParagraphBlocks);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private chunkArray<T>(items: T[], size: number): T[][] {
|
||||||
|
const result: T[][] = [];
|
||||||
|
for (let i = 0; i < items.length; i += size) {
|
||||||
|
result.push(items.slice(i, i + size));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async upsertGeneral(params: GeneralParams): Promise<NotionPageResponse> {
|
||||||
|
const {firstChunk, extraChunks} = this.prepareBlocks(params.childArr);
|
||||||
|
const cover = params.notionId
|
||||||
|
? await this.resolveCoverForUpdate(params.cover)
|
||||||
|
: params.cover;
|
||||||
|
this.debugLog("Upload2Notion", "Upserting general page", {
|
||||||
|
title: params.title,
|
||||||
|
blockCount: params.childArr.length,
|
||||||
|
firstChunkSize: firstChunk.length,
|
||||||
|
extraChunkCount: extraChunks.length,
|
||||||
|
cover: cover ?? null,
|
||||||
|
notionIdExisting: params.notionId ?? null,
|
||||||
|
tagList: params.tags,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (params.notionId) {
|
||||||
|
console.log(`[Upload2Notion] Deleting existing Notion page`, {
|
||||||
|
notionId: params.notionId,
|
||||||
|
});
|
||||||
|
await this.deletePage(params.notionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = this.buildGeneralBody({
|
||||||
|
title: params.title,
|
||||||
|
tags: params.tags,
|
||||||
|
firstChunk,
|
||||||
|
});
|
||||||
|
this.applyCover(body, cover);
|
||||||
|
console.log(body);
|
||||||
|
return this.submitPage(body, extraChunks);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async upsertNext(params: NextParams): Promise<NotionPageResponse> {
|
||||||
|
const {firstChunk, extraChunks} = this.prepareBlocks(params.childArr);
|
||||||
|
const cover = params.notionId
|
||||||
|
? await this.resolveCoverForUpdate(params.cover)
|
||||||
|
: params.cover;
|
||||||
|
this.debugLog("Upload2Notion", "Upserting NotionNext page", {
|
||||||
|
title: params.title,
|
||||||
|
type: params.type,
|
||||||
|
slug: params.slug,
|
||||||
|
blockCount: params.childArr.length,
|
||||||
|
firstChunkSize: firstChunk.length,
|
||||||
|
extraChunkCount: extraChunks.length,
|
||||||
|
cover: cover ?? null,
|
||||||
|
hasEmoji: !!params.emoji,
|
||||||
|
notionIdExisting: params.notionId ?? null,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (params.notionId) {
|
||||||
|
await this.deletePage(params.notionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = this.buildNextBody({
|
||||||
|
firstChunk,
|
||||||
|
title: params.title,
|
||||||
|
emoji: params.emoji,
|
||||||
|
tags: params.tags,
|
||||||
|
type: params.type,
|
||||||
|
slug: params.slug,
|
||||||
|
stats: params.stats,
|
||||||
|
category: params.category,
|
||||||
|
summary: params.summary,
|
||||||
|
password: params.password,
|
||||||
|
favicon: params.favicon,
|
||||||
|
datetime: params.datetime,
|
||||||
|
});
|
||||||
|
this.applyCover(body, cover);
|
||||||
|
console.log(body);
|
||||||
|
return this.submitPage(body, extraChunks);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async upsertCustom(params: CustomParams): Promise<NotionPageResponse> {
|
||||||
|
const {firstChunk, extraChunks} = this.prepareBlocks(params.childArr);
|
||||||
|
const cover = params.notionId
|
||||||
|
? await this.resolveCoverForUpdate(params.cover)
|
||||||
|
: params.cover;
|
||||||
|
this.debugLog("Upload2Notion", "Upserting custom page", {
|
||||||
|
blockCount: params.childArr.length,
|
||||||
|
firstChunkSize: firstChunk.length,
|
||||||
|
extraChunkCount: extraChunks.length,
|
||||||
|
cover: cover ?? null,
|
||||||
|
notionIdExisting: params.notionId ?? null,
|
||||||
|
customValueKeys: Object.keys(params.customValues || {}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (params.notionId) {
|
||||||
|
await this.deletePage(params.notionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = this.buildCustomBody({
|
||||||
|
customValues: params.customValues,
|
||||||
|
firstChunk,
|
||||||
|
});
|
||||||
|
this.applyCover(body, cover);
|
||||||
|
console.log(body);
|
||||||
|
return this.submitPage(body, extraChunks);
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildGeneralBody(params: {title: string; tags: string[]; firstChunk: any[]}): any {
|
||||||
|
const {
|
||||||
|
databaseID,
|
||||||
|
customTitleButton,
|
||||||
|
customTitleName,
|
||||||
|
tagButton,
|
||||||
|
} = this.dbDetails;
|
||||||
|
|
||||||
|
return {
|
||||||
|
parent: {
|
||||||
|
database_id: databaseID,
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
[customTitleButton ? customTitleName : "title"]: {
|
||||||
|
title: [
|
||||||
|
{
|
||||||
|
text: {
|
||||||
|
content: params.title,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
...(tagButton
|
||||||
|
? {
|
||||||
|
tags: {
|
||||||
|
multi_select:
|
||||||
|
params.tags && params.tags.length > 0
|
||||||
|
? params.tags.map((tag) => ({name: tag}))
|
||||||
|
: [],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
|
children: params.firstChunk,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildNextBody(params: {
|
||||||
|
firstChunk: any[];
|
||||||
|
title: string;
|
||||||
|
emoji: string;
|
||||||
|
tags: string[];
|
||||||
|
type: string;
|
||||||
|
slug: string;
|
||||||
|
stats: string;
|
||||||
|
category: string;
|
||||||
|
summary: string;
|
||||||
|
password: string;
|
||||||
|
favicon: string;
|
||||||
|
datetime: string;
|
||||||
|
}): any {
|
||||||
|
const {databaseID} = this.dbDetails;
|
||||||
|
|
||||||
|
const pageProperties: any = {
|
||||||
|
parent: {
|
||||||
|
database_id: databaseID,
|
||||||
|
},
|
||||||
|
properties: {
|
||||||
|
title: {
|
||||||
|
title: [
|
||||||
|
{
|
||||||
|
text: {
|
||||||
|
content: params.title,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
select: {
|
||||||
|
name: params.type || "Post",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
select: {
|
||||||
|
name: params.stats || "Draft",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
category: {
|
||||||
|
select: {
|
||||||
|
name: params.category || "Obsidian",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
rich_text: [
|
||||||
|
{
|
||||||
|
text: {
|
||||||
|
content: params.password || "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
rich_text: [
|
||||||
|
{
|
||||||
|
text: {
|
||||||
|
content: params.favicon || "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
date: {
|
||||||
|
date: {
|
||||||
|
start: params.datetime || new Date().toISOString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (params.tags && params.tags.length > 0) {
|
||||||
|
pageProperties.properties.tags = {
|
||||||
|
multi_select: params.tags.map((tag) => ({name: tag})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.emoji) {
|
||||||
|
pageProperties.icon = {
|
||||||
|
emoji: params.emoji,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.slug) {
|
||||||
|
pageProperties.properties.slug = {
|
||||||
|
rich_text: [
|
||||||
|
{
|
||||||
|
text: {
|
||||||
|
content: params.slug,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.summary) {
|
||||||
|
pageProperties.properties.summary = {
|
||||||
|
rich_text: [
|
||||||
|
{
|
||||||
|
text: {
|
||||||
|
content: params.summary,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...pageProperties,
|
||||||
|
children: params.firstChunk,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildCustomBody(params: {
|
||||||
|
customValues: Record<string, string>;
|
||||||
|
firstChunk: any[];
|
||||||
|
}): any {
|
||||||
|
const {customProperties, databaseID} = this.dbDetails;
|
||||||
|
const properties: { [key: string]: any } = {};
|
||||||
|
|
||||||
|
if (customProperties) {
|
||||||
|
customProperties.forEach(({customName, customType}) => {
|
||||||
|
if (params.customValues[customName] !== undefined) {
|
||||||
|
const propertyValue = this.buildCustomProperty(customName, customType, params.customValues);
|
||||||
|
if (propertyValue !== undefined) {
|
||||||
|
properties[customName] = propertyValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
parent: {
|
||||||
|
database_id: databaseID,
|
||||||
|
},
|
||||||
|
properties,
|
||||||
|
children: params.firstChunk,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildCustomProperty(customName: string, customType: string, customValues: Record<string, any>) {
|
||||||
|
const value = customValues[customName] || "";
|
||||||
|
|
||||||
|
switch (customType) {
|
||||||
|
case "title":
|
||||||
|
return {
|
||||||
|
title: [
|
||||||
|
{
|
||||||
|
text: {
|
||||||
|
content: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
case "rich_text":
|
||||||
|
return {
|
||||||
|
rich_text: [
|
||||||
|
{
|
||||||
|
text: {
|
||||||
|
content: value || "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
case "date":
|
||||||
|
return {
|
||||||
|
date: {
|
||||||
|
start: value || new Date().toISOString(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
case "number":
|
||||||
|
return {
|
||||||
|
number: Number(value),
|
||||||
|
};
|
||||||
|
case "phone_number":
|
||||||
|
return {
|
||||||
|
phone_number: value,
|
||||||
|
};
|
||||||
|
case "email":
|
||||||
|
return {
|
||||||
|
email: value,
|
||||||
|
};
|
||||||
|
case "url":
|
||||||
|
return {
|
||||||
|
url: value,
|
||||||
|
};
|
||||||
|
case "files":
|
||||||
|
return {
|
||||||
|
files: Array.isArray(value)
|
||||||
|
? value.map((url: string) => ({
|
||||||
|
name: url,
|
||||||
|
type: "external",
|
||||||
|
external: {
|
||||||
|
url,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
: [
|
||||||
|
{
|
||||||
|
name: value,
|
||||||
|
type: "external",
|
||||||
|
external: {
|
||||||
|
url: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
case "checkbox":
|
||||||
|
return {
|
||||||
|
checkbox: Boolean(value) || false,
|
||||||
|
};
|
||||||
|
case "select":
|
||||||
|
return {
|
||||||
|
select: {
|
||||||
|
name: value,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
case "multi_select":
|
||||||
|
return {
|
||||||
|
multi_select: Array.isArray(value)
|
||||||
|
? value.map((item: string) => ({name: item}))
|
||||||
|
: [{name: value}],
|
||||||
|
};
|
||||||
|
case "relation":
|
||||||
|
return {
|
||||||
|
relation: Array.isArray(value)
|
||||||
|
? value.map((item: string) => ({id: item}))
|
||||||
|
: [{id: value}],
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
248
src/upload/common/UploadBase.ts
Normal file
248
src/upload/common/UploadBase.ts
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
import { Notice, requestUrl } from "obsidian";
|
||||||
|
import MyPlugin from "src/main";
|
||||||
|
import { DatabaseDetails } from "../../ui/settingTabs";
|
||||||
|
import { i18nConfig } from "../../lang/I18n";
|
||||||
|
|
||||||
|
export interface NotionPageResponse {
|
||||||
|
response: any;
|
||||||
|
data: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PreparedBlocks {
|
||||||
|
firstChunk: any[];
|
||||||
|
extraChunks: any[][];
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class UploadBase {
|
||||||
|
protected plugin: MyPlugin;
|
||||||
|
protected dbDetails: DatabaseDetails;
|
||||||
|
|
||||||
|
protected constructor(plugin: MyPlugin, dbDetails: DatabaseDetails) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.dbDetails = dbDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
async deletePage(notionID: string) {
|
||||||
|
const {notionAPI} = this.dbDetails;
|
||||||
|
return requestUrl({
|
||||||
|
url: `https://api.notion.com/v1/blocks/${notionID}`,
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: "Bearer " + notionAPI,
|
||||||
|
"Notion-Version": "2022-06-28",
|
||||||
|
},
|
||||||
|
body: "",
|
||||||
|
throw: false,
|
||||||
|
}).catch((error) =>
|
||||||
|
this.handleRequestError(error, `Deleting Notion page ${notionID}`),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected prepareBlocks(childArr: any[]): PreparedBlocks {
|
||||||
|
this.stripCodeAnnotations(childArr);
|
||||||
|
|
||||||
|
const childArrLength = childArr.length;
|
||||||
|
|
||||||
|
if (childArrLength <= 100) {
|
||||||
|
this.debugLog("UploadBase", "Blocks fit into a single request", {
|
||||||
|
totalBlocks: childArrLength,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
firstChunk: childArr,
|
||||||
|
extraChunks: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const extraChunks: any[][] = [];
|
||||||
|
for (let i = 100; i < childArr.length; i += 100) {
|
||||||
|
extraChunks.push(childArr.slice(i, i + 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.debugLog("UploadBase", "Blocks split into multiple chunks", {
|
||||||
|
totalBlocks: childArrLength,
|
||||||
|
firstChunkSize: 100,
|
||||||
|
extraChunkCount: extraChunks.length,
|
||||||
|
lastChunkSize: extraChunks[extraChunks.length - 1].length,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
firstChunk: childArr.slice(0, 100),
|
||||||
|
extraChunks,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected applyCover(body: any, cover?: string) {
|
||||||
|
if (cover) {
|
||||||
|
body.cover = {
|
||||||
|
type: "external",
|
||||||
|
external: {
|
||||||
|
url: cover,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (!body.cover && this.plugin.settings.bannerUrl) {
|
||||||
|
body.cover = {
|
||||||
|
type: "external",
|
||||||
|
external: {
|
||||||
|
url: this.plugin.settings.bannerUrl,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.debugLog("UploadBase", "Cover applied to payload", {
|
||||||
|
chosenCover: body.cover?.external?.url ?? null,
|
||||||
|
defaultBannerUsed: !cover && !!this.plugin.settings.bannerUrl,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async resolveCoverForUpdate(cover?: string): Promise<string | undefined> {
|
||||||
|
if (cover) {
|
||||||
|
this.debugLog("UploadBase", "Existing cover retained for update", {
|
||||||
|
cover,
|
||||||
|
});
|
||||||
|
return cover;
|
||||||
|
}
|
||||||
|
const databaseCover = await this.fetchDatabaseCover();
|
||||||
|
return databaseCover ?? undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async submitPage(body: any, extraChunks: any[][]): Promise<NotionPageResponse> {
|
||||||
|
const {notionAPI} = this.dbDetails;
|
||||||
|
const startedAt = Date.now();
|
||||||
|
|
||||||
|
const response = await requestUrl({
|
||||||
|
url: `https://api.notion.com/v1/pages`,
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: "Bearer " + notionAPI,
|
||||||
|
"Notion-Version": "2022-06-28",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
throw: false,
|
||||||
|
}).catch((error) =>
|
||||||
|
this.handleRequestError(error, "Creating Notion page"),
|
||||||
|
);
|
||||||
|
|
||||||
|
const data = await response.json;
|
||||||
|
this.debugLog("UploadBase", "Page creation response received", {
|
||||||
|
status: response.status,
|
||||||
|
durationMs: Date.now() - startedAt,
|
||||||
|
notionUrl: data?.url ?? null,
|
||||||
|
pageId: data?.id ?? null,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status !== 200) {
|
||||||
|
new Notice(`Error ${data.status}: ${data.code} \n ${i18nConfig["CheckConsole"]}`, 5000);
|
||||||
|
console.log(`Error message: \n ${data.message}`);
|
||||||
|
} else {
|
||||||
|
if (extraChunks.length > 0) {
|
||||||
|
await this.appendExtraBlocks(data.id, extraChunks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {response, data};
|
||||||
|
}
|
||||||
|
|
||||||
|
private stripCodeAnnotations(childArr: any[]) {
|
||||||
|
childArr.forEach((block: any) => {
|
||||||
|
if (block.type === "code") {
|
||||||
|
block.code.rich_text.forEach((item: any) => {
|
||||||
|
if (item.type === "text" && item.annotations) {
|
||||||
|
delete item.annotations;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async appendExtraBlocks(pageId: string, extraChunks: any[][]) {
|
||||||
|
const {notionAPI} = this.dbDetails;
|
||||||
|
|
||||||
|
for (let i = 0; i < extraChunks.length; i++) {
|
||||||
|
const chunk = extraChunks[i];
|
||||||
|
const extraBlocks = {
|
||||||
|
children: chunk,
|
||||||
|
};
|
||||||
|
|
||||||
|
const extraResponse = await requestUrl({
|
||||||
|
url: `https://api.notion.com/v1/blocks/${pageId}/children`,
|
||||||
|
method: "PATCH",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: "Bearer " + notionAPI,
|
||||||
|
"Notion-Version": "2022-06-28",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(extraBlocks),
|
||||||
|
throw: false,
|
||||||
|
}).catch((error) =>
|
||||||
|
this.handleRequestError(error, `Appending blocks to page ${pageId}`),
|
||||||
|
);
|
||||||
|
|
||||||
|
const extraData: any = await extraResponse.json;
|
||||||
|
|
||||||
|
if (extraResponse.status !== 200) {
|
||||||
|
new Notice(`Error ${extraData.status}: ${extraData.code} \n ${i18nConfig["CheckConsole"]}`, 5000);
|
||||||
|
console.log(`Error message: \n ${extraData.message}`);
|
||||||
|
} else {
|
||||||
|
console.log(`${i18nConfig["ExtraBlockUploaded"]} to page: ${pageId}`);
|
||||||
|
if (i === extraChunks.length - 1) {
|
||||||
|
console.log(`${i18nConfig["BlockUploaded"]} to page: ${pageId}`);
|
||||||
|
new Notice(`${i18nConfig["BlockUploaded"]} page: ${pageId}`, 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async fetchDatabaseCover(): Promise<string | null> {
|
||||||
|
const {notionAPI, databaseID} = this.dbDetails;
|
||||||
|
const response = await requestUrl({
|
||||||
|
url: `https://api.notion.com/v1/databases/${databaseID}`,
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
Authorization: "Bearer " + notionAPI,
|
||||||
|
"Notion-Version": "2022-06-28",
|
||||||
|
},
|
||||||
|
throw: false,
|
||||||
|
}).catch((error) =>
|
||||||
|
this.handleRequestError(error, `Fetching database ${databaseID}`),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.json.cover && response.json.cover.external) {
|
||||||
|
return response.json.cover.external.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected debugLog(context: string, message: string, payload?: Record<string, unknown>): void {
|
||||||
|
if (payload) {
|
||||||
|
console.log(`[${context}] ${message}`, payload);
|
||||||
|
} else {
|
||||||
|
console.log(`[${context}] ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected maskValue(value?: string, visibleChars = 4): string | undefined {
|
||||||
|
if (!value) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (value.length <= visibleChars * 2) {
|
||||||
|
return `${value.slice(0, visibleChars)}***`;
|
||||||
|
}
|
||||||
|
return `${value.slice(0, visibleChars)}***${value.slice(-visibleChars)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleRequestError(error: unknown, context: string): never {
|
||||||
|
const message = error instanceof Error && error.message
|
||||||
|
? error.message
|
||||||
|
: String(error);
|
||||||
|
console.error(`[UploadBase] ${context} failed`, {
|
||||||
|
message,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
throw new Error(
|
||||||
|
`${context} failed: ${message}. Please check your network connection or proxy settings.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
62
src/upload/common/getMarkdownCustom.ts
Normal file
62
src/upload/common/getMarkdownCustom.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
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
|
||||||
|
// Only collect data 'Relation' should be handled separately in the function buildBodyString
|
||||||
|
customPropertyNames.forEach(propertyName => {
|
||||||
|
if (FileCache.frontmatter && FileCache.frontmatter[propertyName] !== undefined) {
|
||||||
|
customValues[propertyName] = FileCache.frontmatter[propertyName];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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] =
|
||||||
|
(FileCache.frontmatter && FileCache.frontmatter[titleProperty.customName]) ?
|
||||||
|
FileCache.frontmatter[titleProperty.customName] : // use the front matter value if it exists
|
||||||
|
nowFile.basename; // Use 'basename' for the file name without extension
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
33
src/upload/common/getMarkdownGeneral.ts
Normal file
33
src/upload/common/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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ export async function getNowFileMarkdownContentNext(
|
|||||||
let type = '';
|
let type = '';
|
||||||
let slug = '';
|
let slug = '';
|
||||||
let stats = '';
|
let stats = '';
|
||||||
|
let status = '';
|
||||||
let category = '';
|
let category = '';
|
||||||
let summary = '';
|
let summary = '';
|
||||||
let paword = '';
|
let paword = '';
|
||||||
@@ -26,7 +27,7 @@ export async function getNowFileMarkdownContentNext(
|
|||||||
tags = FileCache.frontmatter.tags;
|
tags = FileCache.frontmatter.tags;
|
||||||
type = FileCache.frontmatter.type;
|
type = FileCache.frontmatter.type;
|
||||||
slug = FileCache.frontmatter.slug;
|
slug = FileCache.frontmatter.slug;
|
||||||
stats = FileCache.frontmatter.stats;
|
stats = FileCache.frontmatter.stats || FileCache.frontmatter.status;
|
||||||
category = FileCache.frontmatter.category;
|
category = FileCache.frontmatter.category;
|
||||||
summary = FileCache.frontmatter.summary;
|
summary = FileCache.frontmatter.summary;
|
||||||
paword = FileCache.frontmatter.password;
|
paword = FileCache.frontmatter.password;
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { App, Notice, TFile } from "obsidian";
|
import { App, Notice, TFile } from "obsidian";
|
||||||
import ObsidianSyncNotionPlugin from "../main";
|
import ObsidianSyncNotionPlugin from "../main";
|
||||||
import { PluginSettings } from "../ui/settingTabs";
|
import { DatabaseDetails } from "../ui/settingTabs";
|
||||||
|
import { i18nConfig } from "src/lang/I18n";
|
||||||
|
|
||||||
export async function updateYamlInfo(
|
export async function updateYamlInfo(
|
||||||
yamlContent: string,
|
yamlContent: string,
|
||||||
@@ -8,10 +9,14 @@ export async function updateYamlInfo(
|
|||||||
res: any,
|
res: any,
|
||||||
app: App,
|
app: App,
|
||||||
plugin: ObsidianSyncNotionPlugin,
|
plugin: ObsidianSyncNotionPlugin,
|
||||||
|
dbDetails: DatabaseDetails,
|
||||||
) {
|
) {
|
||||||
let { url, id } = res.json
|
let { url, id } = res;
|
||||||
// replace www to notionID
|
// replace www to notionID
|
||||||
const { notionUser } = plugin.settings;
|
const { notionUser, NotionLinkDisplay } = plugin.settings;
|
||||||
|
const { abName } = dbDetails
|
||||||
|
const notionIDKey = `NotionID-${abName}`;
|
||||||
|
const linkKey = `link-${abName}`;
|
||||||
|
|
||||||
if (notionUser !== "") {
|
if (notionUser !== "") {
|
||||||
// replace url str "www" to notionID
|
// replace url str "www" to notionID
|
||||||
@@ -19,33 +24,21 @@ export async function updateYamlInfo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
await app.fileManager.processFrontMatter(nowFile, yamlContent => {
|
await app.fileManager.processFrontMatter(nowFile, yamlContent => {
|
||||||
if (yamlContent['notionID']) {
|
if (yamlContent[notionIDKey]) {
|
||||||
delete yamlContent['notionID']
|
delete yamlContent[notionIDKey]
|
||||||
}
|
}
|
||||||
if (yamlContent['link']) {
|
if (yamlContent[linkKey]) {
|
||||||
delete yamlContent['link']
|
delete yamlContent[linkKey]
|
||||||
}
|
}
|
||||||
// add new notionID and link
|
// add new notionID and link
|
||||||
yamlContent.notionID = id;
|
yamlContent[notionIDKey] = id;
|
||||||
yamlContent.link = url;
|
(NotionLinkDisplay) ? yamlContent[linkKey] = url : null;
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(url)
|
await navigator.clipboard.writeText(url)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
new Notice(`复制链接失败,请手动复制${error}`)
|
console.log(error)
|
||||||
|
new Notice(`${i18nConfig.CopyErrorMessage}`);
|
||||||
}
|
}
|
||||||
// const __content = yamlContent.__content;
|
|
||||||
// delete yamlContent.__content
|
|
||||||
// const yamlhead = yaml.stringify(yamlContent)
|
|
||||||
// // if yamlhead hava last \n remove it
|
|
||||||
// const yamlhead_remove_n = yamlhead.replace(/\n$/, '')
|
|
||||||
// // if __content have start \n remove it
|
|
||||||
// const __content_remove_n = __content.replace(/^\n/, '')
|
|
||||||
// const content = '---\n' +yamlhead_remove_n +'\n---\n' + __content_remove_n;
|
|
||||||
// try {
|
|
||||||
// await nowFile.vault.modify(nowFile, content)
|
|
||||||
// } catch (error) {
|
|
||||||
// new Notice(`write file error ${error}`)
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,62 +1,158 @@
|
|||||||
import {i18nConfig} from "../lang/I18n";
|
import {i18nConfig} from "../lang/I18n";
|
||||||
import {App, Notice} from "obsidian";
|
import {App, Notice} from "obsidian";
|
||||||
import { Upload2NotionNext } from "./upload_next/Upload2NotionNext";
|
import {Upload2Notion} from "./Upload2Notion";
|
||||||
import { Upload2NotionGeneral } from "./upload_general/Upload2NotionGeneral";
|
import type {NotionPageResponse} from "./common/UploadBase";
|
||||||
import { PluginSettings } from "../ui/settingTabs";
|
import {DatabaseDetails, PluginSettings} from "../ui/settingTabs";
|
||||||
import ObsidianSyncNotionPlugin from "../main";
|
import ObsidianSyncNotionPlugin from "../main";
|
||||||
import { getNowFileMarkdownContentNext } from "./upload_next/getMarkdownNext";
|
import {getNowFileMarkdownContentNext} from "./common/getMarkdownNext";
|
||||||
import { getNowFileMarkdownContentGeneral } from "./upload_general/getMarkdownGeneral";
|
import {getNowFileMarkdownContentGeneral} from "./common/getMarkdownGeneral";
|
||||||
|
import {getNowFileMarkdownContentCustom} from "./common/getMarkdownCustom";
|
||||||
|
|
||||||
|
const SYNC_ERROR_NOTICE_DURATION = 8000;
|
||||||
|
|
||||||
|
function extractErrorMessage(error: unknown): string {
|
||||||
|
if (error instanceof Error && error.message) {
|
||||||
|
return error.message;
|
||||||
|
}
|
||||||
|
return String(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
function notifySyncError(prefix: string, basename: string, error: unknown): void {
|
||||||
|
const errorMessage = extractErrorMessage(error);
|
||||||
|
console.error(`${prefix} Sync failed`, error);
|
||||||
|
new Notice(
|
||||||
|
`${i18nConfig["sync-fail"]} ${basename}. ${errorMessage}\n${i18nConfig["CheckConsole"]}`,
|
||||||
|
SYNC_ERROR_NOTICE_DURATION,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function logCommandDebug(context: string, message: string, payload?: Record<string, unknown>): void {
|
||||||
|
if (payload) {
|
||||||
|
console.log(`[${context}] ${message}`, payload);
|
||||||
|
} else {
|
||||||
|
console.log(`[${context}] ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function uploadCommandNext(
|
export async function uploadCommandNext(
|
||||||
plugin: ObsidianSyncNotionPlugin,
|
plugin: ObsidianSyncNotionPlugin,
|
||||||
settings: PluginSettings,
|
settings: PluginSettings,
|
||||||
|
dbDetails: DatabaseDetails,
|
||||||
app: App,
|
app: App,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
const { notionAPINext, databaseIDNext } = settings;
|
const {notionAPI, databaseID} = dbDetails;
|
||||||
|
console.log(`[uploadCommandNext] ${new Date().toISOString()} Triggered for file`, app.workspace.getActiveFile()?.path);
|
||||||
// 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
|
// Check if the user has set up the Notion API and database ID
|
||||||
if (notionAPINext === "" || databaseIDNext === "") {
|
if (notionAPI === "" || databaseID === "") {
|
||||||
const setAPIMessage = i18nConfig["set-api-id"];
|
const setAPIMessage = i18nConfig["set-api-id"];
|
||||||
new Notice(setAPIMessage);
|
new Notice(setAPIMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { markDownData, nowFile, emoji, cover, tags, type, slug, stats, category, summary, paword, favicon, datetime } = await getNowFileMarkdownContentNext(app, settings)
|
const {
|
||||||
|
markDownData,
|
||||||
|
nowFile,
|
||||||
|
emoji,
|
||||||
|
cover,
|
||||||
|
tags,
|
||||||
|
type,
|
||||||
|
slug,
|
||||||
|
stats,
|
||||||
|
category,
|
||||||
|
summary,
|
||||||
|
paword,
|
||||||
|
favicon,
|
||||||
|
datetime
|
||||||
|
} = await getNowFileMarkdownContentNext(app, settings);
|
||||||
|
|
||||||
|
logCommandDebug("uploadCommandNext", "Metadata extracted from markdown", {
|
||||||
|
hasMarkdown: !!markDownData,
|
||||||
|
markdownLength: markDownData?.length ?? 0,
|
||||||
|
tagCount: tags?.length ?? 0,
|
||||||
|
coverIncluded: !!cover,
|
||||||
|
hasEmoji: !!emoji,
|
||||||
|
type,
|
||||||
|
slug,
|
||||||
|
hasPassword: !!paword,
|
||||||
|
datetime,
|
||||||
|
});
|
||||||
|
|
||||||
if (markDownData) {
|
if (markDownData) {
|
||||||
const {basename} = nowFile;
|
const {basename} = nowFile;
|
||||||
const upload = new Upload2NotionNext(plugin);
|
logCommandDebug("uploadCommandNext", "Preparing to upload", {
|
||||||
const res = await upload.syncMarkdownToNotionNext(basename, emoji, cover, tags, type, slug, stats, category, summary, paword, favicon, datetime, markDownData, nowFile, this.app);
|
filename: basename,
|
||||||
|
filePath: nowFile.path,
|
||||||
|
});
|
||||||
|
|
||||||
if (res.status === 200) {
|
const upload = new Upload2Notion(plugin, dbDetails);
|
||||||
new Notice(`${i18nConfig["sync-success"]}${basename}`);
|
let res: NotionPageResponse;
|
||||||
|
try {
|
||||||
|
res = await upload.sync({
|
||||||
|
dataset: "next",
|
||||||
|
title: basename,
|
||||||
|
emoji: emoji || "",
|
||||||
|
cover: cover || "",
|
||||||
|
tags: tags || [],
|
||||||
|
type: type || "",
|
||||||
|
slug: slug || "",
|
||||||
|
stats: stats || "",
|
||||||
|
category: category || "",
|
||||||
|
summary: summary || "",
|
||||||
|
password: paword || "",
|
||||||
|
favicon: favicon || "",
|
||||||
|
datetime: datetime || "",
|
||||||
|
markdown: markDownData,
|
||||||
|
nowFile,
|
||||||
|
app,
|
||||||
|
});
|
||||||
|
} catch (error: unknown) {
|
||||||
|
notifySyncError("[uploadCommandNext]", basename, error);
|
||||||
|
logCommandDebug("uploadCommandNext", "Sync threw error", {
|
||||||
|
filename: basename,
|
||||||
|
error: extractErrorMessage(error),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {response} = res;
|
||||||
|
if (response.status === 200) {
|
||||||
|
new Notice(`${i18nConfig["sync-preffix"]} ${basename} ${i18nConfig["sync-success"]}`).noticeEl.style.color = "green";
|
||||||
|
|
||||||
|
logCommandDebug("uploadCommandNext", "Sync succeeded", {
|
||||||
|
filename: basename,
|
||||||
|
status: response.status,
|
||||||
|
pageId: res.data?.id ?? null,
|
||||||
|
pageUrl: res.data?.url ?? null,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
new Notice(`${i18nConfig["sync-fail"]} ${basename}`, 5000);
|
new Notice(`${i18nConfig["sync-fail"]} ${basename}`, 5000);
|
||||||
|
console.log(`${i18nConfig["sync-fail"]} ${basename}`);
|
||||||
|
logCommandDebug("uploadCommandNext", "Sync failed with status", {
|
||||||
|
filename: basename,
|
||||||
|
status: response.status,
|
||||||
|
errorCode: res.data?.code ?? null,
|
||||||
|
errorStatus: res.data?.status ?? null,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function uploadCommandGeneral(
|
export async function uploadCommandGeneral(
|
||||||
plugin: ObsidianSyncNotionPlugin,
|
plugin: ObsidianSyncNotionPlugin,
|
||||||
settings: PluginSettings,
|
settings: PluginSettings,
|
||||||
|
dbDetails: DatabaseDetails,
|
||||||
app: App,
|
app: App,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
const { notionAPIGeneral, databaseIDGeneral } = settings;
|
const {notionAPI, databaseID} = dbDetails;
|
||||||
|
console.log(`[uploadCommandGeneral] ${new Date().toISOString()} Triggered for file`, app.workspace.getActiveFile()?.path);
|
||||||
|
|
||||||
// Check if the user has set up the Notion API and database ID
|
// Check if the user has set up the Notion API and database ID
|
||||||
if (notionAPIGeneral === "" || databaseIDGeneral === "") {
|
if (notionAPI === "" || databaseID === "") {
|
||||||
const setAPIMessage = i18nConfig["set-api-id"];
|
const setAPIMessage = i18nConfig["set-api-id"];
|
||||||
new Notice(setAPIMessage);
|
new Notice(setAPIMessage);
|
||||||
return;
|
return;
|
||||||
@@ -64,16 +160,135 @@ export async function uploadCommandGeneral(
|
|||||||
|
|
||||||
const {markDownData, nowFile, cover, tags} = await getNowFileMarkdownContentGeneral(app, settings)
|
const {markDownData, nowFile, cover, tags} = await getNowFileMarkdownContentGeneral(app, settings)
|
||||||
|
|
||||||
|
new Notice(`Start upload ${nowFile.basename}`);
|
||||||
|
console.log(`Start upload ${nowFile.basename}`);
|
||||||
|
|
||||||
if (markDownData) {
|
if (markDownData) {
|
||||||
const {basename} = nowFile;
|
const {basename} = nowFile;
|
||||||
|
logCommandDebug("uploadCommandGeneral", "Preparing to upload", {
|
||||||
|
filename: basename,
|
||||||
|
markdownLength: markDownData.length,
|
||||||
|
tagCount: tags?.length ?? 0,
|
||||||
|
hasCover: !!cover,
|
||||||
|
});
|
||||||
|
|
||||||
const upload = new Upload2NotionGeneral(plugin);
|
const upload = new Upload2Notion(plugin, dbDetails);
|
||||||
const res = await upload.syncMarkdownToNotionGeneral(basename, cover, tags, markDownData, nowFile, this.app);
|
let res: NotionPageResponse;
|
||||||
|
try {
|
||||||
|
res = await upload.sync({
|
||||||
|
dataset: "general",
|
||||||
|
title: basename,
|
||||||
|
cover: cover || "",
|
||||||
|
tags: tags || [],
|
||||||
|
markdown: markDownData,
|
||||||
|
nowFile,
|
||||||
|
app,
|
||||||
|
});
|
||||||
|
} catch (error: unknown) {
|
||||||
|
notifySyncError("[uploadCommandGeneral]", basename, error);
|
||||||
|
logCommandDebug("uploadCommandGeneral", "Sync threw error", {
|
||||||
|
filename: basename,
|
||||||
|
error: extractErrorMessage(error),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (res.status === 200) {
|
const {response} = res;
|
||||||
new Notice(`${i18nConfig["sync-success"]}${basename}`);
|
if (response.status === 200) {
|
||||||
|
new Notice(`${i18nConfig["sync-preffix"]} ${basename} ${i18nConfig["sync-success"]}`).noticeEl.style.color = "green";
|
||||||
|
|
||||||
|
logCommandDebug("uploadCommandGeneral", "Sync succeeded", {
|
||||||
|
filename: basename,
|
||||||
|
status: response.status,
|
||||||
|
pageId: res.data?.id ?? null,
|
||||||
|
pageUrl: res.data?.url ?? null,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
new Notice(`${i18nConfig["sync-fail"]} ${basename}`, 5000);
|
new Notice(`${i18nConfig["sync-fail"]} ${basename}`, 5000);
|
||||||
|
console.log(`${i18nConfig["sync-fail"]} ${basename}`);
|
||||||
|
logCommandDebug("uploadCommandGeneral", "Sync failed with status", {
|
||||||
|
filename: basename,
|
||||||
|
status: response.status,
|
||||||
|
errorCode: res.data?.code ?? null,
|
||||||
|
errorStatus: res.data?.status ?? null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export async function uploadCommandCustom(
|
||||||
|
plugin: ObsidianSyncNotionPlugin,
|
||||||
|
settings: PluginSettings,
|
||||||
|
dbDetails: DatabaseDetails,
|
||||||
|
app: App,
|
||||||
|
) {
|
||||||
|
|
||||||
|
const {notionAPI, databaseID} = dbDetails;
|
||||||
|
console.log(`[uploadCommandCustom] ${new Date().toISOString()} Triggered for file`, app.workspace.getActiveFile()?.path);
|
||||||
|
|
||||||
|
// Check if the user has set up the Notion API and database ID
|
||||||
|
if (notionAPI === "" || databaseID === "") {
|
||||||
|
const setAPIMessage = i18nConfig["set-api-id"];
|
||||||
|
new Notice(setAPIMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {markDownData, nowFile, cover, customValues} = await getNowFileMarkdownContentCustom(app, dbDetails)
|
||||||
|
|
||||||
|
new Notice(`Start upload ${nowFile.basename}`);
|
||||||
|
console.log(`Start upload ${nowFile.basename}`);
|
||||||
|
|
||||||
|
if (markDownData) {
|
||||||
|
const {basename} = nowFile;
|
||||||
|
logCommandDebug("uploadCommandCustom", "Preparing to upload", {
|
||||||
|
filename: basename,
|
||||||
|
markdownLength: markDownData.length,
|
||||||
|
hasCover: !!cover,
|
||||||
|
customValueKeys: Object.keys(customValues || {}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const upload = new Upload2Notion(plugin, dbDetails);
|
||||||
|
let res: NotionPageResponse;
|
||||||
|
try {
|
||||||
|
res = await upload.sync({
|
||||||
|
dataset: "custom",
|
||||||
|
cover: cover || "",
|
||||||
|
customValues,
|
||||||
|
markdown: markDownData,
|
||||||
|
nowFile,
|
||||||
|
app,
|
||||||
|
});
|
||||||
|
} catch (error: unknown) {
|
||||||
|
notifySyncError("[uploadCommandCustom]", basename, error);
|
||||||
|
logCommandDebug("uploadCommandCustom", "Sync threw error", {
|
||||||
|
filename: basename,
|
||||||
|
error: extractErrorMessage(error),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {response} = res;
|
||||||
|
|
||||||
|
if (response.status === 200) {
|
||||||
|
new Notice(`${i18nConfig["sync-preffix"]} ${basename} ${i18nConfig["sync-success"]}`).noticeEl.style.color = "green";
|
||||||
|
|
||||||
|
logCommandDebug("uploadCommandCustom", "Sync succeeded", {
|
||||||
|
filename: basename,
|
||||||
|
status: response.status,
|
||||||
|
pageId: res.data?.id ?? null,
|
||||||
|
pageUrl: res.data?.url ?? null,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
new Notice(`${i18nConfig["sync-fail"]} ${basename}`, 5000);
|
||||||
|
console.log(`${i18nConfig["sync-fail"]} ${basename}`);
|
||||||
|
logCommandDebug("uploadCommandCustom", "Sync failed with status", {
|
||||||
|
filename: basename,
|
||||||
|
status: response.status,
|
||||||
|
errorCode: res.data?.code ?? null,
|
||||||
|
errorStatus: res.data?.status ?? null,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,90 +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 UploadBaseGeneral {
|
|
||||||
plugin: MyPlugin;
|
|
||||||
notion: Client;
|
|
||||||
agent: any;
|
|
||||||
|
|
||||||
constructor(plugin: MyPlugin) {
|
|
||||||
this.plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
async deletePage(notionID: string) {
|
|
||||||
return requestUrl({
|
|
||||||
url: `https://api.notion.com/v1/blocks/${notionID}`,
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': 'Bearer ' + this.plugin.settings.notionAPINext,
|
|
||||||
'Notion-Version': '2022-06-28',
|
|
||||||
},
|
|
||||||
body: ''
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getDataBase(databaseID: string) {
|
|
||||||
const response = await requestUrl({
|
|
||||||
url: `https://api.notion.com/v1/databases/${databaseID}`,
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Authorization': 'Bearer ' + this.plugin.settings.notionAPINext,
|
|
||||||
'Notion-Version': '2022-06-28',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Check if cover is present in the JSON response and then get the URL
|
|
||||||
if (response.json.cover && response.json.cover.external) {
|
|
||||||
return response.json.cover.external.url;
|
|
||||||
} else {
|
|
||||||
return null; // or some other default value, if you prefer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// async updateYamlInfo(yamlContent: string, nowFile: TFile, res: any, app: App, settings: any) {
|
|
||||||
// let {url, id} = res.json
|
|
||||||
// // replace www to notionID
|
|
||||||
// const {notionUser} = this.plugin.settings;
|
|
||||||
//
|
|
||||||
// if (notionUser !== "") {
|
|
||||||
// // replace url str "www" to notionID
|
|
||||||
// url = url.replace("www.notion.so", `${notionUser}.notion.site`)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// await app.fileManager.processFrontMatter(nowFile, yamlContent => {
|
|
||||||
// if (yamlContent['notionID']) {
|
|
||||||
// delete yamlContent['notionID']
|
|
||||||
// }
|
|
||||||
// if (yamlContent['link']) {
|
|
||||||
// delete yamlContent['link']
|
|
||||||
// }
|
|
||||||
// // add new notionID and link
|
|
||||||
// yamlContent.notionID = id;
|
|
||||||
// yamlContent.link = url;
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// try {
|
|
||||||
// await navigator.clipboard.writeText(url)
|
|
||||||
// } catch (error) {
|
|
||||||
// new Notice(`复制链接失败,请手动复制${error}`)
|
|
||||||
// }
|
|
||||||
// // const __content = yamlContent.__content;
|
|
||||||
// // delete yamlContent.__content
|
|
||||||
// // const yamlhead = yaml.stringify(yamlContent)
|
|
||||||
// // // if yamlhead hava last \n remove it
|
|
||||||
// // const yamlhead_remove_n = yamlhead.replace(/\n$/, '')
|
|
||||||
// // // if __content have start \n remove it
|
|
||||||
// // const __content_remove_n = __content.replace(/^\n/, '')
|
|
||||||
// // const content = '---\n' +yamlhead_remove_n +'\n---\n' + __content_remove_n;
|
|
||||||
// // try {
|
|
||||||
// // await nowFile.vault.modify(nowFile, content)
|
|
||||||
// // } catch (error) {
|
|
||||||
// // new Notice(`write file error ${error}`)
|
|
||||||
// // }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
import { App, Notice, requestUrl, TFile } from "obsidian";
|
|
||||||
import { Client } from "@notionhq/client";
|
|
||||||
import { markdownToBlocks } from "@tryfabric/martian";
|
|
||||||
import * as yamlFrontMatter from "yaml-front-matter";
|
|
||||||
// import * as yaml from "yaml"
|
|
||||||
import MyPlugin from "src/main";
|
|
||||||
import { PluginSettings } from "../../ui/settingTabs";
|
|
||||||
import { UploadBaseGeneral } from "./BaseUpload2NotionGeneral";
|
|
||||||
import { updateYamlInfo } from "../updateYaml";
|
|
||||||
|
|
||||||
export class Upload2NotionGeneral extends UploadBaseGeneral {
|
|
||||||
settings: PluginSettings;
|
|
||||||
|
|
||||||
constructor(plugin: MyPlugin) {
|
|
||||||
super(plugin);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 因为需要解析notion的block进行对比,非常的麻烦,
|
|
||||||
// 暂时就直接删除,新建一个page
|
|
||||||
async updatePage(
|
|
||||||
notionID: string,
|
|
||||||
title: string,
|
|
||||||
cover: string,
|
|
||||||
tags: string[],
|
|
||||||
childArr: any,
|
|
||||||
) {
|
|
||||||
await this.deletePage(notionID);
|
|
||||||
|
|
||||||
const databasecover = await this.getDataBase(
|
|
||||||
this.plugin.settings.databaseIDGeneral,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (cover == null) {
|
|
||||||
cover = databasecover;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.createPage(title, cover, tags, childArr);
|
|
||||||
}
|
|
||||||
|
|
||||||
async createPage(
|
|
||||||
title: string,
|
|
||||||
cover: string,
|
|
||||||
tags: string[],
|
|
||||||
childArr: any,
|
|
||||||
) {
|
|
||||||
const bodyString: any = {
|
|
||||||
parent: {
|
|
||||||
database_id: this.plugin.settings.databaseIDGeneral,
|
|
||||||
},
|
|
||||||
properties: {
|
|
||||||
[this.plugin.settings.CustomTitleButton
|
|
||||||
? this.plugin.settings.CustomTitleName
|
|
||||||
: "title"]: {
|
|
||||||
title: [
|
|
||||||
{
|
|
||||||
text: {
|
|
||||||
content: title,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
...(this.plugin.settings.tagsButton
|
|
||||||
? {
|
|
||||||
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 " + this.plugin.settings.notionAPIGeneral,
|
|
||||||
"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 notionID = frontmasster ? frontmasster.notionID : 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);
|
|
||||||
} else {
|
|
||||||
new Notice(`${res.text}`);
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,90 +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 UploadBaseNext {
|
|
||||||
plugin: MyPlugin;
|
|
||||||
notion: Client;
|
|
||||||
agent: any;
|
|
||||||
|
|
||||||
constructor(plugin: MyPlugin) {
|
|
||||||
this.plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
async deletePage(notionID: string) {
|
|
||||||
return requestUrl({
|
|
||||||
url: `https://api.notion.com/v1/blocks/${notionID}`,
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': 'Bearer ' + this.plugin.settings.notionAPINext,
|
|
||||||
'Notion-Version': '2022-06-28',
|
|
||||||
},
|
|
||||||
body: ''
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getDataBase(databaseID: string) {
|
|
||||||
const response = await requestUrl({
|
|
||||||
url: `https://api.notion.com/v1/databases/${databaseID}`,
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Authorization': 'Bearer ' + this.plugin.settings.notionAPINext,
|
|
||||||
'Notion-Version': '2022-06-28',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Check if cover is present in the JSON response and then get the URL
|
|
||||||
if (response.json.cover && response.json.cover.external) {
|
|
||||||
return response.json.cover.external.url;
|
|
||||||
} else {
|
|
||||||
return null; // or some other default value, if you prefer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// async updateYamlInfo(yamlContent: string, nowFile: TFile, res: any, app: App, settings: any) {
|
|
||||||
// let {url, id} = res.json
|
|
||||||
// // replace www to notionID
|
|
||||||
// const { notionUser} = this.plugin.settings;
|
|
||||||
//
|
|
||||||
// if (notionUser !== "") {
|
|
||||||
// // replace url str "www" to notionID
|
|
||||||
// url = url.replace("www.notion.so", `${notionUser}.notion.site`)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// await app.fileManager.processFrontMatter(nowFile, yamlContent => {
|
|
||||||
// if (yamlContent['notionID']) {
|
|
||||||
// delete yamlContent['notionID']
|
|
||||||
// }
|
|
||||||
// if (yamlContent['link']) {
|
|
||||||
// delete yamlContent['link']
|
|
||||||
// }
|
|
||||||
// // add new notionID and link
|
|
||||||
// yamlContent.notionID = id;
|
|
||||||
// yamlContent.link = url;
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// try {
|
|
||||||
// await navigator.clipboard.writeText(url)
|
|
||||||
// } catch (error) {
|
|
||||||
// new Notice(`复制链接失败,请手动复制${error}`)
|
|
||||||
// }
|
|
||||||
// // const __content = yamlContent.__content;
|
|
||||||
// // delete yamlContent.__content
|
|
||||||
// // const yamlhead = yaml.stringify(yamlContent)
|
|
||||||
// // // if yamlhead hava last \n remove it
|
|
||||||
// // const yamlhead_remove_n = yamlhead.replace(/\n$/, '')
|
|
||||||
// // // if __content have start \n remove it
|
|
||||||
// // const __content_remove_n = __content.replace(/^\n/, '')
|
|
||||||
// // const content = '---\n' +yamlhead_remove_n +'\n---\n' + __content_remove_n;
|
|
||||||
// // try {
|
|
||||||
// // await nowFile.vault.modify(nowFile, content)
|
|
||||||
// // } catch (error) {
|
|
||||||
// // new Notice(`write file error ${error}`)
|
|
||||||
// // }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
@@ -1,262 +0,0 @@
|
|||||||
import { UploadBaseNext } from "./BaseUpload2NotionNext";
|
|
||||||
import { App, Notice, requestUrl, TFile } from "obsidian";
|
|
||||||
import { Client } from '@notionhq/client';
|
|
||||||
import { markdownToBlocks, } from "@tryfabric/martian";
|
|
||||||
import * as yamlFrontMatter from "yaml-front-matter";
|
|
||||||
// import * as yaml from "yaml"
|
|
||||||
import MyPlugin from "src/main";
|
|
||||||
import { PluginSettings } from "../../ui/settingTabs";
|
|
||||||
import { updateYamlInfo } from "../updateYaml";
|
|
||||||
|
|
||||||
export class Upload2NotionNext extends UploadBaseNext {
|
|
||||||
settings: PluginSettings;
|
|
||||||
|
|
||||||
constructor(plugin: MyPlugin) {
|
|
||||||
super(plugin);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 因为需要解析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 databasecover = await this.getDataBase(this.plugin.settings.databaseIDNext)
|
|
||||||
|
|
||||||
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 bodyString: any = {
|
|
||||||
parent: {
|
|
||||||
database_id: this.plugin.settings.databaseIDNext
|
|
||||||
},
|
|
||||||
icon: {
|
|
||||||
emoji: emoji || '📜'
|
|
||||||
},
|
|
||||||
properties: {
|
|
||||||
title: {
|
|
||||||
title: [
|
|
||||||
{
|
|
||||||
text: {
|
|
||||||
content: title
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
tags: {
|
|
||||||
multi_select: tags && true ? 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.notionAPINext,
|
|
||||||
'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 notionID = frontmasster ? frontmasster.notionID : null
|
|
||||||
|
|
||||||
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)
|
|
||||||
} else {
|
|
||||||
new Notice(`${res.text}`)
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user