diff --git a/.github/DEVELOPMENT.md b/.github/DEVELOPMENT.md index 240f3969ec..be1ba9e817 100644 --- a/.github/DEVELOPMENT.md +++ b/.github/DEVELOPMENT.md @@ -27,9 +27,6 @@ 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 @@ -37,7 +34,7 @@ This command starts two services: 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. @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 2553a39e17..b88b10406b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/package-lock.json b/package-lock.json index ca55387fff..f90a18f6be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17584,6 +17584,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", diff --git a/packages/quill/src/modules/syntax.ts b/packages/quill/src/modules/syntax.ts index f1c8b48004..6ca5931f7a 100644 --- a/packages/quill/src/modules/syntax.ts +++ b/packages/quill/src/modules/syntax.ts @@ -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 { static DEFAULTS: SyntaxOptions & { hljs: any }; @@ -204,7 +215,6 @@ class Syntax extends Module { constructor(quill: Quill, options: Partial) { 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.', @@ -292,8 +302,7 @@ class Syntax extends Module { } 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, diff --git a/packages/quill/webpack.config.ts b/packages/quill/webpack.config.ts index 7228ce86ab..5c9a0d1435 100644 --- a/packages/quill/webpack.config.ts +++ b/packages/quill/webpack.config.ts @@ -33,9 +33,6 @@ export default (env: Record) => 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', diff --git a/packages/website/content/docs/formats.mdx b/packages/website/content/docs/formats.mdx index 6f469e0405..902eda9a20 100644 --- a/packages/website/content/docs/formats.mdx +++ b/packages/website/content/docs/formats.mdx @@ -16,7 +16,7 @@ By default, all formats are enabled and allowed in a Quill editor. They can be c diff --git a/packages/website/content/docs/modules/syntax.mdx b/packages/website/content/docs/modules/syntax.mdx index 0921c58eec..7ca2416be4 100644 --- a/packages/website/content/docs/modules/syntax.mdx +++ b/packages/website/content/docs/modules/syntax.mdx @@ -4,23 +4,28 @@ 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 < v11. +Quill supports highlight.js v9.12.0 and later. -### Example +### Usage -```html + - + - + - + +
+ -``` +`}} +/> + +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 }, + }, +}); +``` \ No newline at end of file diff --git a/packages/website/env.js b/packages/website/env.js index 0ee2c39877..b6fddb25a4 100644 --- a/packages/website/env.js +++ b/packages/website/env.js @@ -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', diff --git a/packages/website/next.config.mjs b/packages/website/next.config.mjs index ad737e0c3d..c8fba63367 100644 --- a/packages/website/next.config.mjs +++ b/packages/website/next.config.mjs @@ -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*`, - }, - ]; - }, }); diff --git a/packages/website/package.json b/packages/website/package.json index ab30bbf3e4..7a4641a84d 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -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", @@ -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", diff --git a/packages/website/src/components/Header.jsx b/packages/website/src/components/Header.jsx index a14d61ebfc..09094201b8 100644 --- a/packages/website/src/components/Header.jsx +++ b/packages/website/src/components/Header.jsx @@ -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'; diff --git a/packages/website/src/components/Sandpack.jsx b/packages/website/src/components/Sandpack.jsx index 1d51391316..352c0c5dd1 100644 --- a/packages/website/src/components/Sandpack.jsx +++ b/packages/website/src/components/Sandpack.jsx @@ -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(); @@ -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 ( +
+
+ +
+ ); +}; + +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 ( + { + const fullName = name.startsWith('/') ? name : `/${name}`; + return { + ...f, + [name]: replaceCDN(overrides[fullName] ?? files[name]).trim(), + }; + }, {})} + > + +
+
+ +
+
+ +
+
+ +
+
+
+ ); + }, +); + const Sandpack = ({ defaultShowPreview, preferPreview, @@ -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); diff --git a/packages/website/src/components/Sandpack.module.scss b/packages/website/src/components/Sandpack.module.scss index d23838849c..ea45da01c8 100644 --- a/packages/website/src/components/Sandpack.module.scss +++ b/packages/website/src/components/Sandpack.module.scss @@ -86,24 +86,101 @@ $borderColor: #dadcdc; background: var(--color-bg-inset); padding: 8px 12px; border-top: 1px solid $borderColor; +} + +.button { + font-family: 'Sofia Pro', sans-serif; + font-weight: bold; + border: 0; + border-radius: 2px; + padding: 8px 12px; + transition: all 0.2s; + background: var(--color-accent); + border: 2px solid transparent; + + &:hover { + background: white; + border: 2px solid var(--color-accent); + } + + &:active { + border: 2px solid var(--color-accent-emphasis); + } +} + +.standaloneWrapper { + display: grid; + grid-template-columns: min-content fit-content(400px) 1fr; + height: 100vh; + + .standaloneFileTree { + border-right: 1px solid #ccc; + } + + .standaloneEditor { + border-right: 1px solid #ccc; + width: 400px; + overflow-y: auto; + scrollbar-gutter: stable; + } + + .standalonePreview { + display: flex; + } + + @media (max-width: 900px) { + display: flex; + flex-direction: column-reverse; + height: auto; + + .standalonePreview { + height: 320px; + } - .button { - font-family: 'Sofia Pro', sans-serif; - font-weight: bold; - border: 0; - border-radius: 2px; - padding: 8px 12px; - transition: all 0.2s; - background: var(--color-accent); - border: 2px solid transparent; - - &:hover { - background: white; - border: 2px solid var(--color-accent); + .standaloneEditor { + width: auto; + border-top: 1px solid #ccc; + border-right: 0; } - &:active { - border: 2px solid var(--color-accent-emphasis); + .standaloneFileTree { + border-top: 1px solid #ccc; + border-right: 0; } } } + +.copied { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + color: white; + display: flex; + align-items: center; + place-content: center; + font-size: 20px; + opacity: 0; + transition: opacity 0.2s; + pointer-events: none; + z-index: 99999; + + &.active { + opacity: 1; + } + + &::before { + content: 'URL copied to clipboard'; + background-color: rgba(0, 0, 0, 0.5); + width: max-content; + padding: 10px 20px; + border-radius: 10px; + line-height: 20px; + } +} + +.shareButton { + padding: 10px; + border-bottom: 1px solid #ccc; +} diff --git a/packages/website/src/pages/_document.jsx b/packages/website/src/pages/_document.jsx index d6810d7a37..4e098b2d16 100644 --- a/packages/website/src/pages/_document.jsx +++ b/packages/website/src/pages/_document.jsx @@ -32,7 +32,7 @@ export default function Document() { /> Bubble Theme -import Sandpack from '../../components/Sandpack'; +import { StandaloneSandpack } from '../../components/Sandpack'; -
- + + + + +
@@ -25,5 +24,4 @@ const quill = new Quill('#editor', { }); ` }} -/> -
\ No newline at end of file +/> \ No newline at end of file diff --git a/packages/website/src/pages/standalone/full.mdx b/packages/website/src/pages/standalone/full.mdx index fc161ddab5..f5eda72573 100644 --- a/packages/website/src/pages/standalone/full.mdx +++ b/packages/website/src/pages/standalone/full.mdx @@ -1,19 +1,20 @@ -import Sandpack from '../../components/Sandpack'; +import { StandaloneSandpack } from '../../components/Sandpack'; Full Editor -
- + + + + + + +
@@ -77,4 +78,3 @@ const quill = new Quill('#editor', { ` }} /> -
diff --git a/packages/website/src/pages/standalone/snow.mdx b/packages/website/src/pages/standalone/snow.mdx index ab4eec5c85..160b79b2e5 100644 --- a/packages/website/src/pages/standalone/snow.mdx +++ b/packages/website/src/pages/standalone/snow.mdx @@ -1,19 +1,18 @@ Snow Theme -import Sandpack from '../../components/Sandpack'; +import { StandaloneSandpack } from '../../components/Sandpack'; -
- + + + + +
@@ -25,5 +24,4 @@ const quill = new Quill('#editor', { }); ` }} -/> -
\ No newline at end of file +/> \ No newline at end of file diff --git a/packages/website/src/pages/standalone/stress.mdx b/packages/website/src/pages/standalone/stress.mdx index dacb3533a1..67d119a6c1 100644 --- a/packages/website/src/pages/standalone/stress.mdx +++ b/packages/website/src/pages/standalone/stress.mdx @@ -1,19 +1,18 @@ Stress -import Sandpack from '../../components/Sandpack'; +import { StandaloneSandpack } from '../../components/Sandpack'; -
- + + + + +
@@ -42,5 +41,4 @@ const quill = new Quill('#editor', { }); ` }} -/> -
\ No newline at end of file +/> \ No newline at end of file diff --git a/packages/website/src/templates/standalone.jsx b/packages/website/src/templates/standalone.jsx deleted file mode 100644 index c98a4a9d63..0000000000 --- a/packages/website/src/templates/standalone.jsx +++ /dev/null @@ -1,38 +0,0 @@ -import { graphql } from 'gatsby'; -import SEO from '../components/SEO'; - -const Standalone = ({ data, children }) => children; - -export const query = graphql` - query ($id: String) { - site { - siteMetadata { - cdn - version - } - } - mdx(id: { eq: $id }) { - fields { - permalink - } - frontmatter { - title - } - } - } -`; - -export const Head = ({ data }) => ( - <> - - - -); - -export default Standalone;