diff --git a/app/typr/PageContent.js b/app/typr/PageContent.js new file mode 100644 index 00000000..7de8cd05 --- /dev/null +++ b/app/typr/PageContent.js @@ -0,0 +1,650 @@ +"use client"; + +import React, { useState, useEffect } from "react"; +import { useRouter, useSearchParams, usePathname } from "next/navigation"; +import { + savePost, + createPost, + loadPostById, + DB_NAME, + STORE_NAME, +} from "@/lib/typr-demo/indexedDB"; // Import the IndexedDB utility + + + +import "tiptypr/dist/styles.css"; +import Tiptypr from "tiptypr"; +// import Head from 'next/head'; + +import { + Cross2Icon, + HamburgerMenuIcon, + ChevronDownIcon, + CubeIcon, + EyeOpenIcon, + EyeClosedIcon, +} from "@radix-ui/react-icons"; + +import GitHubButton from "react-github-btn"; +import ThemeSelector from "@/components/typr-demo/ThemeSelector"; +import RequireLoginCheckbox from "@/components/typr-demo/RequireLoginCheckbox"; +import EnablePublishingFlowCheckbox from "@/components/typr-demo/EnablePublishingFLowCheckbox"; +import NavSettings from "@/components/typr-demo/NavSettings"; +import DemoCodeDialog from "@/components/typr-demo/DemoCodeDialog"; +import GeneralSettingsPanel from "@/components/typr-demo/GeneralSettingsPanel"; +import SeoPanel from "@/components/typr-demo/SeoPanel"; +import UserPopover from "@/components/typr-demo/UserPopover"; +import Layout from "@/components/new-index/layoutForDemo"; + +import * as Accordion from "@radix-ui/react-accordion"; + +import { defaultProps } from "tiptypr"; +import { customDeepMerge } from "@/lib/typr-demo/customDeepMerge"; +import IndexedDBBrowser from "@/components/typr-demo/IndexedDBBrowser"; +import { openDB } from "idb"; +import KoFiDialog from "@/components/ko-fi-button/KoFiDialog"; + +const avatarOptions = [ + { + name: "Avatartion", + imgSrc: + "https://prototypr-media.sfo2.digitaloceanspaces.com/strapi/32a56359cbe6a680ac1eb8eb659c46eb.png", + }, + { name: "Pravatar", imgSrc: "https://i.pravatar.cc/300" }, + { + name: "Pixel (xsgames)", + imgSrc: "https://xsgames.co/randomusers/avatar.php?g=pixel", + }, + { + name: "UI Avatars", + imgSrc: "https://ui-avatars.com/api/?background=random", + }, + { name: "RoboHash", imgSrc: "https://robohash.org/random" }, + { + name: "Female (xsgames)", + imgSrc: "https://xsgames.co/randomusers/avatar.php?g=female", + }, + { + name: "Male (xsgames)", + imgSrc: "https://xsgames.co/randomusers/avatar.php?g=male", + }, +]; + +const filterModifiedProps = (defaultProps, currentProps) => { + const modifiedProps = {}; + for (const key in currentProps) { + if ( + typeof currentProps[key] === "object" && + !Array.isArray(currentProps[key]) + ) { + const nestedModifiedProps = filterModifiedProps( + defaultProps[key] || {}, + currentProps[key] + ); + if (Object.keys(nestedModifiedProps).length > 0) { + modifiedProps[key] = nestedModifiedProps; + } + } else if (currentProps[key] !== defaultProps[key]) { + modifiedProps[key] = currentProps[key]; + } + } + return modifiedProps; +}; + +const serializeComponents = components => { + const replacer = (key, value) => { + if (React.isValidElement(value)) { + return `<${value.type.name || value.type.displayName || "Component"} />`; + } + if (typeof value === "function") { + return true; // Replace functions with true + } + return value; + }; + return JSON.stringify(components, replacer, 2); +}; +function DemoPageContent() { + const router = useRouter(); + const searchParams = useSearchParams(); + const pathname = usePathname(); + + const [postId, setPostId] = useState(-1); + + useEffect(() => { + // Check if the router is ready + if (pathname !== null) { + const id = searchParams.get("id"); + + if (id) { + setPostId(id); + } else { + // create new post + setPostId(false); + } + } + }, [searchParams, pathname]); + + const [editorProps, setEditorProps] = useState(() => { + return customDeepMerge(defaultProps, { + requireLogin: true, + enablePublishingFlow: true, + theme: "blue", + user: { + id: 1, + isLoggedIn: true, + isAdmin: true, + }, + components: { + nav: { + show: true, + position: "sticky", + userBadge: { + avatarPlaceholder: avatarOptions[0].imgSrc, + }, + }, + }, + }); + }); + + const [isPanelOpen, setIsPanelOpen] = useState(false); + const [isDialogOpen, setIsDialogOpen] = useState(false); + const [openAccordion, setOpenAccordion] = useState(null); + + const [isDatabasePanelOpen, setIsDatabasePanelOpen] = useState(true); + + useEffect(() => { + if (openAccordion) { + setIsDatabasePanelOpen(false); + } + }, [openAccordion]); + + const handleSeoMenuChange = newSeoMenu => { + setEditorProps(prevProps => ({ + ...prevProps, + components: { + ...prevProps.components, + settingsPanel: { + ...prevProps.components.settingsPanel, + seoTab: { + ...prevProps.components.settingsPanel.seoTab, + menu: newSeoMenu, + }, + }, + }, + })); + }; + const handleGeneralMenuChange = newGeneralMenu => { + setEditorProps(prevProps => ({ + ...prevProps, + components: { + ...prevProps.components, + settingsPanel: { + ...prevProps.components.settingsPanel, + generalTab: { + ...prevProps.components.settingsPanel.generalTab, + menu: newGeneralMenu, + }, + }, + }, + })); + }; + + const themeOptions = [ + { value: "gray", label: "Gray" }, + { value: "blue", label: "Blue" }, + ]; + + const handleThemeChange = value => { + setEditorProps({ ...editorProps, theme: value }); + }; + + const handleRequireLoginChange = checked => { + setEditorProps({ ...editorProps, requireLogin: checked }); + }; + + const handleEnablePublishingFlowChange = checked => { + setEditorProps({ ...editorProps, enablePublishingFlow: checked }); + }; + + // const handleCustomPostStatusesChange = checked => { + // setEditorProps({ ...editorProps, customPostStatuses: checked }); + // }; + + const handleNavChange = nav => { + setEditorProps({ + ...editorProps, + components: { + ...editorProps.components, + nav, + }, + }); + }; + + // const handleSettingsChange = (field, value) => { + // // Update the settings based on the field and value + // console.log(`${field} changed to:`, value); + // }; + + const handleUserChange = (field, value) => { + setEditorProps(prevProps => ({ + ...prevProps, + user: { + ...prevProps.user, + [field]: value, + }, + })); + }; + + const modifiedProps = filterModifiedProps(defaultProps, editorProps); + + const demoCode = ` + { + const params = new URLSearchParams(searchParams); + params.set("id", id); + router.push(\`?\${params.toString()}\`, undefined, { + shallow: true, + scroll: false, + }); + }, + }} + /> + `; + + const [data, setData] = useState([]); + + const fetchData = async () => { + // const db = await openDB(DB_NAME, 1); + const db = await openDB(DB_NAME); + const tx = db.transaction(STORE_NAME, "readonly"); + const store = tx.objectStore(STORE_NAME); + const allData = await store.getAll(); + setData(allData); + }; + + useEffect(() => { + fetchData(); + }, []); + + return ( + +
+ +
+
+

+ Editor Config +

+ + { + setOpenAccordion(tabName); + if (tabName == "general-settings" || tabName == "seo-panel") { + const settingsMenuBtn = + document.getElementById("settings-menu-btn"); + if ( + settingsMenuBtn && + !settingsMenuBtn.classList.contains("is-open") + ) { + settingsMenuBtn.click(); + setTimeout(() => { + const generalTab = document.getElementById("general-tab"); + if (generalTab) { + generalTab.click(); + } + }, 1000); // 100ms delay to ensure the settings panel is open + } + } + }} + > + + +
+ + Editor Settings + + +
+
+ +
+ + + +
+
+
+ + +
+ + Navbar Settings + + +
+
+ + + +
+ + + +
+ + SEO Panel + + +
+
+ + + +
+ + + +
+ + General Panel + + +
+
+ + + +
+ +
+
+
setIsDatabasePanelOpen(!isDatabasePanelOpen)} + className="flex bg-slate-100 border-b border-gray-300 p-4 py-3 justify-between w-full mb-3 cursor-pointer" + > +
+ {/* Database icon */} +

+ Database{" "} + + (local storage) + +

{" "} + {/* Text */} +
+
+ +
+
+ +
+
{ + setOpenAccordion(false); + setIsDatabasePanelOpen(true); + }} + > +
+ {/* Database icon */} +

+ Database{" "} + + (local storage) + +

{" "} + {/* Text */} +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+
+ { + const params = new URLSearchParams(searchParams); + params.set("id", id); + router.push(`?${params.toString()}`, undefined, { + shallow: true, + scroll: false, + }); + }, + }} + /> +
+
+
+
+

GitHub

+ + Star + + + Sponsor + {" "} +
+
+

About

+

+ Typr Editor is an open-source writing tool with ready-made user state management and publishing workflows. +
+ + Learn more + {" "}→ +

+

+ Made with 🧠 by{" "} + + Graeme + +

+ +
+
+ + +
+ {/* */} +
+ ); + } + +export default DemoPageContent; \ No newline at end of file diff --git a/app/typr/metadata.jsx b/app/typr/metadata.jsx deleted file mode 100644 index ba414fd9..00000000 --- a/app/typr/metadata.jsx +++ /dev/null @@ -1,12 +0,0 @@ -export default function Metadata({ seoTitle, seoDescription }) { - return ( - <> - {seoTitle} - - - - - - - ); - } \ No newline at end of file diff --git a/app/typr/page.js b/app/typr/page.js index ea89b247..9c0b7d0b 100644 --- a/app/typr/page.js +++ b/app/typr/page.js @@ -1,108 +1,8 @@ -"use client"; -import React, { useState, useEffect, Suspense } from "react"; -import { useRouter, useSearchParams, usePathname } from "next/navigation"; -import { - savePost, - createPost, - loadPostById, - DB_NAME, - STORE_NAME, -} from "@/lib/typr-demo/indexedDB"; // Import the IndexedDB utility -import "tiptypr/dist/styles.css"; -import Tiptypr from "tiptypr"; -// import Head from 'next/head'; +import React, { Suspense } from "react"; +import DemoPageContent from "./PageContent"; -import { - Cross2Icon, - HamburgerMenuIcon, - ChevronDownIcon, - CubeIcon, - EyeOpenIcon, - EyeClosedIcon, -} from "@radix-ui/react-icons"; - -import GitHubButton from "react-github-btn"; -import ThemeSelector from "@/components/typr-demo/ThemeSelector"; -import RequireLoginCheckbox from "@/components/typr-demo/RequireLoginCheckbox"; -import EnablePublishingFlowCheckbox from "@/components/typr-demo/EnablePublishingFLowCheckbox"; -import NavSettings from "@/components/typr-demo/NavSettings"; -import DemoCodeDialog from "@/components/typr-demo/DemoCodeDialog"; -import GeneralSettingsPanel from "@/components/typr-demo/GeneralSettingsPanel"; -import SeoPanel from "@/components/typr-demo/SeoPanel"; -import UserPopover from "@/components/typr-demo/UserPopover"; -import Layout from "@/components/new-index/layoutForDemo"; - -import * as Accordion from "@radix-ui/react-accordion"; - -import { defaultProps } from "tiptypr"; -import { customDeepMerge } from "@/lib/typr-demo/customDeepMerge"; -import IndexedDBBrowser from "@/components/typr-demo/IndexedDBBrowser"; -import { openDB } from "idb"; -import KoFiDialog from "@/components/ko-fi-button/KoFiDialog"; -import Metadata from "./metadata"; - -const avatarOptions = [ - { - name: "Avatartion", - imgSrc: - "https://prototypr-media.sfo2.digitaloceanspaces.com/strapi/32a56359cbe6a680ac1eb8eb659c46eb.png", - }, - { name: "Pravatar", imgSrc: "https://i.pravatar.cc/300" }, - { - name: "Pixel (xsgames)", - imgSrc: "https://xsgames.co/randomusers/avatar.php?g=pixel", - }, - { - name: "UI Avatars", - imgSrc: "https://ui-avatars.com/api/?background=random", - }, - { name: "RoboHash", imgSrc: "https://robohash.org/random" }, - { - name: "Female (xsgames)", - imgSrc: "https://xsgames.co/randomusers/avatar.php?g=female", - }, - { - name: "Male (xsgames)", - imgSrc: "https://xsgames.co/randomusers/avatar.php?g=male", - }, -]; - -const filterModifiedProps = (defaultProps, currentProps) => { - const modifiedProps = {}; - for (const key in currentProps) { - if ( - typeof currentProps[key] === "object" && - !Array.isArray(currentProps[key]) - ) { - const nestedModifiedProps = filterModifiedProps( - defaultProps[key] || {}, - currentProps[key] - ); - if (Object.keys(nestedModifiedProps).length > 0) { - modifiedProps[key] = nestedModifiedProps; - } - } else if (currentProps[key] !== defaultProps[key]) { - modifiedProps[key] = currentProps[key]; - } - } - return modifiedProps; -}; - -const serializeComponents = components => { - const replacer = (key, value) => { - if (React.isValidElement(value)) { - return `<${value.type.name || value.type.displayName || "Component"} />`; - } - if (typeof value === "function") { - return true; // Replace functions with true - } - return value; - }; - return JSON.stringify(components, replacer, 2); -}; - -const metadata = { +export const metadata = { title: 'Typr Editor - Open-Source Writing Tool by Prototypr', description: 'A customizable WYSIWYG editor with publishing flows and user state management for React.js. Built with Tiptap and ProseMirror.', openGraph: { @@ -110,7 +10,7 @@ const metadata = { description: 'A customizable WYSIWYG editor with publishing flows and user state management for React.js. Built with Tiptap and ProseMirror.', images: [ { - url: '/static/images/typr.jpg', + url: '/static/images/typr-og.jpg', width: 1200, height: 630, alt: 'Tiptypr Editor Preview', @@ -119,550 +19,6 @@ const metadata = { }, }; -function DemoPageContent() { - const router = useRouter(); - const searchParams = useSearchParams(); - const pathname = usePathname(); - - const [postId, setPostId] = useState(-1); - - useEffect(() => { - // Check if the router is ready - if (pathname !== null) { - const id = searchParams.get("id"); - - if (id) { - setPostId(id); - } else { - // create new post - setPostId(false); - } - } - }, [searchParams, pathname]); - - const [editorProps, setEditorProps] = useState(() => { - return customDeepMerge(defaultProps, { - requireLogin: true, - enablePublishingFlow: true, - theme: "blue", - user: { - id: 1, - isLoggedIn: true, - isAdmin: true, - }, - components: { - nav: { - show: true, - position: "sticky", - userBadge: { - avatarPlaceholder: avatarOptions[0].imgSrc, - }, - }, - }, - }); - }); - - const [isPanelOpen, setIsPanelOpen] = useState(false); - const [isDialogOpen, setIsDialogOpen] = useState(false); - const [openAccordion, setOpenAccordion] = useState(null); - - const [isDatabasePanelOpen, setIsDatabasePanelOpen] = useState(true); - - useEffect(() => { - if (openAccordion) { - setIsDatabasePanelOpen(false); - } - }, [openAccordion]); - - const handleSeoMenuChange = newSeoMenu => { - setEditorProps(prevProps => ({ - ...prevProps, - components: { - ...prevProps.components, - settingsPanel: { - ...prevProps.components.settingsPanel, - seoTab: { - ...prevProps.components.settingsPanel.seoTab, - menu: newSeoMenu, - }, - }, - }, - })); - }; - const handleGeneralMenuChange = newGeneralMenu => { - setEditorProps(prevProps => ({ - ...prevProps, - components: { - ...prevProps.components, - settingsPanel: { - ...prevProps.components.settingsPanel, - generalTab: { - ...prevProps.components.settingsPanel.generalTab, - menu: newGeneralMenu, - }, - }, - }, - })); - }; - - const themeOptions = [ - { value: "gray", label: "Gray" }, - { value: "blue", label: "Blue" }, - ]; - - const handleThemeChange = value => { - setEditorProps({ ...editorProps, theme: value }); - }; - - const handleRequireLoginChange = checked => { - setEditorProps({ ...editorProps, requireLogin: checked }); - }; - - const handleEnablePublishingFlowChange = checked => { - setEditorProps({ ...editorProps, enablePublishingFlow: checked }); - }; - - // const handleCustomPostStatusesChange = checked => { - // setEditorProps({ ...editorProps, customPostStatuses: checked }); - // }; - - const handleNavChange = nav => { - setEditorProps({ - ...editorProps, - components: { - ...editorProps.components, - nav, - }, - }); - }; - - // const handleSettingsChange = (field, value) => { - // // Update the settings based on the field and value - // console.log(`${field} changed to:`, value); - // }; - - const handleUserChange = (field, value) => { - setEditorProps(prevProps => ({ - ...prevProps, - user: { - ...prevProps.user, - [field]: value, - }, - })); - }; - - const modifiedProps = filterModifiedProps(defaultProps, editorProps); - - const demoCode = ` - { - const params = new URLSearchParams(searchParams); - params.set("id", id); - router.push(\`?\${params.toString()}\`, undefined, { - shallow: true, - scroll: false, - }); - }, - }} - /> - `; - - const [data, setData] = useState([]); - - const fetchData = async () => { - // const db = await openDB(DB_NAME, 1); - const db = await openDB(DB_NAME); - const tx = db.transaction(STORE_NAME, "readonly"); - const store = tx.objectStore(STORE_NAME); - const allData = await store.getAll(); - setData(allData); - }; - - useEffect(() => { - fetchData(); - }, []); - - return ( - - -
- -
-
-

- Editor Config -

- - { - setOpenAccordion(tabName); - if (tabName == "general-settings" || tabName == "seo-panel") { - const settingsMenuBtn = - document.getElementById("settings-menu-btn"); - if ( - settingsMenuBtn && - !settingsMenuBtn.classList.contains("is-open") - ) { - settingsMenuBtn.click(); - setTimeout(() => { - const generalTab = document.getElementById("general-tab"); - if (generalTab) { - generalTab.click(); - } - }, 1000); // 100ms delay to ensure the settings panel is open - } - } - }} - > - - -
- - Editor Settings - - -
-
- -
- - - -
-
-
- - -
- - Navbar Settings - - -
-
- - - -
- - - -
- - SEO Panel - - -
-
- - - -
- - - -
- - General Panel - - -
-
- - - -
- -
-
-
setIsDatabasePanelOpen(!isDatabasePanelOpen)} - className="flex bg-slate-100 border-b border-gray-300 p-4 py-3 justify-between w-full mb-3 cursor-pointer" - > -
- {/* Database icon */} -

- Database{" "} - - (local storage) - -

{" "} - {/* Text */} -
-
- -
-
- -
-
{ - setOpenAccordion(false); - setIsDatabasePanelOpen(true); - }} - > -
- {/* Database icon */} -

- Database{" "} - - (local storage) - -

{" "} - {/* Text */} -
-
- -
-
-
-
- -
-
- -
-
-
-
-
-
- { - const params = new URLSearchParams(searchParams); - params.set("id", id); - router.push(`?${params.toString()}`, undefined, { - shallow: true, - scroll: false, - }); - }, - }} - /> -
-
-
-
-

GitHub

- - Star - - - Sponsor - {" "} -
-
-

About

-

- Typr Editor is an open-source writing tool with ready-made user state management and publishing workflows. -
- - Learn more - {" "}→ -

-

- Made with 🧠 by{" "} - - Graeme - -

- -
-
- - -
- {/* */} -
- ); -} export default function DemoPage() { return (