Skip to content

Commit c1a5485

Browse files
committed
feat: add tags support
1 parent ff2d825 commit c1a5485

File tree

17 files changed

+550
-68
lines changed

17 files changed

+550
-68
lines changed

README.md

+11-4
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,31 @@
11
# ChatGPT to Notion
2+
23
ChatGPT to Notion brings the cleverness of ChatGPT into your favorite app!
34

45
## Overview
6+
57
For information about the extension itself more than the code behind it, check out [this notion page](https://theo-lartigau.notion.site/theo-lartigau/ChatGPT-to-Notion-af29d9538dca4493a15bb4ed0fde7f91). This extension was built using the [Plasmo framework](https://www.plasmo.com/) and Typescript. A simple Express server (hosted on a free Render webservice) that can be found on the "server" branch, it allows the secure long-term storage of Notion's access tokens.
68

79
## Motive
10+
811
While some tools already exist to save a webpage to Notion, and some going as far as allowing the user to save the page's contents, these solutions fall short when trying to save a conversation with ChatGPT. As such, providing a specialized extension to do this work efficiently felt natural.
912

1013
## Usage
14+
1115
On ChatGPT's page, you'll notice a new pin icon under each of its answers. You can use it to save specifically that answer and the related prompt to your Notion database of choice. If you want to save the whole discussion, you can do so from the extension popup.
1216

1317
You may link as many databases with the extension as you need to, and you can then choose which database to save your discussion at saving time. If a page with the same title already exists in the database, the newly saved content will be appended to the end of said page.
1418

1519
## Contribution
20+
1621
As this is my first time building a project that is open to contributions I will need a little time to sort things out and learn the best practices for great collaboration on GitHub. If you want to help (thanks!), please stay patient for a few days.
1722

1823
## Roadmap
24+
1925
These are the things that I plan to work on at some point. It might be a few weeks before these get implemented as I’m currently quite busy, but I hope to have the following (ranked by priority) done in the near future:
2026

21-
- [ ] Saving to a page & not only to a database
22-
- [ ] Customize the page title upon saving
23-
- [ ] Add custom tags when saving
24-
- [x] Upgrade backend (free Render webservice, fixed by setting up a keepalive loop)
27+
- [x] Add custom tags when saving
28+
- [ ] Customize the page title upon saving
29+
- [ ] Refactor the saving logic into a react hook
30+
- [ ] Saving to a page & not only to a database
31+
- [x] Upgrade backend (free Render webservice, fixed by setting up a keepalive loop)

assets/locales/en/messages.json

+24
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@
6767
"message": "Saved!",
6868
"description": "Success message when discussion is saved."
6969
},
70+
"save_noTag": {
71+
"message": "No tag",
72+
"description": "No tag label."
73+
},
7074
"settings_noLinkedDb": {
7175
"message": "No linked DB",
7276
"description": "No linked database message."
@@ -91,6 +95,26 @@
9195
"message": "Add an URL proprty to this DB inside Notion to use it",
9296
"description": "No URL property message."
9397
},
98+
"dbsettings_loading": {
99+
"message": "Loading database",
100+
"description": "Loading database message."
101+
},
102+
"dbsettings_tagsProperty": {
103+
"message": "Tags property",
104+
"description": "Tags property label."
105+
},
106+
"dbsettings_tags": {
107+
"message": "Tags",
108+
"description": "Tags label."
109+
},
110+
"dbsettings_explainer": {
111+
"message": "Upon saving a chat, you'll be prompted to choose one of these tags for the newly created Notion page",
112+
"description": "Tags explainer."
113+
},
114+
"dbsettings_noTags": {
115+
"message": "This database doesn't have any tags (select/multiselect properties)",
116+
"description": "No tags message."
117+
},
94118
"wrongpage_goTo": {
95119
"message": "Go to",
96120
"description": "Go to label."

assets/locales/fr/messages.json

+24
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@
6767
"message": "Enregistré !",
6868
"description": "Success message when discussion is saved."
6969
},
70+
"save_noTag": {
71+
"message": "Aucun",
72+
"description": "No tag label."
73+
},
7074
"settings_noLinkedDb": {
7175
"message": "Pas de BDD reliée",
7276
"description": "No linked database message."
@@ -91,6 +95,26 @@
9195
"message": "Ajoutez une propriété URL à cette BDD dans Notion pour l'utiliser",
9296
"description": "No URL property message."
9397
},
98+
"dbsettings_loading": {
99+
"message": "Chargement de la base de données",
100+
"description": "Loading database message."
101+
},
102+
"dbsettings_tagsProperty": {
103+
"message": "Propriété des mots clés",
104+
"description": "Tags property label."
105+
},
106+
"dbsettings_tags": {
107+
"message": "Mots clés",
108+
"description": "Tags label."
109+
},
110+
"dbsettings_explainer": {
111+
"message": "À l'enregistrement d'une discussion, vous pourrez choisir un de ces mots clés pour la page nouvellement créée",
112+
"description": "Tags explainer."
113+
},
114+
"dbsettings_noTags": {
115+
"message": "Cette BDD n'a pas de mots clés (propriétés sélection)",
116+
"description": "No tags message."
117+
},
94118
"wrongpage_goTo": {
95119
"message": "Allez sur le site de",
96120
"description": "Go to label."

src/api/saveAnswer.ts

+9-7
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { decompress } from "shrink-string"
22

33
import getNotion from "~config/notion"
4-
import Notion from "~config/notion"
5-
import { generateBlocks } from "~utils/functions/notion"
4+
import { generateBlocks, generateTag } from "~utils/functions/notion"
65
import type { StoredDatabase } from "~utils/types"
76

87
// save new page to notion database
@@ -15,18 +14,20 @@ export const saveAnswer = async ({
1514
}: SaveAnswerParams) => {
1615
try {
1716
const notion = await getNotion()
18-
const properties = database.properties
17+
const { propertiesIds, tagPropertyIndex, tagIndex, tags } = database
1918
const decompressedAnswer = await decompress(answer)
2019
const decompressedPrompt = await decompress(prompt)
2120
const { answerBlocks, promptBlocks } = generateBlocks(
2221
decompressedPrompt,
2322
decompressedAnswer
2423
)
2524

25+
const tag = generateTag(tags[tagPropertyIndex], tagIndex)
26+
2627
const searchRes = await notion.databases.query({
2728
database_id: database.id,
2829
filter: {
29-
property: properties.title,
30+
property: propertiesIds.title,
3031
title: {
3132
equals: title
3233
}
@@ -54,7 +55,7 @@ export const saveAnswer = async ({
5455
}
5556
},
5657
properties: {
57-
[properties.title]: {
58+
[propertiesIds.title]: {
5859
title: [
5960
{
6061
text: {
@@ -63,9 +64,10 @@ export const saveAnswer = async ({
6364
}
6465
]
6566
},
66-
[properties.url]: {
67+
[propertiesIds.url]: {
6768
url
68-
}
69+
},
70+
[tags[tagPropertyIndex].id]: tag
6971
},
7072
children: [
7173
{

src/api/saveChat.ts

+9-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import getNotion from "~config/notion"
2-
import { generateBlocks } from "~utils/functions/notion"
2+
import { generateBlocks, generateTag } from "~utils/functions/notion"
33
import type { StoredDatabase } from "~utils/types"
44

55
// save new page to notion database
@@ -12,7 +12,7 @@ export const saveChat = async ({
1212
}: SaveChatParams) => {
1313
try {
1414
const notion = await getNotion()
15-
const { properties } = database
15+
const { propertiesIds, tags, tagIndex, tagPropertyIndex } = database
1616
const blocks = []
1717
for (let i = 0; i < prompts.length; i++) {
1818
const { answerBlocks, promptBlocks } = generateBlocks(
@@ -22,10 +22,12 @@ export const saveChat = async ({
2222
blocks.push(...promptBlocks, ...answerBlocks)
2323
}
2424

25+
const tag = generateTag(tags[tagPropertyIndex], tagIndex)
26+
2527
const searchRes = await notion.databases.query({
2628
database_id: database.id,
2729
filter: {
28-
property: properties.title,
30+
property: propertiesIds.title,
2931
title: {
3032
equals: title
3133
}
@@ -52,7 +54,7 @@ export const saveChat = async ({
5254
}
5355
},
5456
properties: {
55-
[properties.title]: {
57+
[propertiesIds.title]: {
5658
title: [
5759
{
5860
text: {
@@ -61,9 +63,10 @@ export const saveChat = async ({
6163
}
6264
]
6365
},
64-
[properties.url]: {
66+
[propertiesIds.url]: {
6567
url
66-
}
68+
},
69+
[tags[tagPropertyIndex].id]: tag
6770
},
6871
children: [
6972
{

src/background/index.ts

+38-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { getDatabase } from "~api/getDatabase"
55
import { getToken } from "~api/getToken"
66
import { saveAnswer } from "~api/saveAnswer"
77
import { searchNotion } from "~api/search"
8+
import { formatDB } from "~utils/functions/notion"
89
import type { StoredDatabase } from "~utils/types"
910

1011
// API calls that can be made from content scripts transit trough the background script
@@ -34,6 +35,11 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
3435
})
3536
})
3637
break
38+
case "getDB":
39+
getDatabase(message.body.id).then((res) => {
40+
sendResponse(res)
41+
})
42+
break
3743
default:
3844
return true
3945
}
@@ -51,7 +57,7 @@ const authenticate = async () => {
5157
if (_token) {
5258
console.log("token already exists")
5359
await storage.set("authenticated", true)
54-
return
60+
return true
5561
}
5662
// await session.set("token", null)
5763
// await storage.set("workspace_id", null)
@@ -63,7 +69,7 @@ const authenticate = async () => {
6369
])
6470
if (!workspace_id || !user_id) {
6571
console.log("no ids found")
66-
return
72+
return false
6773
}
6874
const token = await getToken({
6975
workspace_id,
@@ -72,6 +78,7 @@ const authenticate = async () => {
7278
await session.set("token", token)
7379
await storage.set("authenticated", true)
7480
console.log("authenticated")
81+
return true
7582
}
7683

7784
const refreshIcons = async () => {
@@ -93,8 +100,36 @@ const refreshIcons = async () => {
93100
}
94101
}
95102

103+
const refreshDatabases = async () => {
104+
console.log("refreshing databases")
105+
const storage = new Storage()
106+
const databases = await storage.get<StoredDatabase[]>("databases")
107+
if (!databases) return
108+
109+
const apiCalls = databases.map((db) => getDatabase(db.id))
110+
const fullDatabases = await Promise.all(apiCalls)
111+
112+
let refreshedDatabases: StoredDatabase[] = []
113+
for (let i = 0; i < fullDatabases.length; i++) {
114+
const db = fullDatabases[i]
115+
if (!db) continue
116+
const formattedDB = formatDB(db)
117+
if (!formattedDB) continue
118+
refreshedDatabases.push(formattedDB)
119+
}
120+
121+
const selectedDB = await storage.get<number>("selectedDB")
122+
if (!!selectedDB && selectedDB >= refreshedDatabases.length) {
123+
await storage.set("selectedDB", 0)
124+
}
125+
await storage.set("databases", refreshedDatabases)
126+
await storage.set("refreshed", true)
127+
}
128+
96129
const main = async () => {
97-
await authenticate()
130+
const authenticated = await authenticate()
131+
if (!authenticated) return
132+
await refreshDatabases()
98133
refreshIcons()
99134
}
100135

src/common/components/Dropdown.tsx

+7-4
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,13 @@ export default function DropdownPopup({
1111
const pos = { up: "bottom-2", down: "top-2" }
1212

1313
return (
14-
<div>
14+
<span className="w-fit">
1515
<Menu>
16-
<Menu.Button className={className}>{children}</Menu.Button>
16+
<Menu.Button className={`relative ${className}`}>
17+
{children}
18+
</Menu.Button>
1719
<Transition
20+
className="relative"
1821
enter="transition duration-100 ease-out"
1922
enterFrom="transform scale-95 opacity-0"
2023
enterTo="transform scale-100 opacity-100"
@@ -23,7 +26,7 @@ export default function DropdownPopup({
2326
leaveTo="transform scale-95 opacity-0">
2427
<Menu.Items
2528
className={`absolute flex flex-col bg-white border border-gray-400
26-
rounded shadow ${pos[position]} -right-2 z-10`}>
29+
rounded shadow ${pos[position]} -right-2 z-10 max-h-36 overflow-auto`}>
2730
{items.map((item, idx) => (
2831
<Menu.Item key={`${keysBase}${idx}`}>
2932
{({ active }) => (
@@ -39,7 +42,7 @@ export default function DropdownPopup({
3942
</Menu.Items>
4043
</Transition>
4144
</Menu>
42-
</div>
45+
</span>
4346
)
4447
}
4548

0 commit comments

Comments
 (0)