From ecc8c8245a521d9687a30c663c00008d3d934458 Mon Sep 17 00:00:00 2001 From: Arek Nawo Date: Mon, 27 May 2024 21:03:11 +0200 Subject: [PATCH] fix: MDX table transform --- apps/backend/extensions/package.json | 3 + .../src/routes/mdx/output-transformer.ts | 34 +++----- apps/web/src/context/confirmation-modal.tsx | 7 +- .../src/views/editor/menus/bubble/table.tsx | 24 ++++- pnpm-lock.yaml | 87 +++++++++++++++++++ 5 files changed, 125 insertions(+), 30 deletions(-) diff --git a/apps/backend/extensions/package.json b/apps/backend/extensions/package.json index 2491b2b1..22a02771 100644 --- a/apps/backend/extensions/package.json +++ b/apps/backend/extensions/package.json @@ -20,7 +20,9 @@ "@vrite/sdk": "workspace:*", "dayjs": "^1.11.10", "fastify": "^4.26.0", + "hast-util-from-html": "^2.0.1", "hast-util-to-html": "^9.0.0", + "hast-util-to-mdast": "^10.1.0", "html-to-text": "^9.0.5", "js-yaml": "^4.1.0", "mdast-util-from-markdown": "^2.0.0", @@ -28,6 +30,7 @@ "mdast-util-gfm": "^3.0.0", "mdast-util-mdx": "^3.0.0", "mdast-util-to-hast": "^13.1.0", + "mdast-util-to-markdown": "^2.1.0", "micromark-extension-frontmatter": "^2.0.0", "micromark-extension-gfm": "^3.0.0", "micromark-extension-mdxjs": "^3.0.0", diff --git a/apps/backend/extensions/src/routes/mdx/output-transformer.ts b/apps/backend/extensions/src/routes/mdx/output-transformer.ts index 42014831..f4bd87ca 100644 --- a/apps/backend/extensions/src/routes/mdx/output-transformer.ts +++ b/apps/backend/extensions/src/routes/mdx/output-transformer.ts @@ -10,9 +10,15 @@ import babelPlugin from "prettier/plugins/babel"; import estreePlugin from "prettier/plugins/estree"; import markdownPlugin from "prettier/plugins/markdown"; import { ContentPiece } from "@vrite/sdk/api"; +import { htmlOutputTransformer } from "@vrite/sdk/transformers"; import { convert as convertToText } from "html-to-text"; import { dump } from "js-yaml"; import dayjs from "dayjs"; +import { fromHtml } from "hast-util-from-html"; +import { toMdast } from "hast-util-to-mdast"; +import { toMarkdown } from "mdast-util-to-markdown"; +import { gfmToMarkdown } from "mdast-util-gfm"; +import { mdxToMarkdown } from "mdast-util-mdx"; import type { Plugin } from "prettier"; const processCode = async (code: string, hasContent?: boolean): Promise => { @@ -90,31 +96,11 @@ const mdxAsyncOutputTransformer = async ( .join("")}`; }; const transformTable = (tableWalker: JSONContentNodeWalker): string => { - let output = ""; - - tableWalker.children.forEach((tableRowWalker, rowIndex) => { - let isHeader = false; - - const columns = tableRowWalker.children.map((tableCellWalker) => { - if (tableCellWalker.node.type === "tableHeader") { - isHeader = true; - } - - return tableCellWalker.children.map(transformTextNode).join("\n"); - }); - - if (rowIndex === tableWalker.children.length - 1) { - output += `| ${columns.map((row) => row.replace(/\n/g, " ")).join(" | ")} |`; - } else { - output += `| ${columns.map((row) => row.replace(/\n/g, " ")).join(" | ")} |\n`; - } + const html = htmlOutputTransformer(tableWalker.node); + const hast = fromHtml(html, { fragment: true }); + const mdast = toMdast(hast); - if (isHeader && rowIndex === 0) { - output += `| ${columns.map(() => "---").join(" | ")} |\n`; - } - }); - - return output; + return toMarkdown(mdast, { extensions: [gfmToMarkdown(), mdxToMarkdown()] }); }; const transformCodeBlock = ( codeBlockWalker: JSONContentNodeWalker diff --git a/apps/web/src/context/confirmation-modal.tsx b/apps/web/src/context/confirmation-modal.tsx index 1f510c1d..f63477b9 100644 --- a/apps/web/src/context/confirmation-modal.tsx +++ b/apps/web/src/context/confirmation-modal.tsx @@ -11,7 +11,8 @@ import { createMemo, onMount, createEffect, - on + on, + onCleanup } from "solid-js"; import clsx from "clsx"; import { tinykeys } from "tinykeys"; @@ -69,10 +70,12 @@ const ConfirmationModalProvider: ParentComponent = (props) => { createEffect( on(config, (config) => { if (config) { - return tinykeys(window, { + const unbind = tinykeys(window, { ArrowRight: handleArrowKey, ArrowLeft: handleArrowKey }); + + onCleanup(() => unbind()); } }) ); diff --git a/apps/web/src/views/editor/menus/bubble/table.tsx b/apps/web/src/views/editor/menus/bubble/table.tsx index 58e4bc6b..500caaa0 100644 --- a/apps/web/src/views/editor/menus/bubble/table.tsx +++ b/apps/web/src/views/editor/menus/bubble/table.tsx @@ -7,12 +7,12 @@ import { mdiTableSplitCell } from "@mdi/js"; import clsx from "clsx"; -import { Component, For, Show, createEffect, on } from "solid-js"; +import { Component, For, Show } from "solid-js"; import { SolidEditor } from "@vrite/tiptap-solid"; import { CellSelection } from "@tiptap/pm/tables"; import { Node } from "@tiptap/pm/model"; +import { TextSelection } from "@tiptap/pm/state"; import { Card, IconButton, Tooltip } from "#components/primitives"; -import { createRef } from "#lib/utils"; const TableMenu: Component<{ class?: string; @@ -54,7 +54,7 @@ const TableMenu: Component<{ const tableNode = getTableNode(selection); const rowNode = getTableRowNode(selection); - if (tableNode && rowNode && tableNode.child(0) === rowNode) { + if (!tableNode || !rowNode || tableNode.child(0) !== rowNode) { return false; } @@ -126,7 +126,23 @@ const TableMenu: Component<{ return true; }, onClick() { - props.editor.chain().deleteColumn().focus().run(); + props.editor + .chain() + .deleteColumn() + .command(({ tr }) => { + tr.setSelection( + TextSelection.near( + tr.doc.resolve( + Math.min(props.editor.state.selection.$from.pos - 2, tr.doc.nodeSize) + ), + -1 + ) + ); + + return true; + }) + .focus() + .run(); } }, { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8b80f2b3..fbd4bbce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -222,9 +222,15 @@ importers: fastify: specifier: ^4.26.0 version: 4.26.0 + hast-util-from-html: + specifier: ^2.0.1 + version: 2.0.1 hast-util-to-html: specifier: ^9.0.0 version: 9.0.0 + hast-util-to-mdast: + specifier: ^10.1.0 + version: 10.1.0 html-to-text: specifier: ^9.0.5 version: 9.0.5 @@ -246,6 +252,9 @@ importers: mdast-util-to-hast: specifier: ^13.1.0 version: 13.1.0 + mdast-util-to-markdown: + specifier: ^2.1.0 + version: 2.1.0 micromark-extension-frontmatter: specifier: ^2.0.0 version: 2.0.0 @@ -10867,6 +10876,13 @@ packages: dependencies: function-bind: 1.1.2 + /hast-util-embedded@3.0.0: + resolution: {integrity: sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==} + dependencies: + '@types/hast': 3.0.4 + hast-util-is-element: 3.0.0 + dev: false + /hast-util-from-html@2.0.1: resolution: {integrity: sha512-RXQBLMl9kjKVNkJTIO6bZyb2n+cUH8LFaSSzo82jiLT6Tfc+Pt7VQCS+/h3YwG4jaNE2TA2sdJisGWR+aJrp0g==} dependencies: @@ -10906,6 +10922,18 @@ packages: '@types/hast': 3.0.4 dev: false + /hast-util-is-body-ok-link@3.0.0: + resolution: {integrity: sha512-VFHY5bo2nY8HiV6nir2ynmEB1XkxzuUffhEGeVx7orbu/B1KaGyeGgMZldvMVx5xWrDlLLG/kQ6YkJAMkBEx0w==} + dependencies: + '@types/hast': 3.0.4 + dev: false + + /hast-util-is-element@3.0.0: + resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + dependencies: + '@types/hast': 3.0.4 + dev: false + /hast-util-parse-selector@3.1.1: resolution: {integrity: sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==} dependencies: @@ -10916,6 +10944,16 @@ packages: dependencies: '@types/hast': 3.0.4 + /hast-util-phrasing@3.0.1: + resolution: {integrity: sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==} + dependencies: + '@types/hast': 3.0.4 + hast-util-embedded: 3.0.0 + hast-util-has-property: 3.0.0 + hast-util-is-body-ok-link: 3.0.0 + hast-util-is-element: 3.0.0 + dev: false + /hast-util-raw@7.2.3: resolution: {integrity: sha512-RujVQfVsOrxzPOPSzZFiwofMArbQke6DJjnFfceiEbFh7S05CbPt0cYN+A5YeD3pso0JQk6O1aHBnx9+Pm2uqg==} dependencies: @@ -11022,6 +11060,25 @@ packages: stringify-entities: 4.0.3 zwitch: 2.0.4 + /hast-util-to-mdast@10.1.0: + resolution: {integrity: sha512-DsL/SvCK9V7+vfc6SLQ+vKIyBDXTk2KLSbfBYkH4zeF/uR1yBajHRhkzuaUSGOB1WJSTieJBdHwxlC+HLKvZZw==} + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.3 + '@ungap/structured-clone': 1.2.0 + hast-util-phrasing: 3.0.1 + hast-util-to-html: 9.0.0 + hast-util-to-text: 4.0.2 + hast-util-whitespace: 3.0.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-hast: 13.1.0 + mdast-util-to-string: 4.0.0 + rehype-minify-whitespace: 6.0.0 + trim-trailing-lines: 2.1.0 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + dev: false + /hast-util-to-parse5@7.1.0: resolution: {integrity: sha512-YNRgAJkH2Jky5ySkIqFXTQiaqcAtJyVE+D5lkN6CdtOqrnkLfGYYrEcKuHOJZlp+MwjSwuD3fZuawI+sic/RBw==} dependencies: @@ -11049,6 +11106,15 @@ packages: '@types/hast': 3.0.4 dev: false + /hast-util-to-text@4.0.2: + resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.2 + hast-util-is-element: 3.0.0 + unist-util-find-after: 5.0.0 + dev: false + /hast-util-whitespace@2.0.1: resolution: {integrity: sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==} @@ -15196,6 +15262,16 @@ packages: engines: {node: '>=0.1.14'} dev: true + /rehype-minify-whitespace@6.0.0: + resolution: {integrity: sha512-i9It4YHR0Sf3GsnlR5jFUKXRr9oayvEk9GKQUkwZv6hs70OH9q3OCZrq9PpLvIGKt3W+JxBOxCidNVpH/6rWdA==} + dependencies: + '@types/hast': 3.0.4 + hast-util-embedded: 3.0.0 + hast-util-is-element: 3.0.0 + hast-util-whitespace: 3.0.0 + unist-util-is: 6.0.0 + dev: false + /rehype-parse@8.0.5: resolution: {integrity: sha512-Ds3RglaY/+clEX2U2mHflt7NlMA72KspZ0JLUJgBBLpRddBcEw3H8uYZQliQriku22NZpYMfjDdSgHcjxue24A==} dependencies: @@ -16500,6 +16576,10 @@ packages: /trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + /trim-trailing-lines@2.1.0: + resolution: {integrity: sha512-5UR5Biq4VlVOtzqkm2AZlgvSlDJtME46uV0br0gENbwN4l5+mMKT4b9gJKqWtuL2zAIqajGJGuvbCbcAJUZqBg==} + dev: false + /trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} @@ -16887,6 +16967,13 @@ packages: trough: 2.2.0 vfile: 6.0.1 + /unist-util-find-after@5.0.0: + resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} + dependencies: + '@types/unist': 3.0.2 + unist-util-is: 6.0.0 + dev: false + /unist-util-generated@2.0.1: resolution: {integrity: sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==}