mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-04 00:28:33 +00:00
feat: integrate typst preview & fix mermaid style (#14168)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Typst code block preview with interactive rendering controls (zoom,
pan, reset) and user-friendly error messages
* **Style**
* Centered Mermaid diagram rendering for improved layout
* **Tests**
* Added end-to-end preview validation tests for Typst and Mermaid
* **Chores**
* Added WebAssembly type declarations and updated frontend packages;
removed a build debug configuration entry
<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -34,6 +34,9 @@
|
||||
"@juggle/resize-observer": "^3.4.0",
|
||||
"@lit/context": "^1.1.4",
|
||||
"@marsidev/react-turnstile": "^1.1.0",
|
||||
"@myriaddreamin/typst-ts-renderer": "^0.7.0-rc2",
|
||||
"@myriaddreamin/typst-ts-web-compiler": "^0.7.0-rc2",
|
||||
"@myriaddreamin/typst.ts": "^0.7.0-rc2",
|
||||
"@preact/signals-core": "^1.8.0",
|
||||
"@radix-ui/react-collapsible": "^1.1.2",
|
||||
"@radix-ui/react-context-menu": "^2.1.15",
|
||||
|
||||
@@ -13,6 +13,10 @@ import {
|
||||
CodeBlockMermaidPreview,
|
||||
effects as mermaidPreviewEffects,
|
||||
} from './mermaid-preview';
|
||||
import {
|
||||
CodeBlockTypstPreview,
|
||||
effects as typstPreviewEffects,
|
||||
} from './typst-preview';
|
||||
|
||||
const optionsSchema = z.object({
|
||||
framework: z.instanceof(FrameworkProvider).optional(),
|
||||
@@ -28,6 +32,7 @@ export class CodeBlockPreviewViewExtension extends ViewExtensionProvider {
|
||||
|
||||
htmlPreviewEffects();
|
||||
mermaidPreviewEffects();
|
||||
typstPreviewEffects();
|
||||
}
|
||||
|
||||
override setup(
|
||||
@@ -37,5 +42,6 @@ export class CodeBlockPreviewViewExtension extends ViewExtensionProvider {
|
||||
super.setup(context, options);
|
||||
context.register(CodeBlockHtmlPreview);
|
||||
context.register(CodeBlockMermaidPreview);
|
||||
context.register(CodeBlockTypstPreview);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,9 @@ export class MermaidPreview extends SignalWatcher(
|
||||
}
|
||||
|
||||
.mermaid-preview-svg > div {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,457 @@
|
||||
import { CodeBlockPreviewExtension } from '@blocksuite/affine/blocks/code';
|
||||
import { SignalWatcher, WithDisposable } from '@blocksuite/affine/global/lit';
|
||||
import type { CodeBlockModel } from '@blocksuite/affine/model';
|
||||
import { unsafeCSSVarV2 } from '@blocksuite/affine/shared/theme';
|
||||
import { ShadowlessElement } from '@blocksuite/std';
|
||||
import { css, html, nothing } from 'lit';
|
||||
import { property, query, state } from 'lit/decorators.js';
|
||||
import { choose } from 'lit/directives/choose.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
|
||||
import { ensureTypstReady, getTypst } from './typst';
|
||||
|
||||
const RENDER_DEBOUNCE_MS = 200;
|
||||
|
||||
export const CodeBlockTypstPreview = CodeBlockPreviewExtension(
|
||||
'typst',
|
||||
model => html`<typst-preview .model=${model}></typst-preview>`
|
||||
);
|
||||
|
||||
export class TypstPreview extends SignalWatcher(
|
||||
WithDisposable(ShadowlessElement)
|
||||
) {
|
||||
static override styles = css`
|
||||
.typst-preview-loading {
|
||||
color: ${unsafeCSSVarV2('text/placeholder')};
|
||||
font-family: 'IBM Plex Mono';
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.typst-preview-error,
|
||||
.typst-preview-fallback {
|
||||
color: ${unsafeCSSVarV2('button/error')};
|
||||
font-family: 'IBM Plex Mono';
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
details.typst-error-details {
|
||||
margin-top: 8px;
|
||||
text-align: left;
|
||||
border: 1px dashed ${unsafeCSSVarV2('layer/insideBorder/border')};
|
||||
border-radius: 6px;
|
||||
padding: 8px;
|
||||
background: ${unsafeCSSVarV2('layer/background/secondary')};
|
||||
color: ${unsafeCSSVarV2('text/secondary')};
|
||||
font-family: 'IBM Plex Mono';
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.typst-error-text {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.typst-copy-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.typst-copy-button {
|
||||
padding: 6px 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid ${unsafeCSSVarV2('layer/insideBorder/border')};
|
||||
background: ${unsafeCSSVarV2('layer/background/primary')};
|
||||
color: ${unsafeCSSVarV2('text/primary')};
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
|
||||
.typst-copy-button:hover {
|
||||
background: ${unsafeCSSVarV2('layer/background/hoverOverlay')};
|
||||
}
|
||||
|
||||
.typst-preview-container {
|
||||
width: 100%;
|
||||
min-height: 300px;
|
||||
max-height: 600px;
|
||||
border: 1px solid ${unsafeCSSVarV2('layer/insideBorder/border')};
|
||||
border-radius: 8px;
|
||||
background: ${unsafeCSSVarV2('layer/background/primary')};
|
||||
padding: 12px;
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.typst-preview-svg {
|
||||
width: 100%;
|
||||
transform-origin: center;
|
||||
transition: transform 0.15s ease-out;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.typst-preview-svg > div {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.typst-controls {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.typst-control-button {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border: 1px solid ${unsafeCSSVarV2('layer/insideBorder/border')};
|
||||
border-radius: 4px;
|
||||
background: ${unsafeCSSVarV2('layer/background/primary')};
|
||||
color: ${unsafeCSSVarV2('text/primary')};
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.typst-control-button:hover {
|
||||
background: ${unsafeCSSVarV2('layer/background/hoverOverlay')};
|
||||
border-color: ${unsafeCSSVarV2('layer/insideBorder/primaryBorder')};
|
||||
}
|
||||
|
||||
.typst-control-button:active {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
`;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor model: CodeBlockModel | null = null;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor typstCode: string | null = null;
|
||||
|
||||
@state()
|
||||
accessor state: 'loading' | 'error' | 'finish' | 'fallback' | 'syntax-error' =
|
||||
'loading';
|
||||
|
||||
@state()
|
||||
accessor svgContent: string = '';
|
||||
|
||||
@state()
|
||||
accessor errorMessage: string | null = null;
|
||||
|
||||
@state()
|
||||
accessor copyState: 'idle' | 'copied' | 'failed' = 'idle';
|
||||
|
||||
@query('.typst-preview-container')
|
||||
accessor container!: HTMLDivElement;
|
||||
|
||||
private renderTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
private isRendering = false;
|
||||
private scale = 1;
|
||||
private translateX = 0;
|
||||
private translateY = 0;
|
||||
private isDragging = false;
|
||||
private lastMouseX = 0;
|
||||
private lastMouseY = 0;
|
||||
|
||||
private async _copyError() {
|
||||
if (!this.errorMessage) return;
|
||||
try {
|
||||
await navigator.clipboard.writeText(this.errorMessage);
|
||||
this.copyState = 'copied';
|
||||
setTimeout(() => (this.copyState = 'idle'), 1500);
|
||||
} catch (err) {
|
||||
console.error('Failed to copy Typst error message:', err);
|
||||
this.copyState = 'failed';
|
||||
setTimeout(() => (this.copyState = 'idle'), 1500);
|
||||
}
|
||||
}
|
||||
|
||||
private get _errorMessageDetail() {
|
||||
return this.errorMessage
|
||||
? html`<details class="typst-error-details">
|
||||
<summary>Error details</summary>
|
||||
<pre
|
||||
class="typst-error-text"
|
||||
tabindex="0"
|
||||
aria-label="Typst error message"
|
||||
>
|
||||
${this.errorMessage}</pre
|
||||
>
|
||||
<div class="typst-copy-row">
|
||||
<button class="typst-copy-button" @click=${this._copyError}>
|
||||
${this._copyButtonLabel}
|
||||
</button>
|
||||
</div>
|
||||
</details>`
|
||||
: nothing;
|
||||
}
|
||||
|
||||
private get _errorMessageComponent() {
|
||||
const lower = this.errorMessage?.toLowerCase() ?? '';
|
||||
|
||||
const friendlyMessage = lower.includes('no font could be found')
|
||||
? 'Failed to load fonts. Please check your network or try again.'
|
||||
: 'Failed to render Typst. Please check your code.';
|
||||
|
||||
return [friendlyMessage, this._errorMessageDetail];
|
||||
}
|
||||
|
||||
private get _copyButtonLabel() {
|
||||
if (this.copyState === 'copied') {
|
||||
return 'Copied';
|
||||
} else if (this.copyState === 'failed') {
|
||||
return 'Copy failed';
|
||||
} else {
|
||||
return 'Copy';
|
||||
}
|
||||
}
|
||||
|
||||
private get _finalPreview() {
|
||||
return this.state === 'finish'
|
||||
? html`
|
||||
<div class="typst-controls">
|
||||
<button
|
||||
class="typst-control-button"
|
||||
@click=${this._zoomOut}
|
||||
title="Zoom out"
|
||||
>
|
||||
−
|
||||
</button>
|
||||
<button
|
||||
class="typst-control-button"
|
||||
@click=${this._resetView}
|
||||
title="Reset view"
|
||||
>
|
||||
⟳
|
||||
</button>
|
||||
<button
|
||||
class="typst-control-button"
|
||||
@click=${this._zoomIn}
|
||||
title="Zoom in"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="typst-preview-svg"
|
||||
style=${styleMap({
|
||||
transform: `translate(${this.translateX}px, ${this.translateY}px) scale(${this.scale})`,
|
||||
})}
|
||||
>
|
||||
${this.svgContent
|
||||
? html`<div .innerHTML=${this.svgContent}></div>`
|
||||
: nothing}
|
||||
</div>
|
||||
`
|
||||
: nothing;
|
||||
}
|
||||
|
||||
override firstUpdated() {
|
||||
this._scheduleRender();
|
||||
this._setupDragListeners();
|
||||
|
||||
if (this.model) {
|
||||
this.disposables.add(
|
||||
this.model.props.text$.subscribe(() => {
|
||||
this._scheduleRender();
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
override disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
if (this.renderTimeout) {
|
||||
clearTimeout(this.renderTimeout);
|
||||
this.renderTimeout = null;
|
||||
}
|
||||
}
|
||||
|
||||
get normalizedCode() {
|
||||
return this.model?.props.text.toString() ?? this.typstCode ?? '';
|
||||
}
|
||||
|
||||
private _scheduleRender() {
|
||||
if (this.renderTimeout) {
|
||||
clearTimeout(this.renderTimeout);
|
||||
}
|
||||
|
||||
this.renderTimeout = setTimeout(() => {
|
||||
this._render().catch(error => {
|
||||
console.error('Typst preview render failed:', error);
|
||||
});
|
||||
}, RENDER_DEBOUNCE_MS);
|
||||
}
|
||||
|
||||
private _resetView() {
|
||||
this.scale = 1;
|
||||
this.translateX = 0;
|
||||
this.translateY = 0;
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private _zoomIn() {
|
||||
this.scale = Math.min(this.scale * 1.2, 5);
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private _zoomOut() {
|
||||
this.scale = Math.max(this.scale / 1.2, 0.2);
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private _setupDragListeners() {
|
||||
if (!this.container) return;
|
||||
|
||||
this.disposables.addFromEvent(
|
||||
this.container,
|
||||
'mousedown',
|
||||
(event: MouseEvent) => {
|
||||
if (event.button !== 0) return;
|
||||
this.isDragging = true;
|
||||
this.lastMouseX = event.clientX;
|
||||
this.lastMouseY = event.clientY;
|
||||
this.container.style.cursor = 'grabbing';
|
||||
}
|
||||
);
|
||||
|
||||
this.disposables.addFromEvent(
|
||||
document,
|
||||
'mousemove',
|
||||
(event: MouseEvent) => {
|
||||
if (!this.isDragging) return;
|
||||
const deltaX = event.clientX - this.lastMouseX;
|
||||
const deltaY = event.clientY - this.lastMouseY;
|
||||
|
||||
this.translateX += deltaX;
|
||||
this.translateY += deltaY;
|
||||
this.lastMouseX = event.clientX;
|
||||
this.lastMouseY = event.clientY;
|
||||
this.requestUpdate();
|
||||
}
|
||||
);
|
||||
|
||||
this.disposables.addFromEvent(document, 'mouseup', () => {
|
||||
this.isDragging = false;
|
||||
if (this.container) {
|
||||
this.container.style.cursor = 'grab';
|
||||
}
|
||||
});
|
||||
|
||||
this.disposables.addFromEvent(this.container, 'selectstart', e =>
|
||||
e.preventDefault()
|
||||
);
|
||||
}
|
||||
|
||||
private async _render() {
|
||||
if (this.isRendering) return;
|
||||
this.isRendering = true;
|
||||
this.state = 'loading';
|
||||
this.errorMessage = null;
|
||||
|
||||
const code = this.normalizedCode.trim();
|
||||
if (!code) {
|
||||
this.svgContent = '';
|
||||
this.state = 'fallback';
|
||||
this.isRendering = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await ensureTypstReady();
|
||||
const typst = await getTypst();
|
||||
const svg = await typst.svg({ mainContent: code });
|
||||
this.svgContent = svg;
|
||||
this.state = 'finish';
|
||||
this._resetView();
|
||||
} catch (error) {
|
||||
console.error('Typst preview failed:', error);
|
||||
const message =
|
||||
(error as Error | undefined)?.message ??
|
||||
(typeof error === 'string' ? error : null);
|
||||
this.errorMessage = message;
|
||||
this.state =
|
||||
message && message.toLowerCase().includes('syntax')
|
||||
? 'syntax-error'
|
||||
: 'error';
|
||||
} finally {
|
||||
this.isRendering = false;
|
||||
}
|
||||
}
|
||||
|
||||
override render() {
|
||||
return html`
|
||||
<div class="typst-preview-wrapper">
|
||||
${choose(this.state, [
|
||||
[
|
||||
'loading',
|
||||
() =>
|
||||
html`<div class="typst-preview-loading">
|
||||
Rendering Typst code...
|
||||
</div>`,
|
||||
],
|
||||
[
|
||||
'error',
|
||||
() =>
|
||||
html`<div class="typst-preview-error">
|
||||
${this._errorMessageComponent}
|
||||
</div>`,
|
||||
],
|
||||
[
|
||||
'syntax-error',
|
||||
() =>
|
||||
html`<div class="typst-preview-error">
|
||||
Typst code has errors: ${this.errorMessage ?? 'Unknown error.'}
|
||||
${this._errorMessageDetail}
|
||||
</div>`,
|
||||
],
|
||||
[
|
||||
'fallback',
|
||||
() =>
|
||||
html`<div class="typst-preview-fallback">
|
||||
Enter Typst code to preview.
|
||||
</div>`,
|
||||
],
|
||||
])}
|
||||
<div
|
||||
class="typst-preview-container"
|
||||
style=${styleMap({
|
||||
display: this.state === 'finish' ? undefined : 'none',
|
||||
})}
|
||||
>
|
||||
${this._finalPreview}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export function effects() {
|
||||
customElements.define('typst-preview', TypstPreview);
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'typst-preview': TypstPreview;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { $typst, type BeforeBuildFn, loadFonts } from '@myriaddreamin/typst.ts';
|
||||
|
||||
const FONT_CDN_URLS = [
|
||||
'https://cdn.affine.pro/fonts/Inter-Regular.woff',
|
||||
'https://cdn.affine.pro/fonts/Inter-SemiBold.woff',
|
||||
'https://cdn.affine.pro/fonts/Inter-Italic.woff',
|
||||
'https://cdn.affine.pro/fonts/Inter-SemiBoldItalic.woff',
|
||||
'https://cdn.affine.pro/fonts/SarasaGothicCL-Regular.ttf',
|
||||
] as const;
|
||||
|
||||
const getBeforeBuildHooks = (): BeforeBuildFn[] => [
|
||||
loadFonts([...FONT_CDN_URLS]),
|
||||
];
|
||||
|
||||
const compilerWasmUrl = new URL(
|
||||
'@myriaddreamin/typst-ts-web-compiler/pkg/typst_ts_web_compiler_bg.wasm',
|
||||
import.meta.url
|
||||
).toString();
|
||||
|
||||
const rendererWasmUrl = new URL(
|
||||
'@myriaddreamin/typst-ts-renderer/pkg/typst_ts_renderer_bg.wasm',
|
||||
import.meta.url
|
||||
).toString();
|
||||
|
||||
let typstInitPromise: Promise<void> | null = null;
|
||||
|
||||
export async function ensureTypstReady() {
|
||||
if (typstInitPromise) {
|
||||
return typstInitPromise;
|
||||
}
|
||||
|
||||
typstInitPromise = Promise.resolve()
|
||||
.then(() => {
|
||||
$typst.setCompilerInitOptions({
|
||||
beforeBuild: getBeforeBuildHooks(),
|
||||
getModule: () => compilerWasmUrl,
|
||||
});
|
||||
|
||||
$typst.setRendererInitOptions({
|
||||
beforeBuild: getBeforeBuildHooks(),
|
||||
getModule: () => rendererWasmUrl,
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
typstInitPromise = null;
|
||||
throw error;
|
||||
});
|
||||
|
||||
return typstInitPromise;
|
||||
}
|
||||
|
||||
export async function getTypst() {
|
||||
await ensureTypstReady();
|
||||
return $typst;
|
||||
}
|
||||
|
||||
export const TYPST_FONT_URLS = FONT_CDN_URLS;
|
||||
10
packages/frontend/core/src/types/types.d.ts
vendored
10
packages/frontend/core/src/types/types.d.ts
vendored
@@ -29,3 +29,13 @@ declare module '*.inline.svg' {
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
|
||||
declare module '*.wasm' {
|
||||
const url: string;
|
||||
export default url;
|
||||
}
|
||||
|
||||
declare module '*.wasm?url' {
|
||||
const url: string;
|
||||
export default url;
|
||||
}
|
||||
|
||||
@@ -43,6 +43,44 @@ test.describe('Code Block Preview', () => {
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test('enable mermaid preview', async ({ page }) => {
|
||||
const code = page.locator('affine-code');
|
||||
const mermaidSvg = page.locator('mermaid-preview .mermaid-preview-svg svg');
|
||||
|
||||
await openHomePage(page);
|
||||
await createNewPage(page);
|
||||
await waitForEditorLoad(page);
|
||||
await gotoContentFromTitle(page);
|
||||
await type(page, '```mermaid graph TD;A-->B');
|
||||
await code.hover({
|
||||
position: {
|
||||
x: 155,
|
||||
y: 65,
|
||||
},
|
||||
});
|
||||
await page.getByText('Preview').click();
|
||||
await expect(mermaidSvg).toBeVisible();
|
||||
});
|
||||
|
||||
test('enable typst preview', async ({ page }) => {
|
||||
const code = page.locator('affine-code');
|
||||
const typstPreview = page.locator('typst-preview');
|
||||
|
||||
await openHomePage(page);
|
||||
await createNewPage(page);
|
||||
await waitForEditorLoad(page);
|
||||
await gotoContentFromTitle(page);
|
||||
await type(page, '```typst = Title');
|
||||
await code.hover({
|
||||
position: {
|
||||
x: 155,
|
||||
y: 65,
|
||||
},
|
||||
});
|
||||
await page.getByText('Preview').click();
|
||||
await expect(typstPreview).toBeVisible();
|
||||
});
|
||||
|
||||
test('change lang without preview', async ({ page }) => {
|
||||
const code = page.locator('affine-code');
|
||||
const preview = page.locator('affine-code .affine-code-block-preview');
|
||||
|
||||
1
tools/@types/build-config/__all.d.ts
vendored
1
tools/@types/build-config/__all.d.ts
vendored
@@ -38,7 +38,6 @@ declare interface BUILD_CONFIG_TYPE {
|
||||
CAPTCHA_SITE_KEY: string;
|
||||
SENTRY_DSN: string;
|
||||
MIXPANEL_TOKEN: string;
|
||||
DEBUG_JOTAI: string;
|
||||
}
|
||||
|
||||
declare var BUILD_CONFIG: BUILD_CONFIG_TYPE;
|
||||
|
||||
@@ -54,7 +54,6 @@ export function getBuildConfig(
|
||||
CAPTCHA_SITE_KEY: process.env.CAPTCHA_SITE_KEY ?? '',
|
||||
SENTRY_DSN: process.env.SENTRY_DSN ?? '',
|
||||
MIXPANEL_TOKEN: process.env.MIXPANEL_TOKEN ?? '',
|
||||
DEBUG_JOTAI: process.env.DEBUG_JOTAI ?? '',
|
||||
};
|
||||
},
|
||||
get beta() {
|
||||
|
||||
34
yarn.lock
34
yarn.lock
@@ -416,6 +416,9 @@ __metadata:
|
||||
"@juggle/resize-observer": "npm:^3.4.0"
|
||||
"@lit/context": "npm:^1.1.4"
|
||||
"@marsidev/react-turnstile": "npm:^1.1.0"
|
||||
"@myriaddreamin/typst-ts-renderer": "npm:^0.7.0-rc2"
|
||||
"@myriaddreamin/typst-ts-web-compiler": "npm:^0.7.0-rc2"
|
||||
"@myriaddreamin/typst.ts": "npm:^0.7.0-rc2"
|
||||
"@preact/signals-core": "npm:^1.8.0"
|
||||
"@radix-ui/react-collapsible": "npm:^1.1.2"
|
||||
"@radix-ui/react-context-menu": "npm:^2.1.15"
|
||||
@@ -8869,6 +8872,37 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@myriaddreamin/typst-ts-renderer@npm:^0.7.0-rc2":
|
||||
version: 0.7.0-rc2
|
||||
resolution: "@myriaddreamin/typst-ts-renderer@npm:0.7.0-rc2"
|
||||
checksum: 10/ee5e86ef78effd0ade507e9e8467d23a6e5026e1347e07d2277ecf4b664bfa1329f26006cb34f58e0a347fdf283c32d0548125bdbd675e13e3f26a1822efd977
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@myriaddreamin/typst-ts-web-compiler@npm:^0.7.0-rc2":
|
||||
version: 0.7.0-rc2
|
||||
resolution: "@myriaddreamin/typst-ts-web-compiler@npm:0.7.0-rc2"
|
||||
checksum: 10/a149d6d3644eafcc68ec7b9ac512981b9f530751c2d09f467acc11bd35525e0413daa423b1d3ae1d57da48bf31705e3122a0bee87d5b214693dae9c83a963de7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@myriaddreamin/typst.ts@npm:^0.7.0-rc2":
|
||||
version: 0.7.0-rc2
|
||||
resolution: "@myriaddreamin/typst.ts@npm:0.7.0-rc2"
|
||||
dependencies:
|
||||
idb: "npm:^7.1.1"
|
||||
peerDependencies:
|
||||
"@myriaddreamin/typst-ts-renderer": ^0.7.0-rc2
|
||||
"@myriaddreamin/typst-ts-web-compiler": ^0.7.0-rc2
|
||||
peerDependenciesMeta:
|
||||
"@myriaddreamin/typst-ts-renderer":
|
||||
optional: true
|
||||
"@myriaddreamin/typst-ts-web-compiler":
|
||||
optional: true
|
||||
checksum: 10/b843c4f1f9e33d34d574f8f22108f4b67ac628d8ed5a71fb881f1a98e4f577090a537ce1862e610338de83e5b6a129dff00365a408ee522bb18df302802e5917
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@napi-rs/cli@npm:3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "@napi-rs/cli@npm:3.0.0"
|
||||
|
||||
Reference in New Issue
Block a user