Skip to content

Convert Notion plugin to use CMS Starter #221

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 16 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,286 changes: 1,250 additions & 36 deletions package-lock.json

Large diffs are not rendered by default.

File renamed without changes.
32 changes: 32 additions & 0 deletions plugins/notion-legacy/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

/templates/**/package.json
/templates/**/yarn.lock
/templates/**/node_modules
/templates/**/.pnp
/templates/**/.pnp.js
/templates/**/.yarn


# misc
.DS_Store
*.pem

# files
my-plugin
dev-plugin
dist

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local
7 changes: 7 additions & 0 deletions plugins/notion-legacy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Framer Notion Plugin

Framer Notion Sync Plugin

**By:** @edoardomercati and @niekert

![Notion Image](../../assets/notion.png)
6 changes: 6 additions & 0 deletions plugins/notion-legacy/framer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"id": "e00c8d",
"name": "Notion (Legacy)",
"modes": ["configureManagedCollection", "syncManagedCollection"],
"icon": "/icon.png"
}
13 changes: 13 additions & 0 deletions plugins/notion-legacy/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Notion Plugin</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
40 changes: 40 additions & 0 deletions plugins/notion-legacy/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"name": "notion-legacy",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build --base=${PREFIX_BASE_PATH:+/$npm_package_name}/",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"pack": "npx framer-plugin-tools@latest pack",
"preview": "vite preview",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@notionhq/client": "^2.2.15",
"@tanstack/react-query": "^5.29.2",
"classnames": "^2.5.1",
"framer-plugin": "^3.1.0",
"react": "^18",
"react-dom": "^18",
"react-error-boundary": "^4.0.13",
"vite-plugin-mkcert": "^1"
},
"devDependencies": {
"@types/react": "^18",
"@types/react-dom": "^18",
"@typescript-eslint/eslint-plugin": "^7",
"@typescript-eslint/parser": "^7",
"@vitejs/plugin-react-swc": "^3",
"autoprefixer": "^10.4.19",
"eslint": "^8",
"eslint-plugin-react-hooks": "^4",
"eslint-plugin-react-refresh": "^0",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.3",
"typescript": "^5.3.3",
"vite": "^6",
"vite-plugin-framer": "^1.0.7"
}
}
Binary file added plugins/notion-legacy/public/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 37 additions & 0 deletions plugins/notion-legacy/src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/* Your Plugin CSS */

main {
display: flex;
flex-direction: column;
align-items: start;
padding: 0 15px 15px 15px;
height: 100%;
gap: 15px;
}

/* I know this is not good but I didn't have time to fight with Tailwind. */
[data-framer-theme="light"] .tailwind-hell-escape-hatch-checkbox[type="checkbox"]:not(:checked) {
background: rgba(0, 0, 0, 0.1);
}

[data-framer-theme="dark"] .tailwind-hell-escape-hatch-checkbox[type="checkbox"]:not(:checked) {
background: rgba(255, 255, 255, 0.1);
}

.tailwind-hell-escape-hatch-gradient-top {
background: linear-gradient(var(--framer-color-bg), color-mix(in srgb, transparent, var(--framer-color-bg) 0%));
position: fixed;
left: 15px;
right: 15px;
height: 15px;
top: 0;
}

.tailwind-hell-escape-hatch-gradient-bottom {
background: linear-gradient(color-mix(in srgb, transparent, var(--framer-color-bg) 0%), var(--framer-color-bg));
position: absolute;
width: 100%;
height: 30px;
top: 0;
transform: translateY(calc(-100% - 1px));
}
84 changes: 84 additions & 0 deletions plugins/notion-legacy/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { framer } from "framer-plugin"
import { useEffect, useState } from "react"
import "./App.css"
import { PluginContext, PluginContextNew, PluginContextUpdate, useSynchronizeDatabaseMutation } from "./notion"

import { GetDatabaseResponse } from "@notionhq/client/build/src/api-endpoints"
import { SelectDatabase } from "./SelectDatabase"
import { MapDatabaseFields } from "./MapFields"
import { logSyncResult } from "./debug"
import { Authentication } from "./Authenticate"

interface AppProps {
context: PluginContext
}

interface AuthenticatedAppProps {
context: PluginContextNew | PluginContextUpdate
}

export function AuthenticatedApp({ context }: AuthenticatedAppProps) {
const [databaseConfig, setDatabaseConfig] = useState<GetDatabaseResponse | null>(
context.type === "update" ? context.database : null
)

useEffect(() => {
const width = databaseConfig ? 600 : 325
const height = databaseConfig ? 500 : 370
framer.showUI({
width,
height,
minWidth: width,
minHeight: height,
// Only allow resizing when mapping fields as the default size could not be enough.
// This will keep the given dimensions in the Splash Screen.
resizable: Boolean(databaseConfig),
})
}, [databaseConfig])

const synchronizeMutation = useSynchronizeDatabaseMutation(databaseConfig, {
onError(error) {
framer.notify(error.message, { variant: "error" })
},
onSuccess(result) {
logSyncResult(result)

if (result.status === "success") {
framer.closePlugin("Synchronization successful")
return
}
},
})

const handleDatabaseSelected = (database: GetDatabaseResponse) => {
context.databaseIdMap.set(database.id, context.collection.id)
setDatabaseConfig(database)
}

if (!databaseConfig) {
return <SelectDatabase onDatabaseSelected={handleDatabaseSelected} />
}

return (
<MapDatabaseFields
database={databaseConfig}
pluginContext={context}
onSubmit={synchronizeMutation.mutate}
isLoading={synchronizeMutation.isPending}
/>
)
}

export function App({ context }: AppProps) {
const [pluginContext, setPluginContext] = useState(context)

const handleAuthenticated = (authenticatedContext: PluginContext) => {
setPluginContext(authenticatedContext)
}

if (!pluginContext.isAuthenticated) {
return <Authentication context={pluginContext} onAuthenticated={handleAuthenticated} />
}

return <AuthenticatedApp context={pluginContext} />
}
104 changes: 104 additions & 0 deletions plugins/notion-legacy/src/blocksToHTML.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { BlockObjectResponse, RichTextItemResponse } from "@notionhq/client/build/src/api-endpoints"
import { assert } from "./utils"

export function richTextToHTML(texts: RichTextItemResponse[]) {
return texts
.map(({ plain_text, annotations, href }) => {
let html = plain_text

// Apply formatting based on annotations
if (annotations.bold) {
html = `<strong>${html}</strong>`
}
if (annotations.italic) {
html = `<em>${html}</em>`
}
if (annotations.strikethrough) {
html = `<del>${html}</del>`
}
if (annotations.underline) {
html = `<u>${html}</u>`
}

if (annotations.code) {
html = `<code>${html}</code>`
}

if (annotations.color !== "default") {
const color = annotations.color.replace("_", "")
html = `<span style="color:${color}">${html}</span>`
}

if (href) {
html = `<a href="${href}" target="_blank" rel="noopener noreferrer">${html}</a>`
}

return html
})
.join("")
}

export function blocksToHtml(blocks: BlockObjectResponse[]) {
let htmlContent = ""

for (let i = 0; i < blocks.length; i++) {
const block = blocks[i]
assert(block)

switch (block.type) {
case "paragraph":
htmlContent += `<p>${richTextToHTML(block.paragraph.rich_text)}</p>`
break
case "heading_1":
htmlContent += `<h1>${richTextToHTML(block.heading_1.rich_text)}</h1>`
break
case "heading_2":
htmlContent += `<h2>${richTextToHTML(block.heading_2.rich_text)}</h2>`
break
case "heading_3":
htmlContent += `<h3>${richTextToHTML(block.heading_3.rich_text)}</h3>`
break
case "divider":
htmlContent += "<hr >"
break
case "image":
switch (block.image.type) {
case "external":
htmlContent += `<img src="${block.image.external.url}" alt="${block.image.caption[0]?.plain_text}" />`
break
case "file":
htmlContent += `<img src="${block.image.file.url}" alt="${block.image.caption[0]?.plain_text}" />`
break
}
break
case "bulleted_list_item":
case "numbered_list_item": {
const tag = block.type === "bulleted_list_item" ? "ul" : "ol"

// Start the list if it's the first item of its type or the previous item isn't a list of the same type
if (i === 0 || blocks[i - 1].type !== block.type) htmlContent += `<${tag}>`

if (block.type === "bulleted_list_item") {
htmlContent += `<li>${richTextToHTML(block.bulleted_list_item.rich_text)}</li>`
} else {
// Add the list item
htmlContent += `<li>${richTextToHTML(block.numbered_list_item.rich_text)}</li>`
}

// If next block is not the same type, close the list
if (i === blocks.length - 1 || blocks[i + 1].type !== block.type) {
htmlContent += `</${tag}>`
}
break
}
case "code":
htmlContent += `<pre><code class="language-${block.code.language.replace(" ", "-")}">${richTextToHTML(block.code.rich_text)}</code></pre>`
break
default:
// TODO: More block types can be added here!
break
}
}

return htmlContent
}
File renamed without changes.
File renamed without changes.
Loading