Jacky Zhao
2023-10-22 60b3bc34cb07b5bec87cbd667ea9f804ff14cf3c
fix: catch html to jsx errors (closes #547)
1 files added
6 files modified
50 ■■■■■ changed files
docs/advanced/making plugins.md 2 ●●● patch | view | raw | blame | history
quartz/components/TagList.tsx 5 ●●●● patch | view | raw | blame | history
quartz/components/pages/Content.tsx 8 ●●●●● patch | view | raw | blame | history
quartz/components/pages/FolderContent.tsx 6 ●●●●● patch | view | raw | blame | history
quartz/components/pages/TagContent.tsx 6 ●●●●● patch | view | raw | blame | history
quartz/util/jsx.ts 15 ●●●●● patch | view | raw | blame | history
quartz/util/trace.ts 8 ●●●● patch | view | raw | blame | history
docs/advanced/making plugins.md
@@ -247,7 +247,7 @@
- Your component should use `getQuartzComponents` to declare a list of `QuartzComponents` that it uses to construct the page. See the page on [[creating components]] for more information.
- You can use the `renderPage` function defined in `quartz/components/renderPage.tsx` to render Quartz components into HTML.
- If you need to render an HTML AST to JSX, you can use the `toJsxRuntime` function from `hast-util-to-jsx-runtime` library. An example of this can be found in `quartz/components/pages/Content.tsx`.
- If you need to render an HTML AST to JSX, you can use the `htmlToJsx` function from `quartz/util/jsx.ts`. An example of this can be found in `quartz/components/pages/Content.tsx`.
For example, the following is a simplified version of the content page plugin that renders every single page.
quartz/components/TagList.tsx
@@ -33,9 +33,12 @@
  gap: 0.4rem;
  margin: 1rem 0;
  flex-wrap: wrap;
  justify-content: flex-end;
  justify-self: end;
}
.section-ul .tags {
  justify-content: flex-end;
}
  
.tags > li {
  display: inline-block;
quartz/components/pages/Content.tsx
@@ -1,10 +1,8 @@
import { htmlToJsx } from "../../util/jsx"
import { QuartzComponentConstructor, QuartzComponentProps } from "../types"
import { Fragment, jsx, jsxs } from "preact/jsx-runtime"
import { toJsxRuntime } from "hast-util-to-jsx-runtime"
function Content({ tree }: QuartzComponentProps) {
  // @ts-ignore (preact makes it angry)
  const content = toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: "html" })
function Content({ fileData, tree }: QuartzComponentProps) {
  const content = htmlToJsx(fileData.filePath!, tree)
  return <article class="popover-hint">{content}</article>
}
quartz/components/pages/FolderContent.tsx
@@ -1,6 +1,4 @@
import { QuartzComponentConstructor, QuartzComponentProps } from "../types"
import { Fragment, jsx, jsxs } from "preact/jsx-runtime"
import { toJsxRuntime } from "hast-util-to-jsx-runtime"
import path from "path"
import style from "../styles/listPage.scss"
@@ -8,6 +6,7 @@
import { _stripSlashes, simplifySlug } from "../../util/path"
import { Root } from "hast"
import { pluralize } from "../../util/lang"
import { htmlToJsx } from "../../util/jsx"
function FolderContent(props: QuartzComponentProps) {
  const { tree, fileData, allFiles } = props
@@ -29,8 +28,7 @@
  const content =
    (tree as Root).children.length === 0
      ? fileData.description
      : // @ts-ignore
        toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: "html" })
      : htmlToJsx(fileData.filePath!, tree)
  return (
    <div class="popover-hint">
quartz/components/pages/TagContent.tsx
@@ -1,12 +1,11 @@
import { QuartzComponentConstructor, QuartzComponentProps } from "../types"
import { Fragment, jsx, jsxs } from "preact/jsx-runtime"
import { toJsxRuntime } from "hast-util-to-jsx-runtime"
import style from "../styles/listPage.scss"
import { PageList } from "../PageList"
import { FullSlug, getAllSegmentPrefixes, simplifySlug } from "../../util/path"
import { QuartzPluginData } from "../../plugins/vfile"
import { Root } from "hast"
import { pluralize } from "../../util/lang"
import { htmlToJsx } from "../../util/jsx"
const numPages = 10
function TagContent(props: QuartzComponentProps) {
@@ -26,8 +25,7 @@
  const content =
    (tree as Root).children.length === 0
      ? fileData.description
      : // @ts-ignore
        toJsxRuntime(tree, { Fragment, jsx, jsxs, elementAttributeNameCase: "html" })
      : htmlToJsx(fileData.filePath!, tree)
  if (tag === "") {
    const tags = [...new Set(allFiles.flatMap((data) => data.frontmatter?.tags ?? []))]
quartz/util/jsx.ts
New file
@@ -0,0 +1,15 @@
import { toJsxRuntime } from "hast-util-to-jsx-runtime"
import { QuartzPluginData } from "../plugins/vfile"
import { Node, Root } from "hast"
import { Fragment, jsx, jsxs } from "preact/jsx-runtime"
import { trace } from "./trace"
import { type FilePath } from "./path"
export function htmlToJsx(fp: FilePath, tree: Node<QuartzPluginData>) {
  try {
    // @ts-ignore (preact makes it angry)
    return toJsxRuntime(tree as Root, { Fragment, jsx, jsxs, elementAttributeNameCase: "html" })
  } catch (e) {
    trace(`Failed to parse Markdown in \`${fp}\` into JSX`, e as Error)
  }
}
quartz/util/trace.ts
@@ -4,7 +4,7 @@
const rootFile = /.*at file:/
export function trace(msg: string, err: Error) {
  const stack = err.stack
  let stack = err.stack ?? ""
  const lines: string[] = []
@@ -12,15 +12,11 @@
  lines.push(
    "\n" +
      chalk.bgRed.black.bold(" ERROR ") +
      "\n" +
      "\n\n" +
      chalk.red(` ${msg}`) +
      (err.message.length > 0 ? `: ${err.message}` : ""),
  )
  if (!stack) {
    return
  }
  let reachedEndOfLegibleTrace = false
  for (const line of stack.split("\n").slice(1)) {
    if (reachedEndOfLegibleTrace) {