Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reusable AsciiDoc utils #100

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
59 changes: 59 additions & 0 deletions components/src/asciidoc/Listing.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { LiteralBlock, Title, useConverterContext } from '@oxide/react-asciidoc'
benjaminleonard marked this conversation as resolved.
Show resolved Hide resolved
import cn from 'classnames'

import Mermaid from './Mermaid'

const Listing = ({ node }: { node: LiteralBlock }) => {
const { document } = useConverterContext()

const docAttrs = document.attributes || {}
const nowrap = node.attributes.nowrap || docAttrs['prewrap'] === undefined

if (node.style === 'source') {
const lang = node.language

return (
<div
className="listingblock"
{...(node.lineNumber ? { 'data-lineno': node.lineNumber } : {})}
>
<Title text={node.title} />
<div className="content">
<pre className={cn('highlight', nowrap ? ' nowrap' : '')}>
{lang && lang === 'mermaid' ? (
<Mermaid content={node.content || ''} />
) : (
<code
className={lang && `language-${lang}`}
data-lang={lang || undefined}
dangerouslySetInnerHTML={{
__html: node.content || '',
}}
/>
)}
</pre>
</div>
</div>
)
} else {
// Regular listing blocks are wrapped only in a `pre` tag
return (
<div
className="listingblock"
{...(node.lineNumber ? { 'data-lineno': node.lineNumber } : {})}
>
<Title text={node.title} />
<div className="content">
<pre
className={cn('highlight !block', nowrap ? 'nowrap' : '')}
dangerouslySetInnerHTML={{
__html: node.content || '',
}}
/>
</div>
</div>
)
}
}

export default Listing
41 changes: 41 additions & 0 deletions components/src/asciidoc/Mermaid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* Copyright Oxide Computer Company
*/
import mermaid from 'mermaid'
import { memo, useId, useState } from 'react'

mermaid.initialize({
startOnLoad: false,
theme: 'dark',
})

const Mermaid = memo(function Mermaid({ content }: { content: string }) {
const [showSource, setShowSource] = useState(false)
const id = `mermaid-diagram-${useId().replace(/:/g, '_')}`

const mermaidRef = async (node: HTMLElement | null) => {
if (node) {
const { svg } = await mermaid.render(id, content)
node.innerHTML = svg
}
}

return (
<>
<button
className="absolute right-2 top-2 text-mono-xs text-tertiary"
onClick={() => setShowSource(!showSource)}
>
{showSource ? 'Hide' : 'Show'} Source <span className="text-quinary">|</span>{' '}
Mermaid
</button>
{!showSource ? <code ref={mermaidRef} className="w-full" /> : <code>{content}</code>}
</>
)
})

export default Mermaid
55 changes: 55 additions & 0 deletions components/src/asciidoc/Section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* Copyright Oxide Computer Company
*/
import { Link16Icon } from '@/icons/react'
import { Content, type SectionBlock, parse } from '@oxide/react-asciidoc'
import cn from 'classnames'
import { createElement } from 'react'

// We need to remove anchors from the section title (and table of contents) because having
// an anchor within an anchor causes a client/server render mismatch
export const stripAnchors = (str: string) => str.replace(/<a[^>]*>(.*?)<\/a>/gi, '$1')

const Section = ({ node }: { node: SectionBlock }) => {
const level = node.level
let title: JSX.Element | string = ''

let sectNum = node.num
sectNum = sectNum === '.' ? '' : sectNum

title = (
<>
<span className="anchor" id={node.id || ''} aria-hidden="true" />
<a className="link group" href={`#${node.id}`}>
{parse(stripAnchors(node.title))}
<Link16Icon className="ml-2 hidden text-accent-secondary group-hover:inline-block" />
</a>
</>
)

if (level === 0) {
return (
<>
<h1 className={cn('sect0', node.role)} data-sectnum={sectNum}>
{title}
</h1>
<Content blocks={node.blocks} />
</>
)
} else {
return (
<div className={cn(`sect${level}`, node.role)}>
{createElement(`h${level + 1}`, { 'data-sectnum': sectNum }, title)}
<div className="sectionbody">
<Content blocks={node.blocks} />
</div>
</div>
)
}
}

export default Section
Loading
Loading