mirror of
https://github.com/jxpeng98/obsidian-to-NotionNext
synced 2026-07-30 00:48:36 +08:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b6f1a1f1d | ||
|
|
4a2c59d13b | ||
|
|
8f116956ce | ||
|
|
df138d9304 | ||
|
|
49b8d470fb | ||
|
|
c39323c3fa | ||
|
|
05dff7a389 | ||
|
|
24fcadbc8b | ||
|
|
fdb81f76a7 | ||
|
|
024b7f726e | ||
|
|
722ca7bb56 |
26
.github/workflows/CI.yml
vendored
26
.github/workflows/CI.yml
vendored
@@ -1,26 +0,0 @@
|
|||||||
name: CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- "*"
|
|
||||||
|
|
||||||
env:
|
|
||||||
PLUGIN_NAME: share-to-notionnext
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- name: Use Node.js
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 16
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
id: build
|
|
||||||
run: |
|
|
||||||
npm install
|
|
||||||
npm run build
|
|
||||||
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
@@ -13,11 +13,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- name: Use Node.js
|
- name: Use Node.js
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: "14.x"
|
node-version: "16.x"
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
id: build
|
id: build
|
||||||
@@ -25,10 +25,10 @@ jobs:
|
|||||||
npm install
|
npm install
|
||||||
npm run build
|
npm run build
|
||||||
mkdir ${{ env.PLUGIN_NAME }}
|
mkdir ${{ env.PLUGIN_NAME }}
|
||||||
cp main.js manifest.json styles.css ${{ env.PLUGIN_NAME }}
|
cp main.js manifest.json ${{ env.PLUGIN_NAME }}
|
||||||
zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }}
|
zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }}
|
||||||
ls
|
ls
|
||||||
echo "::set-output name=tag_name::$(git tag --sort version:refname | tail -n 1)"
|
echo "tag_name=$(git tag --sort version:refname | tail -n 1)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
id: create_release
|
id: create_release
|
||||||
|
|||||||
14
README-zh.md
14
README-zh.md
@@ -2,6 +2,11 @@
|
|||||||
感谢[原作者](https://github.com/EasyChris/obsidian-to-notion)开发出了这么好用的插件,能够将obsidian同步到notion。 但是原仓库只能同步Name和Tags信息,如果像我一样通过[NotionNext](https://github.com/tangly1024/NotionNext)来搭建自己的网页,就有一些局限性。每次导入之后都需要进行大量的修改。
|
感谢[原作者](https://github.com/EasyChris/obsidian-to-notion)开发出了这么好用的插件,能够将obsidian同步到notion。 但是原仓库只能同步Name和Tags信息,如果像我一样通过[NotionNext](https://github.com/tangly1024/NotionNext)来搭建自己的网页,就有一些局限性。每次导入之后都需要进行大量的修改。
|
||||||
|
|
||||||
所以我在[原作者](https://github.com/EasyChris/obsidian-to-notion)的基础之上,增加了匹配[NotionNext](https://github.com/tangly1024/NotionNext)模板的功能。这样可以直接在Obsidian编辑,整理好之后一键发布。
|
所以我在[原作者](https://github.com/EasyChris/obsidian-to-notion)的基础之上,增加了匹配[NotionNext](https://github.com/tangly1024/NotionNext)模板的功能。这样可以直接在Obsidian编辑,整理好之后一键发布。
|
||||||
|
## 更新说明
|
||||||
|
### 0.1.8
|
||||||
|
- 重新整理了代码,增加了一个仓库转换按钮为之后的多数据库支持作准备。**但是现在还不支持多数据库,如果你的数据库不是NotionNext,你可以不用更新。**
|
||||||
|
### 0.1.7
|
||||||
|
- [x] 删除了设置中`convert tags`的选项。 你现在可以直接添加tags,而不需要考虑是否开启tags选项。如果你不需要tags,可以直接删除tags选项或者留白。
|
||||||
|
|
||||||
## 使用方式
|
## 使用方式
|
||||||
### 注意事项
|
### 注意事项
|
||||||
@@ -37,6 +42,10 @@
|
|||||||
**⚠️⚠️⚠️:表头全部小写!!!顺序无所谓!**
|
**⚠️⚠️⚠️:表头全部小写!!!顺序无所谓!**
|
||||||
|
|
||||||
### 安装插件
|
### 安装插件
|
||||||
|
#### 社区插件市场下载
|
||||||
|
打开`Obsidian -> 设置 -> 社区插件 -> 浏览 -> 搜索Share to NotionNext`, 然后点击安装。
|
||||||
|
|
||||||
|
#### 手动安装
|
||||||
1. 关闭Obsidian
|
1. 关闭Obsidian
|
||||||
2. 从Release下载插件文件,解压到你的obsidian插件目录下。
|
2. 从Release下载插件文件,解压到你的obsidian插件目录下。
|
||||||
3. 重新打开Obsidian,进入设置,启用插件。
|
3. 重新打开Obsidian,进入设置,启用插件。
|
||||||
@@ -77,11 +86,10 @@ Contents Below
|
|||||||
|
|
||||||
```
|
```
|
||||||
模板使用如下:
|
模板使用如下:
|
||||||

|

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

|

|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
16
README.md
16
README.md
@@ -4,6 +4,12 @@
|
|||||||
Thanks to the [original author](https://github.com/EasyChris/obsidian-to-notion) for developing such a useful plugin that can synchronize Obsidian to Notion. However, the original repository can only sync Name and Tags information. For those like me who use [NotionNext](https://github.com/tangly1024/NotionNext) to set up their website, this presents some limitations. Every time I import, I need to make a lot of modifications.
|
Thanks to the [original author](https://github.com/EasyChris/obsidian-to-notion) for developing such a useful plugin that can synchronize Obsidian to Notion. However, the original repository can only sync Name and Tags information. For those like me who use [NotionNext](https://github.com/tangly1024/NotionNext) to set up their website, this presents some limitations. Every time I import, I need to make a lot of modifications.
|
||||||
|
|
||||||
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.
|
||||||
|
## Update
|
||||||
|
### 0.1.8
|
||||||
|
- Rebuild the upload 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
|
## How to Use
|
||||||
### Precautions
|
### Precautions
|
||||||
@@ -28,7 +34,7 @@ Before installing the plugin, you must have set up the following:
|
|||||||
- password
|
- password
|
||||||
- icon
|
- icon
|
||||||
|
|
||||||
In version 0.0.6, I also add the following contents:
|
From version 0.0.6, I also add the following contents:
|
||||||
- titleicon: the unique icon for each post, it can only support emoji currently.
|
- titleicon: the unique icon for each post, it can only support emoji currently.
|
||||||
- cover url: the cover image for each post, it should be end with a type of image, such as .jpg, .png, .gif, etc.
|
- cover url: the cover image for each post, it should be end with a type of image, such as .jpg, .png, .gif, etc.
|
||||||
|
|
||||||
@@ -37,6 +43,10 @@ In version 0.0.6, I also add the following contents:
|
|||||||
**⚠️⚠️⚠️: All headers are in lowercase!!! The order doesn't matter!**
|
**⚠️⚠️⚠️: All headers are in lowercase!!! The order doesn't matter!**
|
||||||
|
|
||||||
### Plugin Installation
|
### Plugin Installation
|
||||||
|
#### Install via Community Plugins
|
||||||
|
Open `Obsidian settings -> Community Plugins -> Browse -> NotionNext -> Share to NotionNext`
|
||||||
|
|
||||||
|
#### Mannually Install
|
||||||
1. Close Obsidian.
|
1. Close Obsidian.
|
||||||
2. Download the plugin file from Release and unzip it into your Obsidian plugin directory.
|
2. Download the plugin file from Release and unzip it into your Obsidian plugin directory.
|
||||||
3. Re-open Obsidian, go to settings, and enable the plugin.
|
3. Re-open Obsidian, go to settings, and enable the plugin.
|
||||||
@@ -76,10 +86,10 @@ Contents Below
|
|||||||
|
|
||||||
```
|
```
|
||||||
Usage of the template is as follows:
|
Usage of the template is as follows:
|
||||||

|

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

|

|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
230
Upload2Notion.ts
230
Upload2Notion.ts
@@ -1,230 +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 "main";
|
|
||||||
|
|
||||||
export class Upload2Notion {
|
|
||||||
app: MyPlugin;
|
|
||||||
notion: Client;
|
|
||||||
agent: any;
|
|
||||||
constructor(app: MyPlugin) {
|
|
||||||
this.app = app;
|
|
||||||
}
|
|
||||||
|
|
||||||
async deletePage(notionID:string){
|
|
||||||
return await requestUrl({
|
|
||||||
url: `https://api.notion.com/v1/blocks/${notionID}`,
|
|
||||||
method: 'DELETE',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': 'Bearer ' + this.app.settings.notionAPI,
|
|
||||||
'Notion-Version': '2022-06-28',
|
|
||||||
},
|
|
||||||
body: ''
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async getDataBase(databaseID:string){
|
|
||||||
const response = await requestUrl({
|
|
||||||
url: `https://api.notion.com/v1/databases/${databaseID}`,
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Authorization': 'Bearer ' + this.app.settings.notionAPI,
|
|
||||||
'Notion-Version': '2022-06-28',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Check if cover is present in the JSON response and then get the URL
|
|
||||||
if (response.json.cover && response.json.cover.external) {
|
|
||||||
return response.json.cover.external.url;
|
|
||||||
} else {
|
|
||||||
return null; // or some other default value, if you prefer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 因为需要解析notion的block进行对比,非常的麻烦,
|
|
||||||
// 暂时就直接删除,新建一个page
|
|
||||||
async updatePage(notionID:string, title:string, allowTags:boolean, emoji:string, cover:string, tags:string[], type:string, slug:string, stats:string, category:string, summary:string, paword:string, favicon:string, datetime:string, childArr:any) {
|
|
||||||
await this.deletePage(notionID)
|
|
||||||
|
|
||||||
const databasecover = await this.getDataBase(this.app.settings.databaseID)
|
|
||||||
|
|
||||||
if (cover == null) {
|
|
||||||
cover = databasecover
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.createPage(title, allowTags, emoji, cover, tags, type, slug, stats, category, summary, paword, favicon, datetime, childArr)
|
|
||||||
}
|
|
||||||
|
|
||||||
async createPage(title:string, allowTags:boolean, emoji:string, cover:string, tags:string[], type:string, slug:string, stats:string, category:string, summary:string, pawrod:string, favicon:string, datetime:string, childArr: any) {
|
|
||||||
const bodyString:any = {
|
|
||||||
parent: {
|
|
||||||
database_id: this.app.settings.databaseID
|
|
||||||
},
|
|
||||||
icon: {
|
|
||||||
emoji: emoji || '📜'
|
|
||||||
},
|
|
||||||
properties: {
|
|
||||||
title: {
|
|
||||||
title: [
|
|
||||||
{
|
|
||||||
text: {
|
|
||||||
content: title
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
tags: {
|
|
||||||
multi_select: allowTags && tags !== undefined ? tags.map(tag => {
|
|
||||||
return {"name": tag}
|
|
||||||
}) : [],
|
|
||||||
},
|
|
||||||
type: {
|
|
||||||
select: {
|
|
||||||
name: type || 'Post'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
slug: {
|
|
||||||
rich_text: [
|
|
||||||
{
|
|
||||||
text: {
|
|
||||||
content: slug || ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
select: {
|
|
||||||
name: stats || 'Draft'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
category: {
|
|
||||||
select: {
|
|
||||||
name: category || 'Obsidian'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
summary: {
|
|
||||||
rich_text: [
|
|
||||||
{
|
|
||||||
text: {
|
|
||||||
content: summary || ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
password: {
|
|
||||||
rich_text: [
|
|
||||||
{
|
|
||||||
text: {
|
|
||||||
content: pawrod || ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
icon: {
|
|
||||||
rich_text: [
|
|
||||||
{
|
|
||||||
text: {
|
|
||||||
content: favicon || ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
date: {
|
|
||||||
date: {
|
|
||||||
start: datetime || new Date().toISOString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
children: childArr,
|
|
||||||
}
|
|
||||||
if (cover) {
|
|
||||||
bodyString.cover = {
|
|
||||||
type: "external",
|
|
||||||
external: {
|
|
||||||
url: cover
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!bodyString.cover && this.app.settings.bannerUrl) {
|
|
||||||
bodyString.cover = {
|
|
||||||
type: "external",
|
|
||||||
external: {
|
|
||||||
url: this.app.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.app.settings.notionAPI,
|
|
||||||
'Notion-Version': '2022-06-28',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(bodyString),
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
new Notice(`network error ${error}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async syncMarkdownToNotion(title:string, allowTags:boolean, emoji:string, cover:string, tags:string[], type:string, slug:string, stats:string, category:string, summary:string, paword:string, favicon:string, datetime:string, markdown: string, nowFile: TFile, app:App, settings:any): Promise<any> {
|
|
||||||
let res:any
|
|
||||||
const yamlObj:any = yamlFrontMatter.loadFront(markdown);
|
|
||||||
const __content = yamlObj.__content
|
|
||||||
const file2Block = markdownToBlocks(__content);
|
|
||||||
const frontmasster =await app.metadataCache.getFileCache(nowFile)?.frontmatter
|
|
||||||
const notionID = frontmasster ? frontmasster.notionID : null
|
|
||||||
|
|
||||||
if(notionID){
|
|
||||||
res = await this.updatePage(notionID, title, allowTags, emoji, cover, tags, type, slug, stats, category, summary, paword, favicon, datetime, file2Block);
|
|
||||||
} else {
|
|
||||||
res = await this.createPage(title, allowTags, emoji, cover, tags, type, slug, stats, category, summary, paword, favicon, datetime, file2Block);
|
|
||||||
}
|
|
||||||
if (res.status === 200) {
|
|
||||||
await this.updateYamlInfo(markdown, nowFile, res, app, settings)
|
|
||||||
} else {
|
|
||||||
new Notice(`${res.text}`)
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateYamlInfo(yamlContent: string, nowFile: TFile, res: any,app:App, settings:any) {
|
|
||||||
const yamlObj:any = yamlFrontMatter.loadFront(yamlContent);
|
|
||||||
let {url, id} = res.json
|
|
||||||
// replace www to notionID
|
|
||||||
const {notionID} = settings;
|
|
||||||
if(notionID!=="") {
|
|
||||||
// replace url str "www" to notionID
|
|
||||||
url = url.replace("www.notion.so", `${notionID}.notion.site`)
|
|
||||||
}
|
|
||||||
yamlObj.link = url;
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(url)
|
|
||||||
} catch (error) {
|
|
||||||
new Notice(`复制链接失败,请手动复制${error}`)
|
|
||||||
}
|
|
||||||
yamlObj.notionID = id;
|
|
||||||
const __content = yamlObj.__content;
|
|
||||||
delete yamlObj.__content
|
|
||||||
const yamlhead = yaml.stringify(yamlObj)
|
|
||||||
// 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}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,45 +8,51 @@ THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
|
|||||||
if you want to view the source, please visit the github repository of this plugin
|
if you want to view the source, please visit the github repository of this plugin
|
||||||
*/
|
*/
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const prod = (process.argv[2] === 'production');
|
const prod = (process.argv[2] === 'production');
|
||||||
|
|
||||||
esbuild.build({
|
const ctx = await esbuild.context({
|
||||||
banner: {
|
banner: {
|
||||||
js: banner,
|
js: banner,
|
||||||
},
|
},
|
||||||
entryPoints: ['main.ts'],
|
entryPoints: ['src/main.ts'],
|
||||||
bundle: true,
|
bundle: true,
|
||||||
external: [
|
external: [
|
||||||
'obsidian',
|
'obsidian',
|
||||||
'electron',
|
'electron',
|
||||||
'@codemirror/autocomplete',
|
'@codemirror/autocomplete',
|
||||||
'@codemirror/closebrackets',
|
'@codemirror/closebrackets',
|
||||||
'@codemirror/collab',
|
'@codemirror/collab',
|
||||||
'@codemirror/commands',
|
'@codemirror/commands',
|
||||||
'@codemirror/comment',
|
'@codemirror/comment',
|
||||||
'@codemirror/fold',
|
'@codemirror/fold',
|
||||||
'@codemirror/gutter',
|
'@codemirror/gutter',
|
||||||
'@codemirror/highlight',
|
'@codemirror/highlight',
|
||||||
'@codemirror/history',
|
'@codemirror/history',
|
||||||
'@codemirror/language',
|
'@codemirror/language',
|
||||||
'@codemirror/lint',
|
'@codemirror/lint',
|
||||||
'@codemirror/matchbrackets',
|
'@codemirror/matchbrackets',
|
||||||
'@codemirror/panel',
|
'@codemirror/panel',
|
||||||
'@codemirror/rangeset',
|
'@codemirror/rangeset',
|
||||||
'@codemirror/rectangular-selection',
|
'@codemirror/rectangular-selection',
|
||||||
'@codemirror/search',
|
'@codemirror/search',
|
||||||
'@codemirror/state',
|
'@codemirror/state',
|
||||||
'@codemirror/stream-parser',
|
'@codemirror/stream-parser',
|
||||||
'@codemirror/text',
|
'@codemirror/text',
|
||||||
'@codemirror/tooltip',
|
'@codemirror/tooltip',
|
||||||
'@codemirror/view',
|
'@codemirror/view',
|
||||||
...builtins],
|
...builtins
|
||||||
format: 'cjs',
|
],
|
||||||
watch: !prod,
|
format: 'cjs',
|
||||||
target: 'es2016',
|
target: 'es2016',
|
||||||
logLevel: "info",
|
logLevel: "info",
|
||||||
sourcemap: prod ? false : 'inline',
|
sourcemap: prod ? false : 'inline',
|
||||||
treeShaking: true,
|
treeShaking: true,
|
||||||
outfile: 'main.js',
|
outfile: 'main.js',
|
||||||
}).catch(() => process.exit(1));
|
});
|
||||||
|
|
||||||
|
if (!prod) {
|
||||||
|
await ctx.watch();
|
||||||
|
} else {
|
||||||
|
await ctx.rebuild()
|
||||||
|
ctx.dispose().then(r => console.log('Build completed'), e => console.error(e));
|
||||||
|
}
|
||||||
|
|||||||
246
main.ts
246
main.ts
@@ -1,246 +0,0 @@
|
|||||||
import {App, Editor, MarkdownView, Notice, Plugin, PluginSettingTab, Setting} from "obsidian";
|
|
||||||
import {addIcons} from 'icon';
|
|
||||||
import {Upload2Notion} from "Upload2Notion";
|
|
||||||
import {NoticeMConfig} from "Message";
|
|
||||||
|
|
||||||
|
|
||||||
// Remember to rename these classes and interfaces!
|
|
||||||
|
|
||||||
interface PluginSettings {
|
|
||||||
notionAPI: string;
|
|
||||||
databaseID: string;
|
|
||||||
bannerUrl: string;
|
|
||||||
notionID: string;
|
|
||||||
proxy: string;
|
|
||||||
allowTags: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const langConfig = NoticeMConfig( window.localStorage.getItem('language') || 'en')
|
|
||||||
|
|
||||||
const DEFAULT_SETTINGS: PluginSettings = {
|
|
||||||
notionAPI: "",
|
|
||||||
databaseID: "",
|
|
||||||
bannerUrl: "",
|
|
||||||
notionID: "",
|
|
||||||
proxy: "",
|
|
||||||
allowTags: false
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class ObsidianSyncNotionPlugin extends Plugin {
|
|
||||||
settings: PluginSettings;
|
|
||||||
async onload() {
|
|
||||||
await this.loadSettings();
|
|
||||||
addIcons();
|
|
||||||
// This creates an icon in the left ribbon.
|
|
||||||
const ribbonIconEl = this.addRibbonIcon(
|
|
||||||
"notion-logo",
|
|
||||||
"Share to NotionNext",
|
|
||||||
async (evt: MouseEvent) => {
|
|
||||||
// Called when the user clicks the icon.
|
|
||||||
await this.upload();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// This adds a status bar item to the bottom of the app. Does not work on mobile apps.
|
|
||||||
const statusBarItemEl = this.addStatusBarItem();
|
|
||||||
// statusBarItemEl.setText("share to notion");
|
|
||||||
|
|
||||||
this.addCommand({
|
|
||||||
id: "share-to-notionnext",
|
|
||||||
name: "share to notionnext",
|
|
||||||
editorCallback: async (editor: Editor, view: MarkdownView) => {
|
|
||||||
await this.upload()
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// This adds a settings tab so the user can configure various aspects of the plugin
|
|
||||||
this.addSettingTab(new SampleSettingTab(this.app, this));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
onunload() {}
|
|
||||||
|
|
||||||
async upload(){
|
|
||||||
const { notionAPI, databaseID, allowTags } = this.settings;
|
|
||||||
if (notionAPI === "" || databaseID === "") {
|
|
||||||
new Notice(
|
|
||||||
"Please set up the notion API and database ID in the settings tab."
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { markDownData, nowFile, emoji, cover, tags, type, slug, stats, category, summary, paword, favicon, datetime } =await this.getNowFileMarkdownContent(this.app);
|
|
||||||
|
|
||||||
if (markDownData) {
|
|
||||||
const { basename } = nowFile;
|
|
||||||
const upload = new Upload2Notion(this);
|
|
||||||
const res = await upload.syncMarkdownToNotion(basename, allowTags, emoji, cover, tags, type, slug, stats, category, summary, paword, favicon, datetime, markDownData, nowFile, this.app, this.settings)
|
|
||||||
if(res.status === 200){
|
|
||||||
new Notice(`${langConfig["sync-success"]}${basename}`)
|
|
||||||
}else {
|
|
||||||
new Notice(`${langConfig["sync-fail"]}${basename}`, 5000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getNowFileMarkdownContent(app: App) {
|
|
||||||
const nowFile = app.workspace.getActiveFile();
|
|
||||||
const { allowTags } = this.settings;
|
|
||||||
let emoji = ''
|
|
||||||
let cover = ''
|
|
||||||
let tags = []
|
|
||||||
let type = ''
|
|
||||||
let slug = ''
|
|
||||||
let stats = ''
|
|
||||||
let category = ''
|
|
||||||
let summary = ''
|
|
||||||
let paword = ''
|
|
||||||
let favicon = ''
|
|
||||||
let datetime = ''
|
|
||||||
|
|
||||||
const FileCache = app.metadataCache.getFileCache(nowFile)
|
|
||||||
try {
|
|
||||||
if(allowTags) {
|
|
||||||
emoji = FileCache.frontmatter.titleicon;
|
|
||||||
cover = FileCache.frontmatter.coverurl;
|
|
||||||
tags = FileCache.frontmatter.tags;
|
|
||||||
type = FileCache.frontmatter.type;
|
|
||||||
slug = FileCache.frontmatter.slug;
|
|
||||||
stats = FileCache.frontmatter.stats;
|
|
||||||
category = FileCache.frontmatter.category;
|
|
||||||
summary = FileCache.frontmatter.summary;
|
|
||||||
paword = FileCache.frontmatter.password;
|
|
||||||
favicon = FileCache.frontmatter.icon;
|
|
||||||
datetime = FileCache.frontmatter.date;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
new Notice(langConfig["set-tags-fail"]);
|
|
||||||
}
|
|
||||||
if (nowFile) {
|
|
||||||
const markDownData = await nowFile.vault.read(nowFile);
|
|
||||||
return {
|
|
||||||
markDownData,
|
|
||||||
nowFile,
|
|
||||||
emoji,
|
|
||||||
cover,
|
|
||||||
tags,
|
|
||||||
type,
|
|
||||||
slug,
|
|
||||||
stats,
|
|
||||||
category,
|
|
||||||
summary,
|
|
||||||
paword,
|
|
||||||
favicon,
|
|
||||||
datetime,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
new Notice(langConfig["open-file"]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadSettings() {
|
|
||||||
this.settings = Object.assign(
|
|
||||||
{},
|
|
||||||
DEFAULT_SETTINGS,
|
|
||||||
await this.loadData()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async saveSettings() {
|
|
||||||
await this.saveData(this.settings);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SampleSettingTab extends PluginSettingTab {
|
|
||||||
plugin: ObsidianSyncNotionPlugin;
|
|
||||||
|
|
||||||
constructor(app: App, plugin: ObsidianSyncNotionPlugin) {
|
|
||||||
super(app, plugin);
|
|
||||||
this.plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
display(): void {
|
|
||||||
const { containerEl } = this;
|
|
||||||
|
|
||||||
containerEl.empty();
|
|
||||||
|
|
||||||
containerEl.createEl("h2", {
|
|
||||||
text: "Settings for obsidian to NotionNext plugin.",
|
|
||||||
});
|
|
||||||
|
|
||||||
new Setting(containerEl)
|
|
||||||
.setName("Notion API Token")
|
|
||||||
.setDesc("It's a secret")
|
|
||||||
.addText((text) =>{
|
|
||||||
// t.inputEl.type = 'password'
|
|
||||||
return text
|
|
||||||
.setPlaceholder("Enter your Notion API Token")
|
|
||||||
.setValue(this.plugin.settings.notionAPI)
|
|
||||||
.onChange(async (value) => {
|
|
||||||
this.plugin.settings.notionAPI = value;
|
|
||||||
await this.plugin.saveSettings();
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
const notionDatabaseID = new Setting(containerEl)
|
|
||||||
.setName("Database ID")
|
|
||||||
.setDesc("It's a secret")
|
|
||||||
.addText((text) => {
|
|
||||||
// t.inputEl.type = 'password'
|
|
||||||
return text
|
|
||||||
.setPlaceholder("Enter your Database ID")
|
|
||||||
.setValue(this.plugin.settings.databaseID)
|
|
||||||
.onChange(async (value) => {
|
|
||||||
this.plugin.settings.databaseID = value;
|
|
||||||
await this.plugin.saveSettings();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
);
|
|
||||||
|
|
||||||
// notionDatabaseID.controlEl.querySelector('input').type='password'
|
|
||||||
|
|
||||||
new Setting(containerEl)
|
|
||||||
.setName("Banner url(optional)")
|
|
||||||
.setDesc("page banner url(optional), default is empty, if you want to show a banner, please enter the url(like:https://raw.githubusercontent.com/EasyChris/obsidian-to-notion/ae7a9ac6cf427f3ca338a409ce6967ced9506f12/doc/2.png)")
|
|
||||||
.addText((text) =>
|
|
||||||
text
|
|
||||||
.setPlaceholder("Enter banner pic url: ")
|
|
||||||
.setValue(this.plugin.settings.bannerUrl)
|
|
||||||
.onChange(async (value) => {
|
|
||||||
this.plugin.settings.bannerUrl = value;
|
|
||||||
await this.plugin.saveSettings();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
new Setting(containerEl)
|
|
||||||
.setName("Notion ID(optional)")
|
|
||||||
.setDesc("Your notion ID(optional),share link likes:https://username.notion.site/,your notion id is [username]")
|
|
||||||
.addText((text) =>
|
|
||||||
text
|
|
||||||
.setPlaceholder("Enter notion ID(options) ")
|
|
||||||
.setValue(this.plugin.settings.notionID)
|
|
||||||
.onChange(async (value) => {
|
|
||||||
this.plugin.settings.notionID = value;
|
|
||||||
await this.plugin.saveSettings();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
new Setting(containerEl)
|
|
||||||
.setName("Convert tags(optional)")
|
|
||||||
.setDesc("Transfer the Obsidian tags to the Notion table. It requires the column with the name 'Tags'")
|
|
||||||
.addToggle((toggle) =>
|
|
||||||
toggle
|
|
||||||
.setValue(this.plugin.settings.allowTags)
|
|
||||||
.onChange(async (value) => {
|
|
||||||
this.plugin.settings.allowTags = value;
|
|
||||||
await this.plugin.saveSettings();
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"id": "share-to-notionnext",
|
"id": "share-to-notionnext",
|
||||||
"name": "Share to NotionNext",
|
"name": "Share to NotionNext",
|
||||||
"version": "0.0.9",
|
"version": "0.1.8",
|
||||||
"minAppVersion": "0.0.1",
|
"minAppVersion": "0.0.1",
|
||||||
"description": "This is a plugin for Obsidian. This plugin shares obsidian md file to notion with notion api for NotionNext web deploy.",
|
"description": "Shares obsidian md file to notion with notion api for NotionNext web deploy, originally created by EasyChris/obsidian-to-notion.",
|
||||||
"author": "jxpeng98",
|
"author": "EasyChris, jxpeng98",
|
||||||
"authorUrl": "https://github.com/jxpeng98/obsidian-to-NotionNext",
|
"authorUrl": "https://github.com/jxpeng98",
|
||||||
"isDesktopOnly": false
|
"isDesktopOnly": false
|
||||||
}
|
}
|
||||||
20
package.json
20
package.json
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "share-to-notionnext",
|
"name": "share-to-notionnext",
|
||||||
"version": "0.0.9",
|
"version": "0.1.8",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "This is a plugin for Obsidian. This plugin share obsidian md file to notion with notion api for NotionNext web deploy.",
|
"description": "Shares obsidian md file to notion with notion api for NotionNext web deploy, originally created by EasyChris/obsidian-to-notion.",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node esbuild.config.mjs",
|
"dev": "node esbuild.config.mjs",
|
||||||
@@ -13,19 +13,21 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "GNU GPLv3",
|
"license": "GNU GPLv3",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^17.0.35",
|
"@types/node": "^20.5.7",
|
||||||
"@types/yaml-front-matter": "^4.1.0",
|
"@types/yaml-front-matter": "^4.1.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.2.0",
|
"@typescript-eslint/eslint-plugin": "^6.5.0",
|
||||||
"@typescript-eslint/parser": "^5.2.0",
|
"@typescript-eslint/parser": "^6.5.0",
|
||||||
"builtin-modules": "^3.2.0",
|
"builtin-modules": "^3.2.0",
|
||||||
"esbuild": "0.13.12",
|
"esbuild": "0.19.2",
|
||||||
"obsidian": "latest",
|
"obsidian": "latest",
|
||||||
"tslib": "2.3.1",
|
"tslib": "2.6.2",
|
||||||
"typescript": "4.4.4"
|
"typescript": "5.2.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tryfabric/martian": "^1.2.0",
|
"@tryfabric/martian": "^1.2.0",
|
||||||
"https-proxy-agent": "^5.0.1",
|
"https-proxy-agent": "^7.0.1",
|
||||||
|
"process": "^0.11.10",
|
||||||
|
"remark-math": "^6.0.0",
|
||||||
"yaml": "^2.2.2",
|
"yaml": "^2.2.2",
|
||||||
"yaml-front-matter": "^4.1.1"
|
"yaml-front-matter": "^4.1.1"
|
||||||
}
|
}
|
||||||
|
|||||||
87
src/BaseUpload2Notion.ts
Normal file
87
src/BaseUpload2Notion.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
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 UploadBase {
|
||||||
|
plugin: MyPlugin;
|
||||||
|
notion: Client;
|
||||||
|
agent: any;
|
||||||
|
constructor(plugin: MyPlugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
async deletePage(notionID: string) {
|
||||||
|
return requestUrl({
|
||||||
|
url: `https://api.notion.com/v1/blocks/${notionID}`,
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': 'Bearer ' + this.plugin.settings.notionAPI,
|
||||||
|
'Notion-Version': '2022-06-28',
|
||||||
|
},
|
||||||
|
body: ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDataBase(databaseID: string) {
|
||||||
|
const response = await requestUrl({
|
||||||
|
url: `https://api.notion.com/v1/databases/${databaseID}`,
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer ' + this.plugin.settings.notionAPI,
|
||||||
|
'Notion-Version': '2022-06-28',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check if cover is present in the JSON response and then get the URL
|
||||||
|
if (response.json.cover && response.json.cover.external) {
|
||||||
|
return response.json.cover.external.url;
|
||||||
|
} else {
|
||||||
|
return null; // or some other default value, if you prefer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateYamlInfo(yamlContent: string, nowFile: TFile, res: any, app: App, settings: any) {
|
||||||
|
let { url, id } = res.json
|
||||||
|
// replace www to notionID
|
||||||
|
const { notionID } = settings;
|
||||||
|
if (notionID !== "") {
|
||||||
|
// replace url str "www" to notionID
|
||||||
|
url = url.replace("www.notion.so", `${notionID}.notion.site`)
|
||||||
|
}
|
||||||
|
await app.fileManager.processFrontMatter(nowFile, yamlContent => {
|
||||||
|
if (yamlContent['notionID']) {
|
||||||
|
delete yamlContent['notionID']
|
||||||
|
}
|
||||||
|
if (yamlContent['link']) {
|
||||||
|
delete yamlContent['link']
|
||||||
|
}
|
||||||
|
// add new notionID and link
|
||||||
|
yamlContent.notionID = id;
|
||||||
|
yamlContent.link = url;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(url)
|
||||||
|
} catch (error) {
|
||||||
|
new Notice(`复制链接失败,请手动复制${error}`)
|
||||||
|
}
|
||||||
|
// const __content = yamlContent.__content;
|
||||||
|
// delete yamlContent.__content
|
||||||
|
// const yamlhead = yaml.stringify(yamlContent)
|
||||||
|
// // if yamlhead hava last \n remove it
|
||||||
|
// const yamlhead_remove_n = yamlhead.replace(/\n$/, '')
|
||||||
|
// // if __content have start \n remove it
|
||||||
|
// const __content_remove_n = __content.replace(/^\n/, '')
|
||||||
|
// const content = '---\n' +yamlhead_remove_n +'\n---\n' + __content_remove_n;
|
||||||
|
// try {
|
||||||
|
// await nowFile.vault.modify(nowFile, content)
|
||||||
|
// } catch (error) {
|
||||||
|
// new Notice(`write file error ${error}`)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
178
src/Upload2Notion.ts
Normal file
178
src/Upload2Notion.ts
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
import {UploadBase} from "./BaseUpload2Notion";
|
||||||
|
import { App, Notice, requestUrl, TFile } from "obsidian";
|
||||||
|
import { Client } from '@notionhq/client';
|
||||||
|
import { markdownToBlocks, } from "@tryfabric/martian";
|
||||||
|
import * as yamlFrontMatter from "yaml-front-matter";
|
||||||
|
// import * as yaml from "yaml"
|
||||||
|
import MyPlugin from "src/main";
|
||||||
|
|
||||||
|
export class Upload2Notion extends UploadBase {
|
||||||
|
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.databaseID)
|
||||||
|
|
||||||
|
if (cover == null) {
|
||||||
|
cover = databasecover
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.createPage(
|
||||||
|
title,
|
||||||
|
emoji,
|
||||||
|
cover,
|
||||||
|
tags,
|
||||||
|
type,
|
||||||
|
slug,
|
||||||
|
stats,
|
||||||
|
category,
|
||||||
|
summary,
|
||||||
|
paword,
|
||||||
|
favicon,
|
||||||
|
datetime,
|
||||||
|
childArr)
|
||||||
|
}
|
||||||
|
|
||||||
|
async createPage(title: string, emoji: string, cover: string, tags: string[], type: string, slug: string, stats: string, category: string, summary: string, pawrod: string, favicon: string, datetime: string, childArr: any) {
|
||||||
|
const bodyString: any = {
|
||||||
|
parent: {
|
||||||
|
database_id: this.plugin.settings.databaseID
|
||||||
|
},
|
||||||
|
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.notionAPI,
|
||||||
|
'Notion-Version': '2022-06-28',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(bodyString),
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
new Notice(`network error ${error}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async syncMarkdownToNotion(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, settings: any): Promise<any> {
|
||||||
|
let res: any
|
||||||
|
const yamlContent: any = yamlFrontMatter.loadFront(markdown);
|
||||||
|
const __content = yamlContent.__content
|
||||||
|
const file2Block = markdownToBlocks(__content);
|
||||||
|
const frontmasster = app.metadataCache.getFileCache(nowFile)?.frontmatter
|
||||||
|
const notionID = frontmasster ? frontmasster.notionID : null
|
||||||
|
|
||||||
|
if (notionID) {
|
||||||
|
res = await this.updatePage(notionID, title, 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 this.updateYamlInfo(markdown, nowFile, res, app, settings)
|
||||||
|
} else {
|
||||||
|
new Notice(`${res.text}`)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
}
|
||||||
179
src/Upload2Notion_NN.ts
Normal file
179
src/Upload2Notion_NN.ts
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
import {UploadBase} from "./BaseUpload2Notion";
|
||||||
|
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 Upload2NotionNext extends UploadBase {
|
||||||
|
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.databaseID)
|
||||||
|
|
||||||
|
if (cover == null) {
|
||||||
|
cover = databasecover
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.createPage(
|
||||||
|
title,
|
||||||
|
emoji,
|
||||||
|
cover,
|
||||||
|
tags,
|
||||||
|
type,
|
||||||
|
slug,
|
||||||
|
stats,
|
||||||
|
category,
|
||||||
|
summary,
|
||||||
|
paword,
|
||||||
|
favicon,
|
||||||
|
datetime,
|
||||||
|
childArr)
|
||||||
|
}
|
||||||
|
|
||||||
|
async createPage(title: string, emoji: string, cover: string, tags: string[], type: string, slug: string, stats: string, category: string, summary: string, pawrod: string, favicon: string, datetime: string, childArr: any) {
|
||||||
|
const bodyString: any = {
|
||||||
|
parent: {
|
||||||
|
database_id: this.plugin.settings.databaseID
|
||||||
|
},
|
||||||
|
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.notionAPI,
|
||||||
|
'Notion-Version': '2022-06-28',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(bodyString),
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
new Notice(`network error ${error}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async syncMarkdownToNotion(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, settings: any): Promise<any> {
|
||||||
|
let res: any
|
||||||
|
const yamlContent: any = yamlFrontMatter.loadFront(markdown);
|
||||||
|
const __content = yamlContent.__content
|
||||||
|
const file2Block = markdownToBlocks(__content);
|
||||||
|
const frontmasster = app.metadataCache.getFileCache(nowFile)?.frontmatter
|
||||||
|
const notionID = frontmasster ? frontmasster.notionID : null
|
||||||
|
|
||||||
|
if (notionID) {
|
||||||
|
res = await this.updatePage(notionID, title, 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 this.updateYamlInfo(markdown, nowFile, res, app, settings)
|
||||||
|
} else {
|
||||||
|
new Notice(`${res.text}`)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
273
src/main.ts
Normal file
273
src/main.ts
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
import {App, Editor, MarkdownView, Notice, Plugin, PluginSettingTab, Setting} from "obsidian";
|
||||||
|
import {addIcons} from 'src/icon';
|
||||||
|
import {Upload2Notion} from "src/Upload2Notion";
|
||||||
|
import {Upload2NotionNext} from "src/Upload2Notion_NN";
|
||||||
|
import {NoticeMConfig} from "src/Message";
|
||||||
|
|
||||||
|
|
||||||
|
// Remember to rename these classes and interfaces!
|
||||||
|
|
||||||
|
interface PluginSettings {
|
||||||
|
NNon: boolean;
|
||||||
|
notionAPI: string;
|
||||||
|
databaseID: string;
|
||||||
|
bannerUrl: string;
|
||||||
|
notionID: string;
|
||||||
|
proxy: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const langConfig = NoticeMConfig(window.localStorage.getItem('language') || 'en')
|
||||||
|
|
||||||
|
const DEFAULT_SETTINGS: PluginSettings = {
|
||||||
|
NNon: true,
|
||||||
|
notionAPI: "",
|
||||||
|
databaseID: "",
|
||||||
|
bannerUrl: "",
|
||||||
|
notionID: "",
|
||||||
|
proxy: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class ObsidianSyncNotionPlugin extends Plugin {
|
||||||
|
settings: PluginSettings;
|
||||||
|
|
||||||
|
async onload() {
|
||||||
|
await this.loadSettings();
|
||||||
|
addIcons();
|
||||||
|
// This creates an icon in the left ribbon.
|
||||||
|
const ribbonIconEl = this.addRibbonIcon(
|
||||||
|
"notion-logo",
|
||||||
|
"Share to NotionNext",
|
||||||
|
async (evt: MouseEvent) => {
|
||||||
|
// Called when the user clicks the icon.
|
||||||
|
await this.upload();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// This adds a status bar item to the bottom of the app. Does not work on mobile apps.
|
||||||
|
const statusBarItemEl = this.addStatusBarItem();
|
||||||
|
// statusBarItemEl.setText("share to notion");
|
||||||
|
|
||||||
|
this.addCommand({
|
||||||
|
id: "share-to-notionnext",
|
||||||
|
name: "share to notionnext",
|
||||||
|
editorCallback: async (editor: Editor, view: MarkdownView) => {
|
||||||
|
await this.upload()
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// This adds a settings tab so the user can configure various aspects of the plugin
|
||||||
|
this.addSettingTab(new ObsidianSettingTab(this.app, this));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
onunload() {
|
||||||
|
}
|
||||||
|
|
||||||
|
async upload() {
|
||||||
|
const {NNon, notionAPI, databaseID} = this.settings;
|
||||||
|
if (notionAPI === "" || databaseID === "") {
|
||||||
|
new Notice(
|
||||||
|
"Please set up the notion API and database ID in the settings tab."
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const {markDownData, nowFile, emoji, cover, tags, type, slug, stats, category, summary, paword, favicon, datetime} = await this.getNowFileMarkdownContent(this.app);
|
||||||
|
|
||||||
|
|
||||||
|
if (markDownData) {
|
||||||
|
const { basename } = nowFile;
|
||||||
|
let upload;
|
||||||
|
|
||||||
|
if (NNon) {
|
||||||
|
upload = new Upload2NotionNext(this);
|
||||||
|
} else {
|
||||||
|
upload = new Upload2Notion(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await upload.syncMarkdownToNotion(basename, emoji, cover, tags, type, slug, stats, category, summary, paword, favicon, datetime, markDownData, nowFile, this.app, this.settings);
|
||||||
|
|
||||||
|
if (res.status === 200) {
|
||||||
|
new Notice(`${langConfig["sync-success"]}${basename}`);
|
||||||
|
} else {
|
||||||
|
new Notice(`${langConfig["sync-fail"]}${basename}`, 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getNowFileMarkdownContent(app: App) {
|
||||||
|
const nowFile = app.workspace.getActiveFile();
|
||||||
|
const {NNon} = this.settings;
|
||||||
|
let emoji = ''
|
||||||
|
let cover = ''
|
||||||
|
let tags = []
|
||||||
|
let type = ''
|
||||||
|
let slug = ''
|
||||||
|
let stats = ''
|
||||||
|
let category = ''
|
||||||
|
let summary = ''
|
||||||
|
let paword = ''
|
||||||
|
let favicon = ''
|
||||||
|
let datetime = ''
|
||||||
|
|
||||||
|
const FileCache = app.metadataCache.getFileCache(nowFile)
|
||||||
|
try {
|
||||||
|
if (NNon) {
|
||||||
|
emoji = FileCache.frontmatter.titleicon;
|
||||||
|
cover = FileCache.frontmatter.coverurl;
|
||||||
|
tags = FileCache.frontmatter.tags;
|
||||||
|
type = FileCache.frontmatter.type;
|
||||||
|
slug = FileCache.frontmatter.slug;
|
||||||
|
stats = FileCache.frontmatter.stats;
|
||||||
|
category = FileCache.frontmatter.category;
|
||||||
|
summary = FileCache.frontmatter.summary;
|
||||||
|
paword = FileCache.frontmatter.password;
|
||||||
|
favicon = FileCache.frontmatter.icon;
|
||||||
|
datetime = FileCache.frontmatter.date;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
new Notice(langConfig["set-tags-fail"]);
|
||||||
|
}
|
||||||
|
if (nowFile) {
|
||||||
|
const markDownData = await nowFile.vault.read(nowFile);
|
||||||
|
return {
|
||||||
|
markDownData,
|
||||||
|
nowFile,
|
||||||
|
emoji,
|
||||||
|
cover,
|
||||||
|
tags,
|
||||||
|
type,
|
||||||
|
slug,
|
||||||
|
stats,
|
||||||
|
category,
|
||||||
|
summary,
|
||||||
|
paword,
|
||||||
|
favicon,
|
||||||
|
datetime,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
new Notice(langConfig["open-file"]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadSettings() {
|
||||||
|
this.settings = Object.assign(
|
||||||
|
{},
|
||||||
|
DEFAULT_SETTINGS,
|
||||||
|
await this.loadData()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveSettings() {
|
||||||
|
await this.saveData(this.settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ObsidianSettingTab extends PluginSettingTab {
|
||||||
|
plugin: ObsidianSyncNotionPlugin;
|
||||||
|
|
||||||
|
constructor(app: App, plugin: ObsidianSyncNotionPlugin) {
|
||||||
|
super(app, plugin);
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
display(): void {
|
||||||
|
const {containerEl} = this;
|
||||||
|
|
||||||
|
containerEl.empty();
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName("NotionNext version")
|
||||||
|
.setDesc("Turn on this option if you are using NotionNext")
|
||||||
|
.addToggle((toggle) =>
|
||||||
|
toggle
|
||||||
|
.setValue(this.plugin.settings.NNon)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.plugin.settings.NNon = value;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
containerEl.createEl('h2', {text: 'NotionNext Database Settings'})
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName("Notion API Token")
|
||||||
|
.setDesc("It's a secret")
|
||||||
|
.addText((text) => {
|
||||||
|
text.inputEl.type = 'password';
|
||||||
|
return text
|
||||||
|
.setPlaceholder("Enter your Notion API Token")
|
||||||
|
.setValue(this.plugin.settings.notionAPI)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.plugin.settings.notionAPI = value;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const notionDatabaseID = new Setting(containerEl)
|
||||||
|
.setName("Database ID")
|
||||||
|
.setDesc("It's a secret")
|
||||||
|
.addText((text) => {
|
||||||
|
text.inputEl.type = 'password';
|
||||||
|
return text
|
||||||
|
.setPlaceholder("Enter your Database ID")
|
||||||
|
.setValue(this.plugin.settings.databaseID)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.plugin.settings.databaseID = value;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// notionDatabaseID.controlEl.querySelector('input').type='password'
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName("Banner url(optional)")
|
||||||
|
.setDesc("page banner url(optional), default is empty, if you want to show a banner, please enter the url(like:https://raw.githubusercontent.com/EasyChris/obsidian-to-notion/ae7a9ac6cf427f3ca338a409ce6967ced9506f12/doc/2.png)")
|
||||||
|
.addText((text) =>
|
||||||
|
text
|
||||||
|
.setPlaceholder("Enter banner pic url: ")
|
||||||
|
.setValue(this.plugin.settings.bannerUrl)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.plugin.settings.bannerUrl = value;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName("Notion ID(username, optional)")
|
||||||
|
.setDesc("Your notion ID(optional),share link likes:https://username.notion.site/,your notion id is [username]")
|
||||||
|
.addText((text) =>
|
||||||
|
text
|
||||||
|
.setPlaceholder("Enter notion ID(options) ")
|
||||||
|
.setValue(this.plugin.settings.notionID)
|
||||||
|
.onChange(async (value) => {
|
||||||
|
this.plugin.settings.notionID = value;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// General Database Settings
|
||||||
|
containerEl.createEl('h2', {text: 'General Notion Database Settings'});
|
||||||
|
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName("Not finished")
|
||||||
|
.setDesc("This functio will be available in the next version");
|
||||||
|
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,5 +18,7 @@
|
|||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"**/*.ts"
|
"**/*.ts"
|
||||||
, "Upload2Notion.ts" ]
|
,
|
||||||
|
"src/Upload2Notion.ts"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user