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

feat: Implemented transformer for the wavedrom library #1503

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions demo/starter/slides.md
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,22 @@ function fibonacci(n: number): number {
console.log(Array.from({ length: 10 }, (_, i) => fibonacci(i + 1)))
```

--- #15

# Waveforms

You can create waveforms from textual descriptions, directly in your Markdown.

```wavedrom
{signal: [
{name: 'clk', wave: 'p.....|...'},
{name: 'dat', wave: 'x.345x|=.x', data: ['head', 'body', 'tail', 'data']},
{name: 'req', wave: '0.1..0|1.0'},
{},
{name: 'ack', wave: '1.....|01.'}
]}
```

---
layout: center
class: text-center
Expand Down
36 changes: 36 additions & 0 deletions docs/guide/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,42 @@ C -->|Two| E[Result 2]

Learn more: [Demo](https://sli.dev/demo/starter/12) | [Mermaid](https://mermaid-js.github.io/mermaid)

## Waveforms

> Available since v0.48.9

You can also create waveforms from textual descriptions in your Markdown, powered by [Wavedrom](https://wavedrom.com/).

Code blocks marked as `wavedrom` will be converted to waveforms, for example:

````md
```wavedrom
{signal: [
{name: 'clk', wave: 'p.....|...'},
{name: 'dat', wave: 'x.345x|=.x', data: ['head', 'body', 'tail', 'data']},
{name: 'req', wave: '0.1..0|1.0'},
{},
{name: 'ack', wave: '1.....|01.'}
]}
```
````

You can further pass an options object to it to specify the scaling. The syntax of the object is a JavaScript object literal, you will need to add quotes (`'`) for strings and use comma (`,`) between keys.

````md
```wavedrom {scale: 0.8}
{signal: [
{name: 'clk', wave: 'p.....|...'},
{name: 'dat', wave: 'x.345x|=.x', data: ['head', 'body', 'tail', 'data']},
{name: 'req', wave: '0.1..0|1.0'},
{},
{name: 'ack', wave: '1.....|01.'}
]}
```
````

Learn more: [Demo](https://sli.dev/demo/starter/15) | [Wavedrom](https://wavedrom.com/tutorial.html)

## Multiple Entries

> Available since v0.15
Expand Down
85 changes: 85 additions & 0 deletions packages/client/builtin/Wavedrom.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<!--
Wavedrom
(auto transformed, you don't need to use this component directly)

Usage:

```wavedrom
{signal: [
{name: 'clk', wave: 'p.....|...'},
{name: 'dat', wave: 'x.345x|=.x', data: ['head', 'body', 'tail', 'data']},
{name: 'req', wave: '0.1..0|1.0'},
{},
{name: 'ack', wave: '1.....|01.'}
]}
```
-->

<script setup lang="ts">
import { getCurrentInstance, ref, watch, watchEffect } from 'vue'
import { renderWavedrom } from '../modules/wavedrom'
import ShadowRoot from '../internals/ShadowRoot.vue'
import { isDark } from '../logic/dark'

const props = defineProps<{
codeLz: string
scale?: number
theme?: string
}>()

const vm = getCurrentInstance()
const el = ref<ShadowRoot>()
const error = ref<string | null>(null)
const html = ref('')

watchEffect(async (onCleanup) => {
let disposed = false
onCleanup(() => {
disposed = true
})
error.value = null
try {
const svg = await renderWavedrom(
props.codeLz || '',
{
theme: props.theme || (isDark.value ? 'dark' : undefined),
...vm!.attrs,
},
)
if (!disposed)
html.value = svg
}
catch (e) {
error.value = `${e}`
console.warn(e)
}
})

const actualHeight = ref<number>()

watch(html, () => {
actualHeight.value = undefined
})

watchEffect(() => {
const svgEl = el.value?.children?.[0] as SVGElement | undefined
if (svgEl && svgEl.hasAttribute('viewBox') && actualHeight.value == null) {
const v = Number.parseFloat(svgEl.getAttribute('viewBox')?.split(' ')[3] || '')
actualHeight.value = Number.isNaN(v) ? undefined : v
}
}, { flush: 'post' })

watchEffect(() => {
const svgEl = el.value?.children?.[0] as SVGElement | undefined
if (svgEl != null && props.scale != null && actualHeight.value != null) {
svgEl.setAttribute('height', `${actualHeight.value * props.scale}`)
svgEl.removeAttribute('width')
svgEl.removeAttribute('style')
}
}, { flush: 'post' })
</script>

<template>
<pre v-if="error" border="1 red rounded" class="pa-3 text-wrap">{{ error }}</pre>
<ShadowRoot v-else class="wavedrom" :inner-html="html" @shadow="el = $event" />
</template>
33 changes: 33 additions & 0 deletions packages/client/modules/wavedrom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import onml from 'onml'
import wavedrom from 'wavedrom'
import def from 'wavedrom/skins/default'
import * as narrow from 'wavedrom/skins/narrow'
import * as lowkey from 'wavedrom/skins/lowkey'
import json5 from 'json5'

import lz from 'lz-string'
import { makeId } from '../logic/utils'

const skins = Object.assign({}, def, narrow, lowkey)
skins.default = def.default

const cache = new Map<string, string>()

export async function renderWavedrom(lzEncoded: string, options: any) {
const key = lzEncoded + JSON.stringify(options)
const _cache = cache.get(key)
if (_cache)
return _cache

const code = json5.parse(lz.decompressFromBase64(lzEncoded))
const id = makeId()

const res = wavedrom.renderAny(0, code, skins)
if (res && res[1])
res[1].id = id

const svg = onml.s(res)
cache.set(key, svg)

return svg
}
6 changes: 5 additions & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"lz-string": "^1.5.0",
"mermaid": "^10.9.0",
"monaco-editor": "^0.47.0",
"onml": "^2.1.0",
"prettier": "^3.2.5",
"recordrtc": "^5.6.2",
"shiki": "^1.2.3",
Expand All @@ -61,9 +62,12 @@
"unocss": "^0.58.9",
"vue": "^3.4.21",
"vue-demi": "^0.14.7",
"vue-router": "^4.3.0"
"vue-router": "^4.3.0",
"wavedrom": "^3.5.0"
},
"devDependencies": {
"@types/json5": "^2.2.0",
"json5": "^2.2.3",
"vite": "^5.2.7"
}
}
20 changes: 20 additions & 0 deletions packages/client/shim.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,23 @@ declare module 'mermaid/dist/mermaid.esm.mjs' {

export default Mermaid
}

declare module 'wavedrom' {
export function renderAny(number, string, unknown): any[]
}

declare module 'wavedrom/skins/default' {
export default unknown
}

declare module 'wavedrom/skins/narrow' {
export const narrow: unknown
}

declare module 'wavedrom/skins/lowkey' {
export const lowkey: unknown
}

declare module 'onml' {
export function s(value: unknown): string
}
1 change: 1 addition & 0 deletions packages/parser/src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export function detectFeatures(code: string): SlidevFeatureFlags {
monaco: !!code.match(/{monaco.*}/),
tweet: !!code.match(/<Tweet\b/),
mermaid: !!code.match(/^```mermaid/m),
wavedrom: !!code.match(/^```wavedrom/m),
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/slidev/node/syntax/transform/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from './slot-sugar'
export * from './in-page-css'
export * from './monaco'
export * from './katex-wrapper'
export * from './wavedrom'
15 changes: 15 additions & 0 deletions packages/slidev/node/syntax/transform/wavedrom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import lz from 'lz-string'
import type { MarkdownTransformContext } from '@slidev/types'

/**
* Transform Wavedrom code blocks (render done on client side)
*/
export function transformWavedrom(ctx: MarkdownTransformContext) {
ctx.s.replace(/^```wavedrom\s*?({.*?})?\n([\s\S]+?)\n```/mg, (full, options = '', code = '', index: number) => {
code = code.trim()
options = options.trim() || '{}'
const encoded = lz.compressToBase64(code)
ctx.ignores.push([index, index + full.length])
return `<Wavedrom code-lz="${encoded}" v-bind="${options}" />`
})
}
3 changes: 2 additions & 1 deletion packages/slidev/node/vite/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import MarkdownItPrism from '../syntax/markdown-it/markdown-it-prism'

import { loadShikiSetups } from '../setups/shiki'
import { loadSetups } from '../setups/load'
import { transformCodeWrapper, transformKaTexWrapper, transformMagicMove, transformMermaid, transformMonaco, transformPageCSS, transformPlantUml, transformSlotSugar, transformSnippet } from '../syntax/transform'
import { transformCodeWrapper, transformKaTexWrapper, transformMagicMove, transformMermaid, transformMonaco, transformPageCSS, transformPlantUml, transformSlotSugar, transformSnippet, transformWavedrom } from '../syntax/transform'
import { escapeVueInCode } from '../syntax/transform/utils'

let shiki: Highlighter | undefined
Expand Down Expand Up @@ -144,6 +144,7 @@ export async function createMarkdownPlugin(
transformMagicMove(ctx, shiki, shikiOptions)

transformMermaid(ctx)
transformWavedrom(ctx)
transformPlantUml(ctx, config.plantUmlServer)
transformMonaco(ctx, monacoEnabled)
transformCodeWrapper(ctx)
Expand Down
1 change: 1 addition & 0 deletions packages/types/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export interface SlidevFeatureFlags {
monaco: boolean
tweet: boolean
mermaid: boolean
wavedrom: boolean
}

export interface SlidevMarkdown {
Expand Down
Loading