From aa6a0c508b5f60eea0b2e55fb9eb6805349c6cfe Mon Sep 17 00:00:00 2001 From: Arek Nawo Date: Wed, 18 Oct 2023 12:45:47 +0200 Subject: [PATCH] refactor: Improve editor demo --- .../src/routes/mdx/output-transformer.ts | 10 +- apps/docs/src/components/content/card.astro | 3 +- apps/web/src/assets/icons/index.ts | 1 + apps/web/src/assets/icons/mdx.ts | 3 + apps/web/src/layout/toolbar/index.tsx | 4 +- .../src/views/editor/menus/bubble/format.tsx | 2 +- .../src/views/editor/menus/bubble/index.tsx | 4 +- .../src/views/editor/menus/export/index.tsx | 68 ++- .../src/views/standalone-editor/editor.tsx | 3 +- .../standalone-editor/initial-content.ts | 3 +- apps/web/src/views/workspaces/view.tsx | 4 +- .../backend/src/assets/initial-content.json | 536 +++++++++++++++--- 12 files changed, 538 insertions(+), 103 deletions(-) create mode 100644 apps/web/src/assets/icons/mdx.ts diff --git a/apps/backend/extensions/src/routes/mdx/output-transformer.ts b/apps/backend/extensions/src/routes/mdx/output-transformer.ts index 71bf7f07..b391e7d1 100644 --- a/apps/backend/extensions/src/routes/mdx/output-transformer.ts +++ b/apps/backend/extensions/src/routes/mdx/output-transformer.ts @@ -287,7 +287,9 @@ const mdxAsyncOutputTransformer = async ( const content = await transformContentNode( contentWalker as JSONContentNodeWalker ); - const frontmatter = dump( + const { __extensions__, ...customData } = contentPiece?.customData || {}; + + let frontmatter = dump( { ...(contentPiece?.canonicalLink && { canonicalLink: contentPiece.canonicalLink }), ...(contentPiece?.coverUrl && { coverUrl: contentPiece.coverUrl }), @@ -297,11 +299,15 @@ const mdxAsyncOutputTransformer = async ( ...(contentPiece?.date && { date: dayjs(contentPiece.date).format("YYYY-MM-DD") }), ...(contentPiece?.slug && { slug: contentPiece.slug }), ...(contentPiece?.title && { title: contentPiece.title }), - ...(contentPiece?.customData || {}) + ...customData }, { skipInvalid: true, forceQuotes: true, quotingType: '"' } ); + if (frontmatter.trim() === "{}") { + frontmatter = ""; + } + return ( await format( `${frontmatter ? "---" : ""}\n${frontmatter.trim()}\n${ diff --git a/apps/docs/src/components/content/card.astro b/apps/docs/src/components/content/card.astro index 4cb343dd..e5d628bf 100644 --- a/apps/docs/src/components/content/card.astro +++ b/apps/docs/src/components/content/card.astro @@ -20,7 +20,8 @@ const { props } = Astro; > diff --git a/apps/web/src/assets/icons/index.ts b/apps/web/src/assets/icons/index.ts index 499af816..941dc5ed 100644 --- a/apps/web/src/assets/icons/index.ts +++ b/apps/web/src/assets/icons/index.ts @@ -3,3 +3,4 @@ export * from "./discord"; export * from "./google"; export * from "./hashnode"; export * from "./logo"; +export * from "./mdx"; diff --git a/apps/web/src/assets/icons/mdx.ts b/apps/web/src/assets/icons/mdx.ts new file mode 100644 index 00000000..c2bc43fa --- /dev/null +++ b/apps/web/src/assets/icons/mdx.ts @@ -0,0 +1,3 @@ +const mdxIcon = `M.79 7.12h22.42c.436 0 .79.355.79.792v8.176a.79.79 0 0 1-.79.79H.79a.79.79 0 0 1-.79-.79V7.912a.79.79 0 0 1 .79-.791V7.12Zm2.507 7.605v-3.122l1.89 1.89L7.12 11.56v3.122h1.055v-5.67l-2.99 2.99L2.24 9.056v5.67h1.055v-.001Zm8.44-1.845l-1.474-1.473l-.746.746l2.747 2.747l2.745-2.747l-.746-.746l-1.473 1.473v-4h-1.054v4Zm10.041.987l-2.175-2.175l2.22-2.22l-.746-.746l-2.22 2.22l-2.22-2.22l-.747.746l2.22 2.22l-2.176 2.177l.746.746l2.177-2.177l2.176 2.175l.745-.746Z`; + +export { mdxIcon }; diff --git a/apps/web/src/layout/toolbar/index.tsx b/apps/web/src/layout/toolbar/index.tsx index 84154716..53764997 100644 --- a/apps/web/src/layout/toolbar/index.tsx +++ b/apps/web/src/layout/toolbar/index.tsx @@ -94,7 +94,7 @@ const toolbarViews: Record>> = { variant="text" text="soft" label="Usage guide" - link="https://docs.vrite.io/content-editor" + link="https://docs.vrite.io/usage-guide/content-editor" target="_blank" /> >> = { class="m-0" variant="text" text="soft" - link="https://docs.vrite.io/content-editor" + link="https://docs.vrite.io/usage-guide/content-editor" target="_blank" /> diff --git a/apps/web/src/views/editor/menus/bubble/format.tsx b/apps/web/src/views/editor/menus/bubble/format.tsx index 1f3e054d..c3caba38 100644 --- a/apps/web/src/views/editor/menus/bubble/format.tsx +++ b/apps/web/src/views/editor/menus/bubble/format.tsx @@ -1,5 +1,5 @@ import clsx from "clsx"; -import { Component, For, createSignal } from "solid-js"; +import { Component, For, createEffect, createSignal } from "solid-js"; import { SolidEditor } from "@vrite/tiptap-solid"; import { mdiFormatBold, diff --git a/apps/web/src/views/editor/menus/bubble/index.tsx b/apps/web/src/views/editor/menus/bubble/index.tsx index 6118707d..1f348f58 100644 --- a/apps/web/src/views/editor/menus/bubble/index.tsx +++ b/apps/web/src/views/editor/menus/bubble/index.tsx @@ -22,9 +22,7 @@ interface BubbleMenuProps { const BubbleMenu: Component = (props) => { const [mode, setMode] = createSignal("format"); - props.editor.on("selectionUpdate", ({ editor }) => { - const { selection } = editor.state; - + props.editor.on("selectionUpdate", () => { if (props.editor.state.selection instanceof CellSelection) { setMode("table"); } else if (!props.editor.state.selection.empty) { diff --git a/apps/web/src/views/editor/menus/export/index.tsx b/apps/web/src/views/editor/menus/export/index.tsx index 92cf8b41..71f40e20 100644 --- a/apps/web/src/views/editor/menus/export/index.tsx +++ b/apps/web/src/views/editor/menus/export/index.tsx @@ -19,10 +19,12 @@ import { useAuthenticatedUserData, useClient, useCommandPalette, - useNotifications + useNotifications, + useSharedState } from "#context"; import { formatCode } from "#lib/code-editor"; import { escapeHTML } from "#lib/utils"; +import { mdxIcon } from "#assets/icons"; interface ExportMenuProps { editedContentPiece?: App.ContentPieceWithAdditionalData; @@ -32,8 +34,12 @@ interface ExportMenuProps { onClick?(): void; } +type ExportType = "html" | "json" | "md" | "mdx"; + const ExportMenu: Component = (props) => { + const createSharedSignal = useSharedState(); const client = useClient(); + const [editor] = createSharedSignal("editor"); const { registerCommand = () => {} } = useCommandPalette() || {}; const { workspaceSettings = () => null } = useAuthenticatedUserData() || {}; const { notify } = useNotifications(); @@ -41,25 +47,20 @@ const ExportMenu: Component = (props) => { const [exportMenuOpened, setExportMenuOpened] = createSignal(false); const [exportDropdownOpened, setExportDropdownOpened] = createSignal(false); const [code, setCode] = createSignal(""); - const [exportType, setExportType] = createSignal<"html" | "json" | "md">("html"); - const loadContent = async (type: "html" | "json" | "md"): Promise => { + const [exportType, setExportType] = createSignal("html"); + const loadContent = async (type: ExportType): Promise => { try { let { content } = props; if (!content && props.editedContentPiece) { - const contentPiece = await client.contentPieces.get.query({ - content: true, - id: props.editedContentPiece.id - }); - - content = contentPiece.content as JSONContent; + content = (editor()?.getJSON() as JSONContent) || { type: "doc", content: [] }; } const prettierConfig = JSON.parse(workspaceSettings()?.prettierConfig || "{}"); - if (type === "html") { - if (!content) return; + if (!content) return; + if (type === "html") { return formatCode( htmlOutputTransformer(content).replace(/((?:.|\n)+?)<\/code>/g, (_, code) => { return `${escapeHTML(code)}`; @@ -70,18 +71,40 @@ const ExportMenu: Component = (props) => { } if (type === "md") { - if (!content) return; - return formatCode(gfmOutputTransformer(content), "markdown", prettierConfig); } + if (type === "mdx") { + try { + const response = await fetch("https://extensions.vrite.io/mdx/output", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + data: [ + { + content, + metadata: props.editedContentPiece || {} + } + ] + }) + }); + const [result] = await response.json(); + + return result; + } catch (error) { + return; + } + } + return formatCode(JSON.stringify(content), "json", prettierConfig); } catch (e) { notify({ type: "error", text: "Couldn't export the content" }); setLoading(false); } }; - const exportContent = async (type: "html" | "json" | "md"): Promise => { + const exportContent = async (type: ExportType): Promise => { setExportDropdownOpened(false); setLoading(true); @@ -125,6 +148,14 @@ const ExportMenu: Component = (props) => { action() { exportContent("md"); } + }, + { + category: "editor", + icon: mdxIcon, + name: "Export MDX", + action() { + exportContent("mdx"); + } } ]); @@ -178,6 +209,15 @@ const ExportMenu: Component = (props) => { class="justify-start w-full m-0" onClick={() => exportContent("md")} /> + exportContent("mdx")} + /> { } if ( - ["element", "image", "codeBlock", "embed", "horizontalRule"].some((name) => { + isNodeSelection && + ["horizontalRule", "image", "codeBlock", "embed", "element"].some((name) => { return editor.isActive(name); }) ) { diff --git a/apps/web/src/views/standalone-editor/initial-content.ts b/apps/web/src/views/standalone-editor/initial-content.ts index 2a2ef1b0..3a4832df 100644 --- a/apps/web/src/views/standalone-editor/initial-content.ts +++ b/apps/web/src/views/standalone-editor/initial-content.ts @@ -1,4 +1,3 @@ -const initialContent = `

Welcome to Vrite Editor πŸš€

Vrite Editor is an open-source, minimalistic WYSIWYG Markdown editor.

Features

Integrated Monaco editor and Prettier for code snippets

console.log("Hello World!");
-

Tables! πŸŽ‰

Header 1

Header 2

Header 3

Cell 1A

Cell 1B

Cell 1C

Cell 2A

Cell 2B

Cell 2C

Cell 3A

Cell 3B

Cell 3C

Embeds πŸŽ₯

Zen mode, Markdown/HTML export, Stats

Check out the top-right corner!

A lot more stuff!

Full usage guide here: https://docs.vrite.io/content-editor

What is Vrite?

Vrite Editor is a local, standalone, editor extracted from a larger project β€” Vrite β€” open-source, headless CMS for technical content. If you’re interested in real-time collaboration, Kanban-based content management, GPT integration, customization and API access to your content, check out the full version of Vrite by signing in using the button in the top-right corner.

Vrite dashboard

Some links to check out:

`; +const initialContent = `

Welcome to Vrite Editor πŸš€

Open-source, minimalistic WYSIWYG Markdown editor.

Features

Integrated Monaco editor and Prettier for code snippets

console.log("Hello World!");

Tables! πŸŽ‰

Header 1

Header 2

Header 3

Cell 1A

Cell 1B

Cell 1C

Cell 2A

Cell 2B

Cell 2C

Cell 3A

Cell 3B

Cell 3C

Embeds πŸŽ₯

Zen mode, Markdown/HTML export, Stats

Check out the top-right corner!

A lot more stuff!

Full usage guide here: https://docs.vrite.io/content-editor

What is Vrite?

Vrite is an open-source, collaborative, developer content platform for documentation, knowledge bases, and technical blogs.

With an extensible, collaborative editor, content management dashboard, and features like semantic search, GitHub sync, and more β€” all accessible via powerful API β€” Vrite aims to provide an all-in-one experience for creating, managing, and publishing all kinds of technical content.

Vrite Kanban dashboard

Some links to check out:

`; export { initialContent }; diff --git a/apps/web/src/views/workspaces/view.tsx b/apps/web/src/views/workspaces/view.tsx index e0e742f7..c3f585aa 100644 --- a/apps/web/src/views/workspaces/view.tsx +++ b/apps/web/src/views/workspaces/view.tsx @@ -124,7 +124,7 @@ const WorkspacesView: Component = () => { return (
- +
{ } >
-
+