Compare commits

...

259 Commits
0.3.4 ... 2.4.1

Author SHA1 Message Date
Jiaxin Peng
bc166d8db1 Update version to 2.4.1 2024-08-28 20:31:28 +01:00
Jiaxin Peng
7612637951 Merge pull request #34 from jxpeng98/code-highlight-issue
fix code highlight issue
2024-08-28 20:30:49 +01:00
Jiaxin Peng
e8b1617916 fix item type error for iteration 2024-08-28 20:30:05 +01:00
Jiaxin Peng
fd29ed820e fix code highlight issue 2024-08-28 20:26:06 +01:00
Jiaxin Peng
1f58670906 Update version to 2.4.0 2024-08-19 11:24:23 +01:00
Jiaxin Peng
fc47d36e8d Merge pull request #32 from jxpeng98/long-notes-support
Long notes support
2024-08-18 22:39:00 +01:00
Jiaxin Peng
cd4e610c46 modify type of pageProperties 2024-08-18 18:42:59 +01:00
Jiaxin Peng
b05c248c44 feat: support long notes syncing 2024-08-18 17:16:08 +01:00
Jiaxin Peng
800e218dee Update version to 2.3.5 2024-08-18 13:08:04 +01:00
Jiaxin Peng
3b774552af update martian 2024-08-18 13:07:52 +01:00
b8a02c49f1 Update version to 2.3.4 2024-07-28 01:13:39 +01:00
Jiaxin Peng
046a67f22c Merge pull request #29 from jxpeng98/debug-general
General: use node-fetch to post notes
2024-07-28 01:08:08 +01:00
426f05dfbd General: use node-fetch to post notes 2024-07-28 01:07:35 +01:00
Jiaxin Peng
0addaa0d7f Merge pull request #28 from jxpeng98/debug-next
Next: use node-fetch to post notes for a better debug
2024-07-28 00:54:05 +01:00
1d5792c966 Next: use node-fetch to post notes for a better debug 2024-07-28 00:52:27 +01:00
Jiaxin Peng
193d3f6f87 Merge pull request #27 from jxpeng98/better-debug
custom: node-fetch replaces requestUrl
2024-07-27 23:37:27 +01:00
e9c2b4aada Custom: use node-fetch to post notes rather than requestUrl for better debug 2024-07-27 23:36:00 +01:00
b5ac42e6e3 use node-fetch to post notes for a better debug 2024-07-27 22:28:21 +01:00
60d002677b use json to parse the error information 2024-07-27 21:38:09 +01:00
3c3ec6881e Update version to 2.3.3 2024-07-15 14:19:11 +01:00
Jiaxin Peng
a80155fafc Merge pull request #26 from jxpeng98/notice-info
change the success notice colour
2024-07-15 14:17:01 +01:00
3a814b8390 feat: change the success notice colour 2024-07-15 14:15:10 +01:00
bb522db9a2 fix: readme gif link update 2024-07-15 14:13:09 +01:00
201f72ae0e update notice 2024-07-14 23:57:01 +01:00
be6f192dd8 Update readme file 2024-07-14 21:51:53 +01:00
4ce1019094 Update Readme links 2024-07-14 20:27:02 +01:00
095c608511 update readme with new instruction 2024-07-14 17:58:21 +01:00
2d8d7fb11a Update version to 2.3.2 2024-07-14 17:52:59 +01:00
36725cb699 fix: Text property in custom database 2024-07-14 17:52:43 +01:00
Jiaxin Peng
654e7e854d 更新 README.md 2024-07-10 00:32:00 +01:00
Jiaxin Peng
fa67f08512 Update version to 2.3.1 2024-07-05 09:50:31 +01:00
Jiaxin Peng
7e42ef49a7 Merge pull request #24 from jxpeng98/notion-link
feat: add notion link display
2024-07-05 09:49:27 +01:00
Jiaxin Peng
5a95aa0acc feat: add notion link display 2024-07-05 09:48:35 +01:00
Jiaxin Peng
ded01ce678 Update version to 2.3.0 2024-07-04 20:57:35 +01:00
Jiaxin Peng
3535d9fc70 Merge pull request #23 from jxpeng98/edit-modal
edit modal update for customised database
2024-07-04 20:55:41 +01:00
Jiaxin Peng
9c93e38aff reformat code and update readme file 2024-07-04 20:51:07 +01:00
Jiaxin Peng
bfde5dfe1a finish the edit modal 2024-07-04 20:28:28 +01:00
Jiaxin Peng
d8eb3c8984 feat: cancel function works well 2024-07-04 18:12:31 +01:00
Jiaxin Peng
1cee2b67a6 update edit modal with better display setting. 2024-07-04 17:06:23 +01:00
Jiaxin Peng
82529ce56a back up for edit modal 2024-07-04 10:00:46 +01:00
Jiaxin Peng
70f30f0713 Add support for notion link display in the front matter. 2024-07-03 22:35:47 +01:00
Jiaxin Peng
24ebb0bf8a Back up edit modal 2024-07-03 22:01:46 +01:00
Jiaxin Peng
d7372c7c55 Improve preview function for customised database 2024-07-03 16:05:24 +01:00
Jiaxin Peng
f7bcf71020 create retrieve function to get all created properties for customised database 2024-07-03 14:26:02 +01:00
Jiaxin Peng
72146afe48 update settingModal.ts refactor the customise database and improve the creating logic 2024-07-03 13:39:56 +01:00
Jiaxin Peng
9bcdb42edb settingmodal save mapping for reference 2024-07-03 11:06:54 +01:00
Jiaxin Peng
16e0827991 update the basic function of edit modal 2024-07-03 08:47:33 +01:00
Jiaxin Peng
863a6fb0dc more clear logic customised database setting code 2024-07-02 23:56:35 +01:00
Jiaxin Peng
5799ffe79b Update custom function and adjust the custom interface ui 2024-07-02 23:48:37 +01:00
Jiaxin Peng
a8ebab7fcb change tags titleicon and summary to optional function in next database 2024-03-17 14:36:55 +00:00
Jiaxin Peng
e0ec27ad07 update readme 2024-03-12 22:04:34 +00:00
Jiaxin Peng
85220105b0 update README 2024-03-08 15:23:17 +00:00
Jiaxin Peng
9afdb24a82 typo 2024-02-26 11:57:28 +00:00
Jiaxin Peng
3c7ab13995 update readme 2024-02-26 11:53:02 +00:00
Jiaxin Peng
31b72f3bd3 Update version to 2.2.4 2024-02-19 23:08:47 +00:00
Jiaxin Peng
ee5aada1b9 Update version to 2.2.3 2024-02-19 20:51:47 +00:00
Jiaxin Peng
9e15044f6a Update version to 2.2.2 2024-02-16 23:10:22 +00:00
Jiaxin Peng
9d891dae55 Update version to 2.2.1 2024-01-29 20:43:14 +00:00
Jiaxin Peng
bc665253ec Update version to 2.2.0 2024-01-29 19:03:09 +00:00
Jiaxin Peng
125c75f7c0 Merge pull request #13 from jxpeng98/custom
add the customisation function
2024-01-29 19:01:37 +00:00
Jiaxin Peng
8248640b5f fix the extra argument in the uploadcommand 2024-01-29 11:55:52 +00:00
Jiaxin Peng
0198898086 update the readme and changelog 2024-01-29 11:53:18 +00:00
Jiaxin Peng
8c6aea8250 Complete the basic function of customisation 2024-01-29 11:12:07 +00:00
Jiaxin Peng
a179d3da52 almost finish the structure 2024-01-29 00:55:06 +00:00
Jiaxin Peng
569d8eb699 fix the error of the display of title custom button 2024-01-28 00:26:58 +00:00
Jiaxin Peng
2a58dd3258 complete the custom ui design 2024-01-28 00:11:11 +00:00
Jiaxin Peng
8c65e471eb Update version to 2.1.2 2024-01-09 11:44:40 +00:00
Jiaxin Peng
388693d5b4 Update version to 2.1.1 2024-01-09 11:38:03 +00:00
Jiaxin Peng
dda6710fe9 remove redundant settingtabs 2024-01-09 11:37:47 +00:00
Jiaxin Peng
76ea9a4aee Update version to 2.1.0 2024-01-09 11:34:11 +00:00
Jiaxin Peng
856f83e8a1 Add confirmation delete button and modify edit modal interface. 2024-01-09 11:33:39 +00:00
Jiaxin Peng
f670dd972a Update version to 2.0.1 2024-01-03 00:37:47 +00:00
Jiaxin Peng
e6de2d036a add preview and edit function 2024-01-03 00:37:34 +00:00
Jiaxin Peng
dd131f5df1 readme update 2023-12-29 00:41:02 +00:00
Jiaxin Peng
a9fb81fc1b readme error 2023-12-29 00:39:18 +00:00
Jiaxin Peng
4f21e3ff91 Update version to 2.0.0 2023-12-29 00:36:48 +00:00
Jiaxin Peng
4abbbecd37 modify the notionapi variable 2023-12-29 00:36:29 +00:00
Jiaxin Peng
35746f1c51 Merge pull request #12 from jxpeng98/add-button
rebuild the plugin and add new button for multi database support
2023-12-29 00:26:16 +00:00
Jiaxin Peng
9ac64268fc Merge branch 'master' into add-button 2023-12-29 00:26:02 +00:00
Jiaxin Peng
3123d91e97 complete the new version for add button 2023-12-29 00:21:21 +00:00
Jiaxin Peng
cf08703fea complete the basic notionnext function 2023-12-28 00:55:06 +00:00
Jiaxin Peng
fb463ceaae modify language map 2023-12-27 00:48:39 +00:00
Jiaxin Peng
306573d81e merge 1.1.2 2023-12-27 00:37:54 +00:00
Jiaxin Peng
fb5f7b502d finish some command coding 2023-12-27 00:27:32 +00:00
Jiaxin Peng
59c15f1206 modify setting modal 2023-12-26 21:19:24 +00:00
Jiaxin Peng
370444fb94 basic display of modal 2023-12-26 09:03:29 +00:00
Jiaxin Peng
11aaf14c0b update general tabs 2023-12-25 22:47:24 +00:00
Jiaxin Peng
f3fb251cf7 set the class next tabs 2023-12-25 22:43:11 +00:00
Jiaxin Peng
913fa4dd03 Update version to 1.1.2 2023-12-06 15:22:53 +00:00
Jiaxin Peng
f638310f31 modify workflow 2023-12-06 15:22:09 +00:00
Jiaxin Peng
c2421f7a84 add the property 'status' for NotionNext 2023-12-06 15:19:57 +00:00
Jiaxin Peng
6c30d0c2b4 adjust the limitation of blocks 2023-12-06 14:40:15 +00:00
Jiaxin Peng
ccb5a20abe add custom frontmatter 2023-11-23 22:11:04 +00:00
Jiaxin Peng
630e3a20c2 Update version to 1.1.1 2023-11-23 11:54:50 +00:00
Jiaxin Peng
040b72b760 Update version to 1.1.0 2023-11-20 22:01:20 +00:00
Jiaxin Peng
7df4b5e9c8 Merge pull request #9 from jxpeng98/package-test
package update and add tag syncing button
2023-11-20 21:59:53 +00:00
Jiaxin Peng
3f35a56b6e rebuild the locale file and add tag button to control tag syncing function 2023-11-20 21:57:30 +00:00
Jiaxin Peng
3c7e266a35 setting tab bug fix 2023-11-20 17:07:04 +00:00
Jiaxin Peng
8201c706ea remove block limit and update packages 2023-11-16 21:09:31 +00:00
Jiaxin Peng
4433ea449d Update version to 1.0.1 2023-11-16 13:55:14 +00:00
Jiaxin Peng
96126d1334 Jump to version 1.0.0 2023-11-16 13:32:07 +00:00
Jiaxin Peng
b3702a9cc1 Update version to 1.0.0 2023-11-16 13:29:29 +00:00
Jiaxin Peng
ba9b477db7 Update version to 0.2.7 2023-11-16 13:27:48 +00:00
Jiaxin Peng
fb6951cfdf Update version to 0.3.5 2023-11-16 13:25:56 +00:00
Jiaxin Peng
6454c0417d Update version to 0.3.4 2023-11-16 13:10:53 +00:00
Jiaxin Peng
35f19a9f43 modify the action file 2023-11-16 13:10:27 +00:00
Jiaxin Peng
8e32e8f0f5 Update version to 0.3.3 2023-11-16 12:59:41 +00:00
Jiaxin Peng
880f256ae1 Update version to 0.3.2 2023-11-16 12:55:01 +00:00
Jiaxin Peng
bf9c3f2933 Update version to 0.3.1 2023-11-16 11:58:58 +00:00
Jiaxin Peng
2ac354299a update action file 2023-11-16 11:58:44 +00:00
Jiaxin Peng
8317abb667 Update version to 0.3.0 2023-11-16 11:50:36 +00:00
Jiaxin Peng
d74634aca6 Merge pull request #7 from jxpeng98/custom-title
Custom title
2023-11-16 11:48:14 +00:00
Jiaxin Peng
d400b92263 update readme 2023-11-16 11:44:45 +00:00
Jiaxin Peng
e7fa597e5a update translate 2023-11-16 11:14:19 +00:00
Jiaxin Peng
6ce689ba96 complete the basic customisation feature 2023-11-16 01:00:52 +00:00
Jiaxin Peng
b978cf7e01 complete the setting ui 2023-11-16 00:02:35 +00:00
Jiaxin Peng
4e3c88f129 reorganise the code for add new setting container 2023-11-15 12:25:30 +00:00
Jiaxin Peng
6550bca70f reorganise the setting coding 2023-11-15 09:34:22 +00:00
Jiaxin Peng
662edbb84c remove the collapse details 2023-11-15 00:16:19 +00:00
Jiaxin Peng
671936ee41 add some details for collapse 2023-11-15 00:15:52 +00:00
Jiaxin Peng
2b16ea76c6 update the setting ui 2023-11-14 23:27:38 +00:00
Jiaxin Peng
399a02187f update packages and complete the setting UI 2023-11-14 15:42:30 +00:00
Jiaxin Peng
eee349509a Update version to 0.2.6 2023-11-12 21:02:00 +00:00
Jiaxin Peng
57c0941fed Merge pull request #6 from jxpeng98/add-button
Add switch button
2023-11-12 21:00:43 +00:00
Jiaxin Peng
8734af6dbf add switch button 2023-11-12 20:59:39 +00:00
Jiaxin Peng
041a78fb52 add button to display the command in the command palette 2023-11-11 00:29:35 +00:00
Jiaxin Peng
afec5d8364 Update version to 0.2.5 2023-11-05 20:49:28 +00:00
Jiaxin Peng
75926a38d4 Update version to 0.2.4 2023-11-05 20:46:42 +00:00
Jiaxin Peng
793dd67e0a Update version to 0.2.3 2023-11-05 20:44:51 +00:00
Jiaxin Peng
f235c80d18 update readme 2023-11-05 20:44:42 +00:00
Jiaxin Peng
f33315a4b6 fix github action bug 2023-11-05 20:43:47 +00:00
Jiaxin Peng
b62bd4ba2e Update version to 0.2.2 2023-11-05 17:58:19 +00:00
Jiaxin Peng
402d15a38b Merge pull request #5 from jxpeng98/general-database
General database
2023-11-05 17:57:21 +00:00
Jiaxin Peng
87f1ce0146 update readme and settingtabs 2023-11-05 17:50:51 +00:00
Jiaxin Peng
398b785e03 update the script and support both notionnext and general database 2023-11-05 17:32:10 +00:00
Jiaxin Peng
8617878582 restructure the code for further feature 2023-11-04 22:37:02 +00:00
Jiaxin Peng
ae9e0c14fb modify the directory structure and rename the function 2023-11-02 14:32:25 +00:00
Jiaxin Peng
9be9b18efb Update version to 0.2.0 2023-11-01 23:24:34 +00:00
Jiaxin Peng
588f57205b Modified the template and rebuilt the interactive logic 2023-11-01 23:24:18 +00:00
Jiaxin Peng
cfbedea91d Update version to 0.1.10 2023-11-01 10:32:35 +00:00
Jiaxin Peng
4cd6557444 chinese support in settings 2023-11-01 10:32:22 +00:00
Jiaxin Peng
407688c303 Update version to 0.1.9 2023-11-01 09:53:29 +00:00
Jiaxin Peng
84b998aa93 initialise the NNon value and add i18n 2023-11-01 09:53:14 +00:00
Jiaxin Peng
4b6f1a1f1d Update version to 0.1.8 2023-10-31 23:56:50 +00:00
Jiaxin Peng
4a2c59d13b Update version to 0.1.7 2023-10-11 23:05:46 +01:00
Jiaxin Peng
8f116956ce Update version to 0.1.6 2023-09-28 12:00:40 +01:00
Jiaxin Peng
df138d9304 Update version to 0.1.5 2023-08-29 15:18:41 +01:00
Jiaxin Peng
49b8d470fb Update version to 0.1.4 2023-08-29 14:48:50 +01:00
Jiaxin Peng
c39323c3fa Update version to 0.1.3 2023-08-29 14:29:42 +01:00
Jiaxin Peng
05dff7a389 Update version to 0.1.2 2023-08-29 14:28:22 +01:00
Jiaxin Peng
24fcadbc8b modify the action 2023-08-29 14:27:10 +01:00
Jiaxin Peng
fdb81f76a7 Update version to 0.1.1 2023-08-29 14:21:12 +01:00
Jiaxin Peng
024b7f726e Update version to 0.1.0 2023-08-29 13:39:52 +01:00
Jiaxin Peng
722ca7bb56 update packages and modify upload2notion 2023-08-29 13:39:36 +01:00
Jiaxin Peng
fb7294d607 Update version to 0.0.9 2023-08-11 23:11:24 +01:00
Jiaxin Peng
4f06d81588 change colour and size for the icon 2023-08-11 23:11:02 +01:00
Jiaxin Peng
2434ddd532 fix the notification bug 2023-08-10 23:20:26 +01:00
Jiaxin Peng
55804e491a modify the cover image url setting 2023-08-10 22:50:48 +01:00
Jiaxin Peng
679e7ec4f1 add support for cover image and title icon 2023-08-10 22:04:57 +01:00
Jiaxin Peng
9be679f456 amend the template 2023-08-10 09:44:06 +01:00
Jiaxin Peng
a0f5d2d51b update the notion version 2023-08-10 09:30:27 +01:00
Jiaxin Peng
a68a3920ad minor update 2023-08-10 09:26:50 +01:00
Jiaxin Peng
76d3fc9872 Update version to 0.0.3 2023-08-10 09:15:26 +01:00
Jiaxin Peng
e3fb50b205 Update version to 0.0.2 2023-08-10 09:07:22 +01:00
Jiaxin Peng
5411b0bac6 Update version to 0.0.2 2023-08-10 09:02:51 +01:00
Jiaxin Peng
f974d6dc66 Update version to 0.0.1 2023-08-09 22:12:14 +01:00
Jiaxin Peng
de110334e6 Update version to 0.02 2023-08-09 22:11:44 +01:00
Jiaxin Peng
d8c8e78625 Update version to 2023-08-09 22:04:47 +01:00
Jiaxin Peng
feab049033 Update version to 2023-08-09 21:36:54 +01:00
Jiaxin Peng
04d73fdccc Merge pull request #4 from jxpeng98/test-date
complete the code
2023-08-09 20:42:17 +01:00
Jiaxin Peng
f4ab15c0d5 complete the code 2023-08-09 20:41:32 +01:00
Jiaxin Peng
0bd4427f7a Merge pull request #3 from jxpeng98/password-test
update the function of password and icon
2023-08-09 20:29:35 +01:00
Jiaxin Peng
221cf62fcc update the function of password and icon 2023-08-09 20:15:47 +01:00
Jiaxin Peng
7a2610c4b9 Merge pull request #2 from jxpeng98/test-status
Test status
2023-08-09 19:48:27 +01:00
Jiaxin Peng
f9f30df7ea successful category and summary 2023-08-09 19:48:03 +01:00
Jiaxin Peng
1d22f2e741 success status 2023-08-09 19:41:08 +01:00
Jiaxin Peng
935ecacd2b Merge pull request #1 from jxpeng98/test-slug
Test slug
2023-08-09 19:33:01 +01:00
Jiaxin Peng
6724399628 successful build for type and slug 2023-08-09 19:32:25 +01:00
Jiaxin Peng
8fd6f7c701 add type and slug 2023-08-09 16:50:03 +01:00
chris
f3e3ccdeb1 Update version to 0.7.6 2023-01-01 14:02:15 +08:00
chris
c65cb6a4df Fixed tags 2023-01-01 14:01:39 +08:00
chris
7c3028dd63 Update version to 0.7.5 2023-01-01 12:34:12 +08:00
chris
d2a40694e3 Fixed tags bug 2023-01-01 12:34:10 +08:00
chris
9f7298a880 Update version to 0.7.4 2023-01-01 11:53:53 +08:00
chris
b8860beb85 Fixed tags bug 2023-01-01 11:53:47 +08:00
chris
5e8c8f6f63 Update version to 0.7.3 2023-01-01 11:44:31 +08:00
chris
1887ad8f3d Fixed tags bug 2023-01-01 11:44:18 +08:00
chris
517426ea7a Update version to 0.7.2 2022-12-19 21:07:34 +08:00
chris
b6d5ff0093 Fixed error: when switch tags is null 2022-12-19 21:07:26 +08:00
chris
41ebff9067 Update version to 0.7.1 2022-12-18 11:37:11 +08:00
Easy
96b83b6da0 Merge pull request #35 from jannikbuscha/master
fix: error if no tags but option enabled
2022-12-18 11:36:29 +08:00
Jannik | Kuqs
63ded7d50e fix: error if no tags but option enabled 2022-12-17 17:10:22 +01:00
chris
8ef9a007e3 Merge custion tags usage 2022-12-17 14:29:34 +08:00
chris
2d57952c38 Added tags show 2022-12-17 14:26:04 +08:00
chris
e3a74d61cc Added tags usage and thanks for jannikbuscha 2022-12-17 12:26:42 +08:00
chris
47e902a6ee Update version to 0.7.0 2022-12-17 12:17:51 +08:00
Easy
098110d9ba Merge pull request #34 from jannikbuscha/master
feat: convert tags support
2022-12-17 12:16:27 +08:00
Jannik | Kuqs
82629c122f feat: convert tags support 2022-12-16 16:29:35 +01:00
chris
7be8f1c799 Added error delay to 3000 and add some readme to dev 2022-12-14 12:09:46 +08:00
chris
df58b7acb6 Fixed new integration 2022-09-15 16:43:05 +08:00
chris
0fedf4e83f Update version to 0.6.9 2022-08-22 09:50:36 +08:00
chris
444ccfcd0f Update version to 0.6.8 2022-08-22 09:36:17 +08:00
chris
05a8b789d9 Fixed delete old page 2022-08-22 09:35:52 +08:00
chris
d845dc7945 Added upload image readme 2022-07-23 09:45:34 +08:00
chris
26a326bb0d Update readme 2022-07-01 13:36:33 +08:00
chris
539a2d1731 Added market download 2022-06-28 21:44:44 +08:00
chris
3eb9b0305a Added market download 2022-06-28 21:42:59 +08:00
chris
a6df90dc56 Update version to 0.6.7 2022-06-22 21:36:55 +08:00
chris
46e5477509 Refactor password and notice 2022-06-22 21:36:42 +08:00
chris
2364a6c756 Update readme 2022-06-18 10:23:06 +08:00
chris
21133862ff Update readme 2022-06-18 09:50:17 +08:00
chris
68a0796e9b Update readme 2022-06-18 09:47:10 +08:00
chris
058d45e834 Update version to 0.6.6 2022-06-18 09:35:28 +08:00
chris
fdc0b3e756 Update version to 0.6.5 2022-06-18 09:09:40 +08:00
chris
27d88a9b94 Update support mobile 2022-06-18 09:09:27 +08:00
chris
089dca83bb Update version to 0.6.3 2022-06-18 00:16:32 +08:00
chris
fc0954540a Update version to 0.6.2 2022-06-18 00:12:40 +08:00
chris
f3fe8e7835 Added right share url link 2022-06-18 00:12:36 +08:00
chris
f60bc598a8 Update version to 0.6.1 2022-06-17 23:55:16 +08:00
chris
0752c5bbd9 Added right share url link 2022-06-17 23:55:07 +08:00
chris
22f12063e4 Update version to 0.6.0 2022-06-17 23:54:09 +08:00
chris
e2defbd248 Update readme 2022-06-17 23:53:58 +08:00
chris
0b7d5b3435 Added right share url link 2022-06-17 23:47:38 +08:00
chris
2a4e03e319 Update use file modify instead of fs write 2022-06-17 23:11:38 +08:00
chris
c0d9f41555 Update use file modify instead of fs write 2022-06-17 23:10:37 +08:00
chris
bc65eb2781 Update use file modify instead of fs write 2022-06-17 23:09:14 +08:00
chris
22d2f79612 Fixed pull requests and Added api error alert 2022-06-17 22:18:39 +08:00
chris
c62482d83b uodate readme add todo board 2022-06-01 21:00:12 +08:00
chris
bf512fe2a3 Update readme with banner option 2022-05-26 15:41:11 +08:00
chris
ae7a9ac6cf Update license 2022-05-26 09:56:33 +08:00
chris
595935ffae Added new lang config and remove no use clg 2022-05-25 15:21:29 +08:00
chris
cf3129b90b Update version to 0.5.6 2022-05-25 15:14:56 +08:00
chris
6d5ece76b6 Added auto copy share link to clipboard 2022-05-25 15:14:31 +08:00
chris
021584dac6 Update version to 0.5.5 2022-05-25 14:54:15 +08:00
chris
e6fc5e522b Remove styless.css 2022-05-25 14:54:06 +08:00
chris
e6099a94d5 Update version to 0.5.3 2022-05-25 14:50:13 +08:00
chris
04b8ba65de Added styles.css 2022-05-25 14:49:36 +08:00
chris
43a7165983 Update version to 0.5.2 2022-05-25 14:47:45 +08:00
chris
3740680bdd Update version to 0.5.1 2022-05-25 14:45:06 +08:00
chris
5ac25c3de9 Added style.css 2022-05-25 14:43:57 +08:00
chris
8bc84b8d61 Update version to 0.5.0 2022-05-25 14:41:12 +08:00
chris
7aae1b23e9 Added mult language show error and notice 2022-05-25 14:40:51 +08:00
chris
511a3e2289 Added update 2022-05-25 14:15:09 +08:00
chris
22bf322127 Remove no-used styles.css 2022-05-24 21:58:45 +08:00
chris
4497d9cd7f Added feature 2022-05-24 20:15:46 +08:00
chris
d4aeaaa0f9 Update version to 0.3.8 2022-05-24 18:03:07 +08:00
chris
bf6ef7af0c Added custom banner url 2022-05-24 18:02:52 +08:00
chris
27c8e0f610 Added custom banner url 2022-05-24 18:00:44 +08:00
chris
c9a5c72c6c Added desk only 2022-05-24 16:31:42 +08:00
chris
7bc0b43d4f Update chinese doc 2022-05-24 15:55:10 +08:00
chris
d5f908ed34 Remove main.js 2022-05-24 15:53:30 +08:00
chris
f87e024e77 Added README 2022-05-24 15:52:14 +08:00
chris
e667bec7e0 Added README 2022-05-24 15:51:46 +08:00
chris
845cb7bf66 Update version to 0.3.7 2022-05-24 15:42:12 +08:00
chris
7d096e1cfb Added Ci 2022-05-24 15:41:36 +08:00
chris
f3346c455e Added workflows CI 2022-05-24 15:40:51 +08:00
chris
208fb40f72 Added english readme 2022-05-24 15:30:45 +08:00
chris
e3669e7a83 Added auto shell 2022-05-24 15:23:43 +08:00
chris
617bb370dc FIxed BRAT download 2022-05-24 15:13:42 +08:00
48 changed files with 5693 additions and 16183 deletions

View File

@@ -1,87 +1,108 @@
name: Release Obsidian plugin
name: Release
on:
push:
tags:
- "*"
push:
tags:
- "*"
env:
PLUGIN_NAME: obsidian-to-notion # 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:
build:
runs-on: ubuntu-latest
build:
name: release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: "14.x"
steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: "18"
- name: Build
id: build
run: |
npm install
npm run build
mkdir ${{ env.PLUGIN_NAME }}
cp main.js manifest.json styles.css ${{ env.PLUGIN_NAME }}
zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }}
ls
echo "::set-output name=tag_name::$(git tag --sort version:refname | tail -n 1)"
# - 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: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ github.ref }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
draft: false
prerelease: false
- name: Build
id: build
run: |
npm install
npm run build
mkdir ${{ env.PLUGIN_NAME }}
cp main.js manifest.json ${{ env.PLUGIN_NAME }}
zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }}
ls
echo "tag_name=$(git tag --sort version:refname | tail -n 1)" >> $GITHUB_OUTPUT
- name: Upload zip file
id: upload-zip
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./${{ env.PLUGIN_NAME }}.zip
asset_name: ${{ env.PLUGIN_NAME }}-${{ steps.build.outputs.tag_name }}.zip
asset_content_type: application/zip
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.REPO_ACCESS_TOKEN }}
VERSION: ${{ github.ref }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
body_path: ${{ env.CHANGELOG_FILENAME }}
draft: false
prerelease: false
- name: Upload main.js
id: upload-main
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./main.js
asset_name: main.js
asset_content_type: text/javascript
- name: Upload zip file
id: upload-zip
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.REPO_ACCESS_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./${{ env.PLUGIN_NAME }}.zip
asset_name: ${{ env.PLUGIN_NAME }}-${{ steps.build.outputs.tag_name }}.zip
asset_content_type: application/zip
- name: Upload manifest.json
id: upload-manifest
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./manifest.json
asset_name: manifest.json
asset_content_type: application/json
- name: Upload main.js
id: upload-main
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.REPO_ACCESS_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./main.js
asset_name: main.js
asset_content_type: text/javascript
- name: Upload styles.css
id: upload-css
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./styles.css
asset_name: styles.css
asset_content_type: text/css
- name: Upload manifest.json
id: upload-manifest
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.REPO_ACCESS_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./manifest.json
asset_name: manifest.json
asset_content_type: application/json
- name: Upload markdown template
id: upload-md
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.REPO_ACCESS_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./obsidian_template.md
asset_name: template.md
asset_content_type: text/markdown
# - name: Upload styles.css
# id: upload-css
# uses: actions/upload-release-asset@v1
# env:
# GITHUB_TOKEN: ${{ secrets.REPO_ACCESS_TOKEN }}
# with:
# upload_url: ${{ steps.create_release.outputs.upload_url }}
# asset_path: ./styles.css
# asset_name: styles.css
# asset_content_type: text/css

32
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: Test and Build
on:
push:
branches:
- "*"
env:
PLUGIN_NAME: share-to-notionnext # Change this to match the id of your plugin.
jobs:
build:
name: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: "18"
- name: Build
id: build
run: |
npm install
npm run build
mkdir ${{ env.PLUGIN_NAME }}
cp main.js manifest.json ${{ env.PLUGIN_NAME }}
zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }}
ls
echo "tag_name=$(git tag --sort version:refname | tail -n 1)" >> $GITHUB_OUTPUT

3
.gitignore vendored
View File

@@ -10,7 +10,7 @@ node_modules
# Don't include the compiled main.js file in the repo.
# They should be uploaded to GitHub releases instead.
# main.js
main.js
# Exclude sourcemaps
*.map
@@ -20,3 +20,4 @@ data.json
# Exclude macOS Finder (System Explorer) View States
.DS_Store
.history

4
CHANGELOG.md Normal file
View File

@@ -0,0 +1,4 @@
## Fix
- Fix code block not being highlighted in Notion after syncing.
- 修复代码块同步之后,在 Notion 中未高亮的问题。

307
README-zh.md Normal file
View File

@@ -0,0 +1,307 @@
# Obsidian share to NotionNext
感谢[原作者](https://github.com/EasyChris/obsidian-to-notion)开发出了这么好用的插件能够将obsidian同步到notion。 但是原仓库只能同步Name和Tags信息如果像我一样通过[NotionNext](https://github.com/tangly1024/NotionNext)来搭建自己的网页,就有一些局限性。每次导入之后都需要进行大量的修改。
所以我在[原作者](https://github.com/EasyChris/obsidian-to-notion)的基础之上,增加了匹配[NotionNext](https://github.com/tangly1024/NotionNext)模板的功能。这样可以直接在Obsidian编辑整理好之后一键发布。
## 更新说明
### 1.1.2
- 修复了一个拼写错误,导致无法同步`status`到NotionNext。现在你可以使用`stats`或者`status`来同步文章的状态到NotionNext。这个更新不会影响到同步到General数据库的功能。
- **`stats``status`都可以使用,但是你只能使用其中一个。**
```yaml
stats: Draft # Draft, Invisible, Published, default is Draft 默认是Draft
# or
status: Draft # Draft, Invisible, Published, default is Draft 默认是Draft
# both of them will work, but you can only use one of them.
```
### 1.1.1
- 修复了在日语系统下,无法显示设置按钮的错误。
- 增加日语翻译。
### 1.1.0
- 修复设置中自定义标题的显示问题。
- 增加了标签同步的控制按钮。以减少空标签集造成的上传失败。
如果你不想使用同步标签功能你可以在设置中关闭。不论你在md文件表头添加了tags与否都不会将标签同步到Notion数据库中。
如果你需要同步标签到Notion数据库中**你需要通过如下格式添加表填到md表头中。**
```yaml
tags: #空标签选项1
tags: [test,test1,test2] # 用中括号将所有的标签括起来选项2
tags:
- test
- test1
- test2 # 每个标签单独一行选项3
```
### 1.0.1
- 修复了自定义标题在设置菜单中显示错误的问题。
### 1.0.0 (大更新)
- 这个版本开始,你可以**任意修改第一列表头的名字 (标题列,默认:'title')**。 **注意你必须保证你的Notion数据库中有'tags这一列的存在并且在markdown文件中把`tags:`添加到你的YAML表头中。否则会返回`400 错误`。但是你可以`tags:`设置为空。**
![](https://minioapi.pjx.ac.cn/img1/2023/11/4a298b9be3990e9d2201bf2f50ca5a0a.png)
例如这样:
![](https://minioapi.pjx.ac.cn/img1/2023/11/4cd8d79cd9dd9dde299e39c666cb3473.gif)
- 增加了一个控制按钮来控制是否开启当前数据库同步命令的功能。如果你关闭了这个按钮,你在上传命令列表中就不会出现当前数据库的选项。
![](https://minioapi.pjx.ac.cn/img1/2023/11/becb60fc44783842da4b3cf4c322f363.gif)
### 0.2.6
- 增加一个按钮用来控制是否在名列列表中显示如下数据库。
![](https://minioapi.pjx.ac.cn/img1/2023/11/147c6a4eaa34da41a6f102558ed77106.png)
如果你关闭普通数据库的按钮你在上传命令列表中就不会出现普通数据库的选项。如果你关闭NotionNext数据库的按钮你在上传命令列表中就不会出现NotionNext数据库的选项。
![](https://minioapi.pjx.ac.cn/img1/2023/11/70b8e4fc2148688ccbd6cfc53ce339a2.png)
### 0.2.3
- 修复了一个bug现在可以正常更新了。
### 0.2.2
- 支持NotionNext 和 普通Notion 数据库的同步。
- 你可以有一个NotionNext和一个普通Notion数据库。
- 普通Notion数据库的同步方式和原作者的一样只同步`title``tags`。**如果使用普通数据库功能,你需要保证你的数据库中有`title``tags`这两个表头。必须使用小写。**
![](https://minioapi.pjx.ac.cn/img1/2023/11/712a12081d855aa60f82a7b46913ab7e.gif)
![](https://minioapi.pjx.ac.cn/img1/2023/11/9de76cecceef74c78884ddfc1c221659.gif)
### 0.2.1
- 重构代码。
### 0.2.0
- 从这个版本开始,重构了交互逻辑。现在当你点击左侧的按钮,会弹出可同步的数据库选项。你可以选择你想要同步的数据库,然后点击同步按钮。**但是现在仅支持同步单个数据库如果你的数据库不是NotionNext你可以不用更新。**
### 0.1.10
- 修正了设置中的中文显示。
### 0.1.8
- 重新整理了代码,增加了一个仓库转换按钮为之后的多数据库支持作准备。**但是现在还不支持多数据库如果你的数据库不是NotionNext你可以不用更新。**
### 0.1.7
- [x] 删除了设置中`convert tags`的选项。 你现在可以直接添加tags而不需要考虑是否开启tags选项。如果你不需要tags可以直接删除tags选项或者留白。
## 使用方式
### 注意事项
本插件现在暂时只能用于匹配[NotionNext](https://github.com/tangly1024/NotionNext)。如果你不是使用这个模板,你只会无限返回`error 400`.
所以没有NotionNext需求的请使用原作者的[Obsidian-to-notion](https://github.com/EasyChris/obsidian-to-notion)
### 准备安装
在安装插件之前,你必须配置好了以下内容:
1. 你的NotionNext数据库。
2. 根据原作者的readme.md配置好了Notion API并且已经和你的NotionNext仓库关联。
3. NotionNext Database ID
4. 你的NotionNext数据库有如下内容
- type
- title
- slug
- category
- tags
- date
- status
- summary
- password
- icon
在最新的0.0.6版本,我还增加了以下内容:
- titleicon 标题的emoji图标,暂时只能支持emoji
- coverurl 封面图片的url url可以是任何一种图片的url 例如jpgpngsvg等等。
**如果你是直接复制的NotionNext的模板这些内容应该已经有了。我在原作者的基础之上对本插件的内容进行了更改所以你只需要保证你的数据库有如上内容并且所有的字母都是小写**
**⚠️⚠️⚠️:表头全部小写!!!顺序无所谓!**
### 安装插件
#### 社区插件市场下载
打开`Obsidian -> 设置 -> 社区插件 -> 浏览 -> 搜索Share to NotionNext`, 然后点击安装。
#### 手动安装
1. 关闭Obsidian
2. 从Release下载插件文件解压到你的obsidian插件目录下。
3. 重新打开Obsidian进入设置启用插件。
4. 在设置中找到Obsidian to NotionNext填入你的NotionNext Database ID 和 API token。
### 使用插件
在仓库中我上传了一个模板你可以直接复制到你的模板文件夹中。然后使用Obsidian的模板功能一键生成新笔记。
如果你不想使用模板你也可以直接在Obsidian中创建一个新的文件然后复制下边的内容。然后保存。
```markdown
---
# default value has been set.
# for any unwanted value, you can delete it or set it to empty.
# for example, if you donot want to set password, you can delete password: "1234" or set it to password: ""
# 我已经在插件中设置了默认值,如果有不需要的选项,可以直接删除。
# 例如你不需要密码选项你可以将password: "1234"删除,或者将它设置为空。
# !!!!!!!!!!!!
# 现在阶段一定不要修改表头的名字, please do not change the name of the header in YAML front matter
# !!!!!!!!!!!!
titleicon: 📎 # emoji icon, default is 📜, 默认是📜
date: 2023-07-23 # default is today 默认是今天。 Format is YYYY-MM-DD 格式是YYYY-MM-DD
coverurl: https://img.jxpeng.dev/2023/08/843e27a210847f05a0f7cfb121fec100.jpg # default is empty 默认是空
type: Post # Post or Page, default is Post 默认是Post
slug: test # slug for url, default is empty 默认是空
stats: Draft # Draft, Invisible, Published, default is Draft 默认是Draft
category: test # default is 'Obsidian' 默认是'Obsidian'
summary: this is a summary for test post # default is empty 默认是空
icon: fa-solid fa-camera # you can ignore this, default is empty 默认是空,可直接删除
password: "1234" # if you donot want to set password, you can delete this line, default is empty 默认是空,可直接删除
# 现在必须开启tags选项否则会报错
tags:
- test # tags for post
- web # add more tags if you want
---
Contents Below
```
模板使用如下:
![](https://minioapi.pjx.ac.cn/img1/2023/09/354b950e5777b48832c2475e4a31f2cc.gif)
**插件预览如下**
![](https://minioapi.pjx.ac.cn/img1/2023/09/d6199619b68fab218fca8ae0cebece78.gif)
---
**以下为原README.md**
非常感谢原作者的开发,我只是在原作者的基础之上进行了修改,增加了一些功能。如果你觉得这个插件对你有帮助,可以给[原作者](https://github.com/EasyChris/obsidian-to-notion)一个star。
# Obsidian to Notion
[![](https://github.com/Easychris/obsidian-to-notion/actions/workflows/CI.yml/badge.svg)](https://github.com/Easychris/obsidian-to-notion/actions/workflows/CI.yml)
[![Release Obsidian plugin](https://github.com/Easychris/obsidian-to-notion/actions/workflows/release.yml/badge.svg)](https://github.com/Easychris/obsidian-to-notion/actions/workflows/release.yml)
[![GitHub license](https://camo.githubusercontent.com/400c4e52df43f6a0ab8a89b74b1a78d1a64da56a7848b9110c9d2991bb7c3105/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d47504c76332d626c75652e737667)](https://raw.githubusercontent.com/EasyChris/obsidian-to-notion/master/LICENSE)
[![Github all releases](https://img.shields.io/github/downloads/Easychris/obsidian-to-notion/total.svg)](https://GitHub.com/Easychris/obsidian-to-notion/releases/)
[![GitLab latest release](https://badgen.net/github/release/Easychris/obsidian-to-notion/)](https://github.com/Easychris/obsidian-to-notion/releases)
Obsidian share to Notion [English](README.md)
将obsidian文件一键分享到Notion,并在obsidian中添加Notion分享链接
如果能对你有所帮助欢迎给一个star支持。
![](./doc/1.gif)
# 使用方式
## 安装插件
### 市场下载
插件市场搜索 noiton 即可下载
![](https://afox-1256168983.cos.ap-shanghai.myqcloud.com/20220628214145.png)
### BRAT
插件中中心搜索 BRAT
添加 `EasyChris/obsidian-to-notion` 到 BRAT 插件安装列表中
返回插件中心启用即可
### 手动安装
```
cd YOUR_OBSIDIAN_FOLDER/.obsidian/plugins/
git clone https://github.com/EasyChris/obsidian-to-notion.git
```
## 申请 Notion API
官方参考文档:[https://developers.notion.com/docs](https://developers.notion.com/docs)
### 第 1 步创建integration
转到 [https://www.notion.com/my-integrations](https://www.notion.com/my-integrations)
创建完成之后,复制`secrets toekn`
![](https://files.readme.io/2ec137d-093ad49-create-integration.gif)
### 第2步与你的集成共享一个数据库
新建一个的page权限为公开
在page中新建一个数据库 -> 需要`full page database`
![](./doc/3.gif)
`integration`添加到你的新建的数据库中
![](./doc/6.gif)
#### 注意
数据库的第一个自定义名称必须是 "Name",否则同步会失败。
![](https://afox-1256168983.cos.ap-shanghai.myqcloud.com/20220618102029.png)
### 第三步复制database ID
```
https://www.notion.so/myworkspace/a8aec43384f447ed84390e8e42c2e089?v=...
|--------- Database ID --------|
```
## 打开插件配置
将得到的 `NOTION_API_KEY``DATABASE_ID`填入配置当中
![](./doc/2.png)
## 上传文件内容到notion
点击上传notion的按钮
![](./doc/4.png)
上传成功之后会自动生成一个分享链接
![](./doc/5.png)
## 页面 Banner 链接[可选]
默认可以不填写
横幅URL必须是图像URL例如https//i.imgur.com/xxx.jpg
## Notion ID [可选]
Notion ID是你想分享文件的页面ID。
如果你不写它notion将分享到默认的链接<https://www.notion.so/myworkspace/a8aec43384f447ed84390访问这个页面将重定向到你的网站页面>
如果你写了Notion ID它将分享到页面链接如<https://your_user_name.notion.site/myworkspace/a8aec43384f447ed84390不需要重定向网址>
## 同步图片
使用 [Obsidian Image Auto Upload Plugin](https://github.com/renmu123/obsidian-image-auto-upload-plugin) 插件,配置你自己的 cos 或者 oss将图片存储到你自己的云存储然后在 obsidian 中使用图片链接即可。该插件会自动帮你上传图片,并替换成链接。
# 请我喝杯咖啡
[顿顿饭](https://dun.mianbaoduo.com/@easy)
# 感谢
[开发流程 | Obsidian 插件开发文档](https://luhaifeng666.github.io/obsidian-plugin-docs-zh/zh/getting-started/development-workflow.html)
[GitHub - devbean/obsidian-wordpress: An obsidian plugin for publishing docs to WordPress.](https://github.com/devbean/obsidian-wordpress)
[GitHub - obsidianmd/obsidian-api](https://github.com/obsidianmd/obsidian-api)
[GitHub - zhaohongxuan/obsidian-weread-plugin: Obsidian Weread Plugin is an plugin to sync Weread(微信读书) hightlights and annotations into your Obsidian Vault.](https://github.com/zhaohongxuan/obsidian-weread-plugin)

453
README.md
View File

@@ -1,66 +1,453 @@
Obsidian share to Notion
# Obsidian to NotionNext
将obsidian文件一键分享到Notion,并在obsidian中添加Notion分享链接
[![Test](https://github.com/jxpeng98/obsidian-to-NotionNext/actions/workflows/test.yml/badge.svg)](https://github.com/jxpeng98/obsidian-to-NotionNext/actions/workflows/test.yml)
[![Release](https://github.com/jxpeng98/obsidian-to-NotionNext/actions/workflows/release.yml/badge.svg)](https://github.com/jxpeng98/obsidian-to-NotionNext/actions/workflows/release.yml)
[![Obsidian Downloads](https://img.shields.io/badge/dynamic/json?logo=obsidian&color=%23483699&label=downloads&query=%24%5B%22share-to-notionnext%22%5D.downloads&url=https%3A%2F%2Fraw.githubusercontent.com%2Fobsidianmd%2Fobsidian-releases%2Fmaster%2Fcommunity-plugin-stats.json)](https://GitHub.com/jxpeng98/obsidian-to-NotionNext/releases/)
[![GitHub release (with filter)](https://img.shields.io/github/package-json/v/jxpeng98/obsidian-to-NotionNext)](https://github.com/jxpeng98/obsidian-to-NotionNext/releases/)
[//]: # ([![Github all releases]&#40;https://img.shields.io/github/downloads/jxpeng98/obsidian-to-NotionNext/total.svg&#41;]&#40;https://GitHub.com/jxpeng98/obsidian-to-NotionNext/releases/&#41;)
[//]: # ([中文文档]&#40;README-zh.md&#41;)
**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. 支持编辑数据库详情
- [ ] Support group upload with one click 支持一键多数据库上传
## 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**.
![create-api](https://minioapi.pjx.ac.cn/img1/2024/07/28526e416571f8b1fe70d90bd9975b81.gif)
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'.
![create-database](https://minioapi.pjx.ac.cn/img1/2024/07/f1bd993ed667bcc980a45194eb647c34.gif)
Rename the database properties to `title` and `tags`.
**⚠️⚠️`title` is the title column! `tags` is lower case⚠**
![rename-title-tags](https://minioapi.pjx.ac.cn/img1/2024/07/20c7b81761195706dde6e7dae69edbc4.gif)
3). Add the Notion API integration.
Click the right-top '...' -> connections -> connect to -> find the integration you created and connect it.
![add-integration](https://minioapi.pjx.ac.cn/img1/2024/07/bf59397146d96727c96dac06515c6c22.gif)
4). Publish the database to the public.
Click the 'Share' -> 'Publish' -> 'Publish' -> **copy the database id from the URL**.
![share-publish](https://minioapi.pjx.ac.cn/img1/2024/07/5e5b9585cfa4a9086ef6704c3b48eacf.gif)
### 2. Setting up the plugin in Obsidian
1). Go to Obsidian settings -> community plugins -> search 'NotionNext' -> install it.
![plugin](https://minioapi.pjx.ac.cn/img1/2024/07/1bf82618e943ec5eb68d89d4556bc3fc.png)
2). Open the plugin settings and create a new database.
![add-database](https://minioapi.pjx.ac.cn/img1/2024/07/115f47b4180d04fb362b931f8092b5fb.png)
### 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⚠**
![general](https://minioapi.pjx.ac.cn/img1/2024/07/b2d0d0259afae7a542bd9f62256cd2c2.png)
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:
![general-setting](https://minioapi.pjx.ac.cn/img1/2024/07/3fc630171f6e8ae1011271b2b37d5d48.png)
Once you click `save`, the new database will be created in the plugin settings and display in the detabase list.
![database-list](https://minioapi.pjx.ac.cn/img1/2024/07/3dff23b1c13a31f7696dc794fb78eba5.png)
#### 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**)
![notionnext](https://minioapi.pjx.ac.cn/img1/2024/07/b164160ccfaf4fb23aa7ac1ce467e235.png)
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**)
![customise](https://minioapi.pjx.ac.cn/img1/2024/07/612b1377ec94d452abec7dc3fbe6cefb.png)
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.
![customise-add-property](https://minioapi.pjx.ac.cn/img1/2024/07/17cfbfa93d92404ff70d920302f15981.png)
**⚠️⚠️⚠️**: **`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. **⚠️⚠️⚠️**
![title-column](https://minioapi.pjx.ac.cn/img1/2024/07/0521b0aa6cedbe67bca4966bf79041ea.png)
According to the previously shown database, you can use `title` as the `Title column`, and `Tags` as the property 1.
![title-and-tags](https://minioapi.pjx.ac.cn/img1/2024/07/87529214008147d3aa1bbe5868849f31.png)
**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.
![general-sync](https://minioapi.pjx.ac.cn/img1/2024/07/6a690dc4b04d6b74134e9d3d76636c1b.gif)
#### 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
```
![notionnext-sync](https://minioapi.pjx.ac.cn/img1/2024/07/5539ec534e7a855ad87aa7949e5e836f.gif)
#### 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.
![rename-notion](https://minioapi.pjx.ac.cn/img1/2024/07/e4d1c988be22353537998db897c7a471.gif)
The front matter for the custom database is as follows:
```markdown
---
Name: test-custom
Tags:
- test
- web
---
This is a test file.
```
![sync-custom](https://minioapi.pjx.ac.cn/img1/2024/07/973f17955cd921cca6b5007f0990eea4.gif)
##### 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.
![edit](https://minioapi.pjx.ac.cn/img1/2024/07/f50639d8118b1f106d7bb29be5d3527b.png)
2). Click `Add` and add the `summary` and `date` property.
![add-property](https://minioapi.pjx.ac.cn/img1/2024/07/49716657adf0255a37fa214abf4716dc.gif)
3). After adding the properties, you can see the properties in the plugin settings.
![four properties](https://minioapi.pjx.ac.cn/img1/2024/07/63a0a0e09d537b1cf55cfdc49de4db1a.png)
4). Add the `summary` and `date` in Notion
![add-in-notion](https://minioapi.pjx.ac.cn/img1/2024/07/d193fe72ce3676bf643b0d1eb92dd4b7.gif)
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.
![sync-custom](https://minioapi.pjx.ac.cn/img1/2024/07/0e4cfbd61acdaf813fd81ee6d046e067.gif)
---
## 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.
Thus, based on the [original author's work](https://github.com/EasyChris/obsidian-to-notion), I've added a feature to match the [NotionNext](https://github.com/tangly1024/NotionNext) template. This way, you can edit directly in Obsidian and publish with a single click after organizing.
---
<details> <summary> Original README.md </summary>
Many Thanks for the original author's work. I've only made some changes to the original author's work. If you find this plugin useful, please give the [original author](https://github.com/EasyChris/obsidian-to-notion) a star.
# Obsidian to Notion
[![](https://github.com/Easychris/obsidian-to-notion/actions/workflows/CI.yml/badge.svg)](https://github.com/Easychris/obsidian-to-notion/actions/workflows/CI.yml)
[![Release Obsidian plugin](https://github.com/Easychris/obsidian-to-notion/actions/workflows/release.yml/badge.svg)](https://github.com/Easychris/obsidian-to-notion/actions/workflows/release.yml)
[![GitHub license](https://img.shields.io/github/license/EasyChris/obsidian-to-notion)](https://raw.githubusercontent.com/EasyChris/obsidian-to-notion/master/LICENSE)
[![Github all releases](https://img.shields.io/github/downloads/Easychris/obsidian-to-notion/total.svg)](https://GitHub.com/Easychris/obsidian-to-notion/releases/)
[![GitLab latest release](https://badgen.net/github/release/Easychris/obsidian-to-notion/)](https://github.com/Easychris/obsidian-to-notion/releases)
Share of obsidian to Notion [中文文档](README-zh.md)
Sharing files from Obsidian to Notion with a single click, and Obsidian will automatically add the Notion share link
You are welcome to offer it a star if it can benefit you.
![](./doc/1.gif)
# 使用方式
## 安装插件
# TODO
### [TODO Board](https://github.com/users/EasyChris/projects/3/views/1)
- [x] support for custom page banner
- [x] update the exsit page
- [x] support for mult language
- [x] support for auto copy the share link to clipboard
- [x] support for mobile
- [x] support tags thank for [@jannikbuscha](https://github.com/jannikbuscha)
- [ ] transfer the bi-link format like [[]] into the format that Notion supports.
# How to use
## Install the plugin
### Marketplace download
Open obsidian setting -> Add plugin -> Search -> notion
![](https://afox-1256168983.cos.ap-shanghai.myqcloud.com/20220628214145.png)
### BRAT
插件中中心搜索 BRAT
添加 `EasyChris/obsidian-to-notion` 到 BRAT 插件安装列表中
返回插件中心启用即可
### 手动安装
Enter `BRAT` into the plugin market center to find it.
Add `EasyChris/obsidian-to-notion` to the list of BRAT plugins that have been installed.
Return to the plugin center and turn it on.
### Manual installation
```
cd YOUR_OBSIDIAN_FOLDER/.obsidian/plugins/
git clone https://github.com/EasyChris/obsidian-to-notion.git
```
### 市场下载
因为暂时还未上架所以请通过BRAT安装
## 申请 Notion API
官方参考文档:[https://developers.notion.com/docs](https://developers.notion.com/docs)
### 第 1 步创建integration。
转到 [https://www.notion.com/my-integrations](https://www.notion.com/my-integrations)
创建完成之后,复制`secrets toekn`
## Apply Notion API
Official reference documentation: [https://developers.notion.com/docs](https://developers.notion.com/docs)
### Step 1: Create integration
Go to [https://www.notion.com/my-integrations](https://www.notion.com/my-integrations)
Once created, copy `secrets toekn`
![](https://files.readme.io/2ec137d-093ad49-create-integration.gif)
### 第2步与你的集成共享一个数据库
新建一个的page权限为公开
在page中新建一个数据库 -> 需要`full page database`
#### Note
database first custom name must be "Name", otherwise sync to notion will be failed
![](https://afox-1256168983.cos.ap-shanghai.myqcloud.com/20220618102029.png)
### Step 2: Share a database with your integration
Create a new page (with public permissions)
Create a new database in the page -> you need `full page database`
![](./doc/3.gif)
`integration`添加到你的新建的数据库中
Add `integration` to your new database
![](https://files.readme.io/0a267dd-share-database-with-integration.gif)
![](./doc/6.gif)
### 第三步:复制database ID
### Step 3: Copy the database ID
```
https://www.notion.so/myworkspace/a8aec43384f447ed84390e8e42c2e089?v=...
|--------- Database ID --------|
| --------- Database ID --------|
```
## Open the plugin configuration
## 打开插件配置
将得到的 `NOTION_API_KEY``DATABASE_ID`填入配置当中
Fill the configuration with the `NOTION_API_KEY` and `DATABASE_ID` you got
![](./doc/2.png)
## 上传文件内容到notion
点击上传notion的按钮
## Upload file content to notion
Click the uploadCommand notion button
![](./doc/4.png)
上传成功之后会自动生成一个分享链接
A share link will be automatically generated after successful uploadCommand
![](./doc/5.png)
## Banner URL [option]
# 请我喝杯咖啡
Banner url must be a image url like: <https://i.imgur.com/xxx.jpg>
If you don't want to use banner, leave it blank
[顿顿饭](https://dun.mianbaoduo.com/@easy)
## Convert Tags [option]
Transfer the Obsidian tags to the Notion table.
It requires the column with the name 'Tags'.
![](./doc/7.png)
Add tags to your notion page
![](./doc/10.png)
- open plugin convert tags
![](./doc/8.png)
- add tags in the head
```markdown
---
tags: [tag1,tag2]
---
this is test tags
```
```markdown
---
tags:
- tag4
---
this is test tags
```
![](./doc/9.png)
Thanks for [@jannikbuscha](https://github.com/jannikbuscha) contribution
## Notion ID [option]
Notion ID is the your notion site ID that you want to share the file to.
if you don't write it, notion will share to the default link like:
<https://www.notion.so/myworkspace/a8aec43384f447ed84390>
that visit this page need to redirect to your site url
if you write the Notion ID, it will share to the page link like:
<https://your_user_name.notion.site/myworkspace/a8aec43384f447ed84390>.
The visiter don't need to redirect url.
## Sync image to Notion
To sync images to your oss or cos bucket, use the [Obsidian Image Auto Upload Plugin](https://github.com/renmu123/obsidian-image-auto-upload-plugin).
# Development
```
git clone https://github.com/EasyChris/obsidian-to-notion.git
yarn install
yarn dev
```
## Release
```
node update-version.js
./release.sh
```
# Thanks
[Development Process | Obsidian Plugin Development Documentation](https://luhaifeng666.github.io/obsidian-plugin-docs-zh/zh/getting-started/development-workflow.html)
# 感谢
[开发流程 | Obsidian 插件开发文档](https://luhaifeng666.github.io/obsidian-plugin-docs-zh/zh/getting-started/development-workflow.html)
[GitHub - devbean/obsidian-wordpress: An obsidian plugin for publishing docs to WordPress.](https://github.com/devbean/obsidian-wordpress)
[GitHub - obsidianmd/obsidian-api](https://github.com/obsidianmd/obsidian-api)
[GitHub - zhaohongxuan/obsidian-weread-plugin: Obsidian Weread Plugin is an plugin to sync Weread(微信读书) hightlights and annotations into your Obsidian Vault.](https://github.com/zhaohongxuan/obsidian-weread-plugin)
[GitHub - Easychris/obsidian-to-notion: Obsidian Weread Plugin is an plugin to sync Weread(微信读书) hightlights and annotations into your Obsidian Vault.](https://github.dev/zhaohongxuan/obsidian-weread-plugin)
[GitHub - Quorafind/Obsidian-Memos: A quick capture plugin for Obsidian, all data from your notes.](https://github.com/Quorafind/Obsidian-Memos)
[https://github.com/jannikbuscha/obsidian-to-notion](https://github.com/jannikbuscha)
# License
GNU GPLv3
</details>

View File

@@ -1,76 +0,0 @@
import { requestUrl } from "obsidian";
import { Client } from "@notionhq/client";
import { markdownToBlocks, } from "@tryfabric/martian";
import * as fs from "fs";
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 createPage(title:string, childArr: any) {
const bodyString = {
parent: {
database_id: this.app.settings.databaseID
},
properties: {
Name: {
title: [
{
text: {
content: title,
},
},
],
},
},
children: childArr,
}
const response = 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': '2021-08-16',
},
body: JSON.stringify(bodyString),
})
return response;
}
async syncMarkdownToNotion(title:string, markdown: string, fullPath:string): Promise<any> {
const yamlObj:any = yamlFrontMatter.loadFront(markdown);
const __content = yamlObj.__content
const file2md = markdownToBlocks(__content);
const res = await this.createPage(title, file2md);
if (res.status === 200) {
this.updateYamlInfo(markdown, fullPath, res)
}
return res
}
async updateYamlInfo(yamlContent: string, fullPath: string, res: any) {
const yamlObj:any = yamlFrontMatter.loadFront(yamlContent);
const {url} = res.json
yamlObj.link = url;
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;
//write content fo file
fs.writeFileSync(fullPath, content);
return res
}
}

BIN
doc/10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
doc/6.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

BIN
doc/7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
doc/8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

BIN
doc/9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -1,52 +1,61 @@
import esbuild from "esbuild";
import process from "process";
import builtins from 'builtin-modules'
import builtins from "builtin-modules";
const banner =
`/*
const banner = `/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
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: {
js: banner,
},
entryPoints: ['main.ts'],
entryPoints: ["src/main.ts"],
bundle: true,
external: [
'obsidian',
'electron',
'@codemirror/autocomplete',
'@codemirror/closebrackets',
'@codemirror/collab',
'@codemirror/commands',
'@codemirror/comment',
'@codemirror/fold',
'@codemirror/gutter',
'@codemirror/highlight',
'@codemirror/history',
'@codemirror/language',
'@codemirror/lint',
'@codemirror/matchbrackets',
'@codemirror/panel',
'@codemirror/rangeset',
'@codemirror/rectangular-selection',
'@codemirror/search',
'@codemirror/state',
'@codemirror/stream-parser',
'@codemirror/text',
'@codemirror/tooltip',
'@codemirror/view',
...builtins],
format: 'cjs',
watch: !prod,
target: 'es2016',
"obsidian",
"electron",
"@codemirror/autocomplete",
"@codemirror/closebrackets",
"@codemirror/collab",
"@codemirror/commands",
"@codemirror/comment",
"@codemirror/fold",
"@codemirror/gutter",
"@codemirror/highlight",
"@codemirror/history",
"@codemirror/language",
"@codemirror/lint",
"@codemirror/matchbrackets",
"@codemirror/panel",
"@codemirror/rangeset",
"@codemirror/rectangular-selection",
"@codemirror/search",
"@codemirror/state",
"@codemirror/stream-parser",
"@codemirror/text",
"@codemirror/tooltip",
"@codemirror/view",
...builtins,
],
platform: "node",
format: "cjs",
target: "es2016",
logLevel: "info",
sourcemap: prod ? false : 'inline',
sourcemap: prod ? false : "inline",
treeShaking: true,
outfile: 'main.js',
}).catch(() => process.exit(1));
outfile: "main.js",
});
if (!prod) {
await ctx.watch();
} else {
await ctx.rebuild();
ctx.dispose().then(
(r) => console.log("Build completed"),
(e) => console.error(e),
);
}

17
icon.ts
View File

@@ -1,17 +0,0 @@
import { addIcon } from 'obsidian';
const icons: Record<string, string> = {
'notion-logo': `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100px" height="100px" viewBox="0 0 100 100" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:evenodd;fill:currentColor;fill-opacity:1;" d="M 18.738281 17.796875 C 21.832031 20.308594 22.996094 20.117188 28.804688 19.730469 L 83.582031 16.441406 C 84.746094 16.441406 83.78125 15.28125 83.390625 15.089844 L 74.292969 8.511719 C 72.550781 7.160156 70.230469 5.609375 65.777344 5.996094 L 12.734375 9.867188 C 10.800781 10.058594 10.414062 11.023438 11.183594 11.800781 Z M 22.027344 30.5625 L 22.027344 88.199219 C 22.027344 91.296875 23.574219 92.457031 27.058594 92.265625 L 87.261719 88.78125 C 90.746094 88.589844 91.136719 86.457031 91.136719 83.941406 L 91.136719 26.691406 C 91.136719 24.179688 90.167969 22.824219 88.035156 23.019531 L 25.125 26.691406 C 22.800781 26.886719 22.027344 28.046875 22.027344 30.5625 Z M 81.460938 33.652344 C 81.84375 35.398438 81.460938 37.136719 79.714844 37.332031 L 76.8125 37.910156 L 76.8125 80.460938 C 74.292969 81.816406 71.972656 82.589844 70.035156 82.589844 C 66.9375 82.589844 66.164062 81.621094 63.839844 78.722656 L 44.867188 48.9375 L 44.867188 77.753906 L 50.871094 79.109375 C 50.871094 79.109375 50.871094 82.589844 46.027344 82.589844 L 32.675781 83.363281 C 32.285156 82.589844 32.675781 80.65625 34.027344 80.269531 L 37.511719 79.304688 L 37.511719 41.199219 L 32.675781 40.8125 C 32.285156 39.070312 33.253906 36.558594 35.964844 36.363281 L 50.292969 35.398438 L 70.035156 65.570312 L 70.035156 38.878906 L 65.003906 38.300781 C 64.617188 36.171875 66.164062 34.625 68.097656 34.433594 Z M 8.28125 4.644531 L 63.453125 0.582031 C 70.230469 0 71.972656 0.390625 76.230469 3.484375 L 93.84375 15.859375 C 96.75 17.988281 97.71875 18.570312 97.71875 20.890625 L 97.71875 88.78125 C 97.71875 93.035156 96.167969 95.550781 90.75 95.9375 L 26.675781 99.804688 C 22.609375 100 20.671875 99.421875 18.542969 96.710938 L 5.574219 79.882812 C 3.25 76.785156 2.28125 74.46875 2.28125 71.757812 L 2.28125 11.410156 C 2.28125 7.933594 3.832031 5.03125 8.28125 4.644531 Z M 8.28125 4.644531 "/>
</g>
</svg>
`
};
export const addIcons = (): void => {
Object.keys(icons).forEach((key) => {
addIcon(key, icons[key]);
});
};

14931
main.js

File diff suppressed because one or more lines are too long

190
main.ts
View File

@@ -1,190 +0,0 @@
import { timingSafeEqual } from "crypto";
import {
App,
EditableFileView,
requestUrl,
Editor,
MarkdownView,
Modal,
Notice,
Plugin,
PluginSettingTab,
Setting,
normalizePath
} from "obsidian";
import { join } from "path";
import * as fs from "fs";
import {addIcons} from 'icon';
import { Upload2Notion } from "Upload2Notion";
// Remember to rename these classes and interfaces!
interface MyPluginSettings {
notionAPI: string;
databaseID: string;
proxy: string;
}
const DEFAULT_SETTINGS: MyPluginSettings = {
notionAPI: "",
databaseID: "",
proxy: "",
};
export default class MyPlugin extends Plugin {
settings: MyPluginSettings;
async onload() {
await this.loadSettings();
addIcons();
// This creates an icon in the left ribbon.
const ribbonIconEl = this.addRibbonIcon(
"notion-logo",
"Share to notion",
async (evt: MouseEvent) => {
// Called when the user clicks the icon.
this.upload();
}
);
// Perform additional things with the ribbon
ribbonIconEl.addClass("my-plugin-ribbon-class");
// 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-notion",
name: "share to notion",
editorCallback: async (editor: Editor, view: MarkdownView) => {
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 } = this.settings;
if (notionAPI === "" || databaseID === "") {
new Notice(
"Please set up the notion API and database ID in the settings tab."
);
return;
}
const { nowFile, fileData,fullPath } =
await this.getNowFileMarkdwonContent(this.app);
const { basename } = nowFile;
if (fileData) {
const upload = new Upload2Notion(this);
const res = await upload.syncMarkdownToNotion(basename,fileData, fullPath)
console.log(res)
if(res.status === 200){
new Notice(`${basename} 同步成功`)
}else {
new Notice(`${basename} 同步失败`)
}
}
}
async getNowFileMarkdwonContent(app: App) {
const nowFile = app.workspace.getActiveFile();
if (nowFile) {
const filePath: string = nowFile.path;
// @ts-ignore
const basePath: string = nowFile.vault.adapter.basePath;
const fullPath = normalizePath(join(basePath, filePath));
console.log("fullpath", fullPath);
const fileData = fs.readFileSync(fullPath, "utf8");
return {
fileData,
nowFile,
fullPath,
};
} else {
new Notice("请打开需要同步的文件");
return;
}
}
async loadSettings() {
this.settings = Object.assign(
{},
DEFAULT_SETTINGS,
await this.loadData()
);
}
async saveSettings() {
await this.saveData(this.settings);
}
}
class SampleModal extends Modal {
constructor(app: App) {
super(app);
}
onOpen() {
const { contentEl } = this;
contentEl.setText("Woah!");
}
onClose() {
const { contentEl } = this;
contentEl.empty();
}
}
class SampleSettingTab extends PluginSettingTab {
plugin: MyPlugin;
constructor(app: App, plugin: MyPlugin) {
super(app, plugin);
this.plugin = plugin;
}
display(): void {
const { containerEl } = this;
containerEl.empty();
containerEl.createEl("h2", {
text: "Settings for obsidian to notion plugin.",
});
new Setting(containerEl)
.setName("Notion API Token")
.setDesc("It's a secret")
.addText((text) =>
text
.setPlaceholder("Enter your Notion API Token")
.setValue(this.plugin.settings.notionAPI)
.onChange(async (value) => {
console.log("Secret: " + value);
this.plugin.settings.notionAPI = value;
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName("Database ID")
.setDesc("It's a secret")
.addText((text) =>
text
.setPlaceholder("Enter your Database ID")
.setValue(this.plugin.settings.databaseID)
.onChange(async (value) => {
console.log("Secret: " + value);
this.plugin.settings.databaseID = value;
await this.plugin.saveSettings();
})
);
}
}

View File

@@ -1,10 +1,10 @@
{
"id": "obsidian-to-notion",
"name": "Obsidian shared to Notion",
"version": "0.3.0",
"minAppVersion": "0.0.1",
"description": "This is a plugin for Obsidian. This plugin share obsidian md file to notion with notion api",
"author": "Easychris",
"authorUrl": "https://github.com/EasyChris/obsidian-to-notion",
"isDesktopOnly": false
}
"id": "share-to-notionnext",
"name": "Share to NotionNext",
"version": "2.4.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.",
"author": "EasyChris, jxpeng98",
"authorUrl": "https://github.com/jxpeng98",
"isDesktopOnly": false
}

26
obsidian_template.md Normal file
View File

@@ -0,0 +1,26 @@
---
# 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: 📎 # default is 📜, 默认是📜
date: 2023-07-23 # default is today 默认是今天。 Format is YYYY-MM-DD 格式是YYYY-MM-DD
coverurl: https://img.jxpeng.dev/2023/08/843e27a210847f05a0f7cfb121fec100.jpg # default is empty 默认是空
type: Post # Post or Page, default is Post 默认是Post
slug: test # slug for url, default is empty 默认是空
status: Draft # Draft, Invisible, Published, default is Draft 默认是Draft
category: test # default is 'Obsidian' 默认是'Obsidian'
summary: this is a summary for test post # default is empty 默认是空
icon: fa-solid fa-camera # you can ignore this, default is empty 默认是空,可直接删除
password: "1234" # if you donot want to set password, you can delete this line, default is empty 默认是空,可直接删除
# 现在必须开启tags选项否则会报错
tags:
- test # tags for post
- web # add more tags if you want
---
Contents Below

View File

@@ -1,32 +1,35 @@
{
"name": "obsidian-to-notion",
"version": "0.3.0",
"type": "module",
"description": "This is a plugin for Obsidian. This plugin share obsidian md file to notion with notion api",
"main": "main.js",
"scripts": {
"dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"version": "node version-bump.mjs && git add manifest.json versions.json"
},
"keywords": [],
"author": "",
"license": "GNU GPLv3",
"devDependencies": {
"@types/node": "^17.0.35",
"@types/yaml-front-matter": "^4.1.0",
"@typescript-eslint/eslint-plugin": "^5.2.0",
"@typescript-eslint/parser": "^5.2.0",
"builtin-modules": "^3.2.0",
"esbuild": "0.13.12",
"obsidian": "latest",
"tslib": "2.3.1",
"typescript": "4.4.4"
},
"dependencies": {
"@tryfabric/martian": "^1.2.0",
"https-proxy-agent": "^5.0.1",
"yaml": "^2.1.0",
"yaml-front-matter": "^4.1.1"
}
}
"name": "share-to-notionnext",
"version": "2.4.1",
"type": "module",
"description": "Share files to any Notion database using the Notion API, originally created by EasyChris/obsidian-to-notion.",
"main": "main.js",
"scripts": {
"dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"version": "node version-bump.mjs && git add manifest.json versions.json"
},
"keywords": [],
"author": "",
"license": "GNU GPLv3",
"devDependencies": {
"@types/node": "^20.5.7",
"@types/yaml-front-matter": "^4.1.3",
"@typescript-eslint/eslint-plugin": "^6.16.0",
"@typescript-eslint/parser": "^6.16.0",
"builtin-modules": "^3.3.0",
"esbuild": "0.19.5",
"obsidian": "^1.6.6",
"tslib": "2.6.2",
"typescript": "5.2.2"
},
"dependencies": {
"@jxpeng98/martian": "^1.2.7",
"https-proxy-agent": "^7.0.2",
"node-fetch": "^3.3.2",
"process": "^0.11.10",
"remark-math": "^6.0.0",
"yaml": "^2.3.4",
"yaml-front-matter": "^4.1.1"
}
}

6
release.sh Executable file
View File

@@ -0,0 +1,6 @@
#!/bin/bash
node update-version.js $1
git add .
git commit -m "Update version to $1"
git tag -a $1 -m $1
git push origin $1

View File

@@ -0,0 +1,51 @@
import { FuzzySuggestModal, FuzzyMatch } from 'obsidian';
import MyPlugin from "../main";
import { i18nConfig } from "../lang/I18n";
/**
* Simple interface for what should be displayed and stored for suggester
*/
export interface DatabaseList {
name: string, // specific database name
match: any //
}
export class FuzzySuggester extends FuzzySuggestModal<DatabaseList> {
private plugin: MyPlugin;
private data: DatabaseList[];
private callback: any;
constructor(plugin: MyPlugin) {
super(plugin.app);
this.plugin = plugin;
this.setPlaceholder(i18nConfig.PlaceHolder);
}
setSuggesterData(suggesterData: Array<DatabaseList>): void { this.data = suggesterData }
async display(callBack: (item: DatabaseList, evt: MouseEvent | KeyboardEvent) => void): Promise<any> {
this.callback = callBack;
this.open();
}
// Store the data
getItems(): DatabaseList[] {
return this.data
}
getItemText(item: DatabaseList): string {
return item.name
}
onChooseItem(item: DatabaseList, evt: MouseEvent | KeyboardEvent): void { }
onChooseSuggestion(item: FuzzyMatch<DatabaseList>, evt: MouseEvent | KeyboardEvent): void {
this.callback(item.item, evt)
}
renderSuggestion(item: FuzzyMatch<DatabaseList>, el: HTMLElement): void {
el.createEl('div', { text: item.item.name })
}
}

View File

@@ -0,0 +1,107 @@
import { i18nConfig } from "src/lang/I18n";
import { Editor, MarkdownView, setTooltip } from "obsidian";
import { FuzzySuggester, DatabaseList } from "./FuzzySuggester";
import { uploadCommandCustom, uploadCommandGeneral, uploadCommandNext } from "../upload/uploadCommand";
import ObsidianSyncNotionPlugin from "src/main";
import { DatabaseDetails } from "../ui/settingTabs";
interface Command {
id: string;
name: string;
editorCallback: (editor: Editor, view: MarkdownView) => Promise<void>;
}
// create the commands list
export default class RibbonCommands {
plugin: ObsidianSyncNotionPlugin;
Ncommand: Command[] = [];
constructor(plugin: ObsidianSyncNotionPlugin) {
this.plugin = plugin;
// iterate through the database detail
for (let key in this.plugin.settings.databaseDetails) {
let dbDetails = this.plugin.settings.databaseDetails[key];
this.addCommandForDatabase(dbDetails);
}
// Register all the commands
this.Ncommand.forEach(command => {
this.plugin.addCommand(
{
id: command.id,
name: command.name,
editorCallback: command.editorCallback,
}
);
});
}
async ribbonDisplay() {
const NcommandList: DatabaseList[] = [];
this.Ncommand.map(command => NcommandList.push(
{
name: command.name,
match: command.editorCallback
}
)
);
const fusg = new FuzzySuggester(this.plugin);
fusg.setSuggesterData(NcommandList);
await fusg.display(async (results) => { await results.match() })
};
// if the setting has been changed, try to rebuild the command list
async updateCommand() {
this.Ncommand = [];
for (let key in this.plugin.settings.databaseDetails) {
let dbDetails = this.plugin.settings.databaseDetails[key];
this.addCommandForDatabase(dbDetails);
}
this.Ncommand.forEach(command => {
this.plugin.addCommand(
{
id: command.id,
name: command.name,
editorCallback: command.editorCallback,
}
);
});
}
private addCommandForDatabase(dbDetails: DatabaseDetails) {
// Example logic - adjust based on your specific requirements
let commandId = `share-to-${dbDetails.abName}`;
let commandName = `Share to ${dbDetails.fullName} (${dbDetails.abName})`; // or use a translated name
let editorCallback: (editor: Editor, view: MarkdownView) => Promise<void>;
if (dbDetails.format === 'next') {
editorCallback = async (editor, view) => {
await uploadCommandNext(this.plugin, this.plugin.settings, dbDetails, this.plugin.app);
};
} else if (dbDetails.format === 'general') {
editorCallback = async (editor, view) => {
await uploadCommandGeneral(this.plugin, this.plugin.settings, dbDetails, this.plugin.app);
};
}
else if (dbDetails.format === 'custom') {
editorCallback = async (editor, view) => {
await uploadCommandCustom(this.plugin, this.plugin.settings, dbDetails, this.plugin.app);
};
}
this.Ncommand.push({ id: commandId, name: commandName, editorCallback });
}
}

17
src/lang/I18n.ts Normal file
View File

@@ -0,0 +1,17 @@
import {en} from "./locale/en";
import {zh} from "./locale/zh";
import {ja} from "./locale/ja";
export const I18n: {[key: string]:any} = {
en,
zh,
ja,
};
export const I18nConfig = (lang: string): any => {
return I18n[lang];
};
const storedLanguage = window.localStorage.getItem("language");
export const i18nConfig = I18n[storedLanguage || window.navigator.language.split('-')[0] || "en"];

82
src/lang/locale/en.ts Normal file
View File

@@ -0,0 +1,82 @@
export const en = {
databaseFormat: "Database Format",
databaseFormatDesc: "Select the database format you want to sync to NotionNext or General",
databaseNext: "NotionNext",
databaseGeneral: "General",
databaseCustom: "Custom",
databaseFullName: "Database Full Name",
databaseFullNameDesc: "Please give a full name for your database",
databaseFullNameText: "Enter your database full name",
databaseAbbreviateName: "Database Abbreviate Name",
databaseAbbreviateNameDesc: "Please give a nick name for your database",
databaseAbbreviateNameText: "Enter your database nick name",
ribbonIcon: "Share to NotionNext",
GeneralSetting: "General information Settings",
CommandID: "share-to-notionnext",
CommandName: "Share to NotionNext Database",
CommandIDGeneral: "share-to-notion",
CommandNameGeneral: "Share to Notion General Database",
NotionNextButton: "NotionNext Sync",
NotionNextButtonDesc: "Open this option, Sync to NotionNext command will be displayed in the command palette (default: ON)",
NotionNextSettingHeader: "NotionNext Database Settings",
NotionAPI: "Notion API Token",
NotionAPIDesc: "Generate from https://www.notion.so/my-integrations",
NotionAPIText: "Enter your Notion API Token",
DatabaseID: "Database ID",
DatabaseIDDesc: "Collect from the top-right Share --> Publish",
DatabaseIDText: "Enter your Database ID",
BannerUrl: "Banner url (optional)",
BannerUrlDesc:
"Default is empty, if you want to show a banner, please enter the url (like: https://abc.com/b.png)",
BannerUrlText: "Enter your banner url",
NotionUser: "Notion ID (username, optional)",
NotionUserDesc:
"Collect from share link likes:https://username.notion.site. Your notion id is [username]",
NotionUserText: "Enter your notion ID",
NotionLinkDisplay: "Notion Link Display",
NotionLinkDisplayDesc: "Default is ON, if you want to hide the link in the front matter, please turn it off",
NotionGeneralSettingHeader: "General Notion Database Settings",
NotionGeneralButton: "Notion General Sync",
NotionGeneralButtonDesc: "Open this option, Sync to Notion General Database command will be displayed in the command palette (default: ON)",
NotionTagButton: "Notion Tags Sync",
NotionTagButtonDesc: "Sync Tags to Notion General Database (default: ON)",
NotionCustomTitle: "Customise title property",
NotionCustomTitleDesc: "Modify the column name of the Notion database (default: OFF)",
NotionCustomTitleName: "Preferred title name",
NotionCustomTitleNameDesc: "Enter the preferred title name for the first column of the Notion database (default: title)",
NotionCustomTitleText: "Enter the name",
NotionCustomValues: "Customise values property",
NotionCustomValuesDesc: "Modify the column name of the Notion databaseone per line",
NotionCustomValuesText: "Enter all properties that you want to sync",
NotYetFinish: "Not finished. This function will be available in the next version",
PlaceHolder: "Enter database Name",
"notion-logo": "Share to NotionNext",
"sync-preffix": "Sync to ",
"sync-success": "success",
"sync-fail": "failed",
"open-notion": "Please open the file that needs to be synchronized",
"config-secrets-notion-api":
"Please set up the notion API in the settings tab.",
"config-secrets-database-id":
"Please set up the database id in the settings tab.",
"set-tags-fail":
"Set tags fail,please check the frontmatter of the file or close the tag switch in the settings tab.",
NNonMissing:
"The 'NNon' property is missing in the settings. Please set it up.",
"set-api-id":
"Please set up the notion API and database ID in the settings tab.",
NotionCustomSettingHeader: "Notion Custom Database Settings",
NotionCustomButton: "Notion Customised command switch",
NotionCustomButtonDesc: "Open this option, Sync to Notion Customised Database command will be displayed in the command palette",
CustomPropertyName: "Property Name",
CustomPropertyFirstColumn: "Title Column",
CustomPropertyFirstColumnDesc: "The title of the page, must be the first property",
CustomProperty: "Property",
AddCustomProperty: "Add Custom Property",
AddNewProperty: "Add New Property",
AddNewPropertyDesc: "Add new property match with your notion database",
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",
}

74
src/lang/locale/ja.ts Normal file
View File

@@ -0,0 +1,74 @@
export const ja = {
databaseFormat: "データベース形式",
databaseFormatDesc: "同期したいデータベース形式を選択してください",
databaseNext: "NotionNext",
databaseGeneral: "一般的なNotion",
databaseCustom: "カスタム",
databaseFullName: "データベースの全称",
databaseFullNameDesc: "データベースの全称を入力してください",
databaseFullNameText: "データベースの全称を入力",
databaseAbbreviateName: "データベースの略称",
databaseAbbreviateNameDesc: "データベースの略称を入力してください",
databaseAbbreviateNameText: "データベースの略称を入力",
ribbonIcon: "NotionNextで共有",
GeneralSetting: "一般設定",
CommandID: "share-to-notionnext",
CommandName: "NotionNextデータベースに共有",
CommandIDGeneral: "share-to-notion",
CommandNameGeneral: "一般的なNotionデータベースに共有",
NotionNextButton: "NotionNext同期",
NotionNextButtonDesc: "このオプションを開くと、NotionNext同期コマンドがコマンドパレットに表示されますデフォルトON",
NotionNextSettingHeader: "NotionNextデータベース設定",
NotionAPI: "Notion API トークン",
NotionAPIDesc: "https://www.notion.so/my-integrations から生成してください",
NotionAPIText: "Notion API トークンを入力",
DatabaseID: "データベースID",
DatabaseIDDesc: "右上の共有 --> 公開から取得してください",
DatabaseIDText: "データベースIDを入力",
BannerUrl: "バナーのURL任意",
BannerUrlDesc: "デフォルトは空白です。バナーを表示したい場合は、URLを入力してくださいhttps://abc.com/b.png",
BannerUrlText: "バナーのURLを入力",
NotionUser: "Notion IDユーザー名、任意",
NotionUserDesc: "共有リンクから取得https://username.notion.site。Notion IDは[username]です",
NotionUserText: "Notion IDを入力",
NotionLinkDisplay: "Notionリンク表示",
NotionLinkDisplayDesc: "デフォルトはONです。front matterにリンクを非表示にしたい場合は、オフにしてください",
NotionGeneralSettingHeader: "一般的なNotionデータベース設定",
NotionGeneralButton: "一般的なNotion同期",
NotionGeneralButtonDesc: "このオプションを開くと、一般的なNotionデータベース同期コマンドがコマンドパレットに表示されますデフォルトON",
NotionTagButton: "Notionタグ同期",
NotionTagButtonDesc: "タグを一般的なNotionデータベースに同期デフォルトON",
NotionCustomTitle: "タイトルのカスタマイズ",
NotionCustomTitleDesc: "Notionデータベースの列名を変更デフォルトOFF",
NotionCustomTitleName: "希望のタイトル名",
NotionCustomTitleNameDesc: "Notionデータベースの最初の列のための希望のタイトル名を入力デフォルトtitle",
NotionCustomTitleText: "名前を入力",
NotionCustomValues: "値のカスタマイズ",
NotionCustomValuesDesc: "Notionデータベースの列名を変更、1行に1つ",
NotionCustomValuesText: "同期したいすべてのプロパティを入力",
NotYetFinish: "未完了。この機能は次のバージョンで利用可能になります",
PlaceHolder: "データベース名を入力",
"notion-logo": "NotionNextで共有",
"sync-success": "NotionNextへの同期に成功\n",
"sync-fail": "NotionNextへの同期に失敗\n",
"open-notion": "同期が必要なファイルを開いてください",
"config-secrets-notion-api": "設定タブでNotion APIを設定してください",
"config-secrets-database-id": "設定タブでデータベースIDを設定してください",
"set-tags-fail": "タグの設定に失敗。ファイルのfrontmatterを確認するか、設定タブでタグのスイッチをオフにしてください",
NNonMissing: "設定に 'NNon' プロパティがありません。設定してください",
"set-api-id": "設定タブでNotion APIおよびデータベースIDを設定してください",
NotionCustomSettingHeader: "Notionカスタムデータベース設定",
NotionCustomButton: "Notionカスタマイズコマンドの切り替え",
NotionCustomButtonDesc: "このオプションを開くと、Notionカスタムデータベース同期コマンドがコマンドパレットに表示されます",
CustomPropertyName: "カスタムプロパティ名",
CustomPropertyFirstColumn: "最初の列のカスタムプロパティ名",
CustomPropertyFirstColumnDesc: "最初の列のカスタムプロパティ名を入力してください",
CustomProperty: "カスタムプロパティ",
AddCustomProperty: "カスタムプロパティを追加",
AddNewProperty: "新しいプロパティを追加",
AddNewPropertyDesc: "新しいプロパティを追加してください",
CopyErrorMessage: "自動コピーに失敗しました",
BlockUploaded: "ブロックがアップロードされました",
ExtraBlockUploaded: "追加ブロックがアップロードされました",
CheckConsole: "詳細情報を確認するには、コンソールを開いてください \n opt+cmd+i/ctrl+shift+i",
};

77
src/lang/locale/zh.ts Normal file
View File

@@ -0,0 +1,77 @@
export const zh = {
databaseFormat: "数据库格式",
databaseFormatDesc: "选择你想要同步的数据库格式Next 或者 普通",
databaseNext: "NotionNext",
databaseGeneral: "普通",
databaseCustom: "自定义",
databaseFullName: "数据库全称",
databaseFullNameDesc: "给你的数据库起一个全称",
databaseFullNameText: "输入你的数据库全称",
databaseAbbreviateName: "数据库简称",
databaseAbbreviateNameDesc: "给你的数据库起一个简称",
databaseAbbreviateNameText: "输入你的数据库简称",
ribbonIcon: "分享到 NotionNext",
GeneralSetting: "通用设置",
CommandID: "share-to-notionnext",
CommandName: "分享到 NotionNext",
CommandIDGeneral: "share-to-notion",
CommandNameGeneral: "分享到 Notion 普通数据库",
NotionNextButton: "NotionNext 同步",
NotionNextButtonDesc: "打开此选项NotionNext 同步将显示在命令面板中(默认:开)",
NotionNextSettingHeader: "NotionNext 数据库参数设置",
NotionAPI: "Notion API 令牌",
NotionAPIDesc: "从 https://www.notion.so/my-integrations 生成",
NotionAPIText: "输入你的 Notion API 令牌",
DatabaseID: "数据库 ID",
DatabaseIDDesc: "从右上角的分享 --> 发布中获取",
DatabaseIDText: "输入你的数据库 ID",
BannerUrl: "封面图片地址(可选)",
BannerUrlDesc:
"默认为空如果你想显示封面图片请输入图片地址例如https://abc.com/b.png",
BannerUrlText: "输入你的封面图片地址",
NotionUser: "Notion ID用户名可选",
NotionUserDesc:
"数据库分享链接类似https://username.notion.site/。你的 Notion ID 是 [username]",
NotionUserText: "输入你的 Notion ID",
NotionLinkDisplay: "Notion 链接显示",
NotionLinkDisplayDesc: "默认开启如果你不想在front matter中显示链接请关闭",
NotionGeneralSettingHeader: "普通 Notion 数据库设置",
NotionGeneralButton: "普通数据库同步",
NotionGeneralButtonDesc: "打开此选项,同步到普通数据库命令将显示在命令面板中(默认:开)",
NotionTagButton: "标签同步开关",
NotionTagButtonDesc: "将标签同步到普通数据库(默认:开)",
NotionCustomTitle: "修改 Notion 数据库表头开关",
NotionCustomTitleDesc: "自定义Notion 数据库第一列表头名(默认:关)",
NotionCustomTitleName: "想要修改的表头名",
NotionCustomTitleNameDesc: "输入你想要修改的notion数据库的表头名默认title",
NotionCustomTitleText: "输入表头名",
NotionCustomValues: "自定义Notion 数据库表头",
NotionCustomValuesDesc: "自定义Notion 数据库表头,每行一个",
NotionCustomValuesText: "输入你想要同步的所有属性",
NotYetFinish: "未完成。此功能将在之后版本中提供",
PlaceHolder: "输入数据库名称",
"notion-logo": "分享到NotionNext",
"sync-success": "同步到NotionNext成功:\n",
"sync-fail": "同步到NotionNext失败: \n",
"open-file": "请打开需要同步的文件",
"config-secrets-notion-api": "请在插件设置中添加notion API",
"config-secrets-database-id": "请在插件设置中添加database id",
"set-tags-fail":
"设置标签失败,请检查文件的frontmatter,或者在插件设置中关闭设置tags开关",
NNonMissing: "未设置'NNon'属性请在插件设置中选择NotionNext数据库。",
"set-api-id": "请在插件设置中设置notion API和database ID",
NotionCustomSettingHeader: "Notion 自定义数据库设置",
NotionCustomButton: "Notion 自定义数据库同步命令开关",
NotionCustomButtonDesc: "打开此选项,同步到自定义数据库命令将显示在命令面板中",
CustomPropertyName: "自定义属性名",
CustomPropertyFirstColumn: "第一列属性名",
CustomPropertyFirstColumnDesc: "第一列必须为标题属性名",
CustomProperty: "自定义属性",
AddCustomProperty: "添加自定义属性",
AddNewProperty: "添加新属性",
AddNewPropertyDesc: "添加一个和Notion数据库匹配的新属性",
CopyErrorMessage: "复制链接失败,请手动复制",
BlockUploaded: "所有内容已成功上传",
ExtraBlockUploaded: "额外内容已成功上传",
CheckConsole: "opt+cmd+i/ctrl+shift+i\n打开控制台查看更多信息",
}

89
src/main.ts Normal file
View File

@@ -0,0 +1,89 @@
import { App, Editor, MarkdownView, Notice, Plugin, PluginSettingTab, Setting } from "obsidian";
import { addIcons } from 'src/ui/icon';
import { Upload2NotionGeneral } from "src/upload/upload_general/Upload2NotionGeneral";
import { Upload2NotionNext } from "src/upload/upload_next/Upload2NotionNext";
import { i18nConfig } from "src/lang/I18n";
import ribbonCommands from "src/commands/NotionCommands";
import { ObsidianSettingTab, PluginSettings, DEFAULT_SETTINGS, DatabaseDetails } from "src/ui/settingTabs";
// Remember to rename these classes and interfaces!
export default class ObsidianSyncNotionPlugin extends Plugin {
settings: PluginSettings;
commands: ribbonCommands;
app: App;
async onload() {
await this.loadSettings();
this.commands = new ribbonCommands(this);
addIcons();
// This creates an icon in the left ribbon.
const ribbonIconEl = this.addRibbonIcon(
"notion-logo",
i18nConfig.ribbonIcon,
async (evt: MouseEvent) => {
// Called when the user clicks the icon.
// await this.uploadCommand();
await this.commands.ribbonDisplay();
}
);
// This adds a status bar item to the bottom of the app. Does not work on mobile apps.
// const statusBarItemEl = this.addStatusBarItem();
// // statusBarItemEl.setText("share to notion");
// This adds a settings tab so the user can configure various aspects of the plugin
this.addSettingTab(new ObsidianSettingTab(this.app, this));
}
onunload() {
}
async loadSettings() {
this.settings = Object.assign(
{},
DEFAULT_SETTINGS,
await this.loadData()
);
}
async saveSettings() {
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
View 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
View 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
View 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
});
});
}
}

14
src/ui/icon.ts Normal file
View File

@@ -0,0 +1,14 @@
import { addIcon } from 'obsidian';
const icons: Record<string, string> = {
'notion-logo': `
<!-- License: Apache. Made by lawnchairlauncher: https://github.com/lawnchairlauncher/lawnicons -->
<svg width="110px" height="110px" viewBox="0 0 192 192" xmlns="http://www.w3.org/2000/svg" fill="none"><path fill="#817f7a" fill-rule="evenodd" d="m138.462 21.522 27.784 19.588.044.033.275.201c1.713 1.256 3.349 2.455 4.452 3.83 1.411 1.76 1.884 3.644 1.884 5.877v104.706c0 3.587-.635 7.178-3.058 9.934-2.451 2.789-6.145 4.067-10.732 4.394l-.018.001-98.629 5.971-.021.001c-3.242.154-6.094.035-8.669-.907-2.688-.984-4.719-2.727-6.604-5.129l-.01-.012-19.979-25.979-.012-.017c-3.81-5.086-5.723-9.348-5.723-14.509V34.509c0-3.12.688-6.394 2.745-9.033 2.124-2.727 5.356-4.328 9.503-4.686l.058-.005 84.854-4.344c5.192-.445 8.938-.576 12.286.185 3.459.787 6.208 2.452 9.57 4.896ZM56.43 157.336h.002v3.3c0 1.904.47 2.337.613 2.452.296.235 1.203.652 3.642.518l97.449-5.371c1.928-.106 2.256-.649 2.348-.801l.005-.008c.29-.476.486-1.407.486-3.357V60.001c0-1.635-.334-2.218-.421-2.327l-.005-.007-.002-.003-.006-.002a.117.117 0 0 1-.012-.004c-.053-.019-.263-.078-.724-.037l-.057.005-101.622 5.668c-.624.056-.973.163-1.152.242-.142.062-.173.104-.181.116l-.002.002c-.066.085-.36.586-.36 2.321v91.361Zm9.085-106.705 87.074-4.506-21.028-15.375-.039-.031c-1.259-.98-2.507-1.854-4.12-2.46-1.588-.597-3.695-.993-6.669-.734l-.05.005-87.009 4.898h-.01a6.453 6.453 0 0 0-.893.116L49.934 48.56c2.037 1.646 3.109 2.146 4.337 2.367 1.538.277 3.52.167 7.722-.115l3.522-.237v.056Zm-34.231-3.586v83.893c0 .538.175 1.061.498 1.49l13.174 17.464V61.224a2.47 2.47 0 0 0-.877-1.889l-.08-.068-12.715-12.222Zm109.871 35.062c.451 2.04 0 4.082-2.041 4.315l-3.393.673v49.881c-2.947 1.586-5.66 2.492-7.927 2.492-3.622 0-4.528-1.134-7.239-4.53l-.003-.003L98.36 100.02v33.78l7.02 1.59s0 4.082-5.664 4.082l-15.615.906c-.455-.91 0-3.176 1.582-3.627l4.078-1.131V90.955l-5.66-.459c-.454-2.04.677-4.987 3.85-5.216l16.754-1.128 23.09 35.367V88.231l-5.885-.677c-.455-2.499 1.356-4.315 3.618-4.536l15.627-.91v-.001Z" clip-rule="evenodd"/></svg>
`
};
export const addIcons = (): void => {
Object.keys(icons).forEach((key) => {
addIcon(key, icons[key]);
});
};

377
src/ui/settingModal.ts Normal file
View 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();
})
);
}
}
}

307
src/ui/settingTabs.ts Normal file
View File

@@ -0,0 +1,307 @@
import { App, ButtonComponent, PluginSettingTab, Setting } from "obsidian";
import { i18nConfig } from "../lang/I18n";
import ObsidianSyncNotionPlugin from "../main";
import { SettingModal } from "./settingModal";
import { PreviewModal } from "./PreviewModal";
import { EditModal } from "./EditModal";
import { DeleteModal } from "./DeleteModal";
export interface PluginSettings {
NextButton: boolean;
notionAPINext: string;
databaseIDNext: string;
bannerUrl: string;
notionUser: string;
NotionLinkDisplay: boolean;
proxy: string;
GeneralButton: boolean;
tagButton: boolean;
customTitleButton: boolean;
customTitleName: string;
notionAPIGeneral: string;
databaseIDGeneral: string;
CustomButton: boolean;
CustomValues: string;
notionAPICustom: string;
databaseIDCustom: string;
[key: string]: any;
databaseDetails: Record<string, DatabaseDetails>
}
export interface DatabaseDetails {
format: string;
fullName: string;
abName: string;
notionAPI: string;
databaseID: string;
tagButton: boolean;
customTitleButton: boolean;
customTitleName: string;
customProperties: { customName: string, customType: string, index: number }[];
// customValues: string;
saved: boolean;
}
export const DEFAULT_SETTINGS: PluginSettings = {
NextButton: true,
notionAPINext: "",
databaseIDNext: "",
bannerUrl: "",
notionUser: "",
NotionLinkDisplay: true,
proxy: "",
GeneralButton: true,
tagButton: true,
customTitleButton: false,
customTitleName: "",
notionAPIGeneral: "",
databaseIDGeneral: "",
CustomButton: false,
CustomValues: "",
notionAPICustom: "",
databaseIDCustom: "",
databaseDetails: {},
};
export class ObsidianSettingTab extends PluginSettingTab {
plugin: ObsidianSyncNotionPlugin;
databaseEl: HTMLDivElement;
constructor(app: App, plugin: ObsidianSyncNotionPlugin) {
super(app, plugin);
this.plugin = plugin;
}
display(): void {
const { containerEl } = this;
containerEl.empty();
// General Settings
containerEl.createEl('h2', { text: i18nConfig.GeneralSetting });
this.createSettingEl(containerEl, i18nConfig.BannerUrl, i18nConfig.BannerUrlDesc, 'text', i18nConfig.BannerUrlText, this.plugin.settings.bannerUrl, 'bannerUrl')
this.createSettingEl(containerEl, i18nConfig.NotionUser, i18nConfig.NotionUserDesc, 'text', i18nConfig.NotionUserText, this.plugin.settings.notionUser, 'notionUser')
this.createSettingEl(containerEl, i18nConfig.NotionLinkDisplay, i18nConfig.NotionLinkDisplayDesc, 'toggle', i18nConfig.NotionLinkDisplay, this.plugin.settings.NotionLinkDisplay, 'NotionLinkDisplay')
// add new button
new Setting(containerEl)
.setName("Add New Database")
.setDesc("Add New Database")
.addButton((button: ButtonComponent): ButtonComponent => {
return button
.setTooltip("Add New Database")
.setIcon("plus")
.onClick(async () => {
let modal = new SettingModal(this.app, this.plugin, this);
modal.onClose = () => {
if (modal.data.saved) {
const dbDetails = {
format: modal.data.databaseFormat,
fullName: modal.data.databaseFullName,
abName: modal.data.databaseAbbreviateName,
notionAPI: modal.data.notionAPI,
databaseID: modal.data.databaseID,
tagButton: modal.data.tagButton,
customTitleButton: modal.data.customTitleButton,
customTitleName: modal.data.customTitleName,
customProperties: modal.data.customProperties,
// customValues: modal.data.customValues,
saved: modal.data.saved,
}
this.plugin.addDatabaseDetails(dbDetails);
this.plugin.commands.updateCommand();
this.display()
}
}
modal.open();
});
});
// new section to display all created database
containerEl.createEl('h2', { text: "Database List" });
this.databaseEl = containerEl.createDiv('database-list');
// list all created database
this.showDatabase();
}
// create a function to create a div with a style for pop over elements
// public createStyleDiv(className: string, commandValue: boolean = false) {
// return this.containerEl.createDiv(className, (div) => {
// this.updateSettingEl(div, commandValue);
// });
// }
// update the setting display style in the setting tab
public updateSettingEl(element: HTMLElement, commandValue: boolean) {
element.style.borderTop = commandValue ? "1px solid var(--background-modifier-border)" : "none";
element.style.paddingTop = commandValue ? "0.75em" : "0";
element.style.display = commandValue ? "block" : "none";
element.style.alignItems = "center";
}
// function to add one setting element in the setting tab.
public createSettingEl(containerEl: HTMLElement, name: string, desc: string, type: string, placeholder: string, holderValue: any, settingsKey: string) {
if (type === 'password') {
return new Setting(containerEl)
.setName(name)
.setDesc(desc)
.addText((text) => {
text.inputEl.type = type;
return text
.setPlaceholder(placeholder)
.setValue(holderValue)
.onChange(async (value) => {
this.plugin.settings[settingsKey] = value; // Update the plugin settings directly
await this.plugin.saveSettings();
})
});
} else if (type === 'toggle') {
return new Setting(containerEl)
.setName(name)
.setDesc(desc)
.addToggle((toggle) =>
toggle
.setValue(holderValue)
.onChange(async (value) => {
this.plugin.settings[settingsKey] = value; // Update the plugin settings directly
await this.plugin.saveSettings();
await this.plugin.commands.updateCommand();
})
);
} else if (type === 'text') {
return new Setting(containerEl)
.setName(name)
.setDesc(desc)
.addText((text) =>
text
.setPlaceholder(placeholder)
.setValue(holderValue)
.onChange(async (value) => {
this.plugin.settings[settingsKey] = value; // Update the plugin settings directly
await this.plugin.saveSettings();
await this.plugin.commands.updateCommand();
})
);
}
}
// function to show all the database details
showDatabase() {
this.databaseEl.empty();
for (let key in this.plugin.settings.databaseDetails) {
let dbDetails = this.plugin.settings.databaseDetails[key];
const databaseDiv = this.databaseEl.createDiv('database-div');
let settingEl = new Setting(databaseDiv)
.setName(`${dbDetails.fullName} (${dbDetails.abName})`)
.setDesc(dbDetails.format)
// add a button for preview data
settingEl
.addButton((button: ButtonComponent): ButtonComponent => {
return button
.setTooltip("Preview Database")
.setIcon("eye")
.onClick(async () => {
let modal = new PreviewModal(this.app, this.plugin, this, dbDetails);
modal.open();
});
});
// add a button for edit data
settingEl
.addButton((button: ButtonComponent): ButtonComponent => {
return button
.setTooltip("Edit Database")
.setIcon("pencil")
.onClick(async () => {
let modal = new EditModal(this.app, this.plugin, this, dbDetails);
modal.onClose = () => {
if (modal.dataTemp.savedTempInd) {
const dbDetailsNew: DatabaseDetails = {
format: modal.dataTemp.databaseFormatTemp,
fullName: modal.dataTemp.databaseFullNameTemp,
abName: modal.dataTemp.databaseAbbreviateNameTemp,
notionAPI: modal.dataTemp.notionAPITemp,
databaseID: modal.dataTemp.databaseIDTemp,
tagButton: modal.dataTemp.tagButtonTemp,
customTitleButton: modal.dataTemp.customTitleButtonTemp,
customTitleName: modal.dataTemp.customTitleNameTemp,
customProperties: modal.dataTemp.customPropertiesTemp,
// customValues: modal.data.customValues,
saved: modal.dataTemp.savedTemp,
}
const dbDetailsPrev: DatabaseDetails = {
format: modal.dataPrev.databaseFormatPrev,
fullName: modal.dataPrev.databaseFullNamePrev,
abName: modal.dataPrev.databaseAbbreviateNamePrev,
notionAPI: modal.dataPrev.notionAPIPrev,
databaseID: modal.dataPrev.databaseIDPrev,
tagButton: modal.dataPrev.tagButtonPrev,
customTitleButton: modal.dataPrev.customTitleButtonPrev,
customTitleName: modal.dataPrev.customTitleNamePrev,
customProperties: modal.dataPrev.customPropertiesPrev,
// customValues: modal.data.customValues,
saved: modal.dataPrev.savedPrev,
}
this.plugin.deleteDatabaseDetails(dbDetailsPrev);
this.plugin.updateDatabaseDetails(dbDetailsNew);
this.plugin.commands.updateCommand();
this.display()
}
}
modal.open();
});
});
settingEl
.addButton((button: ButtonComponent): ButtonComponent => {
return button
.setTooltip("Delete Database")
.setIcon("trash")
.onClick(async () => {
let modal = new DeleteModal(this.app, this.plugin, this, dbDetails);
modal.onClose = () => {
if (modal.data.deleted) {
this.plugin.deleteDatabaseDetails(dbDetails);
console.log(dbDetails.fullName + " deleted");
this.plugin.commands.updateCommand();
this.display()
}
}
modal.open();
});
});
}
}
}

44
src/upload/updateYaml.ts Normal file
View File

@@ -0,0 +1,44 @@
import { App, Notice, TFile } from "obsidian";
import ObsidianSyncNotionPlugin from "../main";
import { DatabaseDetails } from "../ui/settingTabs";
import { i18nConfig } from "src/lang/I18n";
export async function updateYamlInfo(
yamlContent: string,
nowFile: TFile,
res: any,
app: App,
plugin: ObsidianSyncNotionPlugin,
dbDetails: DatabaseDetails,
) {
let { url, id } = res;
// replace www to notionID
const { notionUser, NotionLinkDisplay } = plugin.settings;
const { abName } = dbDetails
const notionIDKey = `NotionID-${abName}`;
const linkKey = `link-${abName}`;
if (notionUser !== "") {
// replace url str "www" to notionID
url = url.replace("www.notion.so", `${notionUser}.notion.site`)
}
await app.fileManager.processFrontMatter(nowFile, yamlContent => {
if (yamlContent[notionIDKey]) {
delete yamlContent[notionIDKey]
}
if (yamlContent[linkKey]) {
delete yamlContent[linkKey]
}
// add new notionID and link
yamlContent[notionIDKey] = id;
(NotionLinkDisplay) ? yamlContent[linkKey] = url : null;
});
try {
await navigator.clipboard.writeText(url)
} catch (error) {
console.log(error)
new Notice(`${i18nConfig.CopyErrorMessage}`);
}
}

128
src/upload/uploadCommand.ts Normal file
View File

@@ -0,0 +1,128 @@
import {i18nConfig} from "../lang/I18n";
import {App, Notice} from "obsidian";
import {Upload2NotionNext} from "./upload_next/Upload2NotionNext";
import {Upload2NotionGeneral} from "./upload_general/Upload2NotionGeneral";
import {Upload2NotionCustom} from "./upoload_custom/Upload2NotionCustom";
import {DatabaseDetails, PluginSettings} from "../ui/settingTabs";
import ObsidianSyncNotionPlugin from "../main";
import {getNowFileMarkdownContentNext} from "./upload_next/getMarkdownNext";
import {getNowFileMarkdownContentGeneral} from "./upload_general/getMarkdownGeneral";
import {getNowFileMarkdownContentCustom} from "./upoload_custom/getMarkdownCustom";
export async function uploadCommandNext(
plugin: ObsidianSyncNotionPlugin,
settings: PluginSettings,
dbDetails: DatabaseDetails,
app: App,
) {
const {notionAPI, databaseID} = dbDetails;
// Check if the user has set up the Notion API and database ID
if (notionAPI === "" || databaseID === "") {
const setAPIMessage = i18nConfig["set-api-id"];
new Notice(setAPIMessage);
return;
}
const {
markDownData,
nowFile,
emoji,
cover,
tags,
type,
slug,
stats,
category,
summary,
paword,
favicon,
datetime
} = await getNowFileMarkdownContentNext(app, settings)
if (markDownData) {
const {basename} = nowFile;
const upload = new Upload2NotionNext(plugin, dbDetails);
const res = await upload.syncMarkdownToNotionNext(basename, emoji, cover, tags, type, slug, stats, category, summary, paword, favicon, datetime, markDownData, nowFile, this.app);
const {response} = res;
if (response.status === 200) {
new Notice(`${i18nConfig["sync-preffix"]} ${basename} ${i18nConfig["sync-success"]}`).noticeEl.style.color = "green";
} else {
new Notice(`${i18nConfig["sync-fail"]} ${basename}`, 5000);
}
}
}
export async function uploadCommandGeneral(
plugin: ObsidianSyncNotionPlugin,
settings: PluginSettings,
dbDetails: DatabaseDetails,
app: App,
) {
const {notionAPI, databaseID} = dbDetails;
// Check if the user has set up the Notion API and database ID
if (notionAPI === "" || databaseID === "") {
const setAPIMessage = i18nConfig["set-api-id"];
new Notice(setAPIMessage);
return;
}
const {markDownData, nowFile, cover, tags} = await getNowFileMarkdownContentGeneral(app, settings)
if (markDownData) {
const {basename} = nowFile;
const upload = new Upload2NotionGeneral(plugin, dbDetails);
const res = await upload.syncMarkdownToNotionGeneral(basename, cover, tags, markDownData, nowFile, this.app);
const {response} = res;
if (response.status === 200) {
new Notice(`${i18nConfig["sync-preffix"]} ${basename} ${i18nConfig["sync-success"]}`).noticeEl.style.color = "green";
} else {
new Notice(`${i18nConfig["sync-fail"]} ${basename}`, 5000);
}
}
}
export async function uploadCommandCustom(
plugin: ObsidianSyncNotionPlugin,
settings: PluginSettings,
dbDetails: DatabaseDetails,
app: App,
) {
const {notionAPI, databaseID} = settings;
// Check if the user has set up the Notion API and database ID
if (notionAPI === "" || databaseID === "") {
const setAPIMessage = i18nConfig["set-api-id"];
new Notice(setAPIMessage);
return;
}
const {markDownData, nowFile, cover, customValues} = await getNowFileMarkdownContentCustom(app, dbDetails)
if (markDownData) {
const {basename} = nowFile;
const upload = new Upload2NotionCustom(plugin, dbDetails);
const res = await upload.syncMarkdownToNotionCustom(cover, customValues, markDownData, nowFile, this.app);
const {response} = res;
if (response.status === 200) {
new Notice(`${i18nConfig["sync-preffix"]} ${basename} ${i18nConfig["sync-success"]}`).noticeEl.style.color = "green";
} else {
new Notice(`${i18nConfig["sync-fail"]} ${basename}`, 5000);
}
}
}

View File

@@ -0,0 +1,96 @@
import { App, Notice, requestUrl, TFile } from "obsidian";
import { Client } from '@notionhq/client';
import { markdownToBlocks, } from "@jxpeng98/martian";
import * as yamlFrontMatter from "yaml-front-matter";
// import * as yaml from "yaml"
import MyPlugin from "src/main";
import { DatabaseDetails } from "../../ui/settingTabs";
export class UploadBaseGeneral {
plugin: MyPlugin;
notion: Client;
agent: any;
dbDetails: DatabaseDetails
constructor(plugin: MyPlugin, dbDetails: DatabaseDetails) {
this.plugin = plugin;
this.dbDetails = dbDetails
}
async deletePage(notionID: string) {
const { notionAPI } = this.dbDetails
return requestUrl({
url: `https://api.notion.com/v1/blocks/${notionID}`,
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + notionAPI,
'Notion-Version': '2022-06-28',
},
body: ''
});
}
async getDataBase(databaseID: string) {
const { notionAPI } = this.dbDetails
const response = await requestUrl({
url: `https://api.notion.com/v1/databases/${databaseID}`,
method: 'GET',
headers: {
'Authorization': 'Bearer ' + notionAPI,
'Notion-Version': '2022-06-28',
}
}
)
// Check if cover is present in the JSON response and then get the URL
if (response.json.cover && response.json.cover.external) {
return response.json.cover.external.url;
} else {
return null; // or some other default value, if you prefer
}
}
// async updateYamlInfo(yamlContent: string, nowFile: TFile, res: any, app: App, settings: any) {
// let {url, id} = res.json
// // replace www to notionID
// const {notionUser} = this.plugin.settings;
//
// if (notionUser !== "") {
// // replace url str "www" to notionID
// url = url.replace("www.notion.so", `${notionUser}.notion.site`)
// }
//
// await app.fileManager.processFrontMatter(nowFile, yamlContent => {
// if (yamlContent['notionID']) {
// delete yamlContent['notionID']
// }
// if (yamlContent['link']) {
// delete yamlContent['link']
// }
// // add new notionID and link
// yamlContent.notionID = id;
// yamlContent.link = url;
// });
//
// try {
// await navigator.clipboard.writeText(url)
// } catch (error) {
// new Notice(`复制链接失败,请手动复制${error}`)
// }
// // const __content = yamlContent.__content;
// // delete yamlContent.__content
// // const yamlhead = yaml.stringify(yamlContent)
// // // if yamlhead hava last \n remove it
// // const yamlhead_remove_n = yamlhead.replace(/\n$/, '')
// // // if __content have start \n remove it
// // const __content_remove_n = __content.replace(/^\n/, '')
// // const content = '---\n' +yamlhead_remove_n +'\n---\n' + __content_remove_n;
// // try {
// // await nowFile.vault.modify(nowFile, content)
// // } catch (error) {
// // new Notice(`write file error ${error}`)
// // }
// }
}

View File

@@ -0,0 +1,248 @@
import {App, Notice, TFile} from "obsidian";
import {Client} from "@notionhq/client";
import {markdownToBlocks} from "@jxpeng98/martian";
import * as yamlFrontMatter from "yaml-front-matter";
// import * as yaml from "yaml"
import MyPlugin from "src/main";
import {DatabaseDetails, PluginSettings} from "../../ui/settingTabs";
import {UploadBaseGeneral} from "./BaseUpload2NotionGeneral";
import {updateYamlInfo} from "../updateYaml";
import fetch from 'node-fetch';
import {i18nConfig} from "../../lang/I18n";
export class Upload2NotionGeneral extends UploadBaseGeneral {
settings: PluginSettings;
dbDetails: DatabaseDetails;
constructor(plugin: MyPlugin, dbDetails: DatabaseDetails) {
super(plugin, dbDetails);
this.dbDetails = dbDetails;
}
// 因为需要解析notion的block进行对比非常的麻烦
// 暂时就直接删除新建一个page
async updatePage(
notionID: string,
title: string,
cover: string,
tags: string[],
childArr: any,
) {
await this.deletePage(notionID);
const {databaseID} = this.dbDetails;
const databaseCover = await this.getDataBase(
databaseID,
);
if (cover == null) {
cover = databaseCover;
}
return await this.createPage(title, cover, tags, childArr);
}
async createPage(
title: string,
cover: string,
tags: string[],
childArr: any,
) {
const {
databaseID,
customTitleButton,
customTitleName,
tagButton,
notionAPI
} = this.dbDetails;
// remove the annotations from the childArr if type is code block
childArr.forEach((block: any) => {
if (block.type === "code") {
block.code.rich_text.forEach((item: any) => {
if (item.type === "text" && item.annotations) {
delete item.annotations;
}
}
);
}
}
);
// check the length of the childArr and split it into chunks of 100
const childArrLength = childArr.length;
let extraArr: any[] = [];
let firstArr: any;
let pushCount = 0;
console.log(`Page includes ${childArrLength} blocks`)
if (childArrLength > 100) {
for (let i = 0; i < childArr.length; i += 100) {
if (i == 0) {
firstArr = childArr.slice(0, 100);
} else {
const chunk = childArr.slice(i, i + 100);
extraArr.push(chunk);
pushCount++;
}
}
} else {
firstArr = childArr;
}
const bodyString: any = {
parent: {
database_id: databaseID,
},
properties: {
[customTitleButton
? customTitleName
: "title"]: {
title: [
{
text: {
content: title,
},
},
],
},
...(tagButton
? {
tags: {
multi_select: tags && true ? tags.map((tag) => ({name: tag})) : [],
},
}
: {}),
},
children: firstArr,
};
if (cover) {
bodyString.cover = {
type: "external",
external: {
url: cover,
},
};
}
if (!bodyString.cover && this.plugin.settings.bannerUrl) {
bodyString.cover = {
type: "external",
external: {
url: this.plugin.settings.bannerUrl,
},
};
}
console.log(bodyString)
const response = await fetch("https://api.notion.com/v1/pages", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + notionAPI,
"Notion-Version": "2022-06-28",
},
body: JSON.stringify(bodyString),
});
const data: any = await response.json();
if (!response.ok) {
new Notice(`Error ${data.status}: ${data.code} \n ${i18nConfig["CheckConsole"]}`, 5000);
} else {
console.log(`Page created: ${data.url}`);
console.log(`Page ID: ${data.id}`);
}
// upload the rest of the blocks
if (pushCount > 0) {
for (let i = 0; i < pushCount; i++) {
const extraBlocks = {
children: extraArr[i],
};
console.log(extraBlocks)
const extraResponse = await fetch(`https://api.notion.com/v1/blocks/${data.id}/children`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + notionAPI,
"Notion-Version": "2022-06-28",
},
body: JSON.stringify(extraBlocks),
});
const extraData: any = await extraResponse.json();
if (!extraResponse.ok) {
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: ${data.id}`);
if (i === pushCount - 1) {
console.log(`${i18nConfig["BlockUploaded"]} to page: ${data.id}`);
new Notice(`${i18nConfig["BlockUploaded"]} page: ${data.id}`, 5000);
}
}
}
}
return {
response, // for status code
data // for id and url
}
}
async syncMarkdownToNotionGeneral(
title: string,
cover: string,
tags: string[],
markdown: string,
nowFile: TFile,
app: App,
): Promise<any> {
const options = {
notionLimits: {
truncate: false,
}
}
let res: any;
const yamlContent: any = yamlFrontMatter.loadFront(markdown);
const __content = yamlContent.__content;
const file2Block = markdownToBlocks(__content, options);
const frontmasster =
app.metadataCache.getFileCache(nowFile)?.frontmatter;
const {abName} = this.dbDetails
const notionIDKey = `NotionID-${abName}`;
const notionID = frontmasster ? frontmasster[notionIDKey] : null;
if (notionID) {
res = await this.updatePage(
notionID,
title,
cover,
tags,
file2Block,
);
} else {
res = await this.createPage(title, cover, tags, file2Block);
}
let {response, data} = res;
// console.log(response)
if (response && response.status === 200) {
await updateYamlInfo(markdown, nowFile, data, app, this.plugin, this.dbDetails);
}
return res;
}
}

View 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;
}
}

View File

@@ -0,0 +1,96 @@
import { App, Notice, requestUrl, TFile } from "obsidian";
import { Client } from '@notionhq/client';
import { markdownToBlocks, } from "@jxpeng98/martian";
import * as yamlFrontMatter from "yaml-front-matter";
// import * as yaml from "yaml"
import MyPlugin from "src/main";
import { DatabaseDetails } from "../../ui/settingTabs";
export class UploadBaseNext {
plugin: MyPlugin;
notion: Client;
agent: any;
dbDetails: DatabaseDetails
constructor(plugin: MyPlugin, dbDetails: DatabaseDetails) {
this.plugin = plugin;
this.dbDetails = dbDetails
}
async deletePage(notionID: string) {
const { notionAPI } = this.dbDetails
return requestUrl({
url: `https://api.notion.com/v1/blocks/${notionID}`,
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + notionAPI,
'Notion-Version': '2022-06-28',
},
body: ''
});
}
async getDataBase(databaseID: string) {
const { notionAPI } = this.dbDetails
const response = await requestUrl({
url: `https://api.notion.com/v1/databases/${databaseID}`,
method: 'GET',
headers: {
'Authorization': 'Bearer ' + notionAPI,
'Notion-Version': '2022-06-28',
}
}
)
// Check if cover is present in the JSON response and then get the URL
if (response.json.cover && response.json.cover.external) {
return response.json.cover.external.url;
} else {
return null; // or some other default value, if you prefer
}
}
// async updateYamlInfo(yamlContent: string, nowFile: TFile, res: any, app: App, settings: any) {
// let {url, id} = res.json
// // replace www to notionID
// const { notionUser} = this.plugin.settings;
//
// if (notionUser !== "") {
// // replace url str "www" to notionID
// url = url.replace("www.notion.so", `${notionUser}.notion.site`)
// }
//
// await app.fileManager.processFrontMatter(nowFile, yamlContent => {
// if (yamlContent['notionID']) {
// delete yamlContent['notionID']
// }
// if (yamlContent['link']) {
// delete yamlContent['link']
// }
// // add new notionID and link
// yamlContent.notionID = id;
// yamlContent.link = url;
// });
//
// try {
// await navigator.clipboard.writeText(url)
// } catch (error) {
// new Notice(`复制链接失败,请手动复制${error}`)
// }
// // const __content = yamlContent.__content;
// // delete yamlContent.__content
// // const yamlhead = yaml.stringify(yamlContent)
// // // if yamlhead hava last \n remove it
// // const yamlhead_remove_n = yamlhead.replace(/\n$/, '')
// // // if __content have start \n remove it
// // const __content_remove_n = __content.replace(/^\n/, '')
// // const content = '---\n' +yamlhead_remove_n +'\n---\n' + __content_remove_n;
// // try {
// // await nowFile.vault.modify(nowFile, content)
// // } catch (error) {
// // new Notice(`write file error ${error}`)
// // }
// }
}

View File

@@ -0,0 +1,411 @@
import {UploadBaseNext} from "./BaseUpload2NotionNext";
import {App, Notice, TFile} from "obsidian";
import {Client} from "@notionhq/client";
import {markdownToBlocks} from "@jxpeng98/martian";
import * as yamlFrontMatter from "yaml-front-matter";
// import * as yaml from "yaml"
import MyPlugin from "src/main";
import {DatabaseDetails, PluginSettings} from "../../ui/settingTabs";
import {updateYamlInfo} from "../updateYaml";
import {LIMITS, paragraph} from "@jxpeng98/martian/src/notion";
import fetch from "node-fetch";
import {i18nConfig} from "../../lang/I18n";
export class Upload2NotionNext extends UploadBaseNext {
settings: PluginSettings;
dbDetails: DatabaseDetails;
constructor(plugin: MyPlugin, dbDetails: DatabaseDetails) {
super(plugin, dbDetails);
this.dbDetails = dbDetails;
}
// 因为需要解析notion的block进行对比非常的麻烦
// 暂时就直接删除新建一个page
async updatePage(
notionID: string,
title: string,
emoji: string,
cover: string,
tags: string[],
type: string,
slug: string,
stats: string,
category: string,
summary: string,
paword: string,
favicon: string,
datetime: string,
childArr: any,
) {
await this.deletePage(notionID);
const {databaseID} = this.dbDetails;
const databaseCover = await this.getDataBase(databaseID);
if (cover == null) {
cover = databaseCover;
}
return await this.createPage(
title,
emoji,
cover,
tags,
type,
slug,
stats,
category,
summary,
paword,
favicon,
datetime,
childArr,
);
}
async createPage(
title: string,
emoji: string,
cover: string,
tags: string[],
type: string,
slug: string,
stats: string,
category: string,
summary: string,
pawrod: string,
favicon: string,
datetime: string,
childArr: any,
) {
const {databaseID, notionAPI} = this.dbDetails;
// remove the annotations from the childArr if type is code block
childArr.forEach((block: any) => {
if (block.type === "code") {
block.code.rich_text.forEach((item: any) => {
if (item.type === "text" && item.annotations) {
delete item.annotations;
}
}
);
}
}
);
// check the length of the childArr
// if it is too long, split it into multiple pages
const childArrLength = childArr.length;
let extraArr: any[] = [];
let firstArr: any;
let pushCount = 0;
console.log(`Page includes ${childArrLength} blocks`)
if (childArrLength > 100) {
for (let i = 0; i < childArr.length; i += 100) {
if (i == 0) {
firstArr = childArr.slice(0, 100);
} else {
const chunk = childArr.slice(i, i + 100);
extraArr.push(chunk);
pushCount++;
}
}
} else {
firstArr = childArr;
}
const pageProperties: any = {
parent: {
database_id: databaseID,
},
properties: {
title: {
title: [
{
text: {
content: title,
},
},
],
},
type: {
select: {
name: type || "Post",
},
},
status: {
select: {
name: stats || "Draft",
},
},
category: {
select: {
name: category || "Obsidian",
},
},
password: {
rich_text: [
{
text: {
content: pawrod || "",
},
},
],
},
icon: {
rich_text: [
{
text: {
content: favicon || "",
},
},
],
},
date: {
date: {
start: datetime || new Date().toISOString(),
},
},
},
};
// add tags
if (tags) {
pageProperties.properties.tags = {
multi_select: tags.map((tag) => {
return {name: tag};
}),
};
}
// add title icon
if (emoji) {
pageProperties.icon = {
emoji: emoji,
};
}
// add slug
if (slug) {
pageProperties.properties.slug = {
rich_text: [
{
text: {
content: slug,
},
},
],
};
}
// check if summary is available
if (summary) {
pageProperties.properties.summary = {
rich_text: [
{
text: {
content: summary,
},
},
],
};
}
if (cover) {
pageProperties.cover = {
type: "external",
external: {
url: cover,
},
};
}
if (!pageProperties.cover && this.plugin.settings.bannerUrl) {
pageProperties.cover = {
type: "external",
external: {
url: this.plugin.settings.bannerUrl,
},
};
}
const bodyString: any = {
...pageProperties,
children: firstArr,
};
console.log(bodyString);
const response = await fetch("https://api.notion.com/v1/pages", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + notionAPI,
"Notion-Version": "2022-06-28",
},
body: JSON.stringify(bodyString),
});
const data: any = await response.json();
// can use response.ok or response.status === 200
if (!response.ok) {
new Notice(`Error ${data.status}: ${data.code} \n ${i18nConfig["CheckConsole"]}`, 5000);
console.log(`Error message: \n ${data.message}`);
} else {
console.log(`Page created: ${data.url}`);
console.log(`Page ID: ${data.id}`);
}
// if the childArr is over 100, patch the rest of the blocks append to the page
if (pushCount > 0) {
for (let i = 0; i < pushCount; i++) {
const extraBlocks = {
children: extraArr[i]
}
console.log(extraBlocks)
const extraResponse = await fetch(
`https://api.notion.com/v1/blocks/${data.id}/children`,
{
method: "PATCH",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + notionAPI,
"Notion-Version": "2022-06-28",
},
body: JSON.stringify(extraBlocks),
});
const extraData: any = await extraResponse.json();
if (!extraResponse.ok) {
new Notice(
`Extra Block: Error ${extraData.status}: ${extraData.code} \n ${i18nConfig["CheckConsole"]}`,
5000,
);
console.log(`Error message: \n ${extraData.message}`);
} else {
console.log(`${i18nConfig["ExtraBlockUploaded"]} to page: ${data.id}`);
if (i === pushCount - 1) {
console.log(`${i18nConfig["BlockUploaded"]} to page: ${data.id}`);
new Notice(`${i18nConfig["BlockUploaded"]} page: ${data.id}`, 5000);
}
}
}
}
return {
response, // for status code
data, // for id and url
};
}
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 frontmatter =
app.metadataCache.getFileCache(nowFile)?.frontmatter;
const {abName} = this.dbDetails;
const notionIDKey = `NotionID-${abName}`;
const notionID = frontmatter ? frontmatter[notionIDKey] : null;
// increase the limits
// Motivated by https://github.com/tryfabric/martian/issues/51
file2Block.forEach((block, index) => {
if (
block.type === "paragraph" &&
block.paragraph.rich_text.length > LIMITS.RICH_TEXT_ARRAYS
) {
const newParagraphBlocks: any[] = [];
const chunk: any = [];
const richTextChunks = chunk(block.paragraph.rich_text, 100);
richTextChunks.forEach((chunk: any) => {
newParagraphBlocks.push(paragraph(chunk));
});
file2Block.splice(index, 1, ...newParagraphBlocks);
}
});
if (notionID) {
res = await this.updatePage(
notionID,
title,
emoji,
cover,
tags,
type,
slug,
stats,
category,
summary,
paword,
favicon,
datetime,
file2Block,
);
} else {
res = await this.createPage(
title,
emoji,
cover,
tags,
type,
slug,
stats,
category,
summary,
paword,
favicon,
datetime,
file2Block,
);
}
let {response, data} = res;
if (response && response.status === 200) {
await updateYamlInfo(
markdown,
nowFile,
data,
app,
this.plugin,
this.dbDetails,
);
}
return res;
}
}

View File

@@ -0,0 +1,60 @@
import { App, Notice } from "obsidian";
import { i18nConfig } from "../../lang/I18n";
import { PluginSettings } from "../../ui/settingTabs";
export async function getNowFileMarkdownContentNext(
app: App,
settings: PluginSettings,
) {
const nowFile = app.workspace.getActiveFile();
let emoji = '';
let cover = '';
let tags = [];
let type = '';
let slug = '';
let stats = '';
let status = '';
let category = '';
let summary = '';
let paword = '';
let favicon = '';
let datetime = '';
const FileCache = app.metadataCache.getFileCache(nowFile);
try {
emoji = FileCache.frontmatter.titleicon;
cover = FileCache.frontmatter.coverurl;
tags = FileCache.frontmatter.tags;
type = FileCache.frontmatter.type;
slug = FileCache.frontmatter.slug;
stats = FileCache.frontmatter.stats || FileCache.frontmatter.status;
category = FileCache.frontmatter.category;
summary = FileCache.frontmatter.summary;
paword = FileCache.frontmatter.password;
favicon = FileCache.frontmatter.icon;
datetime = FileCache.frontmatter.date;
} catch (error) {
new Notice(i18nConfig["set-tags-fail"]);
}
if (nowFile) {
const markDownData = await nowFile.vault.read(nowFile);
return {
markDownData,
nowFile,
emoji,
cover,
tags,
type,
slug,
stats,
category,
summary,
paword,
favicon,
datetime,
};
} else {
new Notice(i18nConfig["open-file"]);
return;
}
}

View File

@@ -0,0 +1,53 @@
import { App, Notice, requestUrl, TFile } from "obsidian";
import { Client } from '@notionhq/client';
import { markdownToBlocks, } from "@jxpeng98/martian";
import * as yamlFrontMatter from "yaml-front-matter";
// import * as yaml from "yaml"
import MyPlugin from "src/main";
import { DatabaseDetails } from "../../ui/settingTabs";
export class UploadBaseCustom {
plugin: MyPlugin;
notion: Client;
agent: any;
dbDetails: DatabaseDetails
constructor(plugin: MyPlugin, dbDetails: DatabaseDetails) {
this.plugin = plugin;
this.dbDetails = dbDetails
}
async deletePage(notionID: string) {
const { notionAPI } = this.dbDetails
return requestUrl({
url: `https://api.notion.com/v1/blocks/${notionID}`,
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + notionAPI,
'Notion-Version': '2022-06-28',
},
body: ''
});
}
async getDataBase(databaseID: string) {
const { notionAPI } = this.dbDetails
const response = await requestUrl({
url: `https://api.notion.com/v1/databases/${databaseID}`,
method: 'GET',
headers: {
'Authorization': 'Bearer ' + notionAPI,
'Notion-Version': '2022-06-28',
}
}
)
// Check if cover is present in the JSON response and then get the URL
if (response.json.cover && response.json.cover.external) {
return response.json.cover.external.url;
} else {
return null; // or some other default value, if you prefer
}
}
}

View File

@@ -0,0 +1,327 @@
import {App, Notice, TFile} from "obsidian";
import {markdownToBlocks} from "@jxpeng98/martian";
import * as yamlFrontMatter from "yaml-front-matter";
// import * as yaml from "yaml"
import MyPlugin from "src/main";
import {DatabaseDetails, PluginSettings} from "../../ui/settingTabs";
import {updateYamlInfo} from "../updateYaml";
import {UploadBaseCustom} from "./BaseUpload2NotionCustom";
import fetch from 'node-fetch';
import {i18nConfig} from "../../lang/I18n";
export class Upload2NotionCustom extends UploadBaseCustom {
settings: PluginSettings;
dbDetails: DatabaseDetails;
constructor(plugin: MyPlugin, dbDetails: DatabaseDetails) {
super(plugin, dbDetails);
this.dbDetails = dbDetails;
}
// 因为需要解析notion的block进行对比非常的麻烦
// 暂时就直接删除新建一个page
async updatePage(
notionID: string,
cover: string,
customValues: Record<string, string>,
childArr: any,
) {
await this.deletePage(notionID);
const {databaseID} = this.dbDetails;
const databaseCover = await this.getDataBase(
databaseID
);
if (cover == null) {
cover = databaseCover;
}
return await this.createPage(cover, customValues, childArr);
}
async createPage(
cover: string,
customValues: Record<string, string>,
childArr: any,
) {
const {
databaseID,
customProperties,
notionAPI
} = this.dbDetails;
// remove the annotations from the childArr if type is code block
childArr.forEach((block: any) => {
if (block.type === "code") {
block.code.rich_text.forEach((item: any) => {
if (item.type === "text" && item.annotations) {
delete item.annotations;
}
}
);
}
}
);
// check the length of the childArr and split it into chunks of 100
const childArrLength = childArr.length;
let extraArr: any[] = [];
let firstArr: any;
let pushCount = 0;
console.log(`Page includes ${childArrLength} blocks`)
if (childArrLength > 100) {
for (let i = 0; i < childArr.length; i += 100) {
if (i == 0) {
firstArr = childArr.slice(0, 100);
} else {
const chunk = childArr.slice(i, i + 100);
extraArr.push(chunk);
pushCount++;
}
}
} else {
firstArr = childArr;
}
const bodyString: any = this.buildBodyString(customProperties, customValues, firstArr);
if (cover) {
bodyString.cover = {
type: "external",
external: {
url: cover,
},
};
}
if (!bodyString.cover && this.plugin.settings.bannerUrl) {
bodyString.cover = {
type: "external",
external: {
url: this.plugin.settings.bannerUrl,
},
};
}
console.log(bodyString)
const response = await fetch("https://api.notion.com/v1/pages", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + notionAPI,
"Notion-Version": "2022-06-28",
},
body: JSON.stringify(bodyString),
});
const data: any = await response.json();
if (!response.ok) {
new Notice(`Error ${data.status}: ${data.code} \n ${i18nConfig["CheckConsole"]}`, 5000);
console.log(`Error message: \n ${data.message}`);
} else {
console.log(`Page created: ${data.url}`);
console.log(`Page ID: ${data.id}`);
}
// upload the rest of the blocks
if (pushCount > 0) {
for (let i = 0; i < pushCount; i++) {
const extraBlocks = {
children: extraArr[i],
};
console.log(extraBlocks)
const extraResponse = await fetch(`https://api.notion.com/v1/blocks/${data.id}/children`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + notionAPI,
"Notion-Version": "2022-06-28",
},
body: JSON.stringify(extraBlocks),
});
const extraData: any = await extraResponse.json();
if (!extraResponse.ok) {
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: ${data.id}`);
if (i === pushCount - 1) {
console.log(`${i18nConfig["BlockUploaded"]} to page: ${data.id}`);
new Notice(`${i18nConfig["BlockUploaded"]} page: ${data.id}`, 5000);
}
}
}
}
return {
response, // for status code
data // for id and url
}
}
async syncMarkdownToNotionCustom(
cover: string,
customValues: Record<string, string>,
markdown: string,
nowFile: TFile,
app: App,
): Promise<any> {
const options = {
strictImageUrls: true,
notionLimits: {
truncate: false,
}
}
let res: any;
const yamlContent: any = yamlFrontMatter.loadFront(markdown);
const __content = yamlContent.__content;
const file2Block = markdownToBlocks(__content, options);
const frontMatter =
app.metadataCache.getFileCache(nowFile)?.frontmatter;
const {abName} = this.dbDetails
const notionIDKey = `NotionID-${abName}`;
const notionID = frontMatter ? frontMatter[notionIDKey] : null;
if (notionID) {
res = await this.updatePage(
notionID,
cover,
customValues,
file2Block,
);
} else {
res = await this.createPage(cover, customValues, file2Block);
}
let {response, data} = res;
// console.log(response)
if (response && response.status === 200) {
await updateYamlInfo(markdown, nowFile, data, app, this.plugin, this.dbDetails);
}
return res;
}
private buildPropertyObject(customName: string, customType: string, customValues: Record<string, any>) {
const value = customValues[customName] || '';
switch (customType) {
case "title":
return {
title: [
{
text: {
content: value,
},
},
],
};
case "rich_text":
return {
rich_text: [
{
text: {
content: value || '',
},
},
],
};
case "date":
return {
date: {
start: value || new Date().toISOString(),
},
};
case "number":
return {
number: Number(value),
};
case "phone_number":
return {
phone_number: value,
};
case "email":
return {
email: value,
};
case "url":
return {
url: value,
};
case "files":
return {
files: Array.isArray(value) ? value.map(url => ({
name: url,
type: "external",
external: {
url: url,
},
})) : [
{
name: value,
type: "external",
external: {
url: value,
},
},
],
};
case "checkbox":
return {
checkbox: Boolean(value) || false,
};
case "select":
return {
select: {
name: value,
},
};
case "multi_select":
return {
multi_select: Array.isArray(value) ? value.map(item => ({name: item})) : [{name: value}],
};
}
}
private buildBodyString(
customProperties: { customName: string; customType: string }[],
customValues: Record<string, string>,
childArr: any,
) {
const properties: { [key: string]: any } = {};
// Only include custom properties that have values
customProperties.forEach(({customName, customType}) => {
if (customValues[customName] !== undefined) {
properties[customName] = this.buildPropertyObject(customName, customType, customValues);
}
}
);
// console.log(properties)
return {
parent: {
database_id: this.dbDetails.databaseID,
},
properties,
children: childArr,
};
}
}

View File

@@ -0,0 +1,57 @@
import { App, Notice } from "obsidian";
import { i18nConfig } from "../../lang/I18n";
import { DatabaseDetails } from "../../ui/settingTabs";
export async function getNowFileMarkdownContentCustom(
app: App,
dbDetails: DatabaseDetails,
) {
const nowFile = app.workspace.getActiveFile();
if (!nowFile) {
new Notice(i18nConfig["open-file"]);
return;
}
let cover = '';
let customValues: Record<string, any> = {};
const FileCache = app.metadataCache.getFileCache(nowFile);
try {
cover = FileCache.frontmatter.coverurl;
// Get custom property names from dbDetails excluding the title type property
const customPropertyNames = dbDetails.customProperties
.filter(property => property.customType !== 'title') // Exclude 'title' type property
.map(property => property.customName);
// Extract custom values from the front matter based on the names
customPropertyNames.forEach(propertyName => {
if (FileCache.frontmatter && FileCache.frontmatter[propertyName] !== undefined) {
customValues[propertyName] = FileCache.frontmatter[propertyName];
}
});
// Check if any of the customProperties has a customType of 'title'
const titleProperty = dbDetails.customProperties.find(property => property.customType === 'title');
// If a 'title' type property exists, use the file's basename as its value
if (titleProperty) {
customValues[titleProperty.customName] = nowFile.basename; // Use 'basename' for the file name without extension
}
} catch (error) {
new Notice(i18nConfig["set-tags-fail"]);
}
if (nowFile) {
const markDownData = await nowFile.vault.read(nowFile);
return {
markDownData,
nowFile,
cover,
customValues,
};
} else {
new Notice(i18nConfig["open-file"]);
return;
}
}

View File

@@ -1,4 +0,0 @@
/* Sets all the text color to red! */
/* body {
color: red;
} */

View File

@@ -3,6 +3,8 @@
"baseUrl": ".",
"inlineSourceMap": true,
"inlineSources": true,
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"module": "ESNext",
"target": "ES6",
"allowJs": true,
@@ -17,6 +19,8 @@
]
},
"include": [
"**/*.ts"
, "Upload2Notion.ts" ]
"**/*.ts",
"src/Upload2Notion.ts",
"src/*.ts"
]
}

24
update-version.js Normal file
View File

@@ -0,0 +1,24 @@
// update package.json version
import fs from "fs";
const packageJson = JSON.parse(fs.readFileSync("./package.json", "utf8"));
console.log(packageJson.version);
// get node args
const args = process.argv.slice(2);
packageJson.version = args[0];
console.log(packageJson.version);
// write package.json
fs.writeFileSync("./package.json", JSON.stringify(packageJson, null, 2));
// read manifest.json
const manifestJson = JSON.parse(fs.readFileSync("./manifest.json", "utf8"));
manifestJson.version = args[0];
// write manifest.json
fs.writeFileSync("./manifest.json", JSON.stringify(manifestJson, null, 2));

2119
yarn.lock

File diff suppressed because it is too large Load Diff