diff --git a/README.md b/README.md
index ca231fc..4b769bf 100644
--- a/README.md
+++ b/README.md
@@ -18,3 +18,4 @@ LLM Integration ([English](docs/llm/llm-integration.en.md), [Español](docs/llm/
Development Conditions ([English](docs/meta/Copilot.md))
For more details, check the documentation in the [docs](docs/) folder.
+- The [Notion Editor](docs/notion-editor.md) template demonstrates a Notion-style layout with a basic AI extension.
diff --git a/docs/README.md b/docs/README.md
index 91166e0..baa34fb 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -9,6 +9,7 @@ Welcome to the documentation for the macOS editor. Below is a summary of the ava
- [Build Script](build-script.md)
- [Package.json File](package-json.md)
- [Splash Screen Functionality](splash-screen.md)
+- [Notion Editor](notion-editor.md)
- [LLM Integration](llm/llm-integration.en.md)
## Documentation Structure
@@ -22,5 +23,6 @@ graph TD;
A --> F[Build Script];
A --> G[Package.json File];
A --> H[Splash Screen Functionality];
+ A --> I[Notion Editor];
A --> I[LLM Integration];
```
diff --git a/docs/notion-editor.md b/docs/notion-editor.md
new file mode 100644
index 0000000..def458a
--- /dev/null
+++ b/docs/notion-editor.md
@@ -0,0 +1,3 @@
+# Notion Editor
+
+The Notion Editor uses the same toolbar from the Simple Editor but applies a Notion-style layout. It also loads a placeholder AI extension called `Agent` that demonstrates how to insert generated text with a custom command.
diff --git a/src/components/tiptap-extension/agent-extension.ts b/src/components/tiptap-extension/agent-extension.ts
new file mode 100644
index 0000000..6704309
--- /dev/null
+++ b/src/components/tiptap-extension/agent-extension.ts
@@ -0,0 +1,18 @@
+import { Extension } from "@tiptap/react"
+
+export const Agent = Extension.create({
+ name: "agent",
+ addCommands() {
+ return {
+ generate:
+ (prompt: string) =>
+ async ({ commands }) => {
+ const aiText = `AI: ${prompt}`
+ commands.insertContent(aiText)
+ return true
+ },
+ }
+ },
+})
+
+export default Agent
diff --git a/src/components/tiptap-templates/notion/data/content.json b/src/components/tiptap-templates/notion/data/content.json
new file mode 100644
index 0000000..b4e2ae9
--- /dev/null
+++ b/src/components/tiptap-templates/notion/data/content.json
@@ -0,0 +1,477 @@
+{
+ "type": "doc",
+ "content": [
+ {
+ "type": "heading",
+ "attrs": {
+ "textAlign": null,
+ "level": 1
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Getting started"
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "attrs": {
+ "textAlign": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Welcome to the "
+ },
+ {
+ "type": "text",
+ "marks": [
+ {
+ "type": "italic"
+ },
+ {
+ "type": "highlight",
+ "attrs": {
+ "color": "var(--tt-highlight-yellow)"
+ }
+ }
+ ],
+ "text": "Simple Editor"
+ },
+ {
+ "type": "text",
+ "text": " template! This template integrates "
+ },
+ {
+ "type": "text",
+ "marks": [
+ {
+ "type": "bold"
+ }
+ ],
+ "text": "open source"
+ },
+ {
+ "type": "text",
+ "text": " UI components and Tiptap extensions licensed under "
+ },
+ {
+ "type": "text",
+ "marks": [
+ {
+ "type": "bold"
+ }
+ ],
+ "text": "MIT"
+ },
+ {
+ "type": "text",
+ "text": "."
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "attrs": {
+ "textAlign": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Integrate it by following the "
+ },
+ {
+ "type": "text",
+ "marks": [
+ {
+ "type": "link",
+ "attrs": {
+ "href": "https://tiptap.dev/docs/ui-components/templates/simple-editor",
+ "target": "_blank",
+ "rel": "noopener noreferrer nofollow",
+ "class": null
+ }
+ }
+ ],
+ "text": "Tiptap UI Components docs"
+ },
+ {
+ "type": "text",
+ "text": " or using our CLI tool."
+ }
+ ]
+ },
+ {
+ "type": "codeBlock",
+ "attrs": {
+ "language": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "npx @tiptap/cli init"
+ }
+ ]
+ },
+ {
+ "type": "heading",
+ "attrs": {
+ "textAlign": null,
+ "level": 2
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Features"
+ }
+ ]
+ },
+ {
+ "type": "blockquote",
+ "content": [
+ {
+ "type": "paragraph",
+ "attrs": {
+ "textAlign": null
+ },
+ "content": [
+ {
+ "type": "text",
+ "marks": [
+ {
+ "type": "italic"
+ }
+ ],
+ "text": "A fully responsive rich text editor with built-in support for common formatting and layout tools. Type markdown "
+ },
+ {
+ "type": "text",
+ "marks": [
+ {
+ "type": "code"
+ }
+ ],
+ "text": "**"
+ },
+ {
+ "type": "text",
+ "marks": [
+ {
+ "type": "italic"
+ }
+ ],
+ "text": " or use keyboard shortcuts "
+ },
+ {
+ "type": "text",
+ "marks": [
+ {
+ "type": "code"
+ }
+ ],
+ "text": "⌘+B"
+ },
+ {
+ "type": "text",
+ "text": " for "
+ },
+ {
+ "type": "text",
+ "marks": [
+ {
+ "type": "strike"
+ }
+ ],
+ "text": "most"
+ },
+ {
+ "type": "text",
+ "text": " all common markdown marks. 🪄"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "attrs": {
+ "textAlign": "left"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Add images, customize alignment, and apply "
+ },
+ {
+ "type": "text",
+ "marks": [
+ {
+ "type": "highlight",
+ "attrs": {
+ "color": "var(--tt-highlight-blue)"
+ }
+ }
+ ],
+ "text": "advanced formatting"
+ },
+ {
+ "type": "text",
+ "text": " to make your writing more engaging and professional."
+ }
+ ]
+ },
+ {
+ "type": "image",
+ "attrs": {
+ "src": "/images/placeholder-image.png",
+ "alt": "placeholder-image",
+ "title": "placeholder-image"
+ }
+ },
+ {
+ "type": "bulletList",
+ "content": [
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "attrs": {
+ "textAlign": "left"
+ },
+ "content": [
+ {
+ "type": "text",
+ "marks": [
+ {
+ "type": "bold"
+ }
+ ],
+ "text": "Superscript"
+ },
+ {
+ "type": "text",
+ "text": " (x"
+ },
+ {
+ "type": "text",
+ "marks": [
+ {
+ "type": "superscript"
+ }
+ ],
+ "text": "2"
+ },
+ {
+ "type": "text",
+ "text": ") and "
+ },
+ {
+ "type": "text",
+ "marks": [
+ {
+ "type": "bold"
+ }
+ ],
+ "text": "Subscript"
+ },
+ {
+ "type": "text",
+ "text": " (H"
+ },
+ {
+ "type": "text",
+ "marks": [
+ {
+ "type": "subscript"
+ }
+ ],
+ "text": "2"
+ },
+ {
+ "type": "text",
+ "text": "O) for precision."
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "listItem",
+ "content": [
+ {
+ "type": "paragraph",
+ "attrs": {
+ "textAlign": "left"
+ },
+ "content": [
+ {
+ "type": "text",
+ "marks": [
+ {
+ "type": "bold"
+ }
+ ],
+ "text": "Typographic conversion"
+ },
+ {
+ "type": "text",
+ "text": ": automatically convert to "
+ },
+ {
+ "type": "text",
+ "marks": [
+ {
+ "type": "code"
+ }
+ ],
+ "text": "->"
+ },
+ {
+ "type": "text",
+ "text": " an arrow "
+ },
+ {
+ "type": "text",
+ "marks": [
+ {
+ "type": "bold"
+ }
+ ],
+ "text": "→"
+ },
+ {
+ "type": "text",
+ "text": "."
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "attrs": {
+ "textAlign": "left"
+ },
+ "content": [
+ {
+ "type": "text",
+ "marks": [
+ {
+ "type": "italic"
+ }
+ ],
+ "text": "→ "
+ },
+ {
+ "type": "text",
+ "marks": [
+ {
+ "type": "link",
+ "attrs": {
+ "href": "https://tiptap.dev/docs/ui-components/templates/simple-editor#features",
+ "target": "_blank",
+ "rel": "noopener noreferrer nofollow",
+ "class": null
+ }
+ }
+ ],
+ "text": "Learn more"
+ }
+ ]
+ },
+ {
+ "type": "horizontalRule"
+ },
+ {
+ "type": "heading",
+ "attrs": {
+ "textAlign": "left",
+ "level": 2
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Make it your own"
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "attrs": {
+ "textAlign": "left"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Switch between light and dark modes, and tailor the editor's appearance with customizable CSS to match your style."
+ }
+ ]
+ },
+ {
+ "type": "taskList",
+ "content": [
+ {
+ "type": "taskItem",
+ "attrs": {
+ "checked": true
+ },
+ "content": [
+ {
+ "type": "paragraph",
+ "attrs": {
+ "textAlign": "left"
+ },
+ "content": [
+ {
+ "type": "text",
+ "text": "Test template"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "taskItem",
+ "attrs": {
+ "checked": false
+ },
+ "content": [
+ {
+ "type": "paragraph",
+ "attrs": {
+ "textAlign": "left"
+ },
+ "content": [
+ {
+ "type": "text",
+ "marks": [
+ {
+ "type": "link",
+ "attrs": {
+ "href": "https://tiptap.dev/docs/ui-components/templates/simple-editor",
+ "target": "_blank",
+ "rel": "noopener noreferrer nofollow",
+ "class": null
+ }
+ }
+ ],
+ "text": "Integrate the free template"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "type": "paragraph",
+ "attrs": {
+ "textAlign": "left"
+ }
+ }
+ ]
+}
diff --git a/src/components/tiptap-templates/notion/notion-editor.scss b/src/components/tiptap-templates/notion/notion-editor.scss
new file mode 100644
index 0000000..757ec58
--- /dev/null
+++ b/src/components/tiptap-templates/notion/notion-editor.scss
@@ -0,0 +1,74 @@
+@import url("https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap");
+
+body {
+ --tt-toolbar-height: 44px;
+ --tt-theme-text: var(--tt-gray-light-900);
+
+ .dark & {
+ --tt-theme-text: var(--tt-gray-dark-900);
+ }
+}
+
+body {
+ font-family: "Inter", sans-serif;
+ color: var(--tt-theme-text);
+ font-optical-sizing: auto;
+ font-weight: 400;
+ font-style: normal;
+ padding: 0;
+}
+
+html,
+body,
+#root,
+#app {
+ height: 100%;
+ background-color: var(--tt-bg-color);
+}
+
+body {
+ overflow: hidden;
+}
+
+.tiptap.ProseMirror {
+ font-family: "DM Sans", sans-serif;
+}
+
+.content-wrapper {
+ height: calc(100% - var(--tt-toolbar-height));
+ overflow-y: auto;
+
+ &::-webkit-scrollbar {
+ display: block;
+ width: 0.5rem;
+ }
+
+ &::-webkit-scrollbar-track {
+ background: transparent;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background-color: var(--tt-scrollbar-color);
+ border-radius: 4px;
+ }
+
+ /* Firefox scrollbar */
+ scrollbar-width: thin;
+ scrollbar-color: var(--tt-scrollbar-color) transparent;
+}
+
+.notion-editor-content {
+ max-width: 640px;
+ width: 100%;
+ margin: 0 auto;
+}
+
+.notion-editor-content .tiptap.ProseMirror {
+ padding: 3rem 3rem;
+}
+
+@media screen and (max-width: 480px) {
+ .notion-editor-content .tiptap.ProseMirror {
+ padding: 1rem 1.5rem;
+ }
+}
diff --git a/src/components/tiptap-templates/notion/notion-editor.tsx b/src/components/tiptap-templates/notion/notion-editor.tsx
new file mode 100644
index 0000000..3c9bc15
--- /dev/null
+++ b/src/components/tiptap-templates/notion/notion-editor.tsx
@@ -0,0 +1,270 @@
+import * as React from "react"
+import { EditorContent, EditorContext, useEditor } from "@tiptap/react"
+
+// --- Tiptap Core Extensions ---
+import { StarterKit } from "@tiptap/starter-kit"
+import { Image } from "@tiptap/extension-image"
+import { TaskItem } from "@tiptap/extension-task-item"
+import { TaskList } from "@tiptap/extension-task-list"
+import { TextAlign } from "@tiptap/extension-text-align"
+import { Typography } from "@tiptap/extension-typography"
+import { Highlight } from "@tiptap/extension-highlight"
+import { Subscript } from "@tiptap/extension-subscript"
+import { Superscript } from "@tiptap/extension-superscript"
+import { Underline } from "@tiptap/extension-underline"
+
+// --- Custom Extensions ---
+import { Link } from "@/components/tiptap-extension/link-extension"
+import { Selection } from "@/components/tiptap-extension/selection-extension"
+import { TrailingNode } from "@/components/tiptap-extension/trailing-node-extension"
+
+// --- UI Primitives ---
+import { Button } from "@/components/tiptap-ui-primitive/button"
+import { Spacer } from "@/components/tiptap-ui-primitive/spacer"
+import {
+ Toolbar,
+ ToolbarGroup,
+ ToolbarSeparator,
+} from "@/components/tiptap-ui-primitive/toolbar"
+
+// --- Tiptap Node ---
+import { ImageUploadNode } from "@/components/tiptap-node/image-upload-node/image-upload-node-extension"
+import "@/components/tiptap-node/code-block-node/code-block-node.scss"
+import "@/components/tiptap-node/list-node/list-node.scss"
+import "@/components/tiptap-node/image-node/image-node.scss"
+import "@/components/tiptap-node/paragraph-node/paragraph-node.scss"
+
+// --- Tiptap UI ---
+import { HeadingDropdownMenu } from "@/components/tiptap-ui/heading-dropdown-menu"
+import { ImageUploadButton } from "@/components/tiptap-ui/image-upload-button"
+import { ListDropdownMenu } from "@/components/tiptap-ui/list-dropdown-menu"
+import { NodeButton } from "@/components/tiptap-ui/node-button"
+import {
+ HighlightPopover,
+ HighlightContent,
+ HighlighterButton,
+} from "@/components/tiptap-ui/highlight-popover"
+import {
+ LinkPopover,
+ LinkContent,
+ LinkButton,
+} from "@/components/tiptap-ui/link-popover"
+import { MarkButton } from "@/components/tiptap-ui/mark-button"
+import { TextAlignButton } from "@/components/tiptap-ui/text-align-button"
+import { UndoRedoButton } from "@/components/tiptap-ui/undo-redo-button"
+
+// --- Icons ---
+import { ArrowLeftIcon } from "@/components/tiptap-icons/arrow-left-icon"
+import { HighlighterIcon } from "@/components/tiptap-icons/highlighter-icon"
+import { LinkIcon } from "@/components/tiptap-icons/link-icon"
+
+// --- Hooks ---
+import { useMobile } from "@/hooks/use-mobile"
+import { useWindowSize } from "@/hooks/use-window-size"
+import { useCursorVisibility } from "@/hooks/use-cursor-visibility"
+
+// --- Components ---
+import { ThemeToggle } from "@/components/tiptap-templates/simple/theme-toggle"
+
+// --- Lib ---
+import { handleImageUpload, MAX_FILE_SIZE } from "@/lib/tiptap-utils"
+import { Agent } from "@/components/tiptap-extension/agent-extension"
+
+// --- Styles ---
+import "@/components/tiptap-templates/notion/notion-editor.scss"
+
+import content from "@/components/tiptap-templates/notion/data/content.json"
+
+const MainToolbarContent = ({
+ onHighlighterClick,
+ onLinkClick,
+ isMobile,
+}: {
+ onHighlighterClick: () => void
+ onLinkClick: () => void
+ isMobile: boolean
+}) => {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {!isMobile ? (
+
+ ) : (
+
+ )}
+ {!isMobile ? : }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {isMobile && }
+
+
+
+
+ >
+ )
+}
+
+const MobileToolbarContent = ({
+ type,
+ onBack,
+}: {
+ type: "highlighter" | "link"
+ onBack: () => void
+}) => (
+ <>
+
+
+
+
+
+
+ {type === "highlighter" ? : }
+ >
+)
+
+export function NotionEditor() {
+ const isMobile = useMobile()
+ const windowSize = useWindowSize()
+ const [mobileView, setMobileView] = React.useState<
+ "main" | "highlighter" | "link"
+ >("main")
+ const toolbarRef = React.useRef(null)
+
+ const editor = useEditor({
+ immediatelyRender: false,
+ editorProps: {
+ attributes: {
+ autocomplete: "off",
+ autocorrect: "off",
+ autocapitalize: "off",
+ "aria-label": "Main content area, start typing to enter text.",
+ },
+ },
+ extensions: [
+ StarterKit,
+ TextAlign.configure({ types: ["heading", "paragraph"] }),
+ Underline,
+ TaskList,
+ TaskItem.configure({ nested: true }),
+ Highlight.configure({ multicolor: true }),
+ Image,
+ Typography,
+ Superscript,
+ Subscript,
+
+ Selection,
+ ImageUploadNode.configure({
+ accept: "image/*",
+ maxSize: MAX_FILE_SIZE,
+ limit: 3,
+ upload: handleImageUpload,
+ onError: (error) => console.error("Upload failed:", error),
+ }),
+ TrailingNode,
+ Link.configure({ openOnClick: false }),
+ Agent,
+ ],
+ content: content,
+ })
+
+ const bodyRect = useCursorVisibility({
+ editor,
+ overlayHeight: toolbarRef.current?.getBoundingClientRect().height ?? 0,
+ })
+
+ React.useEffect(() => {
+ if (!isMobile && mobileView !== "main") {
+ setMobileView("main")
+ }
+ }, [isMobile, mobileView])
+
+ return (
+
+
+ {mobileView === "main" ? (
+ setMobileView("highlighter")}
+ onLinkClick={() => setMobileView("link")}
+ isMobile={isMobile}
+ />
+ ) : (
+ setMobileView("main")}
+ />
+ )}
+
+
+
+
+
+
+ )
+}
diff --git a/src/main.tsx b/src/main.tsx
index de76f80..527872e 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,6 +1,6 @@
import { createRoot } from "react-dom/client"
import { useState, useEffect } from "react"
-import { SimpleEditor } from "@/components/tiptap-templates/simple/simple-editor"
+import { NotionEditor } from "@/components/tiptap-templates/notion/notion-editor"
import { SplashScreen } from "@/components/splash-screen/splash-screen"
import { AppRegistry } from 'react-native';
import { name as appName } from './app.json';
@@ -26,7 +26,7 @@ const App = () => {
return (
<>
{showSplashScreen && }
- {appReady && }
+ {appReady && }
>
);
};