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

Support highlight.js 10 and 11 #3985

Merged
merged 3 commits into from
Feb 1, 2024
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
7 changes: 2 additions & 5 deletions .github/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,14 @@ Start the development environment using:
npm start
```

> [!NOTE]
> You might be prompted to enter your password. It's needed to set up the local SSL certificate. [Learn More](https://nextjs.org/docs/pages/api-reference/next-cli#https-for-local-development).

This command starts two services:

- Quill's webpack dev server
- Website's Next.js dev server

These servers dynamically build and serve the latest copy of the source code.

Access the running website at [localhost:9000](https://localhost:9000/). By default, the website will use your local Quill build, that includes all the examples in the website. This convenient setup allows for seamless development and ensures changes to Quill do not disrupt the website's content.
Access the running website at [localhost:9000](http://localhost:9000/). By default, the website will use your local Quill build, that includes all the examples in the website. This convenient setup allows for seamless development and ensures changes to Quill do not disrupt the website's content.

If you need to modify only the website's code, start the website with `npm start -w website``. This makes the website use the latest CDN version.

Expand All @@ -56,6 +53,6 @@ To execute the E2E tests, run:
A standard development workflow involves:

1. `npm start` - to run development services
2. [localhost:9000/standalone/snow](https://localhost:9000/standalone/snow) - to interactively develop and test an isolated example
2. [localhost:9000/standalone/snow](http://localhost:9000/standalone/snow) - to interactively develop and test an isolated example
3. `npm run test:unit -w quill` - to run unit tests
4. If everything is working, run the E2E tests
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# [Unreleased]

- **Clipboard** Convert newlines between inline elements to a space.
- **Syntax** Support highlight.js v10 and v11.

# 2.0.0-beta.2

Expand Down
1 change: 1 addition & 0 deletions package-lock.json

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

15 changes: 12 additions & 3 deletions packages/quill/src/modules/syntax.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,19 @@ SyntaxCodeBlock.allowedChildren = [CodeToken, CursorBlot, TextBlot, BreakBlot];
interface SyntaxOptions {
interval: number;
languages: { key: string; label: string }[];
hljs: any;
}

const highlight = (lib: any, language: string, text: string) => {
if (typeof lib.versionString === 'string') {
const majorVersion = lib.versionString.split('.')[0];
if (parseInt(majorVersion, 10) >= 11) {
return lib.highlight(text, { language }).value;
}
}
return lib.highlight(language, text).value;
};

class Syntax extends Module<SyntaxOptions> {
static DEFAULTS: SyntaxOptions & { hljs: any };

Expand All @@ -204,7 +215,6 @@ class Syntax extends Module<SyntaxOptions> {

constructor(quill: Quill, options: Partial<SyntaxOptions>) {
super(quill, options);
// @ts-expect-error
if (this.options.hljs == null) {
throw new Error(
'Syntax module requires highlight.js. Please include the library on the page before Quill.',
Expand Down Expand Up @@ -292,8 +302,7 @@ class Syntax extends Module<SyntaxOptions> {
}
const container = this.quill.root.ownerDocument.createElement('div');
container.classList.add(CodeBlock.className);
// @ts-expect-error
container.innerHTML = this.options.hljs.highlight(language, text).value;
container.innerHTML = highlight(this.options.hljs, language, text);
return traverse(
this.quill.scroll,
container,
Expand Down
3 changes: 0 additions & 3 deletions packages/quill/webpack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,6 @@ export default (env: Record<string, unknown>) =>
directory: path.resolve(__dirname, './dist'),
},
hot: false,
client: {
webSocketURL: `wss://localhost:${process.env.npm_package_config_ports_website}/webpack-dev-server/ws`,
},
allowedHosts: 'all',
devMiddleware: {
stats: 'minimal',
Expand Down
2 changes: 1 addition & 1 deletion packages/website/content/docs/formats.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ By default, all formats are enabled and allowed in a Quill editor. They can be c
<script src="{{site.cdn}}/quill.js"></script>
<link
rel="stylesheet"
href="{{site.highlightjs}}/styles/monokai-sublime.min.css"
href="{{site.highlightjs}}/styles/atom-one-dark.min.css"
/>
<script src="{{site.katex}}/katex.min.js"></script>
<link rel="stylesheet" href="{{site.katex}}/katex.min.css" />
Expand Down
48 changes: 38 additions & 10 deletions packages/website/content/docs/modules/syntax.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,57 @@ title: Syntax Highlighter Module

The Syntax Module enhances the Code Block format by automatically detecting and applying syntax highlighting. The excellent [highlight.js](https://highlightjs.org/) library is used as a dependency to parse and tokenize code blocks.

In general, you may [configure](https://highlightjs.readthedocs.io/en/latest/api.html#configure-options) highlight.js as needed. However, Quill expects and requires the `useBR` option to be `false`.
In general, you may [configure](https://highlightjs.readthedocs.io/en/latest/api.html#configure-options) highlight.js as needed. However, Quill expects and requires the `useBR` option to be `false` if you are using highlight.js &lt; v11.

Quill supports highlight.js v9.12.0 and later.

### Example
### Usage

```html
<Sandpack
defaultShowPreview
files={{
"/index.html": `
<!-- Include your favorite highlight.js stylesheet -->
<link href="highlight.js/monokai-sublime.min.css" rel="stylesheet">
<link href="{{site.highlightjs}}/styles/atom-one-dark.min.css" rel="stylesheet">

<!-- Include the highlight.js library -->
<script href="highlight.js"></script>
<script src="{{site.highlightjs}}/highlight.min.js"></script>

<script>
hljs.configure({ // optionally configure hljs
languages: ['javascript', 'ruby', 'python']
});
<link href="{{site.cdn}}/quill.snow.css" rel="stylesheet" />
<script src="{{site.cdn}}/quill.js"></script>

<div id="editor"></div>

<script>
const quill = new Quill('#editor', {
modules: {
syntax: true, // Include syntax module
toolbar: [['code-block']] // Include button in toolbar
},
theme: 'snow'
});

const Delta = Quill.import('delta');
quill.setContents(
new Delta()
.insert('const language = "JavaScript";')
.insert('\\n', { 'code-block': 'javascript' })
.insert('console.log("I love " + language + "!");')
.insert('\\n', { 'code-block': 'javascript' })
);
</script>
```
`}}
/>

If you install highlight.js as an npm package and don't want to expose it to `window`, you need to pass it to syntax module as an option:

```js
import Quill from 'quill';
import hljs from 'highlight.js';

const quill = new Quill('#editor', {
modules: {
syntax: { hljs },
},
});
```
4 changes: 2 additions & 2 deletions packages/website/env.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
const { version, homepage } = require('./package.json');

const cdn = process.env.NEXT_PUBLIC_LOCAL_QUILL
? `https://localhost:${process.env.npm_package_config_ports_website}/webpack-dev-server`
? `http://localhost:${process.env.npm_package_config_ports_webpack}`
: `https://cdn.jsdelivr.net/npm/quill@${version}/dist`;

module.exports = {
version,
cdn,
github: 'https://github.com/quilljs/quill/tree/develop/packages/website/',
highlightjs: 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0',
highlightjs: 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.12.0',
katex: 'https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.7.1',
url: homepage,
title: 'Quill - Your powerful rich text editor',
Expand Down
8 changes: 0 additions & 8 deletions packages/website/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,4 @@ export default withMDX()({

return config;
},
async rewrites() {
return [
{
source: '/webpack-dev-server/:path*',
destination: `http://localhost:${process.env.npm_package_config_ports_webpack}/:path*`,
},
];
},
});
3 changes: 2 additions & 1 deletion packages/website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"keywords": [],
"license": "BSD-3-Clause",
"scripts": {
"dev": "PORT=$npm_package_config_ports_website next dev --experimental-https",
"dev": "PORT=$npm_package_config_ports_website next dev",
"start": "npm run dev",
"build": "next build",
"lint": "next lint",
Expand All @@ -25,6 +25,7 @@
"@types/mdx": "^2.0.10",
"classnames": "^2.3.2",
"eslint-config-next": "^14.1.0",
"lz-string": "^1.5.0",
"next": "^14.0.4",
"next-mdx-remote": "^4.4.1",
"react": "^18.2.0",
Expand Down
2 changes: 0 additions & 2 deletions packages/website/src/components/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import Link from 'next/link';
import OctocatIcon from '../svg/octocat.svg';
import ExternalLinkIcon from '../svg/external-link.svg';
import DropdownIcon from '../svg/dropdown.svg';
import XIcon from '../svg/x.svg';
import GitHub from './GitHub';
import * as styles from './Header.module.scss';
import { DocSearch } from '@docsearch/react';
import { useState } from 'react';
Expand Down
110 changes: 104 additions & 6 deletions packages/website/src/components/Sandpack.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@ import {
SandpackCodeEditor,
SandpackFileExplorer,
SandpackPreview,
Sandpack as RawSandpack,
useSandpack,
} from '@codesandbox/sandpack-react';
import { useEffect, useState } from 'react';
import env from '../../env';
import * as styles from './Sandpack.module.scss';
import classNames from 'classnames';
import {
compressToEncodedURIComponent,
decompressFromEncodedURIComponent,
} from 'lz-string';
import { withoutSSR } from './NoSSR';

const TogglePreviewButton = ({ isPreviewEnabled, setIsPreviewEnabled }) => {
const { sandpack } = useSandpack();
Expand Down Expand Up @@ -46,6 +52,104 @@ const ToggleCodeButton = ({ isCodeEnabled, setIsCodeEnabled }) => {
);
};

const replaceCDN = (value) => {
return value.replace(/\{\{site\.(\w+)\}\}/g, (_, matched) => {
return matched === 'cdn' ? process.env.cdn : env[matched];
});
};

const LocationOverride = ({ filenames }) => {
const { sandpack } = useSandpack();
const [isCopied, setIsCopied] = useState(false);

useEffect(() => {
if (!isCopied) return undefined;
const timeout = setTimeout(() => {
setIsCopied(false);
}, 1000);

return () => {
clearTimeout(timeout);
};
}, [isCopied]);

return (
<div className={styles.shareButton}>
<div
className={classNames(styles.copied, { [styles.active]: isCopied })}
></div>
<button
className={styles.button}
onClick={() => {
const map = {};
filenames.forEach((name) => {
const fullName = name.startsWith('/') ? name : `/${name}`;
map[fullName] = sandpack.files[fullName]?.code;
});
const encoded = compressToEncodedURIComponent(JSON.stringify(map));
location.hash = `code${encoded}`;
navigator.clipboard.writeText(location.href);
setIsCopied(true);
}}
>
Share Your Edits
</button>
</div>
);
};

export const StandaloneSandpack = withoutSSR(
({ files, visibleFiles, activeFile, externalResources }) => {
const [overrides] = useState(() => {
if (location.hash.startsWith('#code')) {
const code = location.hash.replace('#code', '').trim();
let userCode;
try {
userCode = JSON.parse(decompressFromEncodedURIComponent(code));
} catch (err) {}
return userCode || {};
}
return {};
});

return (
<SandpackProvider
options={{
visibleFiles,
activeFile,
externalResources:
externalResources && externalResources.map(replaceCDN),
}}
template="static"
files={Object.keys(files).reduce((f, name) => {
const fullName = name.startsWith('/') ? name : `/${name}`;
return {
...f,
[name]: replaceCDN(overrides[fullName] ?? files[name]).trim(),
};
}, {})}
>
<LocationOverride filenames={Object.keys(files)} />
<div className={styles.standaloneWrapper}>
<div className={styles.standaloneFileTree}>
<SandpackFileExplorer autoHiddenFiles />
</div>
<div className={styles.standaloneEditor}>
<SandpackCodeEditor
showTabs={false}
wrapContent
showRunButton={false}
/>
</div>
<div className={styles.standalonePreview}>
<SandpackPreview showOpenInCodeSandbox={false} />
</div>
</div>
</SandpackProvider>
);
},
);

const Sandpack = ({
defaultShowPreview,
preferPreview,
Expand All @@ -64,12 +168,6 @@ const Sandpack = ({
);
const [isReady, setIsReady] = useState(false);

const replaceCDN = (value) => {
return value.replace(/\{\{site\.(\w+)\}\}/g, (_, matched) => {
return matched === 'cdn' ? process.env.cdn : env[matched];
});
};

useEffect(() => {
setTimeout(() => {
setIsReady(true);
Expand Down
Loading
Loading