From cd587c8311b98f8145d84d06b20fdaf9182c8b43 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal Date: Wed, 25 Sep 2024 18:01:20 +0530 Subject: [PATCH 1/2] feat: export page as pdf and markdown --- packages/editor/src/styles/editor.css | 2 +- packages/ui/src/modals/constants.ts | 3 + web/app/pdf/page.tsx | 29 ++ web/core/components/editor/index.ts | 1 + web/core/components/editor/pdf/document.tsx | 53 +++ web/core/components/editor/pdf/index.ts | 1 + .../pages/editor/header/options-dropdown.tsx | 59 ++- web/core/components/pages/editor/title.tsx | 12 +- .../pages/modals/export-page-modal.tsx | 279 +++++++++++ web/core/components/pages/modals/index.ts | 1 + web/core/constants/editor.ts | 173 +++++++ web/helpers/common.helper.ts | 2 + web/helpers/editor.helper.ts | 133 ++++++ web/package.json | 2 + web/public/fonts/inter/bold-italic.ttf | Bin 0 -> 348180 bytes web/public/fonts/inter/bold.ttf | Bin 0 -> 344152 bytes web/public/fonts/inter/heavy-italic.ttf | Bin 0 -> 348712 bytes web/public/fonts/inter/heavy.ttf | Bin 0 -> 344820 bytes web/public/fonts/inter/light-italic.ttf | Bin 0 -> 347316 bytes web/public/fonts/inter/light.ttf | Bin 0 -> 343704 bytes web/public/fonts/inter/medium-italic.ttf | Bin 0 -> 346884 bytes web/public/fonts/inter/medium.ttf | Bin 0 -> 343200 bytes web/public/fonts/inter/regular-italic.ttf | Bin 0 -> 346480 bytes web/public/fonts/inter/regular.ttf | Bin 0 -> 342680 bytes web/public/fonts/inter/semibold-italic.ttf | Bin 0 -> 347760 bytes web/public/fonts/inter/semibold.ttf | Bin 0 -> 343828 bytes web/public/fonts/inter/thin-italic.ttf | Bin 0 -> 346916 bytes web/public/fonts/inter/thin.ttf | Bin 0 -> 343088 bytes web/public/fonts/inter/ultrabold-italic.ttf | Bin 0 -> 349064 bytes web/public/fonts/inter/ultrabold.ttf | Bin 0 -> 345008 bytes web/public/fonts/inter/ultralight-italic.ttf | Bin 0 -> 347452 bytes web/public/fonts/inter/ultralight.ttf | Bin 0 -> 343532 bytes yarn.lock | 439 ++++++++++++++++-- 33 files changed, 1123 insertions(+), 66 deletions(-) create mode 100644 web/app/pdf/page.tsx create mode 100644 web/core/components/editor/pdf/document.tsx create mode 100644 web/core/components/editor/pdf/index.ts create mode 100644 web/core/components/pages/modals/export-page-modal.tsx create mode 100644 web/helpers/editor.helper.ts create mode 100644 web/public/fonts/inter/bold-italic.ttf create mode 100644 web/public/fonts/inter/bold.ttf create mode 100644 web/public/fonts/inter/heavy-italic.ttf create mode 100644 web/public/fonts/inter/heavy.ttf create mode 100644 web/public/fonts/inter/light-italic.ttf create mode 100644 web/public/fonts/inter/light.ttf create mode 100644 web/public/fonts/inter/medium-italic.ttf create mode 100644 web/public/fonts/inter/medium.ttf create mode 100644 web/public/fonts/inter/regular-italic.ttf create mode 100644 web/public/fonts/inter/regular.ttf create mode 100644 web/public/fonts/inter/semibold-italic.ttf create mode 100644 web/public/fonts/inter/semibold.ttf create mode 100644 web/public/fonts/inter/thin-italic.ttf create mode 100644 web/public/fonts/inter/thin.ttf create mode 100644 web/public/fonts/inter/ultrabold-italic.ttf create mode 100644 web/public/fonts/inter/ultrabold.ttf create mode 100644 web/public/fonts/inter/ultralight-italic.ttf create mode 100644 web/public/fonts/inter/ultralight.ttf diff --git a/packages/editor/src/styles/editor.css b/packages/editor/src/styles/editor.css index 478180aa2f0..ed44a539d97 100644 --- a/packages/editor/src/styles/editor.css +++ b/packages/editor/src/styles/editor.css @@ -44,7 +44,7 @@ } &.sans-serif { - --font-style: sans-serif; + --font-style: "Inter", sans-serif; } &.serif { diff --git a/packages/ui/src/modals/constants.ts b/packages/ui/src/modals/constants.ts index 0cb268fc882..fe72ef7aea1 100644 --- a/packages/ui/src/modals/constants.ts +++ b/packages/ui/src/modals/constants.ts @@ -4,6 +4,9 @@ export enum EModalPosition { } export enum EModalWidth { + SM = "sm:max-w-sm", + MD = "sm:max-w-md", + LG = "sm:max-w-lg", XL = "sm:max-w-xl", XXL = "sm:max-w-2xl", XXXL = "sm:max-w-3xl", diff --git a/web/app/pdf/page.tsx b/web/app/pdf/page.tsx new file mode 100644 index 00000000000..9cce86165d4 --- /dev/null +++ b/web/app/pdf/page.tsx @@ -0,0 +1,29 @@ +"use client"; + +import { PDFViewer } from "@react-pdf/renderer"; +import { PDFDocument } from "@/components/editor"; +import { replaceCustomComponentsFromHTMLContent } from "@/helpers/editor.helper"; + +const content = `

Page title

Bye, bye, Notion, Google Docs, Evernote, Keep, and Apple Notes. Plane Pages are here!

What is the pages?

Pages is exactly what it sounds like---just an open space for your thoughts, notes, and something more intentful.

This is an inline code block

This is also an inline code block

const keyword = "new-keyword";
+
+const arrowFunction = () => {
+	someFunctionExecution();
+}


With Pages, you can now start jotting down meeting notes, create docs for features, and format for presentation. You see a table of contents on the right when you use headings and you can lock the page so it’s not accidentally editable.

How to get to Pages

Easy. Just find pages under any project on the left nav of your Plane workspace, click Create page, give your new page a cool name, like I have for this page, and start clacking away.No flipping screens, no copy-pasting from anywhere else, no additional hoops.

Like I said "Easy"

What can I do with Pages?

Anything you want to write, you can write on Pages. You can embed Issues into Pages already.

Want to format something a quote?
Easy.

  1. Ah, yes, there’s numbered lists, too.

  2. And they align nicely so you don’t have to battle with the screen.

  3. Format text to

    • bold, italics, underline, or strikethrough.

    • We have a to-do list

  4. Quote your favorite poet.

    Do or do not. There is no try.
    - Yoda

  5. Embed an issue

There’s code snippets that can go as long or as short as you want and 
+include API docs you want to reference. You can even copy code from 
+somewhere and paste it inside Plane to have it show up like this.

There is a table too

You can also color rows
different from columns

Or color columns different

Let's upload an image and make this line a heading 3.

Yep. Genius.

Life is already great with Plane, but it gets a little better with Pages, right ?

Give it a spin, tell us we were right... Or wrong. We will work to make Pages work for you.

`; + +export default function PDFViewerPage() { + const parsedContent = replaceCustomComponentsFromHTMLContent({ + htmlContent: content, + }); + return ( + + + + ); +} diff --git a/web/core/components/editor/index.ts b/web/core/components/editor/index.ts index 72e92a6a8ac..0b14bd13570 100644 --- a/web/core/components/editor/index.ts +++ b/web/core/components/editor/index.ts @@ -1,2 +1,3 @@ export * from "./lite-text-editor"; +export * from "./pdf"; export * from "./rich-text-editor"; diff --git a/web/core/components/editor/pdf/document.tsx b/web/core/components/editor/pdf/document.tsx new file mode 100644 index 00000000000..4dca9e6d53c --- /dev/null +++ b/web/core/components/editor/pdf/document.tsx @@ -0,0 +1,53 @@ +"use client"; + +import { Document, Font, Page, PageProps } from "@react-pdf/renderer"; +import { Html } from "react-pdf-html"; +// constants +import { EDITOR_PDF_DOCUMENT_STYLESHEET } from "@/constants/editor"; + +Font.register({ + family: "Inter", + fonts: [ + { src: "/fonts/inter/thin.ttf", fontWeight: "thin" }, + { src: "/fonts/inter/thin.ttf", fontWeight: "thin", fontStyle: "italic" }, + { src: "/fonts/inter/ultralight.ttf", fontWeight: "ultralight" }, + { src: "/fonts/inter/ultralight.ttf", fontWeight: "ultralight", fontStyle: "italic" }, + { src: "/fonts/inter/light.ttf", fontWeight: "light" }, + { src: "/fonts/inter/light.ttf", fontWeight: "light", fontStyle: "italic" }, + { src: "/fonts/inter/regular.ttf", fontWeight: "normal" }, + { src: "/fonts/inter/regular.ttf", fontWeight: "normal", fontStyle: "italic" }, + { src: "/fonts/inter/medium.ttf", fontWeight: "medium" }, + { src: "/fonts/inter/medium.ttf", fontWeight: "medium", fontStyle: "italic" }, + { src: "/fonts/inter/semibold.ttf", fontWeight: "semibold" }, + { src: "/fonts/inter/semibold.ttf", fontWeight: "semibold", fontStyle: "italic" }, + { src: "/fonts/inter/bold.ttf", fontWeight: "bold" }, + { src: "/fonts/inter/bold.ttf", fontWeight: "bold", fontStyle: "italic" }, + { src: "/fonts/inter/extrabold.ttf", fontWeight: "ultrabold" }, + { src: "/fonts/inter/extrabold.ttf", fontWeight: "ultrabold", fontStyle: "italic" }, + { src: "/fonts/inter/heavy.ttf", fontWeight: "heavy" }, + { src: "/fonts/inter/heavy.ttf", fontWeight: "heavy", fontStyle: "italic" }, + ], +}); + +type Props = { + content: string; + pageFormat: PageProps["size"]; +}; + +export const PDFDocument: React.FC = (props) => { + const { content, pageFormat } = props; + + return ( + + + {content} + + + ); +}; diff --git a/web/core/components/editor/pdf/index.ts b/web/core/components/editor/pdf/index.ts new file mode 100644 index 00000000000..fe6d89c0eb9 --- /dev/null +++ b/web/core/components/editor/pdf/index.ts @@ -0,0 +1 @@ +export * from "./document"; diff --git a/web/core/components/pages/editor/header/options-dropdown.tsx b/web/core/components/pages/editor/header/options-dropdown.tsx index 0560002d840..c7cf53a5f50 100644 --- a/web/core/components/pages/editor/header/options-dropdown.tsx +++ b/web/core/components/pages/editor/header/options-dropdown.tsx @@ -1,12 +1,15 @@ "use client"; +import { useState } from "react"; import { observer } from "mobx-react"; import { useParams, useRouter } from "next/navigation"; -import { ArchiveRestoreIcon, Clipboard, Copy, History, Link, Lock, LockOpen } from "lucide-react"; +import { ArchiveRestoreIcon, ArrowUpToLine, Clipboard, Copy, History, Link, Lock, LockOpen } from "lucide-react"; // document editor import { EditorReadOnlyRefApi, EditorRefApi } from "@plane/editor"; // ui import { ArchiveIcon, CustomMenu, TOAST_TYPE, ToggleSwitch, setToast } from "@plane/ui"; +// components +import { ExportPageModal } from "@/components/pages"; // helpers import { copyTextToClipboard, copyUrlToClipboard } from "@/helpers/string.helper"; // hooks @@ -27,6 +30,7 @@ export const PageOptionsDropdown: React.FC = observer((props) => { const router = useRouter(); // store values const { + name, archived_at, is_locked, id, @@ -38,6 +42,8 @@ export const PageOptionsDropdown: React.FC = observer((props) => { canCurrentUserLockPage, restore, } = page; + // states + const [isExportModalOpen, setIsExportModalOpen] = useState(false); // store hooks const { workspaceSlug, projectId } = useParams(); // page filters @@ -157,26 +163,41 @@ export const PageOptionsDropdown: React.FC = observer((props) => { icon: History, shouldRender: true, }, + { + key: "export", + action: () => setIsExportModalOpen(true), + label: "Export", + icon: ArrowUpToLine, + shouldRender: true, + }, ]; return ( - - handleFullWidth(!isFullWidth)} - > - Full width - {}} /> - - {MENU_ITEMS.map((item) => { - if (!item.shouldRender) return null; - return ( - - - {item.label} - - ); - })} - + <> + setIsExportModalOpen(false)} + pageTitle={name ?? ""} + /> + + handleFullWidth(!isFullWidth)} + > + Full width + {}} /> + + {MENU_ITEMS.map((item) => { + if (!item.shouldRender) return null; + return ( + + + {item.label} + + ); + })} + + ); }); diff --git a/web/core/components/pages/editor/title.tsx b/web/core/components/pages/editor/title.tsx index 6792ebf0ac7..dafda1db3a9 100644 --- a/web/core/components/pages/editor/title.tsx +++ b/web/core/components/pages/editor/title.tsx @@ -1,6 +1,6 @@ "use client"; -import { CSSProperties, useState } from "react"; +import { useState } from "react"; import { observer } from "mobx-react"; // editor import { EditorRefApi } from "@plane/editor"; @@ -23,27 +23,21 @@ export const PageEditorTitle: React.FC = observer((props) => { // states const [isLengthVisible, setIsLengthVisible] = useState(false); // page filters - const { fontSize, fontStyle } = usePageFilters(); + const { fontSize } = usePageFilters(); // ui const titleClassName = cn("bg-transparent tracking-[-2%] font-semibold", { "text-[1.6rem] leading-[1.8rem]": fontSize === "small-font", "text-[2rem] leading-[2.25rem]": fontSize === "large-font", }); - const titleStyle: CSSProperties = { - fontFamily: fontStyle, - }; return ( <> {readOnly ? ( -
- {title} -
+
{title}
) : ( <>