Skip to content
Merged
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
2 changes: 2 additions & 0 deletions .github/workflows/package-consumers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@ jobs:
- run: npm run build
- run: npm run coverage
- run: npm run test:consumers
- run: npm run build --workspace nextjs
- run: npm run test:nextjs
49 changes: 19 additions & 30 deletions core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1064,49 +1064,38 @@ export default function App() {

### Support Nextjs

Use examples in [nextjs](https://nextjs.org/). [`#52`](https://github.com/uiwjs/react-md-editor/issues/52#issuecomment-848969341) [`#224`](https://github.com/uiwjs/react-md-editor/issues/224#issuecomment-901112079)
For Next.js App Router, see the working [Next.js example](https://github.com/uiwjs/react-md-editor/tree/master/example/nextjs) ([#682](https://github.com/uiwjs/react-md-editor/issues/682))

[![Open in CodeSandbox](https://img.shields.io/badge/Open%20in-CodeSandbox-blue?logo=codesandbox)](https://codesandbox.io/embed/nextjs-example-react-md-editor-https-github-com-uiwjs-react-md-editor-issues-516-1z56px?fontsize=14&hidenavigation=1&theme=dark)
[![Open in CodeSandbox](https://img.shields.io/badge/Open%20in-CodeSandbox-blue?logo=codesandbox)](https://codesandbox.io/embed/nextjs-example-react-md-editor-qjhn7?fontsize=14&hidenavigation=1&theme=dark) [![Open in StackBlitz](https://img.shields.io/badge/Open%20In-StackBlitz-green)](https://stackblitz.com/edit/nextjs-react-md-editor?embed=1&file=pages/index.js&hideExplorer=1&hideNavigation=1&theme=dark)
[![#52](https://img.shields.io/github/issues/detail/state/uiwjs/react-md-editor/52)](https://github.com/uiwjs/react-md-editor/issues/52#issuecomment-848969341)
[![#224](https://img.shields.io/github/issues/detail/state/uiwjs/react-md-editor/224)](https://github.com/uiwjs/react-md-editor/issues/224#issuecomment-901112079)
Install both packages so their published styles can be imported directly:

```bash
npm install next-remove-imports
npm install @uiw/react-md-editor@v3.6.0
npm install @uiw/react-md-editor @uiw/react-markdown-preview
```

```js
// next.config.js
const removeImports = require('next-remove-imports')();
module.exports = removeImports({});
Load global styles from `app/layout.tsx`:

```tsx
import '@uiw/react-md-editor/markdown-editor.css';
import '@uiw/react-markdown-preview/markdown.css';
```

```jsx
import "@uiw/react-md-editor/markdown-editor.css";
import "@uiw/react-markdown-preview/markdown.css";
import dynamic from "next/dynamic";
import { useState } from "react";
In a client component, load the editor without server-side rendering:

import * as commands from "@uiw/react-md-editor/commands"
```tsx
'use client';
import dynamic from 'next/dynamic';
import { useState } from 'react';

const MDEditor = dynamic(
() => import("@uiw/react-md-editor"),
{ ssr: false }
);
const MDEditor = dynamic(() => import('@uiw/react-md-editor'), { ssr: false });

function HomePage() {
const [value, setValue] = useState("**Hello world!!!**");
return (
<div>
<MDEditor value={value} onChange={setValue} />
</div>
);
export default function Page() {
const [value, setValue] = useState(['```js', 'const answer = 42;', '```'].join('\n'));
return <MDEditor value={value} onChange={setValue} />;
}

export default HomePage;
```

If highlighting is missing with older Turbopack versions, try `next dev` without `--turbopack`

### Support dark-mode/night-mode

By default, the [`dark-mode`](https://github.com/jaywcjlove/dark-mode/) is automatically switched according to the system. If you need to switch manually, just set the `data-color-mode="dark"` parameter for body.
Expand Down
37 changes: 6 additions & 31 deletions example/nextjs/README.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,11 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
# Next.js App Router example

## Getting Started
Run `npm install` from the repository root, then `npm run dev --workspace nextjs`

First, run the development server:
The page shows a JavaScript code block in the live preview, with syntax highlighting from `@uiw/react-markdown-preview`

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
The global editor and preview styles are imported by `src/app/layout.tsx`, not by the client component

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
The editor in `src/app/page.tsx` uses `next/dynamic` with `ssr: false` because the editor needs browser APIs

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
This example uses Webpack for `next dev`; earlier Turbopack versions could leave syntax highlighting unstyled (see [#682](https://github.com/uiwjs/react-md-editor/issues/682))
4 changes: 3 additions & 1 deletion example/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
"version": "4.2.2",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@uiw/react-md-editor": "^4.2.2",
"@uiw/react-markdown-preview": "^5.2.0",
"next": "15.3.1",
"react": "^18.0.0",
"react-dom": "^18.0.0"
Expand Down
2 changes: 2 additions & 0 deletions example/nextjs/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { Metadata } from 'next';
import { Geist, Geist_Mono } from 'next/font/google';
import './globals.css';
import '@uiw/react-md-editor/markdown-editor.css';
import '@uiw/react-markdown-preview/markdown.css';

const geistSans = Geist({
variable: '--font-geist-sans',
Expand Down
11 changes: 10 additions & 1 deletion example/nextjs/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,17 @@ import type { ContextStore } from '@uiw/react-md-editor';
const MDEditor = dynamic(() => import('@uiw/react-md-editor'), { ssr: false });
type OnChange = (value?: string, event?: React.ChangeEvent<HTMLTextAreaElement>, state?: ContextStore) => void;

const initialMarkdown = [
'# Syntax highlighting',
'',
'```js',
'const answer = 42;',
'console.log(answer);',
'```',
].join('\n');

export default function Home() {
const [value, setValue] = React.useState('**Hello world!!!**');
const [value, setValue] = React.useState(initialMarkdown);

const onChange = React.useCallback<OnChange>((val) => {
setValue(val || '');
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
"remove": "npm run clean && lerna exec \"rm -rf package-lock.json\" --scope react-code-preview-layout --scope website",
"clean": "lerna clean --yes",
"test:consumers": "node --test test/package-exports.test.mjs && node test/package-consumers.mjs"
"test:consumers": "node --test test/package-exports.test.mjs && node test/package-consumers.mjs",
"test:nextjs": "node --test test/nextjs-styles.test.mjs"
},
"repository": {
"type": "git",
Expand Down
22 changes: 22 additions & 0 deletions test/nextjs-highlight.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* @jest-environment jsdom
*/
import React from 'react';
import { render } from '@testing-library/react';
import MDEditor from '../core/src';
import MDEditorCommon from '../core/src/index.common';

const source = ['# Syntax highlighting', '', '```js', 'const answer = 42;', 'console.log(answer);', '```'].join('\n');

describe.each([
['default', MDEditor],
['common', MDEditorCommon],
] as const)('%s fenced code preview', (_mode, Editor) => {
it('keeps JavaScript tokens in the preview', () => {
const { container } = render(<Editor value={source} preview="preview" />);
const code = container.querySelector('.w-md-editor-preview code.language-js');
expect(code).not.toBeNull();
expect(code?.querySelector('.token.keyword')?.textContent).toBe('const');
expect(code?.querySelector('.token.function')?.textContent).toBe('log');
});
});
31 changes: 31 additions & 0 deletions test/nextjs-styles.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import assert from 'node:assert/strict';
import { readdir, readFile } from 'node:fs/promises';
import { dirname, join, resolve } from 'node:path';
import { test } from 'node:test';
import { fileURLToPath } from 'node:url';

const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
const staticDir = join(root, 'example/nextjs/.next/static');

async function findCss(directory) {
const entries = await readdir(directory, { withFileTypes: true });
const files = await Promise.all(
entries.map(async (entry) => {
const path = join(directory, entry.name);
if (entry.isDirectory()) return findCss(path);
return entry.isFile() && entry.name.endsWith('.css') ? [path] : [];
}),
);
return files.flat();
}

test('Next.js build includes the editor and syntax highlighting styles', async () => {
const files = await findCss(staticDir);
assert.ok(files.length > 0, 'Next.js did not emit CSS');
const css = (await Promise.all(files.map((file) => readFile(file, 'utf8')))).join('\n');

assert.match(css, /\.w-md-editor\b/, 'editor layout CSS missing');
assert.match(css, /\.wmde-markdown\b/, 'markdown preview CSS missing');
assert.match(css, /\.token\.keyword\b/, 'Prism highlighting CSS missing');
assert.match(css, /--color-prettylights-syntax-keyword\b/, 'syntax colour variables missing');
});
Loading