Skip to content
Draft
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
1 change: 1 addition & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ on:
- 'packages/editor'
- 'packages/latex-extension'
- 'packages/page-constructor-extension'
- 'packages/viewer'
tag:
description: 'Publish with tag'
required: true
Expand Down
3 changes: 2 additions & 1 deletion .release-please/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"packages": {
"packages/editor": {},
"packages/latex-extension": {},
"packages/page-constructor-extension": {}
"packages/page-constructor-extension": {},
"packages/viewer": {}
}
}
3 changes: 2 additions & 1 deletion .release-please/manifest.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"packages/editor": "15.40.0",
"packages/latex-extension": "0.1.0",
"packages/page-constructor-extension": "0.1.0"
"packages/page-constructor-extension": "0.1.0",
"packages/viewer": "0.0.0"
}
1 change: 1 addition & 0 deletions demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@gravity-ui/markdown-editor": "workspace:*",
"@gravity-ui/markdown-editor-latex-extension": "workspace:*",
"@gravity-ui/markdown-editor-page-constructor-extension": "workspace:*",
"@gravity-ui/markdown-viewer": "workspace:*",
"@gravity-ui/page-constructor": "catalog:peer-gravity",
"@gravity-ui/uikit": "catalog:peer-gravity",
"markdown-it": "catalog:peers"
Expand Down
17 changes: 17 additions & 0 deletions demo/src/stories/viewer/MarkdownViewer.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type {StoryObj} from '@storybook/react';

import {markup} from '../../defaults/content';

import {Viewer} from './MarkdownViewer';

export const Story: StoryObj<typeof Viewer> = {
args: {
markdown: markup,
},
};
Story.storyName = 'Markdown Viewer';

export default {
title: 'Viewer / Markdown Viewer',
component: Viewer,
};
28 changes: 28 additions & 0 deletions demo/src/stories/viewer/MarkdownViewer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {useEffect, useState} from 'react';

import transform from '@diplodoc/transform';
import {MarkdownViewer, cnYFM} from '@gravity-ui/markdown-viewer';

export type ViewerProps = {
markdown: string;
};

export const Viewer: React.FC<ViewerProps> = ({markdown}) => {
const [html, setHtml] = useState('');

useEffect(() => {
setHtml(transform(markdown).result.html);
}, [markdown]);

return (
<MarkdownViewer
html={html}
className={cnYFM({
'no-list-reset': true,
'no-stripe-table': true,
})}
qa="demo-markdown-viewer"
role="presentation"
/>
);
};
1 change: 1 addition & 0 deletions packages/viewer/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build
84 changes: 84 additions & 0 deletions packages/viewer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# @gravity-ui/markdown-viewer &middot; [![npm package](https://img.shields.io/npm/v/@gravity-ui/markdown-viewer)](https://www.npmjs.com/package/@gravity-ui/markdown-viewer)

Markdown viewer component for [@gravity-ui/markdown-editor](https://github.com/gravity-ui/markdown-editor). Renders pre-transformed HTML from `@diplodoc/transform` with support for YFM (Yandex Flavored Markdown) styles and runtime extensions.

## Installation

```bash
npm install @gravity-ui/markdown-viewer
```

### Required peer dependencies

```bash
npm install @gravity-ui/uikit react react-dom
```

## Usage

### Basic rendering

```tsx
import {MarkdownViewer} from '@gravity-ui/markdown-viewer';

export function Preview({html}: {html: string}) {
return <MarkdownViewer html={html} />;
}
```

### With YFM styles

To apply YFM styles, pass the `cnYFM()` class to the `className` prop. Import the stylesheets manually:

```tsx
import {MarkdownViewer, cnYFM} from '@gravity-ui/markdown-viewer';

import '@diplodoc/transform/dist/yfm.css';

export function Preview({html}: {html: string}) {
return <MarkdownViewer html={html} className={cnYFM()} />;
}
```

### With YFM modifiers

`cnYFM()` accepts optional modifier flags:

```tsx
<MarkdownViewer html={html} className={cnYFM({'no-list-reset': true, 'no-stripe-table': true})} />
```

Available modifiers:

| Modifier | Description |
|---|---|
| `no-list-reset` | Disable list counter reset |
| `no-stripe-table` | Disable striped table rows |

## API

### `MarkdownViewer`

| Prop | Type | Description |
|---|---|---|
| `html` | `string` | Pre-transformed HTML string to render. The component does not sanitize it — make sure to sanitize before passing. |
| `className` | `string` | CSS class applied to the inner content element |
| `style` | `CSSProperties` | Inline styles applied to the inner content element |
| `ref` | `Ref<HTMLDivElement>` | Ref forwarded to the inner content element |
| `children` | `ReactNode` | Additional nodes rendered alongside the content (e.g. runtime extension portals) |
| `...props` | `HTMLAttributes<HTMLDivElement>` | Any other props are forwarded to the inner content `div` |

### `cnYFM(mods?, mix?)`

BEM class name helper for the `.yfm` scope. Use it to enable YFM styles and modifiers on the viewer.

```ts
import {cnYFM, type YFMMods} from '@gravity-ui/markdown-viewer';

cnYFM() // => 'yfm'
cnYFM({'no-list-reset': true}) // => 'yfm yfm_no-list-reset'
```

## License

MIT
20 changes: 20 additions & 0 deletions packages/viewer/gulpfile.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {dirname, resolve} from 'node:path';
import {fileURLToPath} from 'node:url';

import {series, task} from '@markdown-editor/gulp-tasks';
import {registerBuildTasks} from '@markdown-editor/gulp-tasks/build';

import pkg from './package.json' with {type: 'json'};

const __dirname = dirname(fileURLToPath(import.meta.url));

const BUILD_DIR = resolve('build');
const NODE_MODULES_DIR = resolve(__dirname, 'node_modules');

registerBuildTasks({
version: pkg.version,
buildDir: BUILD_DIR,
nodeModulesDir: NODE_MODULES_DIR,
});

task('default', series('clean', 'build'));
71 changes: 71 additions & 0 deletions packages/viewer/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{
"name": "@gravity-ui/markdown-viewer",
"version": "0.0.0",
"description": "Viewer for markdown content from @gravity-ui/markdown-editor",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/gravity-ui/markdown-editor"
},
"keywords": [
"md",
"yfm",
"markdown"
],
"scripts": {
"clean": "gulp clean",
"build": "gulp build",
"typecheck": "tsc -p tsconfig.json --noEmit",
"lint": "run-p -cs lint:*",
"lint:js": "eslint './**/*.{js,jsx,mjs,ts,tsx}'",
"lint:styles": "stylelint './**/*.{css,scss}' --allow-empty-input",
"lint:prettier": "prettier --check './**/*.{js,jsx,mjs,ts,tsx,css,scss}'",
"test": "exit 0",
"prepublishOnly": "nx lint && nx clean && nx build"
},
"exports": {
".": {
"import": {
"types": "./build/esm/index.d.ts",
"default": "./build/esm/index.js"
},
"require": {
"types": "./build/cjs/index.d.ts",
"default": "./build/cjs/index.js"
}
}
},
"main": "build/cjs/index.js",
"module": "build/esm/index.js",
"types": "build/esm/index.d.ts",
"files": [
"build",
"README.md"
],
"dependencies": {
"@bem-react/classname": "^1.6.0",
"tslib": "catalog:ts"
},
"devDependencies": {
"@gravity-ui/uikit": "catalog:peer-gravity",
"@markdown-editor/gulp-tasks": "workspace:*",
"@markdown-editor/linters": "workspace:*",
"@markdown-editor/tsconfig": "workspace:*",
"@types/react": "catalog:react",
"@types/react-dom": "catalog:react",
"gulp-cli": "catalog:",
"npm-run-all": "^4.1.5",
"react": "catalog:react",
"react-dom": "catalog:react",
"typescript": "catalog:ts"
},
"peerDependencies": {
"@gravity-ui/uikit": "^7.1.0",
"react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
},
"sideEffects": [
"*.css",
"*.scss"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {type HTMLAttributes, type PropsWithChildren, forwardRef} from 'react';

import type {QAProps} from '@gravity-ui/uikit';

import {cn} from '../../utils/classname';

const b = cn('viewer');

export type MarkdownViewerProps = PropsWithChildren<
Omit<HTMLAttributes<HTMLDivElement>, 'dangerouslySetInnerHTML'> &
QAProps & {
/** Pre-transformed HTML string. The component does not sanitize it — make sure to sanitize before passing. */
html: string;
}
>;

export const MarkdownViewer = forwardRef<HTMLDivElement, MarkdownViewerProps>(
function MarkdownViewer({html, className, qa, children, ...restProps}, ref) {
return (
<div className={b()}>
<div
{...restProps}
ref={ref}
data-qa={qa}
className={b('content', className)}
dangerouslySetInnerHTML={{__html: html}}
/>
{children}
</div>
);
},
);
1 change: 1 addition & 0 deletions packages/viewer/src/components/MarkdownViewer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './MarkdownViewer';
1 change: 1 addition & 0 deletions packages/viewer/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './MarkdownViewer';
2 changes: 2 additions & 0 deletions packages/viewer/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export {MarkdownViewer, type MarkdownViewerProps} from './components';
export {type YFMMods, cnYFM} from './utils/cn-yfm';
3 changes: 3 additions & 0 deletions packages/viewer/src/utils/classname.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import {withNaming} from '@bem-react/classname';

export const cn = withNaming({n: 'g-md-', e: '__', m: '_', v: '_'});
13 changes: 13 additions & 0 deletions packages/viewer/src/utils/cn-yfm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {type ClassNameList, cn} from '@bem-react/classname';

const b = cn('yfm');

export type YFMMods = {
/** Disable list counter reset */
'no-list-reset'?: boolean;
/** Disable striped table rows */
'no-stripe-table'?: boolean;
[key: string]: string | boolean | number | undefined;
};

export const cnYFM: (mods?: YFMMods | null, mix?: ClassNameList) => string = b;
10 changes: 10 additions & 0 deletions packages/viewer/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"extends": "@markdown-editor/tsconfig/tsconfig",
"compilerOptions": {
"outDir": "build/esm",
"baseUrl": ".",
"paths": {}
},
"include": ["src/**/*"],
"exclude": ["build"]
}
46 changes: 46 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading