Full Code of slab/quill for AI

main 539cbffd0a13 cached
260 files
968.9 KB
262.8k tokens
631 symbols
1 requests
Download .txt
Showing preview only (1,041K chars total). Download the full file or copy to clipboard to get everything.
Repository: slab/quill
Branch: main
Commit: 539cbffd0a13
Files: 260
Total size: 968.9 KB

Directory structure:
gitextract_t0yo5mmz/

├── .eslintignore
├── .github/
│   ├── CODE_OF_CONDUCT.md
│   ├── CONTRIBUTING.md
│   ├── DEVELOPMENT.md
│   ├── ISSUE_TEMPLATE.md
│   ├── release.yml
│   └── workflows/
│       ├── _test.yml
│       ├── changelog.yml
│       ├── label.yml
│       ├── main.yml
│       ├── pull-request.yml
│       └── release.yml
├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── package.json
├── packages/
│   ├── quill/
│   │   ├── .eslintrc.json
│   │   ├── .gitignore
│   │   ├── LICENSE
│   │   ├── README.md
│   │   ├── babel.config.cjs
│   │   ├── package.json
│   │   ├── playwright.config.ts
│   │   ├── scripts/
│   │   │   ├── babel-svg-inline-import.cjs
│   │   │   └── build
│   │   ├── src/
│   │   │   ├── assets/
│   │   │   │   ├── base.styl
│   │   │   │   ├── bubble/
│   │   │   │   │   ├── toolbar.styl
│   │   │   │   │   └── tooltip.styl
│   │   │   │   ├── bubble.styl
│   │   │   │   ├── core.styl
│   │   │   │   ├── snow/
│   │   │   │   │   ├── toolbar.styl
│   │   │   │   │   └── tooltip.styl
│   │   │   │   └── snow.styl
│   │   │   ├── blots/
│   │   │   │   ├── block.ts
│   │   │   │   ├── break.ts
│   │   │   │   ├── container.ts
│   │   │   │   ├── cursor.ts
│   │   │   │   ├── embed.ts
│   │   │   │   ├── inline.ts
│   │   │   │   ├── scroll.ts
│   │   │   │   └── text.ts
│   │   │   ├── core/
│   │   │   │   ├── composition.ts
│   │   │   │   ├── editor.ts
│   │   │   │   ├── emitter.ts
│   │   │   │   ├── instances.ts
│   │   │   │   ├── logger.ts
│   │   │   │   ├── module.ts
│   │   │   │   ├── quill.ts
│   │   │   │   ├── selection.ts
│   │   │   │   ├── theme.ts
│   │   │   │   └── utils/
│   │   │   │       ├── createRegistryWithFormats.ts
│   │   │   │       └── scrollRectIntoView.ts
│   │   │   ├── core.ts
│   │   │   ├── formats/
│   │   │   │   ├── align.ts
│   │   │   │   ├── background.ts
│   │   │   │   ├── blockquote.ts
│   │   │   │   ├── bold.ts
│   │   │   │   ├── code.ts
│   │   │   │   ├── color.ts
│   │   │   │   ├── direction.ts
│   │   │   │   ├── font.ts
│   │   │   │   ├── formula.ts
│   │   │   │   ├── header.ts
│   │   │   │   ├── image.ts
│   │   │   │   ├── indent.ts
│   │   │   │   ├── italic.ts
│   │   │   │   ├── link.ts
│   │   │   │   ├── list.ts
│   │   │   │   ├── script.ts
│   │   │   │   ├── size.ts
│   │   │   │   ├── strike.ts
│   │   │   │   ├── table.ts
│   │   │   │   ├── underline.ts
│   │   │   │   └── video.ts
│   │   │   ├── modules/
│   │   │   │   ├── clipboard.ts
│   │   │   │   ├── history.ts
│   │   │   │   ├── input.ts
│   │   │   │   ├── keyboard.ts
│   │   │   │   ├── normalizeExternalHTML/
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── normalizers/
│   │   │   │   │       ├── googleDocs.ts
│   │   │   │   │       └── msWord.ts
│   │   │   │   ├── syntax.ts
│   │   │   │   ├── table.ts
│   │   │   │   ├── tableEmbed.ts
│   │   │   │   ├── toolbar.ts
│   │   │   │   ├── uiNode.ts
│   │   │   │   └── uploader.ts
│   │   │   ├── quill.ts
│   │   │   ├── themes/
│   │   │   │   ├── base.ts
│   │   │   │   ├── bubble.ts
│   │   │   │   └── snow.ts
│   │   │   ├── types.d.ts
│   │   │   └── ui/
│   │   │       ├── color-picker.ts
│   │   │       ├── icon-picker.ts
│   │   │       ├── icons.ts
│   │   │       ├── picker.ts
│   │   │       └── tooltip.ts
│   │   ├── test/
│   │   │   ├── e2e/
│   │   │   │   ├── __dev_server__/
│   │   │   │   │   ├── index.html
│   │   │   │   │   └── webpack.config.cjs
│   │   │   │   ├── fixtures/
│   │   │   │   │   ├── Clipboard.ts
│   │   │   │   │   ├── Composition.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── utils/
│   │   │   │   │       └── Locker.ts
│   │   │   │   ├── full.spec.ts
│   │   │   │   ├── history.spec.ts
│   │   │   │   ├── list.spec.ts
│   │   │   │   ├── pageobjects/
│   │   │   │   │   └── EditorPage.ts
│   │   │   │   ├── replaceSelection.spec.ts
│   │   │   │   └── utils/
│   │   │   │       └── index.ts
│   │   │   ├── fuzz/
│   │   │   │   ├── __helpers__/
│   │   │   │   │   └── utils.ts
│   │   │   │   ├── editor.spec.ts
│   │   │   │   ├── tableEmbed.spec.ts
│   │   │   │   └── vitest.config.ts
│   │   │   ├── types/
│   │   │   │   └── quill.test-d.ts
│   │   │   └── unit/
│   │   │       ├── __helpers__/
│   │   │       │   ├── cleanup.ts
│   │   │       │   ├── expect.ts
│   │   │       │   ├── factory.ts
│   │   │       │   ├── utils.ts
│   │   │       │   └── vitest.d.ts
│   │   │       ├── blots/
│   │   │       │   ├── block-embed.spec.ts
│   │   │       │   ├── block.spec.ts
│   │   │       │   ├── inline.spec.ts
│   │   │       │   └── scroll.spec.ts
│   │   │       ├── core/
│   │   │       │   ├── composition.spec.ts
│   │   │       │   ├── editor.spec.ts
│   │   │       │   ├── emitter.spec.ts
│   │   │       │   ├── quill.spec.ts
│   │   │       │   ├── selection.spec.ts
│   │   │       │   └── utils/
│   │   │       │       └── createRegistryWithFormats.spec.ts
│   │   │       ├── formats/
│   │   │       │   ├── align.spec.ts
│   │   │       │   ├── bold.spec.ts
│   │   │       │   ├── code.spec.ts
│   │   │       │   ├── color.spec.ts
│   │   │       │   ├── header.spec.ts
│   │   │       │   ├── indent.spec.ts
│   │   │       │   ├── link.spec.ts
│   │   │       │   ├── list.spec.ts
│   │   │       │   ├── script.spec.ts
│   │   │       │   └── table.spec.ts
│   │   │       ├── modules/
│   │   │       │   ├── clipboard.spec.ts
│   │   │       │   ├── history.spec.ts
│   │   │       │   ├── keyboard.spec.ts
│   │   │       │   ├── normalizeExternalHTML/
│   │   │       │   │   └── normalizers/
│   │   │       │   │       ├── googleDocs.spec.ts
│   │   │       │   │       └── msWord.spec.ts
│   │   │       │   ├── syntax.spec.ts
│   │   │       │   ├── table.spec.ts
│   │   │       │   ├── tableEmbed.spec.ts
│   │   │       │   ├── toolbar.spec.ts
│   │   │       │   └── uiNode.spec.ts
│   │   │       ├── theme/
│   │   │       │   └── base/
│   │   │       │       └── tooltip.spec.ts
│   │   │       ├── ui/
│   │   │       │   └── picker.spec.ts
│   │   │       └── vitest.config.ts
│   │   ├── tsconfig.json
│   │   ├── webpack.common.cjs
│   │   └── webpack.config.cjs
│   └── website/
│       ├── .eslintrc.json
│       ├── .gitignore
│       ├── README.md
│       ├── content/
│       │   ├── blog/
│       │   │   ├── a-new-delta.mdx
│       │   │   ├── an-official-cdn-for-quill.mdx
│       │   │   ├── announcing-quill-1-0.mdx
│       │   │   ├── are-we-there-yet-to-1-0.mdx
│       │   │   ├── quill-1-0-beta-release.mdx
│       │   │   ├── quill-1-0-release-candidate-released.mdx
│       │   │   ├── quill-v0-19-no-more-iframes.mdx
│       │   │   ├── the-road-to-1-0.mdx
│       │   │   ├── the-state-of-quill-and-2-0.mdx
│       │   │   └── upgrading-to-rich-text-deltas.mdx
│       │   └── docs/
│       │       ├── api.mdx
│       │       ├── configuration.mdx
│       │       ├── customization/
│       │       │   ├── registries.mdx
│       │       │   └── themes.mdx
│       │       ├── customization.mdx
│       │       ├── delta.mdx
│       │       ├── formats.mdx
│       │       ├── guides/
│       │       │   ├── building-a-custom-module.mdx
│       │       │   ├── cloning-medium-with-parchment.js
│       │       │   ├── cloning-medium-with-parchment.mdx
│       │       │   └── designing-the-delta-format.mdx
│       │       ├── installation.mdx
│       │       ├── modules/
│       │       │   ├── clipboard.mdx
│       │       │   ├── history.mdx
│       │       │   ├── keyboard.mdx
│       │       │   ├── syntax.mdx
│       │       │   └── toolbar.mdx
│       │       ├── modules.mdx
│       │       ├── quickstart.mdx
│       │       ├── upgrading-to-2-0.mdx
│       │       └── why-quill.mdx
│       ├── env.js
│       ├── next.config.mjs
│       ├── package.json
│       ├── public/
│       │   ├── CNAME
│       │   └── robots.txt
│       └── src/
│           ├── components/
│           │   ├── ActiveLink.jsx
│           │   ├── ClickOutsideHandler.jsx
│           │   ├── Editor.jsx
│           │   ├── GitHub.jsx
│           │   ├── GitHub.module.scss
│           │   ├── Header.jsx
│           │   ├── Header.module.scss
│           │   ├── Heading.jsx
│           │   ├── Hint.jsx
│           │   ├── Hint.module.scss
│           │   ├── Layout.jsx
│           │   ├── Link.jsx
│           │   ├── MDX.jsx
│           │   ├── NoSSR.jsx
│           │   ├── OpenSource.jsx
│           │   ├── OpenSource.module.scss
│           │   ├── PlaygroundLayout.jsx
│           │   ├── PlaygroundLayout.module.scss
│           │   ├── PostLayout.jsx
│           │   ├── PostLayout.module.scss
│           │   ├── SEO.jsx
│           │   ├── Sandpack.jsx
│           │   └── Sandpack.module.scss
│           ├── data/
│           │   ├── api.tsx
│           │   ├── docs.tsx
│           │   └── playground.tsx
│           ├── pages/
│           │   ├── 404.jsx
│           │   ├── _app.jsx
│           │   ├── _document.jsx
│           │   ├── base.css
│           │   ├── docs/
│           │   │   └── [...id].jsx
│           │   ├── docs.jsx
│           │   ├── index.jsx
│           │   ├── playground/
│           │   │   ├── [...id].jsx
│           │   │   └── [...id].module.scss
│           │   ├── playground.jsx
│           │   ├── standalone/
│           │   │   ├── bubble.mdx
│           │   │   ├── full.mdx
│           │   │   ├── snow.mdx
│           │   │   └── stress.mdx
│           │   ├── styles.scss
│           │   └── variables.scss
│           ├── playground/
│           │   ├── custom-formats/
│           │   │   ├── index.css
│           │   │   ├── index.html
│           │   │   ├── index.js
│           │   │   └── playground.json
│           │   ├── form/
│           │   │   ├── index.css
│           │   │   ├── index.html
│           │   │   ├── index.js
│           │   │   └── playground.json
│           │   ├── react/
│           │   │   ├── App.js
│           │   │   ├── Editor.js
│           │   │   ├── playground.json
│           │   │   └── styles.css
│           │   └── snow/
│           │       ├── index.html
│           │       ├── index.js
│           │       └── playground.json
│           └── utils/
│               ├── flattenData.js
│               ├── replaceCDN.js
│               └── slug.js
├── scripts/
│   ├── changelog.mjs
│   ├── release.js
│   └── utils/
│       └── configGit.mjs
└── tsconfig.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .eslintignore
================================================
dist/
scripts/


================================================
FILE: .github/CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment
include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
  address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at conduct@quilljs.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]

[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/


================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contributing

The best way to contribute is to help others in the Quill community. This includes:

- Reporting new [bugs](https://github.com/slab/quill/labels/bug) or adding details to existing ones
- Reproducing [unconfirmed bugs](https://github.com/slab/quill/labels/needs%20reproduction)
- Quick typo fix or documentation improvement [Pull Requests](#pull-requests)
- Participating in [discussions](https://github.com/slab/quill/discussions)

After becoming familiar with Quill and the codebase, likely through using Quill yourself and making some of the above contributions, you may choose to take on a bigger commitment by:

- Helping fix [bugs](https://github.com/slab/quill/labels/bug)
- Implementing new [features](https://github.com/slab/quill/labels/feature)
- Publishing guides, tutorials, and examples
- Supporting Quill in other ecosystems (Angular, React, etc)

Note that if you are going to be making significant contributions, you should first open
a [discussion](https://github.com/slab/quill/discussions) to ensure your work aligns with the project's goals and direction.

## Questions

If you have a question, it is best to ask on [Discussions](https://github.com/slab/quill/discussions) under the Q&A category.

## Bug Reports

Search through [Github Issues](https://github.com/slab/quill/issues) to see if the bug has already been reported. If so, please comment with any additional information.

New bug reports must include:

1. Detailed description of faulty behavior
2. Steps for reproduction or failing test case
3. Expected and actual behaviors
4. Platforms (OS **and** browser combination) affected
5. Version of Quill

Lacking reports it may be autoclosed with a link to these instructions.

## Feature Requests

Search through [Github Issues](https://github.com/slab/quill/labels/feature) to see if someone has already suggested the feature. If so, please provide support with a [reaction](https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comments) and add your own use case.

To open a new feature request, please include:

1. A detailed description of the feature
2. Why this feature belongs in Quill core, instead of your own application logic
3. Background of where and how you are using Quill
4. The use case that would be enabled or improved for your product, if the feature was implemented

Features are prioritized based on real world users and use cases, not theoretically useful additions for other unknown users. Lacking feature requests may be autoclosed with a link to this section.

The more complete and compelling the request, the more likely it will ultimately be implemented. Garnering community support will help as well!

## Pull Requests

Please check to make sure your plans fall within Quill's scope. This often means opening up a [discussion](https://github.com/slab/quill/labels/discussion).

Non-code Pull Requests such as typo fixes or documentation improvements are highly encouraged and are often accepted immediately.

Pull Requests modifying public facing interfaces or APIs, including backwards compatible additions, will undergo the most scrutiny, and will almost certainly require a proper discussion of the motivation and merits beforehand. Simply increasing code complexity is a cost not to be taken lightly.

Pull requests must:

1. Be forked off the [main](https://github.com/slab/quill/tree/main) branch.
2. Pass the linter and conform to existing coding styles.
3. Commits are [squashed](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Squashing-Commits) to minimally coherent units of changes.
4. Are accompanied by tests covering the new feature or demonstrating the bug for fixes.
5. Serve a single atomic purpose (add one feature or fix one bug).
6. Introduce only changes that further the PR's singular purpose (ex. do not tweak an unrelated config along with adding your feature).
7. Not break any existing unit or end to end tests.

**Important:** By issuing a Pull Request you agree to allow the project owners to license your work under the terms of the [License](https://github.com/slab/quill/blob/master/LICENSE).


================================================
FILE: .github/DEVELOPMENT.md
================================================
# Development

This repo is a monorepo powered by npm's official [workspace feature](https://docs.npmjs.com/cli/v10/using-npm/workspaces). It contains the following packages:

### quill

This is the Quill library. It's written in [TypeScript](https://www.typescriptlang.org/), and use [Webpack](https://webpack.js.org/) as the bundler.
It uses [Vitest](https://vitest.dev) for unit testing, and [Playwright](https://playwright.dev/) for E2E testing.

### website

It's Quill's website (hosted at [quilljs.com](https://quilljs.com/)). It's built with [Next.js](https://nextjs.org/).

## Setup

To prepare your local environment for development, ensure you have Node.js installed. The repo uses npm, and doesn't support Yarn and pnpm.

Install the necessary dependencies with the command below:

```shell
npm install
```

Start the development environment using:

```shell
npm start
```

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](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.

## Testing

To run the unit tests in watch mode, run:

    npm run test:unit -w quill

To execute the E2E tests, run:

    npm run test:e2e -w quill

## Workflow

A standard development workflow involves:

1. `npm start` - to run development services
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


================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
Please describe the a concise description and fill out the details below. It will help others efficiently understand your request and get to an answer instead of repeated back and forth. Providing a [minimal, complete and verifiable example](https://stackoverflow.com/help/mcve) will further increase your chances that someone can help.

**Steps for Reproduction**

1. Visit [quilljs.com, jsfiddle.net, codepen.io]
2. Step Two
3. Step Three

**Expected behavior**:

**Actual behavior**:

**Platforms**:

Include browser, operating system and respective versions

**Version**:

Run `Quill.version` to find out


================================================
FILE: .github/release.yml
================================================
changelog:
  exclude:
    authors:
      - quill-bot
  categories:
    - title: Bug Fixes 🛠
      labels:
        - change:bugfix
    - title: New Features 🎉
      labels:
        - change:feature
    - title: Documentation 📚
      labels:
        - change:documentation
    - title: Other Changes
      labels:
        - "*"


================================================
FILE: .github/workflows/_test.yml
================================================
name: Tests
on:
  workflow_call:
jobs:
  e2e:
    name: E2E Tests
    timeout-minutes: 60
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 20
      - name: Install dependencies
        run: npm ci
      - name: Install Playwright Browsers
        run: npx playwright install --with-deps
        working-directory: packages/quill
      - name: Run Playwright tests
        uses: coactions/setup-xvfb@v1
        with:
          run: npm run test:e2e -- --headed
          working-directory: packages/quill
  fuzz:
    name: Fuzz Tests
    runs-on: ubuntu-latest

    steps:
      - name: Git checkout
        uses: actions/checkout@v4

      - name: Use Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - run: npm ci
        env:
          PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 1
      - run: npm run test:fuzz -w quill
  unit:
    name: Unit Tests
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        browser: [chromium, webkit, firefox]

    steps:
      - name: Git checkout
        uses: actions/checkout@v3

      - name: Use Node.js
        uses: actions/setup-node@v3
        with:
          node-version: 20

      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npm run lint
      - run: npm run test:unit -w quill || npm run test:unit -w quill || npm run test:unit -w quill
        env:
          BROWSER: ${{ matrix.browser }}


================================================
FILE: .github/workflows/changelog.yml
================================================
name: Generate Changelog

on:
  release:
    types: [published, created]
  workflow_dispatch: {}

jobs:
  changelog:
    runs-on: ubuntu-latest
    steps:
      - name: Git checkout
        uses: actions/checkout@v4
      - name: Use Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - run: npm ci
      - run: node ./scripts/changelog.mjs
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .github/workflows/label.yml
================================================
name: Pull Requests

on:
  pull_request:
    types: [opened, labeled, unlabeled, synchronize]

jobs:
  label:
    runs-on: ubuntu-latest
    permissions:
      issues: write
      pull-requests: write
    steps:
      - uses: mheap/github-action-required-labels@v5
        with:
          mode: exactly
          count: 1
          labels: |
            change:bugfix
            change:feature
            change:documentation
            change:chore
            change:refactor
          add_comment: false


================================================
FILE: .github/workflows/main.yml
================================================
name: Main

on:
  push:
    branches: [main]

concurrency:
  group: main-build
  cancel-in-progress: true

jobs:
  test:
    uses: ./.github/workflows/_test.yml


================================================
FILE: .github/workflows/pull-request.yml
================================================
name: Pull Requests

on:
  pull_request:
    branches: [main]

jobs:
  test:
    uses: ./.github/workflows/_test.yml


================================================
FILE: .github/workflows/release.yml
================================================
name: Release

on:
  workflow_dispatch:
    inputs:
      version:
        description: 'npm version. Examples: "2.0.0", "2.0.0-beta.0". To deploy an experimental version, type "experimental".'
        default: "experimental"
        required: true
      dry-run:
        description: "Only create a tarball, do not publish to npm or create a release on GitHub."
        type: boolean
        default: true
        required: true

permissions:
  contents: write

jobs:
  test:
    uses: ./.github/workflows/_test.yml

  release:
    runs-on: ubuntu-latest
    needs: test

    steps:
      - name: Git checkout
        uses: actions/checkout@v4

      - name: Use Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - run: npm ci
      - run: ./scripts/release.js --version ${{ github.event.inputs.version }} ${{ github.event.inputs.dry-run == 'true' && '--dry-run' || '' }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

      - name: Archive npm package tarball
        uses: actions/upload-artifact@v4
        with:
          name: npm
          path: |
            packages/quill/dist/*.tgz


================================================
FILE: .gitignore
================================================
.*
!.eslintrc.json
!.eslintignore
!.npmignore
!.gitignore
!.github

node_modules

test-results/
playwright-report/


================================================
FILE: .npmignore
================================================
*.ts
!*.d.ts
.*
.github
.vscode
docs
test
tsconfig.json


================================================
FILE: CHANGELOG.md
================================================
# v2.0.2 (2024-05-13)

<!-- Release notes generated using configuration in .github/release.yml at v2.0.2 -->

## What's Changed

### Bug Fixes 🛠

- Fix typing errors for Quill.register by [@hzgotb](https://github.com/hzgotb) in https://github.com/slab/quill/pull/4127
- Fix event source when deleting a link with shortcuts by [@luin](https://github.com/luin) in https://github.com/slab/quill/pull/4200
- Avoid side effects for Enter/Backspace when composing in Safari by [@luin](https://github.com/luin) in https://github.com/slab/quill/pull/4201
- Ignore pasting images when image format is disallowed by [@luin](https://github.com/luin) in https://github.com/slab/quill/pull/4202

[All changes](https://github.com/slab/quill/releases/tag/v2.0.2)

# v2.0.1 (2024-05-01)

## Bug Fixes

- Prevent overriding of theme's default toolbar settings mistakenly [#4120](https://github.com/slab/quill/pull/4120)
- Improve typings for methods that return a Delta [#4136](https://github.com/slab/quill/pull/4136)
- Fix toolbar icons for h3-h6 [#4131](https://github.com/slab/quill/pull/4131)

[All changes](https://github.com/slab/quill/releases/tag/v2.0.1)

# v2.0.0 (2024-04-17)

We are thrilled to announce the release of Quill 2.0! Please check out the [announcement post](https://slab.com/blog/announcing-quill-2-0/).

## Major Improvements

- Quill is now a valid ESM package for better ecosystem (e.g. bundlers) and tree-shaking support
- Nested Quill support [#3590](https://github.com/slab/quill/pull/3590)
- Improved IME and spell corrector support [#3807](https://github.com/slab/quill/pull/3807)
- Semantic cleanups for TEXT_CHANGE event [#3778](https://github.com/slab/quill/pull/3778)
- **History**: Record selection in history module [#3823](https://github.com/slab/quill/pull/3823)
- Auto detect scrolling container [#3840](https://github.com/slab/quill/pull/3840)
- **Clipboard**: Improve support for pasting from Google Docs and Microsoft Word

## Performance Improvements

Quill 2.0 includes many performance optimizations, the most important of which is the improved rendering speed for large content.

- Improve inserting performance [#3815](https://github.com/slab/quill/pull/3815)
- Avoid fetching selections when possible [#3538](https://github.com/slab/quill/pull/3538)
- No need to setContents when container is empty [#3539](https://github.com/slab/quill/pull/3539)

## Code Modernization

- Migrated to TypeScript
- Provided official TypeScript declarations
- Migrated to Vitest for unit testing
- Migrated to Playwright for E2E testing
- Migrated website to Gatsby

[All changes](https://github.com/slab/quill/releases/tag/v2.0.0)

# v2.0.0-rc.5 (2024-04-04)

- **Clipboard** Add support for Quill v1 list attributes
- Fix overload declarations for `quill.formatText()` and other methods
- Expose Bounds type for getBounds()
- Expose Range type
- Allow ref for insertBefore to be null

[All changes](https://github.com/slab/quill/releases/tag/v2.0.0-rc.5)

# v2.0.0-rc.4 (2024-03-24)

- Include source maps for Parchment
- **Clipboard** Support pasting links copied from iOS share sheets
- Fix config parsing where undefined values were kept
- Expose types for Quill options
- Remove empty .css.js files generated by bundlers

[All changes](https://github.com/slab/quill/releases/tag/v2.0.0-rc.4)

# v2.0.0-rc.3 (2024-03-16)

- Fix `Quill#getSemanticHTML()` for list items
- Remove unnecessary Firefox workaround
- **Clipboard** Fix redundant newlines when pasting from external sources
- Add `formats` option for specifying allowed formats

[All changes](https://github.com/slab/quill/releases/tag/v2.0.0-rc.3)

# v2.0.0-rc.2 (2024-02-15)

- Fix toolbar button state not updated in some cases
- Narrower `BubbleTheme.tooltip` type
- Fix `Selection#getBounds()` when starting range at end of text node
- Improve compatibility with esbuild

[All changes](https://github.com/slab/quill/releases/tag/v2.0.0-rc.2)

# v2.0.0-rc.1 (2024-02-12)

- Remove unnecessary lodash usages.

[All changes](https://github.com/slab/quill/releases/tag/v2.0.0-rc.1)

# v2.0.0-rc.0 (2024-02-03)

- **Clipboard** Convert newlines between inline elements to a space.
- **Clipboard** Avoid generating unsupported formats on paste.
- **Clipboard** Improve support for pasting from Google Docs and Microsoft Word.
- **Clipboard** Ignore whitespace between pasted empty paragraphs.
- **Syntax** Support highlight.js v10 and v11.

[All changes](https://github.com/slab/quill/releases/tag/v2.0.0-rc.0)

# v2.0.0-beta.2 (2024-01-30)

- Fix IME not working correctly in Safari.
- **Clipboard** Support paste as plain text.
- Fix `Quill.getText()` not respecting `length` parameter.
- **History** Fix redo shortcut not working on Linux and Windows.

[All changes](https://github.com/slab/quill/releases/tag/v2.0.0-beta.2)

# v2.0.0-beta.1 (2024-01-21)

- Fix syntax label from "Javascript" to "JavaScript".
- Fix typing errors for emitter.
- Inline SVG images for easier bundler setup.
- Improve typing for Registry.

[All changes](https://github.com/slab/quill/releases/tag/v2.0.0-beta.1)

# v2.0.0-beta.0 (2023-12-08)

In the upcoming 2.0 release, Quill has been significantly modernized. Leveraging the latest browser-supported APIs, Quill now delivers a more efficient and reliable editing experience.

## Major Improvements

- Nested Quill support [#3590](https://github.com/slab/quill/pull/3590)
- Improved IME and spell corrector support [#3807](https://github.com/slab/quill/pull/3807)
- Semantic cleanups for TEXT_CHANGE event [#3778](https://github.com/slab/quill/pull/3778)
- **History**: Record selection in history module [#3823](https://github.com/slab/quill/pull/3823)
- Auto detect scrolling container [#3840](https://github.com/slab/quill/pull/3840)

## Performance Improvements

Quill 2.0 includes many performance optimizations, the most important of which is the improved rendering speed for large content.

- Improve inserting performance [#3815](https://github.com/slab/quill/pull/3815)
- Avoid fetching selections when possible [#3538](https://github.com/slab/quill/pull/3538)
- No need to setContents when container is empty [#3539](https://github.com/slab/quill/pull/3539)

## Code Modernization

- Migrated to TypeScript
- Provided official TypeScript declarations
- Migrated to Vitest for unit testing
- Migrated to Playwright for E2E testing
- Migrated website to Gatsby

[All changes](https://github.com/slab/quill/releases/tag/v2.0.0-beta.0)

# v1.3.7 (2019-09-09)

Security related bug fixes.

- https://app.snyk.io/vuln/npm:extend:20180424
- https://www.npmjs.com/advisories/1039

Thank you [@danfuzz](https://github.com/danfuzz), [@danielw93](https://github.com/danielw93), [@jonathanlloyd](https://github.com/jonathanlloyd), and [@k-sai-kiranmayee](https://github.com/k-sai-kiranmayee) for your contributions to this release.

[All changes](https://github.com/slab/quill/releases/tag/v1.3.7)

# v1.3.6 (2018-03-12)

- Make picker accessible [#1999](https://github.com/slab/quill/pull/1999)
- Fix Japanese composition in Chrome 65 [#2009](https://github.com/slab/quill/issues/2009)

Thanks to [@berylw](https://github.com/berylw) and [@erinsinger93](https://github.com/erinsinger93) for contributions to this release!

[All changes](https://github.com/slab/quill/releases/tag/v1.3.6)

# v1.3.5 (2018-01-22)

- Fix indent preservation of a checked checklist item [#1818](https://github.com/slab/quill/issues/1818)
  - added as a shortcut to trigger bullet list formatting [#1819](https://github.com/slab/quill/pull/1819)
- Fix pasting text-align styles [#1873](https://github.com/slab/quill/issues/1873)
- Fix cursor position after dangerouslyPasteHTML [#1886](https://github.com/slab/quill/issues/1886)
- Fix value of history stack in text-change handler [#1906](https://github.com/slab/quill/pull/1906)
- Workaround for Webkit locking up when navigating around images using hotkeys [#1910](https://github.com/slab/quill/issues/1910)

Thank you [@araruna](https://github.com/araruna), [@bryanrsmith](https://github.com/bryanrsmith), [@haugstrup](https://github.com/haugstrup), [@icylace](https://github.com/icylace), [@leimig](https://github.com/leimig), [@LFDM](https://github.com/LFDM), [@nikparo](https://github.com/nikparo), [@rafpaf](https://github.com/rafpaf) and [@vk2sky](https://github.com/vk2sky) for your contributions to this release.

[All changes](https://github.com/slab/quill/releases/tag/v1.3.5)

# v1.3.4 (2017-11-06)

- Loosen dependency specification [#1748](https://github.com/slab/quill/issues/1748)
- Loosen list autofill constraint [#1749](https://github.com/slab/quill/issues/1749)

Thanks to [@danfuzz](https://github.com/danfuzz) and [@SoftVision-CarmenFat](https://github.com/SoftVision-CarmenFat) for contributions to this release!

[All changes](https://github.com/slab/quill/releases/tag/v1.3.4)

# v1.3.3 (2017-10-09)

- Fix `getFormat` with no parameters while editor is not focused [#1548](https://github.com/slab/quill/issues/1548)
- Remove automatic highlighting across embeds [#1691](https://github.com/slab/quill/issues/1691)
- Support checking checklist on mobile [#1693](https://github.com/slab/quill/pull/1711)
- Fix list creation keyboard shortcuts [#1723](https://github.com/slab/quill/issues/1723)
- Show KaTex rendering errors [#1738](https://github.com/slab/quill/pull/1738)

Thank you [@altschuler](https://github.com/altschuler), [@arrocke](https://github.com/arrocke), [@guillaumepotier](https://github.com/guillaumepotier), [@sferoze](https://github.com/sferoze) and [@volser](https://github.com/volser) for your contributions to this release.

[All changes](https://github.com/slab/quill/releases/tag/v1.3.3)

# v1.3.2 (2017-09-04)

- Pasting into code block should always paste as code [#1624](https://github.com/slab/quill/issues/1624)
- Fix removing embed selection when arrow keys change selection [#1633](https://github.com/slab/quill/issues/1633)
- Fix selection restoration after image insertion [#1649](https://github.com/slab/quill/issues/1649)
- Fix selection-change firing after dragging off screen [#1654](https://github.com/slab/quill/issues/1654)
- Fix placeholder text spacing [#1677](https://github.com/slab/quill/issues/1677)

Thanks to [@abramz](https://github.com/abramz), [@amitm02](https://github.com/amitm02), [@eamodio](https://github.com/eamodio), [@HWliao](https://github.com/HWliao), [@mmitis](https://github.com/mmitis), [@nelsonpecora](https://github.com/nelsonpecora), [@nipunjain87](https://github.com/nipunjain87), and [@ValueBerry](https://github.com/ValueBerry) for contributions to this release!

[All changes](https://github.com/slab/quill/releases/tag/v1.3.2)

# v1.3.1 (2017-08-07)

- Fix placeholder when emptying text [#1594](https://github.com/slab/quill/issues/1594)
- Fix inserting newline after header [#1616](https://github.com/slab/quill/issues/1616)

Thank you [@Natim](https://github.com/Natim) and [@stephenLYao](https://github.com/stephenLYao) for your contributions to this release.

[All changes](https://github.com/slab/quill/releases/tag/v1.3.1)

# v1.3.0 (2017-07-17)

Add `matchVisual` [configuration](https://quilljs.com/docs/modules/clipboard/#configuration) to Clipboard.

- Use DOM API to determine selected `<select>` option [#1576](https://github.com/slab/quill/pull/1576)
- Add `:focus` styles to toolbar [#1540](https://github.com/slab/quill/issues/1540)
- Allow users to undo automatic keyboard completions [#1538](https://github.com/slab/quill/issues/1538)
- Use github-pages gem to make development environment consistent [#1536](https://github.com/slab/quill/issues/1536) [#1544](https://github.com/slab/quill/pull/1544)
- Fix composing Chinese with preformatting [#1514](https://github.com/slab/quill/issues/1514)
- Fix example clipboard module in docs [#1502](https://github.com/slab/quill/issues/1502)
- Fix list layout in RTL mode [#1498](https://github.com/slab/quill/issues/1498)
- Clarify documentation for scrollingContainer [#1496](https://github.com/slab/quill/issues/1496)
- Add `tel` to default link whitelist [#1436](https://github.com/slab/quill/pull/1436)
- Fix cursor interaction with custom contenteditable=false embeds [#1172](https://github.com/slab/quill/issues/1172) [#1181](https://github.com/slab/quill/issues/1181)
- Fix rendered cursor in Chrome when interacting with scrollbar [#1114](https://github.com/slab/quill/issues/1114)

Thanks to [@alexkrolick](https://github.com/alexkrolick), [@amitm02](https://github.com/amitm02), [@Christilut](https://github.com/Christilut), [@danielschwartz](https://github.com/danielschwartz), [@emanuelbsilva](https://github.com/emanuelbsilva), [@ersommer](https://github.com/ersommer), [@fiurrr](https://github.com/fiurrr), [@jackmu95](https://github.com/jackmu95), [@jmzhang](https://github.com/jmzhang), [@mdpye](https://github.com/mdpye), [@ralrom](https://github.com/ralrom), [@sferoze](https://github.com/sferoze), [@simon-at-fugu](https://github.com/simon-at-fugu), and [@yogadzx](https://github.com/yogadzx) for contributions to this release!

[All changes](https://github.com/slab/quill/releases/tag/v1.3.0)

# v1.2.6 (2017-06-05)

- Disable Grammarly by default [#574](https://github.com/slab/quill/issues/574)
- Fix RTL list spacing [#1485](https://github.com/slab/quill/pull/1485)
- Add support for mobile Youtube links [#1489](https://github.com/slab/quill/pull/1489)

Thank you [@amitm02](https://github.com/amitm02), [@benbro](https://github.com/benbro)
[@nickbaum](https://github.com/nickbaum), [@stalniy](https://github.com/stalniy) and [@ygrishajev](https://github.com/ygrishajev) for your contributions to this release.

[All changes](https://github.com/slab/quill/releases/tag/v1.2.6)

# v1.2.5 (2017-05-29)

- Fix cursor shifting to be exclusive of user cursor [#1367](https://github.com/slab/quill/issues/1367)
- Fix iOS hover state on toolbar [#1388](https://github.com/slab/quill/issues/1388)
- Fix `getText()` for Bangla [#1427](https://github.com/slab/quill/issues/1427)
- Fix Korean character composition in Safari [#1437](https://github.com/slab/quill/issues/1437)
- Fix pasting HTML handling special class names [#1445](https://github.com/slab/quill/issues/1445)
- Fix paste or initializing with font-weight [#1456](https://github.com/slab/quill/issues/1456)
- Fix updating active picker logic [#1468](https://github.com/slab/quill/issues/1468)

Thanks to [@aliciawood](https://github.com/aliciawood), [@benbro](https://github.com/benbro), [@denis-aes](https://github.com/denis-aes), [@despreju](https://github.com/despreju), [@GlenKPeterson](https://github.com/GlenKPeterson), [@haugstrup](https://github.com/haugstrup), [@jziggas](https://github.com/jziggas), [@RobAley](https://github.com/RobAley), [@sheley1998](https://github.com/sheley1998), [@silverprize](https://github.com/silverprize), and [@yairy](https://github.com/yairy) for contributions to this release!

[All changes](https://github.com/slab/quill/releases/tag/v1.2.5)

# v1.2.4 (2017-04-18)

- Fix pasting nested list [#906](https://github.com/slab/quill/issues/906)
- Fix delete key interaction at end of list [#1277](https://github.com/slab/quill/issues/1277)
- Fix pasting whitespace prefix [#1244](https://github.com/slab/quill/issues/1244)
- Fix file dialog open speed [#1265](https://github.com/slab/quill/issues/1265)
- Fix backspace with at beginning of list interaction with meta keys [#1307](https://github.com/slab/quill/issues/1307)
- Fix pasting nested styles [#1333](https://github.com/slab/quill/issues/1333)
- Fix backspacing into an empty line should keep own formats [#1339](https://github.com/slab/quill/issues/1339)
- Fix IE11 autolinking interaction [#1390](https://github.com/slab/quill/issues/1390)
- Fix persistent focus interaction with tabbing away [#1404](https://github.com/slab/quill/issues/1404)

Thanks to [@bigggge](https://github.com/bigggge), [@CoenWarmer](https://github.com/CoenWarmer), [@cutteroid](https://github.com/cutteroid), [@jay-cox](https://github.com/jay-cox), [@kiewic](https://github.com/kiewic), [@kloots](https://github.com/kloots), [@MichaelTontchev](https://github.com/MichaelTontchev), [@montlebalm](https://github.com/montlebalm), [@RichardNeill](https://github.com/RichardNeill), and [@vasconita](https://github.com/vasconita) for your contributions to this release.

[All changes](https://github.com/slab/quill/releases/tag/v1.2.4)

# v1.2.3 (2017-03-29)

- Fix scrolling when appending new lines [#1276](https://github.com/slab/quill/issues/1276) [#1361](https://github.com/slab/quill/issues/1361)
- Fix binding to explicit shortcut key [#1365](https://github.com/slab/quill/issues/1365)
- Merge clone update [#1359](https://github.com/slab/quill/pull/1359)

Thank you [@artaommahe](https://github.com/artaommahe), [@c-w](https://github.com/c-w), [@EladBet](https://github.com/EladBet), [@emenoh](https://github.com/emenoh), and [@montlebalm](https://github.com/montlebalm) for contributions to this release!

[All changes](https://github.com/slab/quill/releases/tag/v1.2.3)

# v1.2.2 (2017-02-27)

- Fix backspace/delete on Windows/Ubuntu [#1334](https://github.com/slab/quill/issues/1334)

Thanks to [@dinusuresh](https://github.com/dinusuresh) for your contributions to this release.

[All changes](https://github.com/slab/quill/releases/tag/v1.2.2)

# v1.2.1 (2017-02-27)

- Fix link removal on Snow theme [#1259](https://github.com/slab/quill/issues/1259)
- Fix CMD+backspace on empty editor [#1319](https://github.com/slab/quill/issues/1319)
- Fix disabled checklist behavior [#1312](https://github.com/slab/quill/issues/1312)

Thank you [@danielschwartz](https://github.com/@danielschwartz), [@JedWatson](https://github.com/@JedWatson), [@montlebalm](https://github.com/@montlebalm), and [@simi](https://github.com/@simi) for contributions to this release!

[All changes](https://github.com/slab/quill/releases/tag/v1.2.1)

# v1.2.0 (2017-01-21)

Add concept of experimental APIs: they are APIs meant to try out support for use cases we would like to address, but gives flexibility to find the right API interface. As such they are not covered by Semantic Versioning. Several are added to start things off: `find`, `getIndex`, `getLeaf`, `getLine`, `getLines`.

- Merge disabling list keyboard shortcut when list format is disabled [#1257](https://github.com/slab/quill/pull/1257)

Thanks to [@haugstrup](https://github.com/haugstrup) for your contributions to this release.

[All changes](https://github.com/slab/quill/releases/tag/v1.2.0)

# v1.1.10 (2017-01-16)

- Preserve user selection on API changes [#1152](https://github.com/slab/quill/issues/1152)
- Fix backspacing into emojis [#1230](https://github.com/slab/quill/issues/1230)
- Fix ability to type after emptying line in IE/Firefox [#1254](https://github.com/slab/quill/issues/1254)
- Fix whitelisting block formats [#1256](https://github.com/slab/quill/issues/1256)

Thank you [@benbro](https://github.com/benbro), [@haugstrup](https://github.com/haugstrup), [@peterweck](https://github.com/peterweck) and [@sbevels](https://github.com/sbevels) for contributions to this release!

[All changes](https://github.com/slab/quill/releases/tag/v1.1.10)

# v1.1.9 (2017-01-02)

- Support pasting italics from Google Docs [#1185](https://github.com/slab/quill/issues/1185)
- Fix setting dropdown picker back to default [#1191](https://github.com/slab/quill/issues/1191)
- Fix code-block formatting on empty first line in Firefox [#1195](https://github.com/slab/quill/issues/1195)
- Prevent formatting via keyboard shortcuts when not whitelisted [#1197](https://github.com/slab/quill/issues/1197)
- Fix select-all copy and overwrite paste in Firefox [#1202](https://github.com/slab/quill/issues/1202)

Thank you [@adfaure](https://github.com/adfaure), [@berndschimmer](https://github.com/berndschimmer), [@CoenWarmer](https://github.com/CoenWarmer), [@montlebalm](https://github.com/montlebalm), and [@TraceyYau](https://github.com/TraceyYau) for contributions to this release!

[All changes](https://github.com/slab/quill/releases/tag/v1.1.9)

# v1.1.8 (2016-12-23)

- Support pasting italics from Google Docs [#1185](https://github.com/slab/quill/issues/1185)
- Fix setting dropdown picker back to default [#1191](https://github.com/slab/quill/issues/1191)
- Fix code-block formatting on empty first line in Firefox [#1195](https://github.com/slab/quill/issues/1195)
- Prevent formatting via keyboard shortcuts when not whitelisted [#1197](https://github.com/slab/quill/issues/1197)
- Fix select-all copy and overwrite paste in Firefox [#1202](https://github.com/slab/quill/issues/1202)

Thank you [@adfaure](https://github.com/adfaure), [@berndschimmer](https://github.com/berndschimmer), [@CoenWarmer](https://github.com/CoenWarmer), [@montlebalm](https://github.com/montlebalm), and [@TraceyYau](https://github.com/TraceyYau) for contributions to this release!

[All changes](https://github.com/slab/quill/releases/tag/v1.1.8)

# v1.1.7 (2016-12-13)

- Fix dropdown values reverting to default [#901](https://github.com/slab/quill/issues/901)
- Add config to prevent scroll jumping on paste [#1082](https://github.com/slab/quill/issues/1082)
- Prevent scrolling on API source calls [#1152](https://github.com/slab/quill/issues/1152)
- Fix tsconfig build error [#1165](https://github.com/slab/quill/issues/1165)
- Fix delete and formatting interaction in Firefox [#1171](https://github.com/slab/quill/issues/1171)
- Fix cursor jump on formatting in middle of text [#1176](https://github.com/slab/quill/issues/1176)

Thanks to [@cutteroid](https://github.com/cutteroid), [@houxg](https://github.com/houxg), [@jasongisstl](https://github.com/jasongisstl), [@nikparo](https://github.com/nikparo), [@sbevels](https://github.com/sbevels), and [sferoze](https://github.com/sferoze) for your contributions to this release.

[All changes](https://github.com/slab/quill/releases/tag/v1.1.7)

# v1.1.6 (2016-12-06)

### Features

Checklists [#759](https://github.com/slab/quill/issues/759) support has been added to the API. UI and relevant interactions are still forthcoming.

### Bug Fixes

- Fix bug that allowed edits in readOnly mode [#1151](https://github.com/slab/quill/issues/1151)
- Fix max call stack bug on large paste [#1123](https://github.com/slab/quill/issues/1123)

Thank you [@jgmediadesign](https://github.com/jgmediadesign) and [@julienbmobile](https://github.com/julienbmobile) for contributions to this release!

[All changes](https://github.com/slab/quill/releases/tag/v1.1.6)

# v1.1.5 (2016-11-07)

- Remove unnecessary type attribute in documentation [#1087](https://github.com/slab/quill/pull/1087)
- Fix chrome 52+ input file label open slow [#1090](https://github.com/slab/quill/pull/1090)
- Only query the last op's insertion string if it's actually an insert [#1095](https://github.com/slab/quill/pull/1095)

Thank you [@jleen](https://github.com/jleen), [@kaelig](https://github.com/kaelig), and [@YouHan26](https://github.com/YouHan26) for your contributions to this release.

[All changes](https://github.com/slab/quill/releases/tag/v1.1.5)

# v1.1.3 (2016-10-24)

- Update quill-delta [delta#2](https://github.com/quilljs/delta/issues/2)
- Fix link creation [#1073](https://github.com/slab/quill/issues/1073)

Thanks to [@eamodio](https://github.com/eamodio) and [@metsavir](https://github.com/metsavir) for contributions to this release!

[All changes](https://github.com/slab/quill/releases/tag/v1.1.3)

# v1.1.2 (2016-10-24)

- Fix setContents on already formatted text [#1065](https://github.com/slab/quill/issues/1065)
- Fix regression [#1067](https://github.com/slab/quill/issues/1067)
- Improve documentation [#1069](https://github.com/slab/quill/pull/1069) [#1070](https://github.com/slab/quill/pull/1070)

Thank you [benbro](https://github.com/benbro), [derickruiz](https://github.com/derickruiz), [eamodio](https://github.com/eamodio), [hallaathrad](https://github.com/hallaathrad), and [philly385](https://github.com/philly385) for your contributions to this release.

[All changes](https://github.com/slab/quill/releases/tag/v1.1.2)

# v1.1.1 (2016-10-21)

### Bug fixes

- TEXT_CHANGE event now use cursor position to inform change location [#746](https://github.com/slab/quill/issues/746)
- Fix inconsistent cursor reporting between browsers [#1007](https://github.com/slab/quill/issues/1007)
- Fix tooltip overflow in docs [#1060](https://github.com/slab/quill/issues/1060)
- Fix naming [#1063](https://github.com/slab/quill/pull/1063)
- Fix Medium example [#1064](https://github.com/slab/quill/issues/1064)

Thanks to [@artaommahe](https://github.com/artaommahe), [@benbro](https://github.com/benbro), [@fuffalist](https://github.com/fuffalist), [@sachinrekhi](https://github.com/sachinrekhi), [@sergop321](https://github.com/sergop321), and [@tlg](https://github.com/tlg) for contributions to this release!

Special thanks to [@DadaMonad](https://github.com/DadaMonad) for contributions on [fast-diff](https://github.com/jhchen/fast-diff) that enabled the [#746](https://github.com/slab/quill/issues/746) fix.

[All changes](https://github.com/slab/quill/releases/tag/v1.1.1)

# v1.1.0 (2016-10-17)

### Additions

Quill has always allowed API calls, even when the editor is in readOnly mode. All API calls also took a `source` parameter to indicate the origin of the change. For example, a click handler in the toolbar would call `formatText` with `source` set to `"user"`. When the editor is in readOnly mode, it would make sense for user initiated actions to be ignored. For example the user cannot focus or type into the editor. However because API calls are allowed, the user could still modify the editor contents [#909](https://github.com/slab/quill/issues/909). The natural fix is to ignore user initiated actions, even if it came through an API call, when the editor is in readOnly mode.

However, the documentation never stated API calls with `source` set to `"user"` would be ignored sometimes, so this would be a breaking change under semver. Some could argue this is a bug fix and would only warrant a patch version bump, but this seems disingenuous for this particular case. The fact that almost no one took advantage of the `source` beyond default values is irrelevant under the eyes of semver.

So a `strict` configuration option has been added. It is true by default so the above behavior is unchanged, and [#909](https://github.com/slab/quill/issues/909) is unfixed. Changing this to `false`, will use new behavior of ignoring user initiated changes on a disabled editor, even if through an API call.

### Fixes

- Fix undo when preformatted text inserted before plain text [#1019](https://github.com/slab/quill/issues/1019)
- Add focus indicator on toolbar buttons [#1020](https://github.com/slab/quill/issues/1020)
- Do not steal focus on API calls [#1029](https://github.com/slab/quill/issues/1029)
- Disable paste when Quill is disabled [#1038](https://github.com/slab/quill/issues/1038)
- Fix blank detection [#1043](https://github.com/slab/quill/issues/1043)
- Enable yarn [#1041](https://github.com/slab/quill/issues/1041)
- Documentation fixes [#1026](https://github.com/slab/quill/pull/1026), [#1027](https://github.com/slab/quill/pull/1027), [#1032](https://github.com/slab/quill/pull/1032)

Thank you [@benbro](https://github.com/benbro), [@cutteroid](https://github.com/cutteroid), [@evansolomon](https://github.com/evansolomon), [@felipeochoa](https://github.com/felipeochoa), [jackmu95](https://github.com/jackmu95), [@joedynamite](https://github.com/joedynamite), [@lance13c](https://github.com/lance13c), [@leebenson](https://github.com/leebenson), [@maartenvanvliet](https://github.com/maartenvanvliet), [@sarbbottam](https://github.com/sarbbottam), [@viljark](https://github.com/viljark), [@w00fz](https://github.com/w00fz) for their contributions to this release.

[All changes](https://github.com/slab/quill/releases/tag/v1.1.0)

# v1.0.6 (2016-09-30)

Documentation clarifications and bug fixes.

- Fix attaching toolbar to `<select>` without themes [#997](https://github.com/slab/quill/issues/997)
- Link `code` icon to `code-block` [#998](https://github.com/slab/quill/issues/998)
- Fix undo stack when at size limit [#1001](https://github.com/slab/quill/pull/1001)
- Fix bug where `formatLine` did not ignore inline formats [8a7190](https://github.com/quilljs/parchment/commit/8a71905b2dd02d003edb02a15fdc727b26914e49)

Thanks to [@dropfen](https://github.com/dropfen), [@evansolomon](https://github.com/evansolomon), [@hallaathrad](https://github.com/hallaathrad), [@janyksteenbeek](https://github.com/janyksteenbeek), [@jackmu95](https://github.com/jackmu95), [@marktron](https://github.com/marktron), [@mcat-ee](https://github.com/mcat-ee), [@unhammer](https://github.com/unhammer), and [@zeke](https://github.com/zeke) for contributions to this release!

[All changes](https://github.com/slab/quill/releases/tag/v1.0.6)

# v1.0.4 (2016-09-19)

- Fix bubble theme defaults [#963](https://github.com/slab/quill/issues/963)
- Fix browsers modifying inline nesting order [#971](https://github.com/slab/quill/issues/971)
- Do not fire selection-change event on paste [#974](https://github.com/slab/quill/issues/974)
- Support alt attribute in images [#975](https://github.com/slab/quill/issues/975)
- Deprecate `pasteHTML` for removal in Quill 2.0 [#981](https://github.com/slab/quill/issues/981)

Thank you [jackmu95](https://github.com/jackmu95), [kristeehan](https://github.com/kristeehan), [ruffle1986](https://github.com/ruffle1986), [sergop321](https://github.com/sergop321), [sferoze](https://github.com/sferoze), and [sijad](https://github.com/sijad) for contributions to this release.

[All changes](https://github.com/slab/quill/releases/tag/v1.0.4)

# v1.0.3 (2016-09-07)

- Fix [#928](https://github.com/slab/quill/issues/928)

Thank you [@scottmessinger](https://github.com/scottmessinger) for the bug report.

[All changes](https://github.com/slab/quill/releases/tag/v1.0.3)

# v1.0.2 (2016-09-07)

- Fix building quill.core.js [docs #11](https://github.com/quilljs/quilljs.github.io/issues/11)
- Fix regression of [#793](https://github.com/slab/quill/issues/793)

Thanks to [@eamodio](https://github.com/eamodio) and [@neandrake](https://github.com/neandrake) for their contributions to this release.

[All changes](https://github.com/slab/quill/releases/tag/v1.0.2)

# v1.0.0 (2016-09-06)

Quill 1.0 is released! Read the [official announcement](https://quilljs.com/blog/announcing-quill-1-0/).

[All changes](https://github.com/slab/quill/releases/tag/v1.0.0)

# v1.0.0-rc.4 (2016-08-31)

Fix one important bug [fdd920](https://github.com/slab/quill/commit/fdd920250c05403ed9e5d6d86826a00167ba0b09)

[All changes](https://github.com/slab/quill/releases/tag/v1.0.0-rc.4)

# v1.0.0-rc.3 (2016-08-29)

A few bug fixes, one with with possibly significant implications. See the [issue #889](https://github.com/slab/quill/issues/889) and [commit fix](https://github.com/slab/quill/commit/be24c62a6234818548658fcb5e1935a0c07b4eb7) for more details.

### Bug Fixes

- Fix indenting beyond first level with toolbar [#882](https://github.com/slab/quill/issues/882)
- Fix toolbar font/size display on Safari [#884](https://github.com/slab/quill/issues/884)
- Fix pasting from Gmail from on different browser [#886](https://github.com/slab/quill/issues/886)
- Fix undo/redo consistency [#889](https://github.com/slab/quill/issues/889)
- Fix null error when selecting all on Firefox [#891](https://github.com/slab/quill/issues/891)
- Fix merging keyboard options twice [#897](https://github.com/slab/quill/issues/897)

Thank you [@benbro](https://github.com/benbro), [@cgilboy](https://github.com/cgilboy), [@cutteroid](https://github.com/cutteroid), and [@routman](https://github.com/routman) for contributions to this release!

[All changes](https://github.com/slab/quill/releases/tag/v1.0.0-rc.3)

# v1.0.0-rc.2 (2016-08-23)

A few bug fixes, including one significant [one](https://github.com/slab/quill/issues/883)

### Bug Fixes

- Fix icon picker rendering in MS Edge [#877](https://github.com/slab/quill/issues/877)
- Add back minified build to release [#881](https://github.com/slab/quill/issues/881)
- Fix optimized change calculation with preformatted text [#883](https://github.com/slab/quill/issues/883)

Thanks to [benbro](https://github.com/benbro), [cutteroid](https://github.com/cutteroid), and [CapTec](https://github.com/CapTec) for their contributions to this release.

[All changes](https://github.com/slab/quill/releases/tag/v1.0.0-rc.2)

# v1.0.0-rc.1 (2016-08-23)

A few bug fixes and performance improvements.

### Features

- Source maps now available from CDN for minified build

### Bug Fixes

- Fix scroll interaction between two Quill editors [#855](https://github.com/slab/quill/issues/855)
- Fix scroll on paste [#856](https://github.com/slab/quill/issues/856)
- Fix native iOS tooltip formatting [#862](https://github.com/slab/quill/issues/862)
- Remove comments from pasting from Word [#872](https://github.com/slab/quill/issues/872)
- Fix indent at all supported indent levels [#873](https://github.com/slab/quill/issues/873)
- Fix indent interaction with text direction [#874](https://github.com/slab/quill/issues/874)

Thank you [@benbro](https://github.com/benbro), [@fernandogmar](https://github.com/fernandogmar), [@sachinrekhi](https://github.com/sachinrekhi), [@sferoze](https://github.com/sferoze), and [@stalniy](https://github.com/stalniy) for contributions to this release!

[All changes](https://github.com/slab/quill/releases/tag/v1.0.0-rc.1)

# v1.0.0-rc.0 (2016-08-18)

Take a look at [Quill 1.0 Release Candidate](https://quilljs.com/blog/quill-1-0-release-candidate-released/) for more details.

### Updates

- Going forward the minimal stylesheet build will be named quill.core.css, instead of quill.css

### Bug Fixes

- Fix identifying ordered and bulletd lists [#846](https://github.com/slab/quill/issues/846) [#847](https://github.com/slab/quill/issues/847)
- Fix bullet interaction with text direction [#848](https://github.com/slab/quill/issues/848)

A huge thank you to all contributors to through the beta! Special thanks goes to [@benbro](https://github.com/benbro) and [@sachinrekhi](https://github.com/sachinrekhi) who together submitted submitted almost 50 Issues and Pull Requests!

- [@abejdaniels](https://github.com/abejdaniels)
- [@anovi](https://github.com/anovi)
- [@benbro](https://github.com/benbro)
- [@bram2w](https://github.com/bram2w)
- [@brynjagr](https://github.com/brynjagr)
- [@CapTec](https://github.com/CapTec)
- [@Cinamonas](https://github.com/Cinamonas)
- [@clemmy](https://github.com/clemmy)
- [@crisbeto](https://github.com/crisbeto)
- [@cutteroid](https://github.com/cutteroid)
- [@DadaMonad](https://github.com/DadaMonad)
- [@davelozier](https://github.com/davelozier)
- [@emanuelbsilva](https://github.com/emanuelbsilva)
- [@ersommer](https://github.com/ersommer)
- [@fernandogmar](https://github.com/fernandogmar)
- [@george-norris-salesforce](https://github.com/george-norris-salesforce)
- [@jackmu95](https://github.com/jackmu95)
- [@jasonmng](https://github.com/jasonmng)
- [@jbrowning](https://github.com/jbrowning)
- [@jonnolen](https://github.com/jonnolen)
- [@KameSama](https://github.com/KameSama)
- [@kei-ito](https://github.com/kei-ito)
- [@kylebragger](https://github.com/kylebragger)
- [@LucVanPelt](https://github.com/LucVanPelt)
- [@lukechapman](https://github.com/lukechapman)
- [@micimize](https://github.com/micimize)
- [@mmorearty](https://github.com/mmorearty)
- [@mshamaiev-intel471](https://github.com/mshamaiev-intel471)
- [@quentez](https://github.com/quentez)
- [@sachinrekhi](https://github.com/sachinrekhi)
- [@sagacitysite](https://github.com/sagacitysite)
- [@saw](https://github.com/saw)
- [@stalniy](https://github.com/stalniy)
- [@tOgg1](https://github.com/tOgg1)
- [@u9520107](https://github.com/u9520107)
- [@WriterStat](https://github.com/WriterStat)

[All changes](https://github.com/slab/quill/releases/tag/v1.0.0-rc.0)

# v1.0.0-beta.11 (2016-08-03)

Fixed some regressive bugs from previous release.

### Bug Fixes

- Fix activating more than one format before typing [#841](https://github.com/slab/quill/issues/841)
- Run default matchers before before user defined ones [#843](https://github.com/slab/quill/issues/843)
- Fix merging theme configurations [#844](https://github.com/slab/quill/issues/844), [#845](https://github.com/slab/quill/issues/845)

Thanks [benbro](https://github.com/benbro), [jackmu95](https://github.com/jackmu95), and [george-norris-salesforce](https://github.com/george-norris-salesforce) for the bug reports.

[All changes](https://github.com/slab/quill/releases/tag/v1.0.0-beta.11)

# v1.0.0-beta.10 (2016-08-03)

Lots of bug fixes and performance improvements.

### Breaking Changes

- Keyboard handler format in initial [configuration](beta.quilljs.com/docs/modules/keyboard/) has changed. `addBinding` is overloaded to be backwards compatible.

### Bug Fixes

- Preserve last bullet on paste [#696](https://github.com/slab/quill/issues/696)
- Fix getBounds calculation for lists [#765](https://github.com/slab/quill/issues/765)
- Escape quotes in font value [#769](https://github.com/slab/quill/issues/769)
- Fix spacing calculation on paste [#797](https://github.com/slab/quill/issues/797)
- Fix Snow tooltip label [#798](https://github.com/slab/quill/issues/798)
- Fix link tooltip showing up on long click [#799](https://github.com/slab/quill/issues/799)
- Fix entering code block in IE and Firefox [#803](https://github.com/slab/quill/issues/803)
- Fix opening image dialog on Firefox [#805](https://github.com/slab/quill/issues/805)
- Fix focus loss on updateContents [#809](https://github.com/slab/quill/issues/809)
- Reset toolbar of blur [#810](https://github.com/slab/quill/issues/810)
- Fix cursor position calculation on delete [#811](https://github.com/slab/quill/issues/811)
- Fix highlighting across different alignment values [#815](https://github.com/slab/quill/issues/815)
- Allow default active button [#816](https://github.com/slab/quill/issues/816)
- Fix deleting last character of formatted text on Firefox [#824](https://github.com/slab/quill/issues/824)
- Fix Youtube regex [#826](https://github.com/slab/quill/pull/826)
- Fix missing imports when Quill not global [#836](https://github.com/slab/quill/pull/836)

Thanks to [benbro](https://github.com/benbro), [clemmy](https://github.com/clemmy), [crisbeto](https://github.com/crisbeto), [cutteroid](https://github.com/cutteroid), [jackmu95](https://github.com/jackmu95), [kylebragger](https://github.com/kylebragger), [sachinrekhi](https://github.com/sachinrekhi), [stalniy](https://github.com/stalniy), and [tOgg1](https://github.com/tOgg1) for their contributions to this release.

[All changes](https://github.com/slab/quill/releases/tag/v1.0.0-beta.10)

# v1.0.0-beta.9 (2016-07-19)

Potentially the final beta before a release candidate, if no major issues are discovered.

### Breaking Changes

- No longer expose `ui/link-tooltip` through `import` as implementation is now Snow specific
- Significant refactoring of `ui/tooltip`
- Syntax module now autodetects language, instead of defaulting to Javascript

### Features

- Formula and video insertion UI added to Snow and Bubble themes

### Bug Fixes

- Fix toolbar active state after backspacing to previous line [#730](https://github.com/slab/quill/issues/730)
- User selection is now preserved various API calls [#731](https://github.com/slab/quill/issues/731)
- Fix long click on link-tooltip [#747](https://github.com/slab/quill/issues/747)
- Fix ordered list and text-align right interaction [#784](https://github.com/slab/quill/issues/784)
- Fix toggling code block off [#789](https://github.com/slab/quill/issues/789)
- Scroll position is now automatically preserved between editor blur and focus

Thank you [@benbro](https://github.com/benbro), [@KameSama](https://github.com/KameSama), and [@sachinrekhi](https://github.com/sachinrekhi) for contributions to this release!

[All changes](https://github.com/slab/quill/releases/tag/v1.0.0-beta.9)

# v1.0.0-beta.8 (2016-07-08)

Weekly beta preview release. The editor is almost ready for release candidacy but a couple cycles will be spent on the Snow and Bubble interfaces.

### Work in Progress

Image insertion is being reworked in the provided Snow and Bubble themes. The old image-tooltip has been removed in favor of a simpler and native interaction. By default clicking the image icon on the toolbar will open the OS file picker to convert and that into a base64 image. This will allow for a more natural hook to upload to a remote server instead. Some changes to the link tooltip is also being made to accommodate formula and video insertion, currently only available through the API.

### Breaking Changes

- Image tooltip UI has been removed, see above
- Code blocks now use a single `<pre>` tag, instead of one per line [#723](https://github.com/slab/quill/issues/723)

### Bug Fixes

- Fix multiline syntax highlighting [#723](https://github.com/slab/quill/issues/723)
- Keep pickers open on api text-change [#734](https://github.com/slab/quill/issues/734)
- Emit correct source for text-change [#760](https://github.com/slab/quill/issues/760)
- Emit correct parameters in selection-change [#762](https://github.com/slab/quill/issues/762)
- Fix error redoing line insertion [#767](https://github.com/slab/quill/issues/767)
- Better emitted Deltas for text-change [#768](https://github.com/slab/quill/issues/768)
- Better Array.prototype.find polyfill for IE11 [#776](https://github.com/slab/quill/issues/776)
- Fix Parchment errors in replacing text [#779](https://github.com/slab/quill/issues/779) [#783](https://github.com/slab/quill/issues/783)
- Fix align button active state [#780](https://github.com/slab/quill/issues/780)
- Fix format text on falsy value [#782](https://github.com/slab/quill/issues/782)
- Use native cut [#785](https://github.com/slab/quill/issues/785)
- Fix inializing document where last line is formatted [#786](https://github.com/slab/quill/issues/786)

Thanks to [benbro](https://github.com/benbro), [bram2w](https://github.com/bram2w), [clemmy](https://github.com/clemmy), [DadaMonad](https://github.com/DadaMonad), [ersommer](https://github.com/ersommer), [michaeljosephrosenthal](https://github.com/michaeljosephrosenthal), [mmorearty](https://github.com/mmorearty), [mshamaiev-intel471](https://github.com/mshamaiev-intel471), and [sachinrekhi](https://github.com/sachinrekhi) for their contributions to this release.

[All changes](https://github.com/slab/quill/releases/tag/v1.0.0-beta.8)

# v1.0.0-beta.6 (2016-06-21)

Weekly beta preview release.

### Features

- Pickers can now be used and is styled in Bubble theme

### Bug Fixes

- Fix editing within formula [#702](https://github.com/slab/quill/issues/702)
- Fix adding new line when deleting across lists [#741](https://github.com/slab/quill/issues/741)
- Fix placeholder when default block tag is changed [#743](https://github.com/slab/quill/issues/743)
- Keep Bubble tooltip open on format [#744](https://github.com/slab/quill/issues/744)
- Fix format loss when copying from Quill [#748](https://github.com/slab/quill/issues/748) [#750](https://github.com/slab/quill/issues/750)
- Break long lines in Firefox [#751](https://github.com/slab/quill/issues/751)
- Fix cursor position being off after formatting and typing quickly [#752](https://github.com/slab/quill/issues/752)
- Remove image resizing handles on Firefox [#753](https://github.com/slab/quill/issues/753)
- Fix removing blockquote on initialization [#754](https://github.com/slab/quill/issues/754)
- Fix adding blank lines on initialization [#756](https://github.com/slab/quill/issues/756)

Thank you [abejdaniels](https://github.com/abejdaniels), [benbro](https://github.com/benbro), [davelozier](https://github.com/davelozier), [fernandogmar](https://github.com/fernandogmar), [KameSama](https://github.com/KameSama), and [WriterStat](https://github.com/WriterStat) for contributions to this release.

[All changes](https://github.com/slab/quill/releases/tag/v1.0.0-beta.6)

# v1.0.0-beta.5 (2016-06-14)

Weekly beta preview release.

### Features

- Add blur() [#726](https://github.com/slab/quill/pull/726)

### Bug Fixes

- Fix null error [#728](https://github.com/slab/quill/issues/728)
- Fix building with Node v6 [#732](https://github.com/slab/quill/issues/732)
- Ensure button type for supplied buttons [#733](https://github.com/slab/quill/issues/733)
- Fix line break pasting on Firefox [#735](https://github.com/slab/quill/issues/735)
- Fix 'user' source on API calls [#739](https://github.com/slab/quill/issues/739)

Thanks to [benbro](https://github.com/benbro), [lukechapman](https://github.com/lukechapman), [sachinrekhi](https://github.com/sachinrekhi), and [saw](https://github.com/saw) for their contributions to this release.

[All changes](https://github.com/slab/quill/releases/tag/v1.0.0-beta.5)

# v1.0.0-beta.4 (2016-06-03)

Weekly beta preview release.

### Breaking Changes

- Headers no longer generates id attribute [#700](https://github.com/slab/quill/issues/700)
- Add Control+Y hotkey on Windows [#705](https://github.com/slab/quill/issues/705)
- BlockEmbed Blots are now length 1 and represented in a Delta the same as an inline embed
  - value() used to return object and newline, newline is now removed
  - formats used to be attributed on the newline character, it is now attributed on the object

### Features

- Enter on empty and indented list removes indent [#707](https://github.com/slab/quill/issues/707)
- Allow base64 images to be inserted via APIs [#721](https://github.com/slab/quill/issues/721)

### Bug Fixes

- Fix typing after clearing inline format [#703](https://github.com/slab/quill/issues/703)
- Correctly position Bubble tooltip when selecting multiple lines [#706](https://github.com/slab/quill/issues/706)
- Fix typing after link format [#708](https://github.com/slab/quill/issues/708)
- Fix loss of selection on using link tooltip [#709](https://github.com/slab/quill/issues/709)
- Fix `setSelection(null)` [#722](https://github.com/slab/quill/issues/722)

Thank you [@benbro](https://github.com/benbro), [@brynjagr](https://github.com/brynjagr), and [@sachinrekhi](https://github.com/sachinrekhi) for contributions to this release.

[All changes](https://github.com/slab/quill/releases/tag/v1.0.0-beta.4)

# v1.0.0-beta.3 (2016-05-25)

# 1.0.0-beta.3

Weekly beta preview release.

### Breaking Changes

- Keyboard was incorrectly using `metaKey` to refer to the control key on Windows. It now correctly refers to the Window key and `shortKey` has been added to refer the common platform specific modifier for hotkeys (metaKey for Mac, ctrlKey for Windows/Linux)
- Formula is now a module, since it uses KaTeX

### Features

- Picker now uses text from original `<option>` if available
- Tabbing inside code blocks inserts tab to each line

### Bug Fixes

- Enter preserves inline formats [#666](https://github.com/slab/quill/issues/666)
- Fix resetting format button with no selection [#667](https://github.com/slab/quill/issues/667)
- Fix paste interpretation from Word [#668](https://github.com/slab/quill/issues/668)
- Focus scrolls to correct cursor position [#669](https://github.com/slab/quill/issues/669)
- Fix deleting image on otherwise empty document [#670](https://github.com/slab/quill/issues/670)
- Fix bubble toolbar formatting [#679](https://github.com/slab/quill/issues/679)
- Fix pasting ql-indent lines [#681](https://github.com/slab/quill/issues/681)
- Fix getting into state with double underline tag [#695](https://github.com/slab/quill/issues/695)
- Fix source type on delete [#697](https://github.com/slab/quill/issues/697)
- Fix indent becoming NaN [#698](https://github.com/slab/quill/issues/698)

Thanks to [@benbro](https://github.com/benbro), [@Cinamonas](https://github.com/Cinamonas), [@emanuelbsilva](https://github.com/emanuelbsilva), [@jasonmng](https://github.com/jasonmng), [@jonnolen](https://github.com/jonnolen), [@LucVanPelt](https://github.com/LucVanPelt), [@sachinrekhi](https://github.com/sachinrekhi), [@sagacitysite](https://github.com/sagacitysite), [@WriterStat](https://github.com/WriterStat) for their contributions to this release.

[All changes](https://github.com/slab/quill/releases/tag/v1.0.0-beta.3)

# v1.0.0-beta.2 (2016-05-15)

Weekly beta preview release. Major emphasis on keyboard API and customization.

### Breaking Changes

- Rename code highlighter module to syntax
- Clipboard matchers specified in configuration appends to instead of replaces default matchers
- Change video embed to use `<iframe>` instead of `<video>` enabling Youtube/Vimeo links

### Features

- Add contextual keyboard listeners
- Allow indent format to take +1/-1 in addition to target indent level
- Shortcuts for creating ordered or bulleted lists
- Autofill mailto for email links [#278](https://github.com/slab/quill/issues/278)
- Enter does not continue header format [#540](https://github.com/slab/quill/issues/540)

### Bug Fixes

- Allow native handling of backspace [#473](https://github.com/slab/quill/issues/473) [#548](https://github.com/slab/quill/issues/548) [#565](https://github.com/slab/quill/issues/565)
- removeFormat() removes last line block formats [#649](https://github.com/slab/quill/issues/649)
- Fix text direction icon directon [#654](https://github.com/slab/quill/issues/654)
- Fix text insertion into root scroll [#655](https://github.com/slab/quill/issues/655)
- Fix focusing on placeholder text in FF [#656](https://github.com/slab/quill/issues/656)
- Hide placeholder on formatted line [#657](https://github.com/slab/quill/issues/657)
- Fix selection handling on focus and blur [#664](https://github.com/slab/quill/issues/664)

Thanks to [@anovi](https://github.com/anovi), [@benbro](https://github.com/benbro), [@jbrowning](https://github.com/jbrowning), [@kei-ito](https://github.com/kei-ito), [@quentez](https://github.com/quentez), [@u9520107](https://github.com/u9520107) for their contributions to this release!

[All changes](https://github.com/slab/quill/releases/tag/v1.0.0-beta.2)

# v1.0.0-beta.1 (2016-05-10)

Weekly beta preview release.

### Breaking Changes

- Toolbar only attaches to `<button>` and `<select>` elements
- Toolbar uses button `value` attribute, instead of `data-value`
- Toolbar handlers overwrite default handlers instead of possibly cascading
- Deprecate keyboard `removeBinding` and `removeAllBindings`

### Features

- Expose default keyboard bindings in configuration
- Add context listener to keyboard bindings

### Bug Fixes

- Error when cursor places next to video embed [#644](https://github.com/slab/quill/issues/644)
- Selection removed when clicking on a menu button in the toolbar [#645](https://github.com/slab/quill/issues/645)
- Editor looses focus in FF after typing two bold characters [#646](https://github.com/slab/quill/issues/646)
- Get rid of resize boxes in code in IE11 [0ad636](https://github.com/slab/quill/commit/0ad6363c9fcd70c52ca667d39a393760eeb646b5)
- Text direction icon should flip the arrow when pressed [#651](https://github.com/slab/quill/issues/651)
- Not possible to combine direction:rtl with text-align:left [#652](https://github.com/slab/quill/issues/652)

Thanks to [@benbro](https://github.com/benbro) for the bug reports for this release!

[All changes](https://github.com/slab/quill/releases/tag/v1.0.0-beta.1)

# v1.0.0-beta.0 (2016-05-04)

Please see the [Upgrading to 1.0](http://beta.quilljs.com/guides/upgrading-to-1-0/) guide.

[All changes](https://github.com/slab/quill/releases/tag/v1.0.0-beta.0)


================================================
FILE: LICENSE
================================================
Copyright (c) 2017-2024, Slab
Copyright (c) 2014, Jason Chen
Copyright (c) 2013, salesforce.com
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
   contributors may be used to endorse or promote products derived from
   this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


================================================
FILE: README.md
================================================
<h1 align="center">
  <a href="https://quilljs.com/" title="Quill">Quill Rich Text Editor</a>
</h1>
<p align="center">
  <a href="https://quilljs.com/" title="Quill"><img alt="Quill Logo" src="https://quilljs.com/assets/images/logo.svg" width="180"></a>
</p>
<p align="center">
  <a title="Documentation" href="https://quilljs.com/docs/quickstart"><strong>Documentation</strong></a>
  &#x2022;
  <a title="Development" href="https://github.com/slab/quill/blob/main/.github/DEVELOPMENT.md"><strong>Development</strong></a>
  &#x2022;
  <a title="Contributing" href="https://github.com/slab/quill/blob/main/.github/CONTRIBUTING.md"><strong>Contributing</strong></a>
  &#x2022;
  <a title="Interactive Playground" href="https://quilljs.com/playground/"><strong>Interactive Playground</strong></a>
</p>
<p align="center">
  <a href="https://github.com/slab/quill/actions" title="Build Status"><img src="https://github.com/slab/quill/actions/workflows/main.yml/badge.svg" alt="Build Status"></a>
  <a href="https://npmjs.com/package/quill" title="Version"><img src="https://img.shields.io/npm/v/quill.svg" alt="Version"></a>
  <a href="https://npmjs.com/package/quill" title="Downloads"><img src="https://img.shields.io/npm/dm/quill.svg" alt="Downloads"></a>
</p>

<hr/>

[Quill](https://quilljs.com/) is a modern rich text editor built for compatibility and extensibility. It was created by [Jason Chen](https://twitter.com/jhchen) and [Byron Milligan](https://twitter.com/byronmilligan) and actively maintained by [Slab](https://slab.com).

To get started, check out [https://quilljs.com/](https://quilljs.com/) for documentation, guides, and live demos!

## Quickstart

Instantiate a new Quill object with a css selector for the div that should become the editor.

```html
<!-- Include Quill stylesheet -->
<link
  href="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.snow.css"
  rel="stylesheet"
/>

<!-- Create the toolbar container -->
<div id="toolbar">
  <button class="ql-bold">Bold</button>
  <button class="ql-italic">Italic</button>
</div>

<!-- Create the editor container -->
<div id="editor">
  <p>Hello World!</p>
  <p>Some initial <strong>bold</strong> text</p>
  <p><br /></p>
</div>

<!-- Include the Quill library -->
<script src="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.js"></script>

<!-- Initialize Quill editor -->
<script>
  const quill = new Quill("#editor", {
    theme: "snow",
  });
</script>
```

Take a look at the [Quill](https://quilljs.com/) website for more documentation, guides and [live playground](https://quilljs.com/playground/)!

## Download

```shell
npm install quill
```

### CDN

```html
<!-- Main Quill library -->
<script src="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.js"></script>

<!-- Theme included stylesheets -->
<link
  href="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.snow.css"
  rel="stylesheet"
/>
<link
  href="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.bubble.css"
  rel="stylesheet"
/>

<!-- Core build with no theme, formatting, non-essential modules -->
<link
  href="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.core.css"
  rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/quill@2/dist/quill.core.js"></script>
```

## Community

Get help or stay up to date.

- [Contribute](https://github.com/slab/quill/blob/main/.github/CONTRIBUTING.md) on [Issues](https://github.com/slab/quill/issues)
- Ask questions on [Discussions](https://github.com/slab/quill/discussions)

## License

BSD 3-clause


================================================
FILE: package.json
================================================
{
  "name": "quill-monorepo",
  "version": "2.0.3",
  "description": "Quill development environment",
  "private": true,
  "author": "Jason Chen <jhchen7@gmail.com>",
  "homepage": "https://quilljs.com",
  "config": {
    "ports": {
      "webpack": "9080",
      "website": "9000"
    }
  },
  "workspaces": [
    "packages/*"
  ],
  "license": "BSD-3-Clause",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/slab/quill.git"
  },
  "bugs": {
    "url": "https://github.com/slab/quill/issues"
  },
  "scripts": {
    "build": "run-p build:*",
    "build:quill": "npm run build -w quill",
    "build:website": "npm run build -w website",
    "start": "run-p start:*",
    "start:quill": "npm start -w quill",
    "start:website": "NEXT_PUBLIC_LOCAL_QUILL=true npm start -w website",
    "lint": "npm run lint -ws"
  },
  "keywords": [
    "quill",
    "editor",
    "rich text",
    "wysiwyg",
    "operational transformation",
    "ot",
    "framework"
  ],
  "engines": {
    "npm": ">=8.2.3"
  },
  "engineStrict": true,
  "devDependencies": {
    "execa": "^9.0.2",
    "npm-run-all": "^4.1.5"
  }
}


================================================
FILE: packages/quill/.eslintrc.json
================================================
{
  "extends": [
    "eslint:recommended",
    "plugin:prettier/recommended",
    "plugin:import/recommended",
    "plugin:require-extensions/recommended"
  ],
  "env": {
    "browser": true,
    "commonjs": true,
    "es6": true
  },
  "parser": "@typescript-eslint/parser",
  "settings": {
    "import/resolver": {
      "webpack": {
        "env": "development"
      },
      "typescript": true
    }
  },
  "ignorePatterns": ["*.js", "*.d.ts"],
  "overrides": [
    {
      "files": ["**/*.ts"],
      "extends": [
        "plugin:@typescript-eslint/recommended",
        "plugin:import/typescript"
      ],
      "excludedFiles": "*.d.ts",
      "plugins": ["@typescript-eslint", "require-extensions"],
      "rules": {
        "@typescript-eslint/consistent-type-imports": "error",
        "@typescript-eslint/ban-ts-comment": "off",
        "@typescript-eslint/no-empty-function": "off",
        "@typescript-eslint/ban-types": "off",
        "@typescript-eslint/no-explicit-any": "off",
        "import/no-named-as-default-member": "off",
        "prefer-arrow-callback": "error"
      }
    }
  ]
}


================================================
FILE: packages/quill/.gitignore
================================================
dist


================================================
FILE: packages/quill/LICENSE
================================================
Copyright (c) 2017-2024, Slab
Copyright (c) 2014, Jason Chen
Copyright (c) 2013, salesforce.com
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
   contributors may be used to endorse or promote products derived from
   this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


================================================
FILE: packages/quill/README.md
================================================
# Quill

This is the main package of Quill.


================================================
FILE: packages/quill/babel.config.cjs
================================================
const pkg = require('./package.json');

module.exports = {
  presets: [
    ['@babel/preset-env', { modules: false }],
    '@babel/preset-typescript',
  ],
  plugins: [
    ['transform-define', { QUILL_VERSION: pkg.version }],
    './scripts/babel-svg-inline-import.cjs',
  ],
};


================================================
FILE: packages/quill/package.json
================================================
{
  "name": "quill",
  "version": "2.0.3",
  "description": "Your powerful, rich text editor",
  "author": "Jason Chen <jhchen7@gmail.com>",
  "homepage": "https://quilljs.com",
  "main": "quill.js",
  "type": "module",
  "dependencies": {
    "eventemitter3": "^5.0.1",
    "lodash-es": "^4.17.21",
    "parchment": "^3.0.0",
    "quill-delta": "^5.1.0"
  },
  "devDependencies": {
    "@babel/cli": "^7.23.9",
    "@babel/core": "^7.24.0",
    "@babel/preset-env": "^7.24.0",
    "@babel/preset-typescript": "^7.23.3",
    "@playwright/test": "^1.54.1",
    "@types/highlight.js": "^9.12.4",
    "@types/lodash-es": "^4.17.12",
    "@types/node": "^20.10.0",
    "@types/webpack": "^5.28.5",
    "@typescript-eslint/eslint-plugin": "^7.2.0",
    "@typescript-eslint/parser": "^7.2.0",
    "@vitest/browser": "^3.2.4",
    "babel-loader": "^9.1.3",
    "babel-plugin-transform-define": "^2.1.4",
    "css-loader": "^6.10.0",
    "eslint": "^8.57.0",
    "eslint-config-prettier": "^9.1.0",
    "eslint-import-resolver-typescript": "^3.6.1",
    "eslint-import-resolver-webpack": "^0.13.8",
    "eslint-plugin-import": "^2.29.1",
    "eslint-plugin-jsx-a11y": "^6.8.0",
    "eslint-plugin-prettier": "^5.1.3",
    "eslint-plugin-require-extensions": "^0.1.3",
    "glob": "10.4.2",
    "highlight.js": "^9.18.1",
    "html-loader": "^4.2.0",
    "html-webpack-plugin": "^5.5.3",
    "jsdom": "^22.1.0",
    "mini-css-extract-plugin": "^2.7.6",
    "prettier": "^3.0.3",
    "source-map-loader": "^5.0.0",
    "style-loader": "^3.3.3",
    "stylus": "^0.62.0",
    "stylus-loader": "^7.1.3",
    "svgo": "^3.2.0",
    "terser-webpack-plugin": "^5.3.9",
    "transpile-webpack-plugin": "^1.1.3",
    "ts-loader": "^9.5.1",
    "ts-node": "^10.9.2",
    "typescript": "^5.4.2",
    "vitest": "^3.2.4",
    "webpack": "^5.89.0",
    "webpack-cli": "^5.1.4",
    "webpack-dev-server": "^4.15.1",
    "webpack-merge": "^5.10.0"
  },
  "license": "BSD-3-Clause",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/slab/quill.git",
    "directory": "packages/quill"
  },
  "bugs": {
    "url": "https://github.com/slab/quill/issues"
  },
  "prettier": {
    "singleQuote": true
  },
  "browserslist": [
    "defaults"
  ],
  "scripts": {
    "build": "./scripts/build production",
    "lint": "run-s lint:*",
    "lint:eslint": "eslint .",
    "lint:tsc": "tsc --noEmit --skipLibCheck",
    "start": "[[ -z \"$npm_package_config_ports_webpack\" ]] && webpack-dev-server || webpack-dev-server --port $npm_package_config_ports_webpack",
    "test": "run-s test:*",
    "test:unit": "vitest --config test/unit/vitest.config.ts",
    "test:fuzz": "vitest --config test/fuzz/vitest.config.ts",
    "test:e2e": "playwright test"
  },
  "keywords": [
    "quill",
    "editor",
    "rich text",
    "wysiwyg",
    "operational transformation",
    "ot",
    "framework"
  ],
  "engines": {
    "npm": ">=8.2.3"
  },
  "engineStrict": true
}


================================================
FILE: packages/quill/playwright.config.ts
================================================
import { defineConfig, devices } from '@playwright/test';

const port = 9001;

export default defineConfig({
  testDir: './test/e2e',
  testMatch: '*.spec.ts',
  timeout: 30 * 1000,
  expect: {
    timeout: 5000,
  },
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: 'list',
  use: {
    actionTimeout: 0,
    trace: 'on-first-retry',
    baseURL: `https://127.0.0.1:${port}`,
    ignoreHTTPSErrors: true,
  },
  projects: [
    {
      name: 'Chrome',
      use: {
        ...devices['Desktop Chrome'],
        contextOptions: {
          permissions: ['clipboard-read', 'clipboard-write'],
        },
      },
    },
    { name: 'Firefox', use: { ...devices['Desktop Firefox'] } },
    { name: 'Safari', use: { ...devices['Desktop Safari'] } },
  ],
  webServer: {
    command: `npx webpack serve --config test/e2e/__dev_server__/webpack.config.cjs --env port=${port}`,
    port,
    ignoreHTTPSErrors: true,
    reuseExistingServer: !process.env.CI,
    stdout: 'ignore',
    stderr: 'pipe',
  },
});


================================================
FILE: packages/quill/scripts/babel-svg-inline-import.cjs
================================================
const fs = require('fs');
const { dirname, resolve } = require('path');
const { optimize } = require('svgo');

module.exports = ({ types: t }) => {
  class BabelSVGInlineImport {
    constructor() {
      return {
        visitor: {
          ImportDeclaration: {
            exit(path, state) {
              const givenPath = path.node.source.value;
              if (!givenPath.endsWith('.svg')) {
                return;
              }
              const specifier = path.node.specifiers[0];
              const id = specifier.local.name;
              const reference = state && state.file && state.file.opts.filename;
              const absolutePath = resolve(dirname(reference), givenPath);
              const content = optimize(
                fs.readFileSync(absolutePath).toString(),
                { plugins: [] },
              ).data;

              const variableValue = t.stringLiteral(content);
              const variable = t.variableDeclarator(
                t.identifier(id),
                variableValue,
              );

              path.replaceWith({
                type: 'VariableDeclaration',
                kind: 'const',
                declarations: [variable],
              });
            },
          },
        },
      };
    }
  }

  return new BabelSVGInlineImport();
};


================================================
FILE: packages/quill/scripts/build
================================================
#!/usr/bin/env bash

set -e

DIST=dist

TMPDIR=$(mktemp -d 2>/dev/null || mktemp -d -t 'mytmpdir')
npx tsc --declaration --emitDeclarationOnly --outDir $TMPDIR

rm -rf $DIST
mkdir $DIST
mv $TMPDIR/src/* $DIST
rm -rf $TMPDIR
npx babel src --out-dir $DIST --copy-files --no-copy-ignored --extensions .ts --source-maps
npx webpack -- --mode $1
# https://github.com/webpack-contrib/mini-css-extract-plugin/issues/151
rm -rf $DIST/dist/*.css.js $DIST/dist/*.css.js.*
cp package.json $DIST
cp README.md $DIST
cp LICENSE $DIST


================================================
FILE: packages/quill/src/assets/base.styl
================================================
// Styles shared between snow and bubble

controlHeight = 24px
inputPaddingWidth = 5px
inputPaddingHeight = 3px

colorItemMargin = 2px
colorItemSize = 16px
colorItemsPerRow = 7


.ql-{themeName}.ql-toolbar, .ql-{themeName} .ql-toolbar
  &:after
    clear: both
    content: ''
    display: table

  button
    background: none
    border: none
    cursor: pointer
    display: inline-block
    float: left
    height: controlHeight
    padding: inputPaddingHeight inputPaddingWidth
    width: controlHeight + (inputPaddingWidth - inputPaddingHeight)*2

    svg
      float: left
      height: 100%

    &:active:hover
      outline: none

  input.ql-image[type=file]
    display: none

  button:hover, button:focus, button.ql-active,
  .ql-picker-label:hover, .ql-picker-label.ql-active,
  .ql-picker-item:hover, .ql-picker-item.ql-selected
    color: activeColor
    .ql-fill, .ql-stroke.ql-fill
      fill: activeColor
    .ql-stroke, .ql-stroke-miter
      stroke: activeColor

// Fix for iOS not losing hover on touch
@media (pointer: coarse)
  .ql-{themeName}.ql-toolbar, .ql-{themeName} .ql-toolbar
    button:hover:not(.ql-active)
      color: inactiveColor
      .ql-fill, .ql-stroke.ql-fill
        fill: inactiveColor
      .ql-stroke, .ql-stroke-miter
        stroke: inactiveColor

.ql-{themeName}
  box-sizing: border-box
  *
    box-sizing: border-box

  .ql-hidden
    display: none
  .ql-out-bottom, .ql-out-top
    visibility: hidden

  .ql-tooltip
    position: absolute
    transform: translateY(10px)
    a
      cursor: pointer
      text-decoration: none
  .ql-tooltip.ql-flip
    transform: translateY(-10px)

  .ql-formats
    &:after
      clear: both
      content: ''
      display: table
    display: inline-block
    vertical-align: middle

  .ql-stroke
    fill: none
    stroke: inactiveColor
    stroke-linecap: round
    stroke-linejoin: round
    stroke-width: 2
  .ql-stroke-miter
    fill: none
    stroke: inactiveColor
    stroke-miterlimit: 10
    stroke-width: 2

  .ql-fill, .ql-stroke.ql-fill
    fill: inactiveColor

  .ql-empty
    fill: none
  .ql-even
    fill-rule: evenodd
  .ql-thin, .ql-stroke.ql-thin
    stroke-width: 1
  .ql-transparent
    opacity: 0.4

  .ql-direction
    svg:last-child
      display: none
  .ql-direction.ql-active
    svg:last-child
      display: inline
    svg:first-child
      display: none

  .ql-editor
    h1
      font-size: 2em
    h2
      font-size: 1.5em
    h3
      font-size: 1.17em
    h4
      font-size: 1em
    h5
      font-size: 0.83em
    h6
      font-size: 0.67em
    a
      text-decoration: underline
    blockquote
      border-left: 4px solid #ccc
      margin-bottom: 5px
      margin-top: 5px
      padding-left: 16px
    code, .ql-code-block-container
      background-color: #f0f0f0
      border-radius: 3px
    .ql-code-block-container
      margin-bottom: 5px
      margin-top: 5px
      padding: 5px 10px
    code
      font-size: 85%
      padding: 2px 4px
    .ql-code-block-container
      background-color: #23241f
      color: #f8f8f2
      overflow: visible
    img
      max-width: 100%

  .ql-picker
    color: inactiveColor
    display: inline-block
    float: left
    font-size: 14px
    font-weight: 500
    height: controlHeight
    position: relative
    vertical-align: middle
  .ql-picker-label
    cursor: pointer
    display: inline-block
    height: 100%
    padding-left: 8px
    padding-right: 2px
    position: relative
    width: 100%
    &::before
      display: inline-block
      line-height: 22px
  .ql-picker-options
    background-color: backgroundColor
    display: none
    min-width: 100%
    padding: 4px 8px
    position: absolute
    white-space: nowrap
    .ql-picker-item
      cursor: pointer
      display: block
      padding-bottom: 5px
      padding-top: 5px
  .ql-picker.ql-expanded
    .ql-picker-label
      color: borderColor
      z-index: 2
      .ql-fill
        fill: borderColor
      .ql-stroke
        stroke: borderColor
    .ql-picker-options
      display: block
      margin-top: -1px
      top: 100%
      z-index: 1

  .ql-color-picker, .ql-icon-picker
    width: controlHeight + 4
    .ql-picker-label
      padding: 2px 4px
      svg
        right: 4px
  .ql-icon-picker
    .ql-picker-options
      padding: 4px 0px
    .ql-picker-item
      height: controlHeight
      width: controlHeight
      padding: 2px 4px
  .ql-color-picker
    .ql-picker-options
      padding: inputPaddingHeight inputPaddingWidth
      width: (colorItemSize + 2*colorItemMargin) * colorItemsPerRow + 2*inputPaddingWidth + 2  // +2 for the border
    .ql-picker-item
      border: 1px solid transparent
      float: left
      height: colorItemSize
      margin: colorItemMargin
      padding: 0px
      width: colorItemSize

  .ql-picker:not(.ql-color-picker):not(.ql-icon-picker)
    svg
      position: absolute
      margin-top: -9px
      right: 0
      top: 50%
      width: 18px

  .ql-picker.ql-header, .ql-picker.ql-font, .ql-picker.ql-size
    .ql-picker-label[data-label]:not([data-label='']),
    .ql-picker-item[data-label]:not([data-label=''])
      &::before
        content: attr(data-label)

  .ql-picker.ql-header
    width: 98px
    .ql-picker-label::before,
    .ql-picker-item::before
      content: 'Normal'
    for num in (1..6)
      .ql-picker-label[data-value=\"{num}\"]::before,
      .ql-picker-item[data-value=\"{num}\"]::before
        content: 'Heading ' + num
    .ql-picker-item[data-value="1"]::before
      font-size: 2em
    .ql-picker-item[data-value="2"]::before
      font-size: 1.5em
    .ql-picker-item[data-value="3"]::before
      font-size: 1.17em
    .ql-picker-item[data-value="4"]::before
      font-size: 1em
    .ql-picker-item[data-value="5"]::before
      font-size: 0.83em
    .ql-picker-item[data-value="6"]::before
      font-size: 0.67em

  .ql-picker.ql-font
    width: 108px
    .ql-picker-label::before,
    .ql-picker-item::before
      content: 'Sans Serif'
    .ql-picker-label[data-value=serif]::before,
    .ql-picker-item[data-value=serif]::before
      content: 'Serif'
    .ql-picker-label[data-value=monospace]::before,
    .ql-picker-item[data-value=monospace]::before
      content: 'Monospace'
    .ql-picker-item[data-value=serif]::before
      font-family: Georgia, Times New Roman, serif
    .ql-picker-item[data-value=monospace]::before
      font-family: Monaco, Courier New, monospace

  .ql-picker.ql-size
    width: 98px
    .ql-picker-label::before,
    .ql-picker-item::before
      content: 'Normal'
    .ql-picker-label[data-value=small]::before,
    .ql-picker-item[data-value=small]::before
      content: 'Small'
    .ql-picker-label[data-value=large]::before,
    .ql-picker-item[data-value=large]::before
      content: 'Large'
    .ql-picker-label[data-value=huge]::before,
    .ql-picker-item[data-value=huge]::before
      content: 'Huge'
    .ql-picker-item[data-value=small]::before
      font-size: 10px
    .ql-picker-item[data-value=large]::before
      font-size: 18px
    .ql-picker-item[data-value=huge]::before
      font-size: 32px

  .ql-color-picker.ql-background
    .ql-picker-item
      background-color: #fff
  .ql-color-picker.ql-color
    .ql-picker-item
      background-color: #000

.ql-code-block-container
  position: relative
  .ql-ui
    right: 5px
    top: 5px


================================================
FILE: packages/quill/src/assets/bubble/toolbar.styl
================================================
arrowWidth = 6px

.ql-bubble
  .ql-toolbar
    .ql-formats
      margin: 8px 12px 8px 0px
    .ql-formats:first-child
      margin-left: 12px

  .ql-color-picker
    svg
      margin: 1px
    .ql-picker-item.ql-selected, .ql-picker-item:hover
      border-color: activeColor


================================================
FILE: packages/quill/src/assets/bubble/tooltip.styl
================================================
arrowWidth = 6px

.ql-bubble
  .ql-tooltip
    background-color: backgroundColor
    border-radius: 25px
    color: textColor
  .ql-tooltip-arrow
    border-left: arrowWidth solid transparent
    border-right: arrowWidth solid transparent
    content: " "
    display: block
    left: 50%
    margin-left: -1 * arrowWidth
    position: absolute
  .ql-tooltip:not(.ql-flip) .ql-tooltip-arrow
    border-bottom: arrowWidth solid backgroundColor
    top: -1 * arrowWidth
  .ql-tooltip.ql-flip .ql-tooltip-arrow
    border-top: arrowWidth solid backgroundColor
    bottom: -1 * arrowWidth

  .ql-tooltip.ql-editing
    .ql-tooltip-editor
      display: block
    .ql-formats
      visibility: hidden

  .ql-tooltip-editor
    display: none
    input[type=text]
      background: transparent
      border: none
      color: textColor
      font-size: 13px
      height: 100%
      outline: none
      padding: 10px 20px
      position: absolute
      width: 100%
    a
      &:before
        color: inactiveColor
        content: "\00D7"
        font-size: 16px
        font-weight: bold
      top: 10px
      position: absolute
      right: 20px


================================================
FILE: packages/quill/src/assets/bubble.styl
================================================
themeName = 'bubble'
activeColor = #fff
borderColor = #777
backgroundColor = #444
inactiveColor = #ccc
shadowColor = #ddd
textColor = #fff

@import './core'
@import './base'
@import './bubble/*'

.ql-container.ql-bubble:not(.ql-disabled)
  a:not(.ql-close)
    position: relative
    white-space: nowrap
  a:not(.ql-close)::before
    background-color: #444
    border-radius: 15px
    top: -5px
    font-size: 12px
    color: #fff
    content: attr(href)
    font-weight: normal
    overflow: hidden
    padding: 5px 15px
    text-decoration: none
    z-index: 1
  a:not(.ql-close)::after
    border-top: 6px solid #444
    border-left: 6px solid transparent
    border-right: 6px solid transparent
    top: 0
    content: " "
    height: 0
    width: 0
  a:not(.ql-close)::before, a:not(.ql-close)::after
    left: 0
    margin-left: 50%
    position: absolute
    transform: translate(-50%, -100%)
    transition: visibility 0s ease 200ms
    visibility: hidden
  a:not(.ql-close):hover::before, a:not(.ql-close):hover::after
    visibility: visible


================================================
FILE: packages/quill/src/assets/core.styl
================================================
// Styles necessary for Quill

LIST_STYLE = decimal lower-alpha lower-roman
LIST_STYLE_WIDTH = 1.2em
LIST_STYLE_MARGIN = 0.3em
LIST_STYLE_OUTER_WIDTH = LIST_STYLE_MARGIN + LIST_STYLE_WIDTH
MAX_INDENT = 9

resets(arr)
  unquote('list-' + join(' list-', arr))

.ql-container
  box-sizing: border-box
  font-family: Helvetica, Arial, sans-serif
  font-size: 13px
  height: 100%
  margin: 0px
  position: relative

.ql-container.ql-disabled
  .ql-tooltip
    visibility: hidden

.ql-container:not(.ql-disabled)
  li[data-list=checked],
  li[data-list=unchecked]
    > .ql-ui
      cursor: pointer

.ql-clipboard
  left: -100000px
  height: 1px
  overflow-y: hidden
  position: absolute
  top: 50%
  p
    margin: 0
    padding: 0

.ql-editor
  box-sizing: border-box
  counter-reset: resets(0..MAX_INDENT)
  line-height: 1.42
  height: 100%
  outline: none
  overflow-y: auto
  padding: 12px 15px
  tab-size: 4
  -moz-tab-size: 4
  text-align: left
  white-space: pre-wrap
  word-wrap: break-word
  > *
    cursor: text

  p, ol, pre, blockquote, h1, h2, h3, h4, h5, h6
    margin: 0
    padding: 0
  p, h1, h2, h3, h4, h5, h6
    @supports (counter-set: none)
      counter-set: resets(0..MAX_INDENT)
    @supports not (counter-set: none)
      counter-reset: resets(0..MAX_INDENT)
  table
    border-collapse: collapse
  td
    border: 1px solid #000
    padding: 2px 5px
  ol
    padding-left: 1.5em
  li
    list-style-type: none
    padding-left: LIST_STYLE_OUTER_WIDTH
    position: relative

    > .ql-ui:before
      display: inline-block
      margin-left: -1*LIST_STYLE_OUTER_WIDTH
      margin-right: LIST_STYLE_MARGIN
      text-align: right
      white-space: nowrap
      width: LIST_STYLE_WIDTH

  li[data-list=checked],
  li[data-list=unchecked]
    > .ql-ui
      color: #777

  li[data-list=bullet] > .ql-ui:before
    content: '\2022'
  li[data-list=checked] > .ql-ui:before
    content: '\2611'
  li[data-list=unchecked] > .ql-ui:before
    content: '\2610'

  li[data-list]
    @supports (counter-set: none)
      counter-set: resets(1..MAX_INDENT)
    @supports not (counter-set: none)
      counter-reset: resets(1..MAX_INDENT)

  li[data-list=ordered]
    counter-increment: list-0
    > .ql-ui:before
      content: unquote('counter(list-0, ' + LIST_STYLE[0] + ')') '. '
  for num in (1..MAX_INDENT)
    li[data-list=ordered].ql-indent-{num}
      counter-increment: unquote('list-' + num)
      > .ql-ui:before
        content: unquote('counter(list-' + num + ', ' + LIST_STYLE[num%3] + ')') '. '
    if (num < MAX_INDENT)
      li[data-list].ql-indent-{num}
        @supports (counter-set: none)
          counter-set: resets((num+1)..MAX_INDENT)
        @supports not (counter-set: none)
          counter-reset: resets((num+1)..MAX_INDENT)

  for num in (1..MAX_INDENT)
    .ql-indent-{num}:not(.ql-direction-rtl)
      padding-left: (3*num)em
    li.ql-indent-{num}:not(.ql-direction-rtl)
      padding-left: (3*num + LIST_STYLE_OUTER_WIDTH)em
    .ql-indent-{num}.ql-direction-rtl.ql-align-right
      padding-right: (3*num)em
    li.ql-indent-{num}.ql-direction-rtl.ql-align-right
      padding-right: (3*num + LIST_STYLE_OUTER_WIDTH)em

  li.ql-direction-rtl
    padding-right: LIST_STYLE_OUTER_WIDTH
    > .ql-ui:before
      margin-left: LIST_STYLE_MARGIN
      margin-right: -1*LIST_STYLE_OUTER_WIDTH
      text-align: left

  table
    table-layout: fixed
    width: 100%
    td
      outline: none

  .ql-code-block-container
    font-family: monospace

  .ql-video
    display: block
    max-width: 100%
  .ql-video.ql-align-center
    margin: 0 auto
  .ql-video.ql-align-right
    margin: 0 0 0 auto

  .ql-bg-black
    background-color: rgb(0,0,0)
  .ql-bg-red
    background-color: rgb(230,0,0)
  .ql-bg-orange
    background-color: rgb(255,153,0)
  .ql-bg-yellow
    background-color: rgb(255,255,0)
  .ql-bg-green
    background-color: rgb(0,138,0)
  .ql-bg-blue
    background-color: rgb(0,102,204)
  .ql-bg-purple
    background-color: rgb(153,51,255)

  .ql-color-white
    color: rgb(255,255,255)
  .ql-color-red
    color: rgb(230,0,0)
  .ql-color-orange
    color: rgb(255,153,0)
  .ql-color-yellow
    color: rgb(255,255,0)
  .ql-color-green
    color: rgb(0,138,0)
  .ql-color-blue
    color: rgb(0,102,204)
  .ql-color-purple
    color: rgb(153,51,255)

  .ql-font-serif
    font-family: Georgia, Times New Roman, serif
  .ql-font-monospace
    font-family: Monaco, Courier New, monospace

  .ql-size-small
    font-size: 0.75em
  .ql-size-large
    font-size: 1.5em
  .ql-size-huge
    font-size: 2.5em

  .ql-direction-rtl
    direction: rtl
    text-align: inherit

  .ql-align-center
    text-align: center
  .ql-align-justify
    text-align: justify
  .ql-align-right
    text-align: right

  .ql-ui
    position: absolute

.ql-editor.ql-blank::before
  color: rgba(0,0,0,0.6)
  content: attr(data-placeholder)
  font-style: italic
  left: 15px
  pointer-events: none
  position: absolute
  right: 15px


================================================
FILE: packages/quill/src/assets/snow/toolbar.styl
================================================
.ql-toolbar.ql-snow
  border: 1px solid borderColor
  box-sizing: border-box
  font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif
  padding: 8px

  .ql-formats
    margin-right: 15px

  .ql-picker-label
    border: 1px solid transparent
  .ql-picker-options
    border: 1px solid transparent
    box-shadow: rgba(0,0,0,0.2) 0 2px 8px
  .ql-picker.ql-expanded
    .ql-picker-label
      border-color: borderColor
    .ql-picker-options
      border-color: borderColor

  .ql-color-picker
    .ql-picker-item.ql-selected, .ql-picker-item:hover
      border-color: #000

.ql-toolbar.ql-snow + .ql-container.ql-snow
  border-top: 0px;


================================================
FILE: packages/quill/src/assets/snow/tooltip.styl
================================================
tooltipMargin = 8px

.ql-snow
  .ql-tooltip
    background-color: #fff
    border: 1px solid borderColor
    box-shadow: 0px 0px 5px shadowColor
    color: textColor
    padding: 5px 12px
    white-space: nowrap
    &::before
      content: "Visit URL:"
      line-height: 26px
      margin-right: tooltipMargin
    input[type=text]
      display: none
      border: 1px solid borderColor
      font-size: 13px
      height: 26px
      margin: 0px
      padding: 3px 5px
      width: 170px
    a.ql-preview
      display: inline-block
      max-width: 200px
      overflow-x: hidden
      text-overflow: ellipsis
      vertical-align: top
    a.ql-action::after
      border-right: 1px solid borderColor
      content: 'Edit'
      margin-left: tooltipMargin*2
      padding-right: tooltipMargin
    a.ql-remove::before
      content: 'Remove'
      margin-left: tooltipMargin
    a
      line-height: 26px
  .ql-tooltip.ql-editing
    a.ql-preview, a.ql-remove
      display: none
    input[type=text]
      display: inline-block
    a.ql-action::after
      border-right: 0px
      content: 'Save'
      padding-right: 0px
  .ql-tooltip[data-mode=link]::before
    content: "Enter link:"
  .ql-tooltip[data-mode=formula]::before
    content: "Enter formula:"
  .ql-tooltip[data-mode=video]::before
    content: "Enter video:"


================================================
FILE: packages/quill/src/assets/snow.styl
================================================
themeName = 'snow'
activeColor = #06c
borderColor = #ccc
backgroundColor = #fff
inactiveColor = #444
shadowColor = #ddd
textColor = #444

@import './core'
@import './base'
@import './snow/*'

.ql-snow
  a
    color: activeColor

.ql-container.ql-snow
  border: 1px solid borderColor


================================================
FILE: packages/quill/src/blots/block.ts
================================================
import {
  AttributorStore,
  BlockBlot,
  EmbedBlot,
  LeafBlot,
  Scope,
} from 'parchment';
import type { Blot, Parent } from 'parchment';
import Delta from 'quill-delta';
import Break from './break.js';
import Inline from './inline.js';
import TextBlot from './text.js';

const NEWLINE_LENGTH = 1;

class Block extends BlockBlot {
  cache: { delta?: Delta | null; length?: number } = {};

  delta(): Delta {
    if (this.cache.delta == null) {
      this.cache.delta = blockDelta(this);
    }
    return this.cache.delta;
  }

  deleteAt(index: number, length: number) {
    super.deleteAt(index, length);
    this.cache = {};
  }

  formatAt(index: number, length: number, name: string, value: unknown) {
    if (length <= 0) return;
    if (this.scroll.query(name, Scope.BLOCK)) {
      if (index + length === this.length()) {
        this.format(name, value);
      }
    } else {
      super.formatAt(
        index,
        Math.min(length, this.length() - index - 1),
        name,
        value,
      );
    }
    this.cache = {};
  }

  insertAt(index: number, value: string, def?: unknown) {
    if (def != null) {
      super.insertAt(index, value, def);
      this.cache = {};
      return;
    }
    if (value.length === 0) return;
    const lines = value.split('\n');
    const text = lines.shift() as string;
    if (text.length > 0) {
      if (index < this.length() - 1 || this.children.tail == null) {
        super.insertAt(Math.min(index, this.length() - 1), text);
      } else {
        this.children.tail.insertAt(this.children.tail.length(), text);
      }
      this.cache = {};
    }
    // TODO: Fix this next time the file is edited.
    // eslint-disable-next-line @typescript-eslint/no-this-alias
    let block: Blot | this = this;
    lines.reduce((lineIndex, line) => {
      // @ts-expect-error Fix me later
      block = block.split(lineIndex, true);
      block.insertAt(0, line);
      return line.length;
    }, index + text.length);
  }

  insertBefore(blot: Blot, ref?: Blot | null) {
    const { head } = this.children;
    super.insertBefore(blot, ref);
    if (head instanceof Break) {
      head.remove();
    }
    this.cache = {};
  }

  length() {
    if (this.cache.length == null) {
      this.cache.length = super.length() + NEWLINE_LENGTH;
    }
    return this.cache.length;
  }

  moveChildren(target: Parent, ref?: Blot | null) {
    super.moveChildren(target, ref);
    this.cache = {};
  }

  optimize(context: { [key: string]: any }) {
    super.optimize(context);
    this.cache = {};
  }

  path(index: number) {
    return super.path(index, true);
  }

  removeChild(child: Blot) {
    super.removeChild(child);
    this.cache = {};
  }

  split(index: number, force: boolean | undefined = false): Blot | null {
    if (force && (index === 0 || index >= this.length() - NEWLINE_LENGTH)) {
      const clone = this.clone();
      if (index === 0) {
        this.parent.insertBefore(clone, this);
        return this;
      }
      this.parent.insertBefore(clone, this.next);
      return clone;
    }
    const next = super.split(index, force);
    this.cache = {};
    return next;
  }
}
Block.blotName = 'block';
Block.tagName = 'P';
Block.defaultChild = Break;
Block.allowedChildren = [Break, Inline, EmbedBlot, TextBlot];

class BlockEmbed extends EmbedBlot {
  attributes: AttributorStore;
  domNode: HTMLElement;

  attach() {
    super.attach();
    this.attributes = new AttributorStore(this.domNode);
  }

  delta() {
    return new Delta().insert(this.value(), {
      ...this.formats(),
      ...this.attributes.values(),
    });
  }

  format(name: string, value: unknown) {
    const attribute = this.scroll.query(name, Scope.BLOCK_ATTRIBUTE);
    if (attribute != null) {
      // @ts-expect-error TODO: Scroll#query() should return Attributor when scope is attribute
      this.attributes.attribute(attribute, value);
    }
  }

  formatAt(index: number, length: number, name: string, value: unknown) {
    this.format(name, value);
  }

  insertAt(index: number, value: string, def?: unknown) {
    if (def != null) {
      super.insertAt(index, value, def);
      return;
    }
    const lines = value.split('\n');
    const text = lines.pop();
    const blocks = lines.map((line) => {
      const block = this.scroll.create(Block.blotName);
      block.insertAt(0, line);
      return block;
    });
    const ref = this.split(index);
    blocks.forEach((block) => {
      this.parent.insertBefore(block, ref);
    });
    if (text) {
      this.parent.insertBefore(this.scroll.create('text', text), ref);
    }
  }
}
BlockEmbed.scope = Scope.BLOCK_BLOT;
// It is important for cursor behavior BlockEmbeds use tags that are block level elements

function blockDelta(blot: BlockBlot, filter = true) {
  return blot
    .descendants(LeafBlot)
    .reduce((delta, leaf) => {
      if (leaf.length() === 0) {
        return delta;
      }
      return delta.insert(leaf.value(), bubbleFormats(leaf, {}, filter));
    }, new Delta())
    .insert('\n', bubbleFormats(blot));
}

function bubbleFormats(
  blot: Blot | null,
  formats: Record<string, unknown> = {},
  filter = true,
): Record<string, unknown> {
  if (blot == null) return formats;
  if ('formats' in blot && typeof blot.formats === 'function') {
    formats = {
      ...formats,
      ...blot.formats(),
    };
    if (filter) {
      // exclude syntax highlighting from deltas and getFormat()
      delete formats['code-token'];
    }
  }
  if (
    blot.parent == null ||
    blot.parent.statics.blotName === 'scroll' ||
    blot.parent.statics.scope !== blot.statics.scope
  ) {
    return formats;
  }
  return bubbleFormats(blot.parent, formats, filter);
}

export { blockDelta, bubbleFormats, BlockEmbed, Block as default };


================================================
FILE: packages/quill/src/blots/break.ts
================================================
import { EmbedBlot } from 'parchment';

class Break extends EmbedBlot {
  static value() {
    return undefined;
  }

  optimize() {
    if (this.prev || this.next) {
      this.remove();
    }
  }

  length() {
    return 0;
  }

  value() {
    return '';
  }
}
Break.blotName = 'break';
Break.tagName = 'BR';

export default Break;


================================================
FILE: packages/quill/src/blots/container.ts
================================================
import { ContainerBlot } from 'parchment';

class Container extends ContainerBlot {}

export default Container;


================================================
FILE: packages/quill/src/blots/cursor.ts
================================================
import { EmbedBlot, Scope } from 'parchment';
import type { Parent, ScrollBlot } from 'parchment';
import type Selection from '../core/selection.js';
import TextBlot from './text.js';
import type { EmbedContextRange } from './embed.js';

class Cursor extends EmbedBlot {
  static blotName = 'cursor';
  static className = 'ql-cursor';
  static tagName = 'span';
  static CONTENTS = '\uFEFF'; // Zero width no break space

  static value() {
    return undefined;
  }

  selection: Selection;
  textNode: Text;
  savedLength: number;

  constructor(scroll: ScrollBlot, domNode: HTMLElement, selection: Selection) {
    super(scroll, domNode);
    this.selection = selection;
    this.textNode = document.createTextNode(Cursor.CONTENTS);
    this.domNode.appendChild(this.textNode);
    this.savedLength = 0;
  }

  detach() {
    // super.detach() will also clear domNode.__blot
    if (this.parent != null) this.parent.removeChild(this);
  }

  format(name: string, value: unknown) {
    if (this.savedLength !== 0) {
      super.format(name, value);
      return;
    }
    // TODO: Fix this next time the file is edited.
    // eslint-disable-next-line @typescript-eslint/no-this-alias
    let target: Parent | this = this;
    let index = 0;
    while (target != null && target.statics.scope !== Scope.BLOCK_BLOT) {
      index += target.offset(target.parent);
      target = target.parent;
    }
    if (target != null) {
      this.savedLength = Cursor.CONTENTS.length;
      // @ts-expect-error TODO: allow empty context in Parchment
      target.optimize();
      target.formatAt(index, Cursor.CONTENTS.length, name, value);
      this.savedLength = 0;
    }
  }

  index(node: Node, offset: number) {
    if (node === this.textNode) return 0;
    return super.index(node, offset);
  }

  length() {
    return this.savedLength;
  }

  position(): [Text, number] {
    return [this.textNode, this.textNode.data.length];
  }

  remove() {
    super.remove();
    // @ts-expect-error Fix me later
    this.parent = null;
  }

  restore(): EmbedContextRange | null {
    if (this.selection.composing || this.parent == null) return null;
    const range = this.selection.getNativeRange();
    // Browser may push down styles/nodes inside the cursor blot.
    // https://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#push-down-values
    while (
      this.domNode.lastChild != null &&
      this.domNode.lastChild !== this.textNode
    ) {
      // @ts-expect-error Fix me later
      this.domNode.parentNode.insertBefore(
        this.domNode.lastChild,
        this.domNode,
      );
    }

    const prevTextBlot = this.prev instanceof TextBlot ? this.prev : null;
    const prevTextLength = prevTextBlot ? prevTextBlot.length() : 0;
    const nextTextBlot = this.next instanceof TextBlot ? this.next : null;
    // @ts-expect-error TODO: make TextBlot.text public
    const nextText = nextTextBlot ? nextTextBlot.text : '';
    const { textNode } = this;
    // take text from inside this blot and reset it
    const newText = textNode.data.split(Cursor.CONTENTS).join('');
    textNode.data = Cursor.CONTENTS;

    // proactively merge TextBlots around cursor so that optimization
    // doesn't lose the cursor.  the reason we are here in cursor.restore
    // could be that the user clicked in prevTextBlot or nextTextBlot, or
    // the user typed something.
    let mergedTextBlot;
    if (prevTextBlot) {
      mergedTextBlot = prevTextBlot;
      if (newText || nextTextBlot) {
        prevTextBlot.insertAt(prevTextBlot.length(), newText + nextText);
        if (nextTextBlot) {
          nextTextBlot.remove();
        }
      }
    } else if (nextTextBlot) {
      mergedTextBlot = nextTextBlot;
      nextTextBlot.insertAt(0, newText);
    } else {
      const newTextNode = document.createTextNode(newText);
      mergedTextBlot = this.scroll.create(newTextNode);
      this.parent.insertBefore(mergedTextBlot, this);
    }

    this.remove();
    if (range) {
      // calculate selection to restore
      const remapOffset = (node: Node, offset: number) => {
        if (prevTextBlot && node === prevTextBlot.domNode) {
          return offset;
        }
        if (node === textNode) {
          return prevTextLength + offset - 1;
        }
        if (nextTextBlot && node === nextTextBlot.domNode) {
          return prevTextLength + newText.length + offset;
        }
        return null;
      };

      const start = remapOffset(range.start.node, range.start.offset);
      const end = remapOffset(range.end.node, range.end.offset);
      if (start !== null && end !== null) {
        return {
          startNode: mergedTextBlot.domNode,
          startOffset: start,
          endNode: mergedTextBlot.domNode,
          endOffset: end,
        };
      }
    }
    return null;
  }

  update(mutations: MutationRecord[], context: Record<string, unknown>) {
    if (
      mutations.some((mutation) => {
        return (
          mutation.type === 'characterData' && mutation.target === this.textNode
        );
      })
    ) {
      const range = this.restore();
      if (range) context.range = range;
    }
  }

  // Avoid .ql-cursor being a descendant of `<a/>`.
  // The reason is Safari pushes down `<a/>` on text insertion.
  // That will cause DOM nodes not sync with the model.
  //
  // For example ({I} is the caret), given the markup:
  //    <a><span class="ql-cursor">\uFEFF{I}</span></a>
  // When typing a char "x", `<a/>` will be pushed down inside the `<span>` first:
  //    <span class="ql-cursor"><a>\uFEFF{I}</a></span>
  // And then "x" will be inserted after `<a/>`:
  //    <span class="ql-cursor"><a>\uFEFF</a>d{I}</span>
  optimize(context?: unknown) {
    // @ts-expect-error Fix me later
    super.optimize(context);

    let { parent } = this;
    while (parent) {
      if (parent.domNode.tagName === 'A') {
        this.savedLength = Cursor.CONTENTS.length;
        // @ts-expect-error TODO: make isolate generic
        parent.isolate(this.offset(parent), this.length()).unwrap();
        this.savedLength = 0;
        break;
      }
      parent = parent.parent;
    }
  }

  value() {
    return '';
  }
}

export default Cursor;


================================================
FILE: packages/quill/src/blots/embed.ts
================================================
import type { ScrollBlot } from 'parchment';
import { EmbedBlot } from 'parchment';
import TextBlot from './text.js';

const GUARD_TEXT = '\uFEFF';

export interface EmbedContextRange {
  startNode: Node | Text;
  startOffset: number;
  endNode?: Node | Text;
  endOffset?: number;
}

class Embed extends EmbedBlot {
  contentNode: HTMLSpanElement;
  leftGuard: Text;
  rightGuard: Text;

  constructor(scroll: ScrollBlot, node: Node) {
    super(scroll, node);
    this.contentNode = document.createElement('span');
    this.contentNode.setAttribute('contenteditable', 'false');
    Array.from(this.domNode.childNodes).forEach((childNode) => {
      this.contentNode.appendChild(childNode);
    });
    this.leftGuard = document.createTextNode(GUARD_TEXT);
    this.rightGuard = document.createTextNode(GUARD_TEXT);
    this.domNode.appendChild(this.leftGuard);
    this.domNode.appendChild(this.contentNode);
    this.domNode.appendChild(this.rightGuard);
  }

  index(node: Node, offset: number) {
    if (node === this.leftGuard) return 0;
    if (node === this.rightGuard) return 1;
    return super.index(node, offset);
  }

  restore(node: Text): EmbedContextRange | null {
    let range: EmbedContextRange | null = null;
    let textNode: Text;
    const text = node.data.split(GUARD_TEXT).join('');
    if (node === this.leftGuard) {
      if (this.prev instanceof TextBlot) {
        const prevLength = this.prev.length();
        this.prev.insertAt(prevLength, text);
        range = {
          startNode: this.prev.domNode,
          startOffset: prevLength + text.length,
        };
      } else {
        textNode = document.createTextNode(text);
        this.parent.insertBefore(this.scroll.create(textNode), this);
        range = {
          startNode: textNode,
          startOffset: text.length,
        };
      }
    } else if (node === this.rightGuard) {
      if (this.next instanceof TextBlot) {
        this.next.insertAt(0, text);
        range = {
          startNode: this.next.domNode,
          startOffset: text.length,
        };
      } else {
        textNode = document.createTextNode(text);
        this.parent.insertBefore(this.scroll.create(textNode), this.next);
        range = {
          startNode: textNode,
          startOffset: text.length,
        };
      }
    }
    node.data = GUARD_TEXT;
    return range;
  }

  update(mutations: MutationRecord[], context: Record<string, unknown>) {
    mutations.forEach((mutation) => {
      if (
        mutation.type === 'characterData' &&
        (mutation.target === this.leftGuard ||
          mutation.target === this.rightGuard)
      ) {
        const range = this.restore(mutation.target as Text);
        if (range) context.range = range;
      }
    });
  }
}

export default Embed;


================================================
FILE: packages/quill/src/blots/inline.ts
================================================
import { EmbedBlot, InlineBlot, Scope } from 'parchment';
import type { BlotConstructor } from 'parchment';
import Break from './break.js';
import Text from './text.js';

class Inline extends InlineBlot {
  static allowedChildren: BlotConstructor[] = [Inline, Break, EmbedBlot, Text];
  // Lower index means deeper in the DOM tree, since not found (-1) is for embeds
  static order = [
    'cursor',
    'inline', // Must be lower
    'link', // Chrome wants <a> to be lower
    'underline',
    'strike',
    'italic',
    'bold',
    'script',
    'code', // Must be higher
  ];

  static compare(self: string, other: string) {
    const selfIndex = Inline.order.indexOf(self);
    const otherIndex = Inline.order.indexOf(other);
    if (selfIndex >= 0 || otherIndex >= 0) {
      return selfIndex - otherIndex;
    }
    if (self === other) {
      return 0;
    }
    if (self < other) {
      return -1;
    }
    return 1;
  }

  formatAt(index: number, length: number, name: string, value: unknown) {
    if (
      Inline.compare(this.statics.blotName, name) < 0 &&
      this.scroll.query(name, Scope.BLOT)
    ) {
      const blot = this.isolate(index, length);
      if (value) {
        blot.wrap(name, value);
      }
    } else {
      super.formatAt(index, length, name, value);
    }
  }

  optimize(context: { [key: string]: any }) {
    super.optimize(context);
    if (
      this.parent instanceof Inline &&
      Inline.compare(this.statics.blotName, this.parent.statics.blotName) > 0
    ) {
      const parent = this.parent.isolate(this.offset(), this.length());
      // @ts-expect-error TODO: make isolate generic
      this.moveChildren(parent);
      parent.wrap(this);
    }
  }
}

export default Inline;


================================================
FILE: packages/quill/src/blots/scroll.ts
================================================
import { ContainerBlot, LeafBlot, Scope, ScrollBlot } from 'parchment';
import type { Blot, Parent, EmbedBlot, ParentBlot, Registry } from 'parchment';
import Delta, { AttributeMap, Op } from 'quill-delta';
import Emitter from '../core/emitter.js';
import type { EmitterSource } from '../core/emitter.js';
import Block, { BlockEmbed, bubbleFormats } from './block.js';
import Break from './break.js';
import Container from './container.js';

type RenderBlock =
  | {
      type: 'blockEmbed';
      attributes: AttributeMap;
      key: string;
      value: unknown;
    }
  | { type: 'block'; attributes: AttributeMap; delta: Delta };

function isLine(blot: unknown): blot is Block | BlockEmbed {
  return blot instanceof Block || blot instanceof BlockEmbed;
}

interface UpdatableEmbed {
  updateContent(change: unknown): void;
}

function isUpdatable(blot: Blot): blot is Blot & UpdatableEmbed {
  return typeof (blot as unknown as any).updateContent === 'function';
}

class Scroll extends ScrollBlot {
  static blotName = 'scroll';
  static className = 'ql-editor';
  static tagName = 'DIV';
  static defaultChild = Block;
  static allowedChildren = [Block, BlockEmbed, Container];

  emitter: Emitter;
  batch: false | MutationRecord[];

  constructor(
    registry: Registry,
    domNode: HTMLDivElement,
    { emitter }: { emitter: Emitter },
  ) {
    super(registry, domNode);
    this.emitter = emitter;
    this.batch = false;
    this.optimize();
    this.enable();
    this.domNode.addEventListener('dragstart', (e) => this.handleDragStart(e));
  }

  batchStart() {
    if (!Array.isArray(this.batch)) {
      this.batch = [];
    }
  }

  batchEnd() {
    if (!this.batch) return;
    const mutations = this.batch;
    this.batch = false;
    this.update(mutations);
  }

  emitMount(blot: Blot) {
    this.emitter.emit(Emitter.events.SCROLL_BLOT_MOUNT, blot);
  }

  emitUnmount(blot: Blot) {
    this.emitter.emit(Emitter.events.SCROLL_BLOT_UNMOUNT, blot);
  }

  emitEmbedUpdate(blot: Blot, change: unknown) {
    this.emitter.emit(Emitter.events.SCROLL_EMBED_UPDATE, blot, change);
  }

  deleteAt(index: number, length: number) {
    const [first, offset] = this.line(index);
    const [last] = this.line(index + length);
    super.deleteAt(index, length);
    if (last != null && first !== last && offset > 0) {
      if (first instanceof BlockEmbed || last instanceof BlockEmbed) {
        this.optimize();
        return;
      }
      const ref =
        last.children.head instanceof Break ? null : last.children.head;
      // @ts-expect-error
      first.moveChildren(last, ref);
      // @ts-expect-error
      first.remove();
    }
    this.optimize();
  }

  enable(enabled = true) {
    this.domNode.setAttribute('contenteditable', enabled ? 'true' : 'false');
  }

  formatAt(index: number, length: number, format: string, value: unknown) {
    super.formatAt(index, length, format, value);
    this.optimize();
  }

  insertAt(index: number, value: string, def?: unknown) {
    if (index >= this.length()) {
      if (def == null || this.scroll.query(value, Scope.BLOCK) == null) {
        const blot = this.scroll.create(this.statics.defaultChild.blotName);
        this.appendChild(blot);
        if (def == null && value.endsWith('\n')) {
          blot.insertAt(0, value.slice(0, -1), def);
        } else {
          blot.insertAt(0, value, def);
        }
      } else {
        const embed = this.scroll.create(value, def);
        this.appendChild(embed);
      }
    } else {
      super.insertAt(index, value, def);
    }
    this.optimize();
  }

  insertBefore(blot: Blot, ref?: Blot | null) {
    if (blot.statics.scope === Scope.INLINE_BLOT) {
      const wrapper = this.scroll.create(
        this.statics.defaultChild.blotName,
      ) as Parent;
      wrapper.appendChild(blot);
      super.insertBefore(wrapper, ref);
    } else {
      super.insertBefore(blot, ref);
    }
  }

  insertContents(index: number, delta: Delta) {
    const renderBlocks = this.deltaToRenderBlocks(
      delta.concat(new Delta().insert('\n')),
    );
    const last = renderBlocks.pop();
    if (last == null) return;

    this.batchStart();

    const first = renderBlocks.shift();
    if (first) {
      const shouldInsertNewlineChar =
        first.type === 'block' &&
        (first.delta.length() === 0 ||
          (!this.descendant(BlockEmbed, index)[0] && index < this.length()));
      const delta =
        first.type === 'block'
          ? first.delta
          : new Delta().insert({ [first.key]: first.value });
      insertInlineContents(this, index, delta);
      const newlineCharLength = first.type === 'block' ? 1 : 0;
      const lineEndIndex = index + delta.length() + newlineCharLength;
      if (shouldInsertNewlineChar) {
        this.insertAt(lineEndIndex - 1, '\n');
      }

      const formats = bubbleFormats(this.line(index)[0]);
      const attributes = AttributeMap.diff(formats, first.attributes) || {};
      Object.keys(attributes).forEach((name) => {
        this.formatAt(lineEndIndex - 1, 1, name, attributes[name]);
      });

      index = lineEndIndex;
    }

    let [refBlot, refBlotOffset] = this.children.find(index);
    if (renderBlocks.length) {
      if (refBlot) {
        refBlot = refBlot.split(refBlotOffset);
        refBlotOffset = 0;
      }

      renderBlocks.forEach((renderBlock) => {
        if (renderBlock.type === 'block') {
          const block = this.createBlock(
            renderBlock.attributes,
            refBlot || undefined,
          );
          insertInlineContents(block, 0, renderBlock.delta);
        } else {
          const blockEmbed = this.create(
            renderBlock.key,
            renderBlock.value,
          ) as EmbedBlot;
          this.insertBefore(blockEmbed, refBlot || undefined);
          Object.keys(renderBlock.attributes).forEach((name) => {
            blockEmbed.format(name, renderBlock.attributes[name]);
          });
        }
      });
    }

    if (last.type === 'block' && last.delta.length()) {
      const offset = refBlot
        ? refBlot.offset(refBlot.scroll) + refBlotOffset
        : this.length();
      insertInlineContents(this, offset, last.delta);
    }

    this.batchEnd();
    this.optimize();
  }

  isEnabled() {
    return this.domNode.getAttribute('contenteditable') === 'true';
  }

  leaf(index: number): [LeafBlot | null, number] {
    const last = this.path(index).pop();
    if (!last) {
      return [null, -1];
    }

    const [blot, offset] = last;
    return blot instanceof LeafBlot ? [blot, offset] : [null, -1];
  }

  line(index: number): [Block | BlockEmbed | null, number] {
    if (index === this.length()) {
      return this.line(index - 1);
    }
    // @ts-expect-error TODO: make descendant() generic
    return this.descendant(isLine, index);
  }

  lines(index = 0, length = Number.MAX_VALUE): (Block | BlockEmbed)[] {
    const getLines = (
      blot: ParentBlot,
      blotIndex: number,
      blotLength: number,
    ) => {
      let lines: (Block | BlockEmbed)[] = [];
      let lengthLeft = blotLength;
      blot.children.forEachAt(
        blotIndex,
        blotLength,
        (child, childIndex, childLength) => {
          if (isLine(child)) {
            lines.push(child);
          } else if (child instanceof ContainerBlot) {
            lines = lines.concat(getLines(child, childIndex, lengthLeft));
          }
          lengthLeft -= childLength;
        },
      );
      return lines;
    };
    return getLines(this, index, length);
  }

  optimize(context?: { [key: string]: any }): void;
  optimize(
    mutations?: MutationRecord[],
    context?: { [key: string]: any },
  ): void;
  optimize(mutations = [], context = {}) {
    if (this.batch) return;
    super.optimize(mutations, context);
    if (mutations.length > 0) {
      this.emitter.emit(Emitter.events.SCROLL_OPTIMIZE, mutations, context);
    }
  }

  path(index: number) {
    return super.path(index).slice(1); // Exclude self
  }

  remove() {
    // Never remove self
  }

  update(source?: EmitterSource): void;
  update(mutations?: MutationRecord[]): void;
  update(mutations?: MutationRecord[] | EmitterSource): void {
    if (this.batch) {
      if (Array.isArray(mutations)) {
        this.batch = this.batch.concat(mutations);
      }
      return;
    }
    let source: EmitterSource = Emitter.sources.USER;
    if (typeof mutations === 'string') {
      source = mutations;
    }
    if (!Array.isArray(mutations)) {
      mutations = this.observer.takeRecords();
    }
    mutations = mutations.filter(({ target }) => {
      const blot = this.find(target, true);
      return blot && !isUpdatable(blot);
    });
    if (mutations.length > 0) {
      this.emitter.emit(Emitter.events.SCROLL_BEFORE_UPDATE, source, mutations);
    }
    super.update(mutations.concat([])); // pass copy
    if (mutations.length > 0) {
      this.emitter.emit(Emitter.events.SCROLL_UPDATE, source, mutations);
    }
  }

  updateEmbedAt(index: number, key: string, change: unknown) {
    // Currently it only supports top-level embeds (BlockEmbed).
    // We can update `ParentBlot` in parchment to support inline embeds.
    const [blot] = this.descendant((b: Blot) => b instanceof BlockEmbed, index);
    if (blot && blot.statics.blotName === key && isUpdatable(blot)) {
      blot.updateContent(change);
    }
  }

  protected handleDragStart(event: DragEvent) {
    event.preventDefault();
  }

  private deltaToRenderBlocks(delta: Delta) {
    const renderBlocks: RenderBlock[] = [];

    let currentBlockDelta = new Delta();
    delta.forEach((op) => {
      const insert = op?.insert;
      if (!insert) return;
      if (typeof insert === 'string') {
        const splitted = insert.split('\n');
        splitted.slice(0, -1).forEach((text) => {
          currentBlockDelta.insert(text, op.attributes);
          renderBlocks.push({
            type: 'block',
            delta: currentBlockDelta,
            attributes: op.attributes ?? {},
          });
          currentBlockDelta = new Delta();
        });
        const last = splitted[splitted.length - 1];
        if (last) {
          currentBlockDelta.insert(last, op.attributes);
        }
      } else {
        const key = Object.keys(insert)[0];
        if (!key) return;
        if (this.query(key, Scope.INLINE)) {
          currentBlockDelta.push(op);
        } else {
          if (currentBlockDelta.length()) {
            renderBlocks.push({
              type: 'block',
              delta: currentBlockDelta,
              attributes: {},
            });
          }
          currentBlockDelta = new Delta();
          renderBlocks.push({
            type: 'blockEmbed',
            key,
            value: insert[key],
            attributes: op.attributes ?? {},
          });
        }
      }
    });

    if (currentBlockDelta.length()) {
      renderBlocks.push({
        type: 'block',
        delta: currentBlockDelta,
        attributes: {},
      });
    }

    return renderBlocks;
  }

  private createBlock(attributes: AttributeMap, refBlot?: Blot) {
    let blotName: string | undefined;
    const formats: AttributeMap = {};

    Object.entries(attributes).forEach(([key, value]) => {
      const isBlockBlot = this.query(key, Scope.BLOCK & Scope.BLOT) != null;
      if (isBlockBlot) {
        blotName = key;
      } else {
        formats[key] = value;
      }
    });

    const block = this.create(
      blotName || this.statics.defaultChild.blotName,
      blotName ? attributes[blotName] : undefined,
    ) as ParentBlot;

    this.insertBefore(block, refBlot || undefined);

    const length = block.length();
    Object.entries(formats).forEach(([key, value]) => {
      block.formatAt(0, length, key, value);
    });

    return block;
  }
}

function insertInlineContents(
  parent: ParentBlot,
  index: number,
  inlineContents: Delta,
) {
  inlineContents.reduce((index, op) => {
    const length = Op.length(op);
    let attributes = op.attributes || {};
    if (op.insert != null) {
      if (typeof op.insert === 'string') {
        const text = op.insert;
        parent.insertAt(index, text);
        const [leaf] = parent.descendant(LeafBlot, index);
        const formats = bubbleFormats(leaf);
        attributes = AttributeMap.diff(formats, attributes) || {};
      } else if (typeof op.insert === 'object') {
        const key = Object.keys(op.insert)[0]; // There should only be one key
        if (key == null) return index;
        parent.insertAt(index, key, op.insert[key]);
        const isInlineEmbed = parent.scroll.query(key, Scope.INLINE) != null;
        if (isInlineEmbed) {
          const [leaf] = parent.descendant(LeafBlot, index);
          const formats = bubbleFormats(leaf);
          attributes = AttributeMap.diff(formats, attributes) || {};
        }
      }
    }
    Object.keys(attributes).forEach((key) => {
      parent.formatAt(index, length, key, attributes[key]);
    });
    return index + length;
  }, index);
}

export default Scroll;


================================================
FILE: packages/quill/src/blots/text.ts
================================================
import { TextBlot } from 'parchment';

class Text extends TextBlot {}

// https://lodash.com/docs#escape
const entityMap: Record<string, string> = {
  '&': '&amp;',
  '<': '&lt;',
  '>': '&gt;',
  '"': '&quot;',
  "'": '&#39;',
};

function escapeText(text: string) {
  return text.replace(/[&<>"']/g, (s) => entityMap[s]);
}

export { Text as default, escapeText };


================================================
FILE: packages/quill/src/core/composition.ts
================================================
import Embed from '../blots/embed.js';
import type Scroll from '../blots/scroll.js';
import Emitter from './emitter.js';

class Composition {
  isComposing = false;

  constructor(
    private scroll: Scroll,
    private emitter: Emitter,
  ) {
    this.setupListeners();
  }

  private setupListeners() {
    this.scroll.domNode.addEventListener('compositionstart', (event) => {
      if (!this.isComposing) {
        this.handleCompositionStart(event);
      }
    });

    this.scroll.domNode.addEventListener('compositionend', (event) => {
      if (this.isComposing) {
        // Webkit makes DOM changes after compositionend, so we use microtask to
        // ensure the order.
        // https://bugs.webkit.org/show_bug.cgi?id=31902
        queueMicrotask(() => {
          this.handleCompositionEnd(event);
        });
      }
    });
  }

  private handleCompositionStart(event: CompositionEvent) {
    const blot =
      event.target instanceof Node
        ? this.scroll.find(event.target, true)
        : null;

    if (blot && !(blot instanceof Embed)) {
      this.emitter.emit(Emitter.events.COMPOSITION_BEFORE_START, event);
      this.scroll.batchStart();
      this.emitter.emit(Emitter.events.COMPOSITION_START, event);
      this.isComposing = true;
    }
  }

  private handleCompositionEnd(event: CompositionEvent) {
    this.emitter.emit(Emitter.events.COMPOSITION_BEFORE_END, event);
    this.scroll.batchEnd();
    this.emitter.emit(Emitter.events.COMPOSITION_END, event);
    this.isComposing = false;
  }
}

export default Composition;


================================================
FILE: packages/quill/src/core/editor.ts
================================================
import { cloneDeep, isEqual, merge } from 'lodash-es';
import { LeafBlot, EmbedBlot, Scope, ParentBlot } from 'parchment';
import type { Blot } from 'parchment';
import Delta, { AttributeMap, Op } from 'quill-delta';
import Block, { BlockEmbed, bubbleFormats } from '../blots/block.js';
import Break from '../blots/break.js';
import CursorBlot from '../blots/cursor.js';
import type Scroll from '../blots/scroll.js';
import TextBlot, { escapeText } from '../blots/text.js';
import { Range } from './selection.js';

const ASCII = /^[ -~]*$/;

type SelectionInfo = {
  newRange: Range;
  oldRange: Range;
};

class Editor {
  scroll: Scroll;
  delta: Delta;

  constructor(scroll: Scroll) {
    this.scroll = scroll;
    this.delta = this.getDelta();
  }

  applyDelta(delta: Delta): Delta {
    this.scroll.update();
    let scrollLength = this.scroll.length();
    this.scroll.batchStart();
    const normalizedDelta = normalizeDelta(delta);
    const deleteDelta = new Delta();
    const normalizedOps = splitOpLines(normalizedDelta.ops.slice());
    normalizedOps.reduce((index, op) => {
      const length = Op.length(op);
      let attributes = op.attributes || {};
      let isImplicitNewlinePrepended = false;
      let isImplicitNewlineAppended = false;
      if (op.insert != null) {
        deleteDelta.retain(length);
        if (typeof op.insert === 'string') {
          const text = op.insert;
          isImplicitNewlineAppended =
            !text.endsWith('\n') &&
            (scrollLength <= index ||
              !!this.scroll.descendant(BlockEmbed, index)[0]);
          this.scroll.insertAt(index, text);
          const [line, offset] = this.scroll.line(index);
          let formats = merge({}, bubbleFormats(line));
          if (line instanceof Block) {
            const [leaf] = line.descendant(LeafBlot, offset);
            if (leaf) {
              formats = merge(formats, bubbleFormats(leaf));
            }
          }
          attributes = AttributeMap.diff(formats, attributes) || {};
        } else if (typeof op.insert === 'object') {
          const key = Object.keys(op.insert)[0]; // There should only be one key
          if (key == null) return index;
          const isInlineEmbed = this.scroll.query(key, Scope.INLINE) != null;
          if (isInlineEmbed) {
            if (
              scrollLength <= index ||
              !!this.scroll.descendant(BlockEmbed, index)[0]
            ) {
              isImplicitNewlineAppended = true;
            }
          } else if (index > 0) {
            const [leaf, offset] = this.scroll.descendant(LeafBlot, index - 1);
            if (leaf instanceof TextBlot) {
              const text = leaf.value();
              if (text[offset] !== '\n') {
                isImplicitNewlinePrepended = true;
              }
            } else if (
              leaf instanceof EmbedBlot &&
              leaf.statics.scope === Scope.INLINE_BLOT
            ) {
              isImplicitNewlinePrepended = true;
            }
          }
          this.scroll.insertAt(index, key, op.insert[key]);

          if (isInlineEmbed) {
            const [leaf] = this.scroll.descendant(LeafBlot, index);
            if (leaf) {
              const formats = merge({}, bubbleFormats(leaf));
              attributes = AttributeMap.diff(formats, attributes) || {};
            }
          }
        }
        scrollLength += length;
      } else {
        deleteDelta.push(op);

        if (op.retain !== null && typeof op.retain === 'object') {
          const key = Object.keys(op.retain)[0];
          if (key == null) return index;
          this.scroll.updateEmbedAt(index, key, op.retain[key]);
        }
      }
      Object.keys(attributes).forEach((name) => {
        this.scroll.formatAt(index, length, name, attributes[name]);
      });
      const prependedLength = isImplicitNewlinePrepended ? 1 : 0;
      const addedLength = isImplicitNewlineAppended ? 1 : 0;
      scrollLength += prependedLength + addedLength;
      deleteDelta.retain(prependedLength);
      deleteDelta.delete(addedLength);
      return index + length + prependedLength + addedLength;
    }, 0);
    deleteDelta.reduce((index, op) => {
      if (typeof op.delete === 'number') {
        this.scroll.deleteAt(index, op.delete);
        return index;
      }
      return index + Op.length(op);
    }, 0);
    this.scroll.batchEnd();
    this.scroll.optimize();
    return this.update(normalizedDelta);
  }

  deleteText(index: number, length: number): Delta {
    this.scroll.deleteAt(index, length);
    return this.update(new Delta().retain(index).delete(length));
  }

  formatLine(
    index: number,
    length: number,
    formats: Record<string, unknown> = {},
  ): Delta {
    this.scroll.update();
    Object.keys(formats).forEach((format) => {
      this.scroll.lines(index, Math.max(length, 1)).forEach((line) => {
        line.format(format, formats[format]);
      });
    });
    this.scroll.optimize();
    const delta = new Delta().retain(index).retain(length, cloneDeep(formats));
    return this.update(delta);
  }

  formatText(
    index: number,
    length: number,
    formats: Record<string, unknown> = {},
  ): Delta {
    Object.keys(formats).forEach((format) => {
      this.scroll.formatAt(index, length, format, formats[format]);
    });
    const delta = new Delta().retain(index).retain(length, cloneDeep(formats));
    return this.update(delta);
  }

  getContents(index: number, length: number): Delta {
    return this.delta.slice(index, index + length);
  }

  getDelta(): Delta {
    return this.scroll.lines().reduce((delta, line) => {
      return delta.concat(line.delta());
    }, new Delta());
  }

  getFormat(index: number, length = 0): Record<string, unknown> {
    let lines: (Block | BlockEmbed)[] = [];
    let leaves: LeafBlot[] = [];
    if (length === 0) {
      this.scroll.path(index).forEach((path) => {
        const [blot] = path;
        if (blot instanceof Block) {
          lines.push(blot);
        } else if (blot instanceof LeafBlot) {
          leaves.push(blot);
        }
      });
    } else {
      lines = this.scroll.lines(index, length);
      leaves = this.scroll.descendants(LeafBlot, index, length);
    }
    const [lineFormats, leafFormats] = [lines, leaves].map((blots) => {
      const blot = blots.shift();
      if (blot == null) return {};
      let formats = bubbleFormats(blot);
      while (Object.keys(formats).length > 0) {
        const blot = blots.shift();
        if (blot == null) return formats;
        formats = combineFormats(bubbleFormats(blot), formats);
      }
      return formats;
    });
    return { ...lineFormats, ...leafFormats };
  }

  getHTML(index: number, length: number): string {
    const [line, lineOffset] = this.scroll.line(index);
    if (line) {
      const lineLength = line.length();
      const isWithinLine = line.length() >= lineOffset + length;
      if (isWithinLine && !(lineOffset === 0 && length === lineLength)) {
        return convertHTML(line, lineOffset, length, true);
      }
      return convertHTML(this.scroll, index, length, true);
    }
    return '';
  }

  getText(index: number, length: number): string {
    return this.getContents(index, length)
      .filter((op) => typeof op.insert === 'string')
      .map((op) => op.insert)
      .join('');
  }

  insertContents(index: number, contents: Delta): Delta {
    const normalizedDelta = normalizeDelta(contents);
    const change = new Delta().retain(index).concat(normalizedDelta);
    this.scroll.insertContents(index, normalizedDelta);
    return this.update(change);
  }

  insertEmbed(index: number, embed: string, value: unknown): Delta {
    this.scroll.insertAt(index, embed, value);
    return this.update(new Delta().retain(index).insert({ [embed]: value }));
  }

  insertText(
    index: number,
    text: string,
    formats: Record<string, unknown> = {},
  ): Delta {
    text = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
    this.scroll.insertAt(index, text);
    Object.keys(formats).forEach((format) => {
      this.scroll.formatAt(index, text.length, format, formats[format]);
    });
    return this.update(
      new Delta().retain(index).insert(text, cloneDeep(formats)),
    );
  }

  isBlank(): boolean {
    if (this.scroll.children.length === 0) return true;
    if (this.scroll.children.length > 1) return false;
    const blot = this.scroll.children.head;
    if (blot?.statics.blotName !== Block.blotName) return false;
    const block = blot as Block;
    if (block.children.length > 1) return false;
    return block.children.head instanceof Break;
  }

  removeFormat(index: number, length: number): Delta {
    const text = this.getText(index, length);
    const [line, offset] = this.scroll.line(index + length);
    let suffixLength = 0;
    let suffix = new Delta();
    if (line != null) {
      suffixLength = line.length() - offset;
      suffix = line
        .delta()
        .slice(offset, offset + suffixLength - 1)
        .insert('\n');
    }
    const contents = this.getContents(index, length + suffixLength);
    const diff = contents.diff(new Delta().insert(text).concat(suffix));
    const delta = new Delta().retain(index).concat(diff);
    return this.applyDelta(delta);
  }

  update(
    change: Delta | null,
    mutations: MutationRecord[] = [],
    selectionInfo: SelectionInfo | undefined = undefined,
  ): Delta {
    const oldDelta = this.delta;
    if (
      mutations.length === 1 &&
      mutations[0].type === 'characterData' &&
      // @ts-expect-error Fix me later
      mutations[0].target.data.match(ASCII) &&
      this.scroll.find(mutations[0].target)
    ) {
      // Optimization for character changes
      const textBlot = this.scroll.find(mutations[0].target) as Blot;
      const formats = bubbleFormats(textBlot);
      const index = textBlot.offset(this.scroll);
      // @ts-expect-error Fix me later
      const oldValue = mutations[0].oldValue.replace(CursorBlot.CONTENTS, '');
      const oldText = new Delta().insert(oldValue);
      // @ts-expect-error
      const newText = new Delta().insert(textBlot.value());
      const relativeSelectionInfo = selectionInfo && {
        oldRange: shiftRange(selectionInfo.oldRange, -index),
        newRange: shiftRange(selectionInfo.newRange, -index),
      };
      const diffDelta = new Delta()
        .retain(index)
        .concat(oldText.diff(newText, relativeSelectionInfo));
      change = diffDelta.reduce((delta, op) => {
        if (op.insert) {
          return delta.insert(op.insert, formats);
        }
        return delta.push(op);
      }, new Delta());
      this.delta = oldDelta.compose(change);
    } else {
      this.delta = this.getDelta();
      if (!change || !isEqual(oldDelta.compose(change), this.delta)) {
        change = oldDelta.diff(this.delta, selectionInfo);
      }
    }
    return change;
  }
}

interface ListItem {
  child: Blot;
  offset: number;
  length: number;
  indent: number;
  type: string;
}
function convertListHTML(
  items: ListItem[],
  lastIndent: number,
  types: string[],
): string {
  if (items.length === 0) {
    const [endTag] = getListType(types.pop());
    if (lastIndent <= 0) {
      return `</li></${endTag}>`;
    }
    return `</li></${endTag}>${convertListHTML([], lastIndent - 1, types)}`;
  }
  const [{ child, offset, length, indent, type }, ...rest] = items;
  const [tag, attribute] = getListType(type);
  if (indent > lastIndent) {
    types.push(type);
    if (indent === lastIndent + 1) {
      return `<${tag}><li${attribute}>${convertHTML(
        child,
        offset,
        length,
      )}${convertListHTML(rest, indent, types)}`;
    }
    return `<${tag}><li>${convertListHTML(items, lastIndent + 1, types)}`;
  }
  const previousType = types[types.length - 1];
  if (indent === lastIndent && type === previousType) {
    return `</li><li${attribute}>${convertHTML(
      child,
      offset,
      length,
    )}${convertListHTML(rest, indent, types)}`;
  }
  const [endTag] = getListType(types.pop());
  return `</li></${endTag}>${convertListHTML(items, lastIndent - 1, types)}`;
}

function convertHTML(
  blot: Blot,
  index: number,
  length: number,
  isRoot = false,
): string {
  if ('html' in blot && typeof blot.html === 'function') {
    return blot.html(index, length);
  }
  if (blot instanceof TextBlot) {
    const escapedText = escapeText(blot.value().slice(index, index + length));
    return escapedText.replaceAll(' ', '&nbsp;');
  }
  if (blot instanceof ParentBlot) {
    // TODO fix API
    if (blot.statics.blotName === 'list-container') {
      const items: any[] = [];
      blot.children.forEachAt(index, length, (child, offset, childLength) => {
        const formats =
          'formats' in child && typeof child.formats === 'function'
            ? child.formats()
            : {};
        items.push({
          child,
          offset,
          length: childLength,
          indent: formats.indent || 0,
          type: formats.list,
        });
      });
      return convertListHTML(items, -1, []);
    }
    const parts: string[] = [];
    blot.children.forEachAt(index, length, (child, offset, childLength) => {
      parts.push(convertHTML(child, offset, childLength));
    });
    if (isRoot || blot.statics.blotName === 'list') {
      return parts.join('');
    }
    const { outerHTML, innerHTML } = blot.domNode as Element;
    const [start, end] = outerHTML.split(`>${innerHTML}<`);
    // TODO cleanup
    if (start === '<table') {
      return `<table style="border: 1px solid #000;">${parts.join('')}<${end}`;
    }
    return `${start}>${parts.join('')}<${end}`;
  }
  return blot.domNode instanceof Element ? blot.domNode.outerHTML : '';
}

function combineFormats(
  formats: Record<string, unknown>,
  combined: Record<string, unknown>,
): Record<string, unknown> {
  return Object.keys(combined).reduce(
    (merged, name) => {
      if (formats[name] == null) return merged;
      const combinedValue = combined[name];
      if (combinedValue === formats[name]) {
        merged[name] = combinedValue;
      } else if (Array.isArray(combinedValue)) {
        if (combinedValue.indexOf(formats[name]) < 0) {
          merged[name] = combinedValue.concat([formats[name]]);
        } else {
          // If style already exists, don't add to an array, but don't lose other styles
          merged[name] = combinedValue;
        }
      } else {
        merged[name] = [combinedValue, formats[name]];
      }
      return merged;
    },
    {} as Record<string, unknown>,
  );
}

function getListType(type: string | undefined) {
  const tag = type === 'ordered' ? 'ol' : 'ul';
  switch (type) {
    case 'checked':
      return [tag, ' data-list="checked"'];
    case 'unchecked':
      return [tag, ' data-list="unchecked"'];
    default:
      return [tag, ''];
  }
}

function normalizeDelta(delta: Delta) {
  return delta.reduce((normalizedDelta, op) => {
    if (typeof op.insert === 'string') {
      const text = op.insert.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
      return normalizedDelta.insert(text, op.attributes);
    }
    return normalizedDelta.push(op);
  }, new Delta());
}

function shiftRange({ index, length }: Range, amount: number) {
  return new Range(index + amount, length);
}

function splitOpLines(ops: Op[]) {
  const split: Op[] = [];
  ops.forEach((op) => {
    if (typeof op.insert === 'string') {
      const lines = op.insert.split('\n');
      lines.forEach((line, index) => {
        if (index) split.push({ insert: '\n', attributes: op.attributes });
        if (line) split.push({ insert: line, attributes: op.attributes });
      });
    } else {
      split.push(op);
    }
  });

  return split;
}

export default Editor;


================================================
FILE: packages/quill/src/core/emitter.ts
================================================
import { EventEmitter } from 'eventemitter3';
import instances from './instances.js';
import logger from './logger.js';

const debug = logger('quill:events');
const EVENTS = ['selectionchange', 'mousedown', 'mouseup', 'click'];

EVENTS.forEach((eventName) => {
  document.addEventListener(eventName, (...args) => {
    Array.from(document.querySelectorAll('.ql-container')).forEach((node) => {
      const quill = instances.get(node);
      if (quill && quill.emitter) {
        quill.emitter.handleDOM(...args);
      }
    });
  });
});

class Emitter extends EventEmitter<string> {
  static events = {
    EDITOR_CHANGE: 'editor-change',
    SCROLL_BEFORE_UPDATE: 'scroll-before-update',
    SCROLL_BLOT_MOUNT: 'scroll-blot-mount',
    SCROLL_BLOT_UNMOUNT: 'scroll-blot-unmount',
    SCROLL_OPTIMIZE: 'scroll-optimize',
    SCROLL_UPDATE: 'scroll-update',
    SCROLL_EMBED_UPDATE: 'scroll-embed-update',
    SELECTION_CHANGE: 'selection-change',
    TEXT_CHANGE: 'text-change',
    COMPOSITION_BEFORE_START: 'composition-before-start',
    COMPOSITION_START: 'composition-start',
    COMPOSITION_BEFORE_END: 'composition-before-end',
    COMPOSITION_END: 'composition-end',
  } as const;

  static sources = {
    API: 'api',
    SILENT: 'silent',
    USER: 'user',
  } as const;

  protected domListeners: Record<string, { node: Node; handler: Function }[]>;

  constructor() {
    super();
    this.domListeners = {};
    this.on('error', debug.error);
  }

  emit(...args: unknown[]): boolean {
    debug.log.call(debug, ...args);
    // @ts-expect-error
    return super.emit(...args);
  }

  handleDOM(event: Event, ...args: unknown[]) {
    (this.domListeners[event.type] || []).forEach(({ node, handler }) => {
      if (event.target === node || node.contains(event.target as Node)) {
        handler(event, ...args);
      }
    });
  }

  listenDOM(eventName: string, node: Node, handler: EventListener) {
    if (!this.domListeners[eventName]) {
      this.domListeners[eventName] = [];
    }
    this.domListeners[eventName].push({ node, handler });
  }
}

export type EmitterSource =
  (typeof Emitter.sources)[keyof typeof Emitter.sources];

export default Emitter;


================================================
FILE: packages/quill/src/core/instances.ts
================================================
import type Quill from '../core.js';

export default new WeakMap<Node, Quill>();


================================================
FILE: packages/quill/src/core/logger.ts
================================================
const levels = ['error', 'warn', 'log', 'info'] as const;
export type DebugLevel = (typeof levels)[number];
let level: DebugLevel | false = 'warn';

function debug(method: DebugLevel, ...args: unknown[]) {
  if (level) {
    if (levels.indexOf(method) <= levels.indexOf(level)) {
      console[method](...args); // eslint-disable-line no-console
    }
  }
}

function namespace(
  ns: string,
): Record<DebugLevel, (...args: unknown[]) => void> {
  return levels.reduce(
    (logger, method) => {
      logger[method] = debug.bind(console, method, ns);
      return logger;
    },
    {} as Record<DebugLevel, (...args: unknown[]) => void>,
  );
}

namespace.level = (newLevel: DebugLevel | false) => {
  level = newLevel;
};
debug.level = namespace.level;

export default namespace;


================================================
FILE: packages/quill/src/core/module.ts
================================================
import type Quill from './quill.js';

abstract class Module<T extends {} = {}> {
  static DEFAULTS = {};

  constructor(
    public quill: Quill,
    protected options: Partial<T> = {},
  ) {}
}

export default Module;


================================================
FILE: packages/quill/src/core/quill.ts
================================================
import { merge } from 'lodash-es';
import * as Parchment from 'parchment';
import type { Op } from 'quill-delta';
import Delta from 'quill-delta';
import type { BlockEmbed } from '../blots/block.js';
import type Block from '../blots/block.js';
import type Scroll from '../blots/scroll.js';
import type Clipboard from '../modules/clipboard.js';
import type History from '../modules/history.js';
import type Keyboard from '../modules/keyboard.js';
import type Uploader from '../modules/uploader.js';
import Editor from './editor.js';
import Emitter from './emitter.js';
import type { EmitterSource } from './emitter.js';
import instances from './instances.js';
import logger from './logger.js';
import type { DebugLevel } from './logger.js';
import Module from './module.js';
import Selection, { Range } from './selection.js';
import type { Bounds } from './selection.js';
import Composition from './composition.js';
import Theme from './theme.js';
import type { ThemeConstructor } from './theme.js';
import scrollRectIntoView from './utils/scrollRectIntoView.js';
import type {
  Rect,
  ScrollRectIntoViewOptions,
} from './utils/scrollRectIntoView.js';
import createRegistryWithFormats from './utils/createRegistryWithFormats.js';

const debug = logger('quill');

const globalRegistry = new Parchment.Registry();
Parchment.ParentBlot.uiClass = 'ql-ui';

/**
 * Options for initializing a Quill instance
 */
export interface QuillOptions {
  theme?: string;
  debug?: DebugLevel | boolean;
  registry?: Parchment.Registry;
  /**
   * Whether to disable the editing
   * @default false
   */
  readOnly?: boolean;

  /**
   * Placeholder text to display when the editor is empty
   * @default ""
   */
  placeholder?: string;
  bounds?: HTMLElement | string | null;
  modules?: Record<string, unknown>;

  /**
   * A list of formats that are recognized and can exist within the editor contents.
   * `null` means all formats are allowed.
   * @default null
   */
  formats?: string[] | null;
}

/**
 * Similar to QuillOptions, but with all properties expanded to their default values,
 * and all selectors resolved to HTMLElements.
 */
export interface ExpandedQuillOptions
  extends Omit<QuillOptions, 'theme' | 'formats'> {
  theme: ThemeConstructor;
  registry: Parchment.Registry;
  container: HTMLElement;
  modules: Record<string, unknown>;
  bounds?: HTMLElement | null;
  readOnly: boolean;
}

class Quill {
  static DEFAULTS = {
    bounds: null,
    modules: {
      clipboard: true,
      keyboard: true,
      history: true,
      uploader: true,
    },
    placeholder: '',
    readOnly: false,
    registry: globalRegistry,
    theme: 'default',
  } satisfies Partial<QuillOptions>;
  static events = Emitter.events;
  static sources = Emitter.sources;
  static version = typeof QUILL_VERSION === 'undefined' ? 'dev' : QUILL_VERSION;

  static imports: Record<string, unknown> = {
    delta: Delta,
    parchment: Parchment,
    'core/module': Module,
    'core/theme': Theme,
  };

  static debug(limit: DebugLevel | boolean) {
    if (limit === true) {
      limit = 'log';
    }
    logger.level(limit);
  }

  static find(node: Node, bubble = false) {
    return instances.get(node) || globalRegistry.find(node, bubble);
  }

  static import(name: 'core/module'): typeof Module;
  static import(name: `themes/${string}`): typeof Theme;
  static import(name: 'parchment'): typeof Parchment;
  static import(name: 'delta'): typeof Delta;
  static import(name: string): unknown;
  static import(name: string) {
    if (this.imports[name] == null) {
      debug.error(`Cannot import ${name}. Are you sure it was registered?`);
    }
    return this.imports[name];
  }

  static register(
    targets: Record<
      string,
      | Parchment.RegistryDefinition
      | Record<string, unknown> // any objects
      | Theme
      | Module
      | Function // ES5 constructors
    >,
    overwrite?: boolean,
  ): void;
  static register(
    target: Parchment.RegistryDefinition,
    overwrite?: boolean,
  ): void;
  static register(path: string, target: any, overwrite?: boolean): void;
  static register(...args: any[]): void {
    if (typeof args[0] !== 'string') {
      const target = args[0];
      const overwrite = !!args[1];

      const name = 'attrName' in target ? target.attrName : target.blotName;
      if (typeof name === 'string') {
        // Shortcut for formats:
        // register(Blot | Attributor, overwrite)
        this.register(`formats/${name}`, target, overwrite);
      } else {
        Object.keys(target).forEach((key) => {
          this.register(key, target[key], overwrite);
        });
      }
    } else {
      const path = args[0];
      const target = args[1];
      const overwrite = !!args[2];

      if (this.imports[path] != null && !overwrite) {
        debug.warn(`Overwriting ${path} with`, target);
      }
      this.imports[path] = target;
      if (
        (path.startsWith('blots/') || path.startsWith('formats/')) &&
        target &&
        typeof target !== 'boolean' &&
        target.blotName !== 'abstract'
      ) {
        globalRegistry.register(target);
      }
      if (typeof target.register === 'function') {
        target.register(globalRegistry);
      }
    }
  }

  container: HTMLElement;
  root: HTMLDivElement;
  scroll: Scroll;
  emitter: Emitter;
  protected allowReadOnlyEdits: boolean;
  editor: Editor;
  composition: Composition;
  selection: Selection;

  theme: Theme;
  keyboard: Keyboard;
  clipboard: Clipboard;
  history: History;
  uploader: Uploader;

  options: ExpandedQuillOptions;

  constructor(container: HTMLElement | string, options: QuillOptions = {}) {
    this.options = expandConfig(container, options);
    this.container = this.options.container;
    if (this.container == null) {
      debug.error('Invalid Quill container', container);
      return;
    }
    if (this.options.debug) {
      Quill.debug(this.options.debug);
    }
    const html = this.container.innerHTML.trim();
    this.container.classList.add('ql-container');
    this.container.innerHTML = '';
    instances.set(this.container, this);
    this.root = this.addContainer('ql-editor');
    this.root.classList.add('ql-blank');
    this.emitter = new Emitter();
    const scrollBlotName = Parchment.ScrollBlot.blotName;
    const ScrollBlot = this.options.registry.query(scrollBlotName);
    if (!ScrollBlot || !('blotName' in ScrollBlot)) {
      throw new Error(
        `Cannot initialize Quill without "${scrollBlotName}" blot`,
      );
    }
    this.scroll = new ScrollBlot(this.options.registry, this.root, {
      emitter: this.emitter,
    }) as Scroll;
    this.editor = new Editor(this.scroll);
    this.selection = new Selection(this.scroll, this.emitter);
    this.composition = new Composition(this.scroll, this.emitter);
    this.theme = new this.options.theme(this, this.options); // eslint-disable-line new-cap
    this.keyboard = this.theme.addModule('keyboard');
    this.clipboard = this.theme.addModule('clipboard');
    this.history = this.theme.addModule('history');
    this.uploader = this.theme.addModule('uploader');
    this.theme.addModule('input');
    this.theme.addModule('uiNode');
    this.theme.init();
    this.emitter.on(Emitter.events.EDITOR_CHANGE, (type) => {
      if (type === Emitter.events.TEXT_CHANGE) {
        this.root.classList.toggle('ql-blank', this.editor.isBlank());
      }
    });
    this.emitter.on(Emitter.events.SCROLL_UPDATE, (source, mutations) => {
      const oldRange = this.selection.lastRange;
      const [newRange] = this.selection.getRange();
      const selectionInfo =
        oldRange && newRange ? { oldRange, newRange } : undefined;
      modify.call(
        this,
        () => this.editor.update(null, mutations, selectionInfo),
        source,
      );
    });
    this.emitter.on(Emitter.events.SCROLL_EMBED_UPDATE, (blot, delta) => {
      const oldRange = this.selection.lastRange;
      const [newRange] = this.selection.getRange();
      const selectionInfo =
        oldRange && newRange ? { oldRange, newRange } : undefined;
      modify.call(
        this,
        () => {
          const change = new Delta()
            .retain(blot.offset(this))
            .retain({ [blot.statics.blotName]: delta });
          return this.editor.update(change, [], selectionInfo);
        },
        Quill.sources.USER,
      );
    });
    if (html) {
      const contents = this.clipboard.convert({
        html: `${html}<p><br></p>`,
        text: '\n',
      });
      this.setContents(contents);
    }
    this.history.clear();
    if (this.options.placeholder) {
      this.root.setAttribute('data-placeholder', this.options.placeholder);
    }
    if (this.options.readOnly) {
      this.disable();
    }
    this.allowReadOnlyEdits = false;
  }

  addContainer(container: string, refNode?: Node | null): HTMLDivElement;
  addContainer(container: HTMLElement, refNode?: Node | null): HTMLElement;
  addContainer(
    container: string | HTMLElement,
    refNode: Node | null = null,
  ): HTMLDivElement | HTMLElement {
    if (typeof container === 'string') {
      const className = container;
      container = document.createElement('div');
      container.classList.add(className);
    }
    this.container.insertBefore(container, refNode);
    return container;
  }

  blur() {
    this.selection.setRange(null);
  }

  deleteText(range: Range, source?: EmitterSource): Delta;
  deleteText(index: number, length: number, source?: EmitterSource): Delta;
  deleteText(
    index: number | Range,
    length?: number | EmitterSource,
    source?: EmitterSource,
  ): Delta {
    // @ts-expect-error
    [index, length, , source] = overload(index, length, source);
    return modify.call(
      this,
      () => {
        return this.editor.deleteText(index, length);
      },
      source,
      index,
      -1 * length,
    );
  }

  disable() {
    this.enable(false);
  }

  editReadOnly<T>(modifier: () => T): T {
    this.allowReadOnlyEdits = true;
    const value = modifier();
    this.allowReadOnlyEdits = false;
    return value;
  }

  enable(enabled = true) {
    this.scroll.enable(enabled);
    this.container.classList.toggle('ql-disabled', !enabled);
  }

  focus(options: { preventScroll?: boolean } = {}) {
    this.selection.focus();
    if (!options.preventScroll) {
      this.scrollSelectionIntoView();
    }
  }

  format(
    name: string,
    value: unknown,
    source: EmitterSource = Emitter.sources.API,
  ): Delta {
    return modify.call(
      this,
      () => {
        const range = this.getSelection(true);
        let change = new Delta();
        if (range == null) return change;
        if (this.scroll.query(name, Parchment.Scope.BLOCK)) {
          change = this.editor.formatLine(range.index, range.length, {
            [name]: value,
          });
        } else if (range.length === 0) {
          this.selection.format(name, value);
          return change;
        } else {
          change = this.editor.formatText(range.index, range.length, {
            [name]: value,
          });
        }
        this.setSelection(range, Emitter.sources.SILENT);
        return change;
      },
      source,
    );
  }

  formatLine(
    index: number,
    length: number,
    formats: Record<string, unknown>,
    source?: EmitterSource,
  ): Delta;
  formatLine(
    index: number,
    length: number,
    name: string,
    value?: unknown,
    source?: EmitterSource,
  ): Delta;
  formatLine(
    index: number,
    length: number,
    name: string | Record<string, unknown>,
    value?: unknown | EmitterSource,
    source?: EmitterSource,
  ): Delta {
    let formats: Record<string, unknown>;
    // eslint-disable-next-line prefer-const
    [index, length, formats, source] = overload(
      index,
      length,
      // @ts-expect-error
      name,
      value,
      source,
    );
    return modify.call(
      this,
      () => {
        return this.editor.formatLine(index, length, formats);
      },
      source,
      index,
      0,
    );
  }

  formatText(
    range: Range,
    name: string,
    value: unknown,
    source?: EmitterSource,
  ): Delta;
  formatText(
    index: number,
    length: number,
    name: string,
    value: unknown,
    source?: EmitterSource,
  ): Delta;
  formatText(
    index: number,
    length: number,
    formats: Record<string, unknown>,
    source?: EmitterSource,
  ): Delta;
  formatText(
    index: number | Range,
    length: number | string,
    name: string | unknown,
    value?: unknown | EmitterSource,
    source?: EmitterSource,
  ): Delta {
    let formats: Record<string, unknown>;
    // eslint-disable-next-line prefer-const
    [index, length, formats, source] = overload(
      // @ts-expect-error
      index,
      length,
      name,
      value,
      source,
    );
    return modify.call(
      this,
      () => {
        return this.editor.formatText(index, length, formats);
      },
      source,
      index,
      0,
    );
  }

  getBounds(index: number | Range, length = 0): Bounds | null {
    let bounds: Bounds | null = null;
    if (typeof index === 'number') {
      bounds = this.selection.getBounds(index, length);
    } else {
      bounds = this.selection.getBounds(index.index, index.length);
    }
    if (!bounds) return null;
    const containerBounds = this.container.getBoundingClientRect();
    return {
      bottom: bounds.bottom - containerBounds.top,
      height: bounds.height,
      left: bounds.left - containerBounds.left,
      right: bounds.right - containerBounds.left,
      top: bounds.top - containerBounds.top,
      width: bounds.width,
    };
  }

  getContents(index = 0, length = this.getLength() - index) {
    [index, length] = overload(index, length);
    return this.editor.getContents(index, length);
  }

  getFormat(index?: number, length?: number): { [format: string]: unknown };
  getFormat(range?: Range): {
    [format: string]: unknown;
  };
  getFormat(
    index: Range | number = this.getSelection(true),
    length = 0,
  ): { [format: string]: unknown } {
    if (typeof index === 'number') {
      return this.editor.getFormat(index, length);
    }
    return this.editor.getFormat(index.index, index.length);
  }

  getIndex(blot: Parchment.Blot) {
    return blot.offset(this.scroll);
  }

  getLength() {
    return this.scroll.length();
  }

  getLeaf(index: number) {
    return this.scroll.leaf(index);
  }

  getLine(index: number) {
    return this.scroll.line(index);
  }

  getLines(range: Range): (Block | BlockEmbed)[];
  getLines(index?: number, length?: number): (Block | BlockEmbed)[];
  getLines(
    index: Range | number = 0,
    length = Number.MAX_VALUE,
  ): (Block | BlockEmbed)[] {
    if (typeof index !== 'number') {
      return this.scroll.lines(index.index, index.length);
    }
    return this.scroll.lines(index, length);
  }

  getModule(name: string) {
    return this.theme.modules[name];
  }

  getSelection(focus: true): Range;
  getSelection(focus?: boolean): Range | null;
  getSelection(focus = false): Range | null {
    if (focus) this.focus();
    this.update(); // Make sure we access getRange with editor in consistent state
    return this.selection.getRange()[0];
  }

  getSemanticHTML(range: Range): string;
  getSemanticHTML(index?: number, length?: number): string;
  getSemanticHTML(index: Range | number = 0, length?: number) {
    if (typeof index === 'number') {
      length = length ?? this.getLength() - index;
    }
    // @ts-expect-error
    [index, length] = overload(index, length);
    return this.editor.getHTML(index, length);
  }

  getText(range?: Range): string;
  getText(index?: number, length?: number): string;
  getText(index: Range | number = 0, length?: number): string {
    if (typeof index === 'number') {
      length = length ?? this.getLength() - index;
    }
    // @ts-expect-error
    [index, length] = overload(index, length);
    return this.editor.getText(index, length);
  }

  hasFocus() {
    return this.selection.hasFocus();
  }

  insertEmbed(
    index: number,
    embed: string,
    value: unknown,
    source: EmitterSource = Quill.sources.API,
  ): Delta {
    return modify.call(
      this,
      () => {
        return this.editor.insertEmbed(index, embed, value);
      },
      source,
      index,
    );
  }

  insertText(index: number, text: string, source?: EmitterSource): Delta;
  insertText(
    index: number,
    text: string,
    formats: Record<string, unknown>,
    source?: EmitterSource,
  ): Delta;
  insertText(
    index: number,
    text: string,
    name: string,
    value: unknown,
    source?: EmitterSource,
  ): Delta;
  insertText(
    index: number,
    text: string,
    name?: string | Record<string, unknown> | EmitterSource,
    value?: unknown,
    source?: EmitterSource,
  ): Delta {
    let formats: Record<string, unknown>;
    // eslint-disable-next-line prefer-const
    // @ts-expect-error
    [index, , formats, source] = overload(index, 0, name, value, source);
    return modify.call(
      this,
      () => {
        return this.editor.insertText(index, text, formats);
      },
      source,
      index,
      text.length,
    );
  }

  isEnabled() {
    return this.scroll.isEnabled();
  }

  off(...args: Parameters<(typeof Emitter)['prototype']['off']>) {
    return this.emitter.off(...args);
  }

  on(
    event: (typeof Emitter)['events']['TEXT_CHANGE'],
    handler: (delta: Delta, oldContent: Delta, source: EmitterSource) => void,
  ): Emitter;
  on(
    event: (typeof Emitter)['events']['SELECTION_CHANGE'],
    handler: (range: Range, oldRange: Range, source: EmitterSource) => void,
  ): Emitter;
  on(
    event: (typeof Emitter)['events']['EDITOR_CHANGE'],
    handler: (
      ...args:
        | [
            (typeof Emitter)['events']['TEXT_CHANGE'],
            Delta,
            Delta,
            EmitterSource,
          ]
        | [
            (typeof Emitter)['events']['SELECTION_CHANGE'],
            Range,
            Range,
            EmitterSource,
          ]
    ) => void,
  ): Emitter;
  on(event: string, ...args: unknown[]): Emitter;
  on(...args: Parameters<(typeof Emitter)['prototype']['on']>): Emitter {
    return this.emitter.on(...args);
  }

  once(...args: Parameters<(typeof Emitter)['prototype']['once']>) {
    return this.emitter.once(...args);
  }

  removeFormat(index: number, length: number, source?: EmitterSource): Delta {
    [index, length, , source] = overload(index, length, source);
    return modify.call(
      this,
      () => {
        return this.editor.removeFormat(index, length);
      },
      source,
      index,
    );
  }

  scrollRectIntoView(rect: Rect, options: ScrollRectIntoViewOptions = {}) {
    scrollRectIntoView(this.root, rect, options);
  }

  /**
   * @deprecated Use Quill#scrollSelectionIntoView() instead.
   */
  scrollIntoView() {
    console.warn(
      'Quill#scrollIntoView() has been deprecated and will be removed in the near future. Please use Quill#scrollSelectionIntoView() instead.',
    );
    this.scrollSelectionIntoView();
  }

  /**
   * Scroll the current selection into the visible area.
   * If the selection is already visible, no scrolling will occur.
   */
  scrollSelectionIntoView(options: ScrollRectIntoViewOptions = {}) {
    const range = this.selection.lastRange;
    const bounds = range && this.selection.getBounds(range.index, range.length);
    if (bounds) {
      this.scrollRectIntoView(bounds, options);
    }
  }

  setContents(
    delta: Delta | Op[],
    source: EmitterSource = Emitter.sources.API,
  ): Delta {
    return modify.call(
      this,
      () => {
        delta = new Delta(delta);
        const length = this.getLength();
        // Quill will set empty editor to \n
        const delete1 = this.editor.deleteText(0, length);
        const applied = this.editor.insertContents(0, delta);
        // Remove extra \n from empty editor initialization
        const delete2 = this.editor.deleteText(this.getLength() - 1, 1);
        return delete1.compose(applied).compose(delete2);
      },
      source,
    );
  }
  setSelection(range: Range | null, source?: EmitterSource): void;
  setSelection(index: number, source?: EmitterSource): void;
  setSelection(index: number, length?: number, source?: EmitterSource): void;
  setSelection(index: number, source?: EmitterSource): void;
  setSelection(
    index: Range | null | number,
    length?: EmitterSource | number,
    source?: EmitterSource,
  ): void {
    if (index == null) {
      // @ts-expect-error https://github.com/microsoft/TypeScript/issues/22609
      this.selection.setRange(null, length || Quill.sources.API);
    } else {
      // @ts-expect-error
      [index, length, , source] = overload(index, length, source);
      this.selection.setRange(new Range(Math.max(0, index), length), source);
      if (source !== Emitter.sources.SILENT) {
        this.scrollSelectionIntoView();
      }
    }
  }

  setText(text: string, source: EmitterSource = Emitter.sources.API) {
    const delta = new Delta().insert(text);
    return this.setContents(delta, source);
  }

  update(source: EmitterSource = Emitter.sources.USER) {
    const change = this.scroll.update(source); // Will update selection before selection.update() does if text changes
    this.selection.update(source);
    // TODO this is usually undefined
    return change;
  }

  updateContents(
    delta: Delta | Op[],
    source: EmitterSource = Emitter.sources.API,
  ): Delta {
    return modify.call(
      this,
      () => {
        delta = new Delta(delta);
        return this.editor.applyDelta(delta);
      },
      source,
      true,
    );
  }
}

function resolveSelector(selector: string | HTMLElement | null | undefined) {
  return typeof selector === 'string'
    ? document.querySelector<HTMLElement>(selector)
    : selector;
}

function expandModuleConfig(config: Record<string, unknown> | undefined) {
  return Object.entries(config ?? {}).reduce(
    (expanded, [key, value]) => ({
      ...expanded,
      [key]: value === true ? {} : value,
    }),
    {} as Record<string, unknown>,
  );
}

function omitUndefinedValuesFromOptions(obj: QuillOptions) {
  return Object.fromEntries(
    Object.entries(obj).filter((entry) => entry[1] !== undefined),
  );
}

function expandConfig(
  containerOrSelector: HTMLElement | string,
  options: QuillOptions,
): ExpandedQuillOptions {
  const container = resolveSelector(containerOrSelector);
  if (!container) {
    throw new Error('Invalid Quill container');
  }

  const shouldUseDefaultTheme =
    !options.theme || options.theme === Quill.DEFAULTS.theme;
  const theme = shouldUseDefaultTheme
    ? Theme
    : Quill.import(`themes/${options.theme}`);
  if (!theme) {
    throw new Error(`Invalid theme ${options.theme}. Did you register it?`);
  }

  const { modules: quillModuleDefaults, ...quillDefaults } = Quill.DEFAULTS;
  const { modules: themeModuleDefaults, ...themeDefaults } = theme.DEFAULTS;

  let userModuleOptions = expandModuleConfig(options.modules);
  // Special case toolbar shorthand
  if (
    userModuleOptions != null &&
    userModuleOptions.toolbar &&
    userModuleOptions.toolbar.constructor !== Object
  ) {
    userModuleOptions = {
      ...userModuleOptions,
      toolbar: { container: userModuleOptions.toolbar },
    };
  }

  const modules: ExpandedQuillOptions['modules'] = merge(
    {},
    expandModuleConfig(quillModuleDefaults),
    expandModuleConfig(themeModuleDefaults),
    userModuleOptions,
  );

  const config = {
    ...quillDefaults,
    ...omitUndefinedValuesFromOptions(themeDefaults),
    ...omitUndefinedValuesFromOptions(options),
  };

  let registry = options.registry;
  if (registry) {
    if (options.formats) {
      debug.warn('Ignoring "formats" option because "registry" is specified');
    }
  } else {
    registry = options.formats
      ? createRegistryWithFormats(options.formats, config.registry, debug)
      : config.registry;
  }

  return {
    ...config,
    registry,
    container,
    theme,
    modules: Object.entries(modules).reduce(
      (modulesWithDefaults, [name, value]) => {
        if (!value) return modulesWithDefaults;

        const moduleClass = Quill.import(`modules/${name}`);
        if (moduleClass == null) {
          debug.error(
            `Cannot load ${name} module. Are you sure you registered it?`,
          );
          return modulesWithDefaults;
        }
        return {
          ...modulesWithDefaults,
          // @ts-expect-error
          [name]: merge({}, moduleClass.DEFAULTS || {}, value),
        };
      },
      {},
    ),
    bounds: resolveSelector(config.bounds),
  };
}

// Handle selection preservation and TEXT_CHANGE emission
// common to modification APIs
function modify(
  modifier: () => Delta,
  source: EmitterSource,
  index: number | boolean,
  shift: number | null,
) {
  if (
    !this.isEnabled() &&
    source === Emitter.sources.USER &&
    !this.allowReadOnlyEdits
  ) {
    return new Delta();
  }
  let range = index == null ? null : this.getSelection();
  const oldDelta = this.editor.delta;
  const change = modifier();
  if (range != null) {
    if (index === true) {
      index = range.index; // eslint-disable-line prefer-destructuring
    }
    if (shift == null) {
      range = shiftRange(range, change, source);
    } else if (shift !== 0) {
      // @ts-expect-error index should always be number
      range = shiftRange(range, index, shift, source);
    }
    this.setSelection(range, Emitter.sources.SILENT);
  }
  if (change.length() > 0) {
    const args = [Emitter.events.TEXT_CHANGE, change, oldDelta, source];
    this.emitter.emit(Emitter.events.EDITOR_CHANGE, ...args);
    if (source !== Emitter.sources.SILENT) {
      this.emitter.emit(...args);
    }
  }
  return change;
}

type NormalizedIndexLength = [
  number,
  number,
  Record<string, unknown>,
  EmitterSource,
];
function overload(index: number, source?: EmitterSource): NormalizedIndexLength;
function overload(
  index: number,
  length: number,
  source?: EmitterSource,
): NormalizedIndexLength;
function overload(
  index: number,
  length: number,
  format: string,
  value: unknown,
  source?: EmitterSource,
): NormalizedIndexLength;
function overload(
  index: number,
  length: number,
  format: Record<string, unknown>,
  source?: EmitterSource,
): NormalizedIndexLength;
function overload(range: Range, source?: EmitterSource): NormalizedIndexLength;
function overload(
  range: Range,
  format: string,
  value: unknown,
  source?: EmitterSource,
): NormalizedIndexLength;
function overload(
  range: Range,
  format: Record<string, unknown>,
  source?: EmitterSource,
): NormalizedIndexLength;
function overload(
  index: Range | number,
  length?: number | string | Record<string, unknown> | EmitterSource,
  name?: string | unknown | Record<string, unknown> | EmitterSource,
  value?: unknown | EmitterSource,
  source?: EmitterSource,
): NormalizedIndexLength {
  let formats: Record<string, unknown> = {};
  // @ts-expect-error
  if (typeof index.index === 'number' && typeof index.length === 'number') {
    // Allow for throwaway end (used by insertText/insertEmbed)
    if (typeof length !== 'number') {
      // @ts-expect-error
      source = value;
      value = name;
      name = length;
      // @ts-expect-error
      length = index.length; // eslint-disable-line prefer-destructuring
      // @ts-expect-error
      index = index.index; // eslint-disable-line prefer-destructuring
    } else {
      // @ts-expect-error
      length = index.length; // eslint-disable-line prefer-destructuring
      // @ts-expect-error
      index = index.index; // eslint-disable-line prefer-destructuring
    }
  } else if (typeof length !== 'number') {
    // @ts-expect-error
    source = value;
    value = name;
    name = length;
    length = 0;
  }
  // Handle format being object, two format name/value strings or excluded
  if (typeof name === 'object') {
    // @ts-expect-error Fix me later
    formats = name;
    // @ts-expect-error
    source = value;
  } else if (typeof name === 'string') {
    if (value != null) {
      formats[name] = value;
    } else {
      // @ts-expect-error
      source = name;
    }
  }
  // Handle optional source
  source = source || Emitter.sources.API;
  // @ts-expect-error
  return [index, length, formats, source];
}

function shiftRange(range: Range, change: Delta, source?: EmitterSource): Range;
function shiftRange(
  range: Range,
  index: number,
  length?: number,
  source?: EmitterSource,
): Range;
function shiftRange(
  range: Range,
  index: number | Delta,
  lengthOrSource?: number | EmitterSource,
  source?: EmitterSource,
) {
  const length = typeof lengthOrSource === 'number' ? lengthOrSource : 0;
  if (range == null) return null;
  let start;
  let end;
  // @ts-expect-error -- TODO: add a better type guard around `index`
  if (index && typeof index.transformPosition === 'function') {
    [start, end] = [range.index, range.index + range.length].map((pos) =>
      // @ts-expect-error -- TODO: add a better type guard around `index`
      index.transformPosition(pos, source !== Emitter.sources.USER),
    );
  } else {
    [start, end] = [range.index, range.index + range.length].map((pos) => {
      // @ts-expect-error -- TODO: add a better type guard around `index`
      if (pos < index || (pos === index && source === Emitter.sources.USER))
        return pos;
      if (length >= 0) {
        return pos + length;
      }
      // @ts-expect-error -- TODO: add a better type guard around `index`
      return Math.max(index, pos + length);
    });
  }
  return new Range(start, end - start);
}

export type { Bounds, DebugLevel, EmitterSource };
export { Parchment, Range };

export { globalRegistry, expandConfig, overload, Quill as default };


================================================
FILE: packages/quill/src/core/selection.ts
================================================
import { LeafBlot, Scope } from 'parchment';
import { cloneDeep, isEqual } from 'lodash-es';
import Emitter from './emitter.js';
import type { EmitterSource } from './emitter.js';
import logger from './logger.js';
import type Cursor from '../blots/cursor.js';
import type Scroll from '../blots/scroll.js';

const debug = logger('quill:selection');

type NativeRange = AbstractRange;

interface NormalizedRange {
  start: {
    node: NativeRange['startContainer'];
    offset: NativeRange['startOffset'];
  };
  end: { node: NativeRange['endContainer']; offset: NativeRange['endOffset'] };
  native: NativeRange;
}

export interface Bounds {
  bottom: number;
  height: number;
  left: number;
  right: number;
  top: number;
  width: number;
}

export class Range {
  constructor(
    public index: number,
    public length = 0,
  ) {}
}

class Selection {
  scroll: Scroll;
  emitter: Emitter;
  composing: boolean;
  mouseDown: boolean;

  root: HTMLElement;
  cursor: Cursor;
  savedRange: Range;
  lastRange: Range | null;
  lastNative: NormalizedRange | null;

  constructor(scroll: Scroll, emitter: Emitter) {
    this.emitter = emitter;
    this.scroll = scroll;
    this.composing = false;
    this.mouseDown = false;
    this.root = this.scroll.domNode;
    // @ts-expect-error
    this.cursor = this.scroll.create('cursor', this);
    // savedRange is last non-null range
    this.savedRange = new Range(0, 0);
    this.lastRange = this.savedRange;
    this.lastNative = null;
    this.handleComposition();
    this.handleDragging();
    this.emitter.listenDOM('selectionchange', document, () => {
      if (!this.mouseDown && !this.composing) {
        setTimeout(this.update.bind(this, Emitter.sources.USER), 1);
      }
    });
    this.emitter.on(Emitter.events.SCROLL_BEFORE_UPDATE, () => {
      if (!this.hasFocus()) return;
      const native = this.getNativeRange();
      if (native == null) return;
      if (native.start.node === this.cursor.textNode) return; // cursor.restore() will handle
      this.emitter.once(
        Emitter.events.SCROLL_UPDATE,
        (source, mutations: MutationRecord[]) => {
          try {
            if (
              this.root.contains(native.start.node) &&
              this.root.contains(native.end.node)
            ) {
              this.setNativeRange(
                native.start.node,
                native.start.offset,
                native.end.node,
                native.end.offset,
              );
            }
            const triggeredByTyping = mutations.some(
              (mutation) =>
                mutation.type === 'characterData' ||
                mutation.type === 'childList' ||
                (mutation.type === 'attributes' &&
                  mutation.target === this.root),
            );
            this.update(triggeredByTyping ? Emitter.sources.SILENT : source);
          } catch (ignored) {
            // ignore
          }
        },
      );
    });
    this.emitter.on(Emitter.events.SCROLL_OPTIMIZE, (mutations, context) => {
      if (context.range) {
        const { startNode, startOffset, endNode, endOffset } = context.range;
        this.setNativeRange(startNode, startOffset, endNode, endOffset);
        this.update(Emitter.sources.SILENT);
      }
    });
    this.update(Emitter.sources.SILENT);
  }

  handleComposition() {
    this.emitter.on(Emitter.events.COMPOSITION_BEFORE_START, () => {
      this.composing = true;
    });
    this.emitter.on(Emitter.events.COMPOSITION_END, () => {
      this.composing = false;
      if (this.cursor.parent) {
        const range = this.cursor.restore();
        if (!range) return;
        setTimeout(() => {
          this.setNativeRange(
            range.startNode,
            range.startOffset,
            range.endNode,
            range.endOffset,
          );
        }, 1);
      }
    });
  }

  handleDragging() {
    this.emitter.listenDOM('mousedown', document.body, () => {
      this.mouseDown = true;
    });
    this.emitter.listenDOM('mouseup', document.body, () => {
      this.mouseDown = false;
      this.update(Emitter.sources.USER);
    });
  }

  focus() {
    if (this.hasFocus()) return;
    this.root.focus({ preventScroll: true });
    this.setRange(this.savedRange);
  }

  format(format: string, value: unknown) {
    this.scroll.update();
    const nativeRange = this.getNativeRange();
    if (
      nativeRange == null ||
      !nativeRange.native.collapsed ||
      this.scroll.query(format, Scope.BLOCK)
    )
      return;
    if (nativeRange.start.node !== this.cursor.textNode) {
      const blot = this.scroll.find(nativeRange.start.node, false);
      if (blot == null) return;
      // TODO Give blot ability to not split
      if (blot instanceof LeafBlot) {
        const after = blot.split(nativeRange.start.offset);
        blot.parent.insertBefore(this.cursor, after);
      } else {
        // @ts-expect-error TODO: nativeRange.start.node doesn't seem to match function signature
        blot.insertBefore(this.cursor, nativeRange.start.node); // Should never happen
      }
      this.cursor.attach();
    }
    this.cursor.format(format, value);
    this.scroll.optimize();
    this.setNativeRange(this.cursor.textNode, this.cursor.textNode.data.length);
    this.update();
  }

  getBounds(index: number, length = 0) {
    const scrollLength = this.scroll.length();
    index = Math.min(index, scrollLength - 1);
    length = Math.min(index + length, scrollLength - 1) - index;
    let node: Node;
    let [leaf, offset] = this.scroll.leaf(index);
    if (leaf == null) return null;
    if (length > 0 && offset === leaf.length()) {
      const [next] = this.scroll.leaf(index + 1);
      if (next) {
        const [line] = this.scroll.line(index);
        const [nextLine] = this.scroll.line(index + 1);
        if (line === nextLine) {
          leaf = next;
          offset = 0;
        }
      }
    }
    [node, offset] = leaf.position(offset, true);
    const range = document.createRange();
    if (length > 0) {
      range.setStart(node, offset);
      [leaf, offset] = this.scroll.leaf(index + length);
      if (leaf == null) return null;
      [node, offset] = leaf.position(offset, true);
      range.setEnd(node, offset);
      return range.getBoundingClientRect();
    }
    let side: 'left' | 'right' = 'left';
    let rect: DOMRect;
    if (node instanceof Text) {
      // Return null if the text node is empty because it is
      // not able to get a useful client rect:
      // https://github.com/w3c/csswg-drafts/issues/2514.
      // Empty text nodes are most likely caused by TextBlot#optimize()
      // not getting called when editor content changes.
      if (!node.data.length) {
        return null;
      }
      if (offset < node.data.length) {
        range.setStart(node, offset);
        range.setEnd(node, offset + 1);
      } else {
        range.setStart(node, offset - 1);
        range.setEnd(node, offset);
        side = 'right';
      }
      rect = range.getBoundingClientRect();
    } else {
      if (!(leaf.domNode instanceof Element)) return null;
      rect = leaf.domNode.getBoundingClientRect();
      if (offset > 0) side = 'right';
    }
    return {
      bottom: rect.top + rect.height,
      height: rect.height,
      left: rect[side],
      right: rect[side],
      top: rect.top,
      width: 0,
    };
  }

  getNativeRange(): NormalizedRange | null {
    const selection = document.getSelection();
    if (selection == null || selection.rangeCount <= 0) return null;
    const nativeRange = selection.getRangeAt(0);
    if (nativeRange == null) return null;
    const range = this.normalizeNative(nativeRange);
    debug.info('getNativeRange', range);
    return range;
  }

  getRange(): [Range, NormalizedRange] | [null, null] {
    const root = this.scroll.domNode;
    if ('isConnected' in root && !root.isConnected) {
      // document.getSelection() forces layout on Blink, so we trend to
      // not calling it.
      return [null, null];
    }
    const normalized = this.getNativeRange();
    if (normalized == null) return [null, null];
    const range = this.normalizedToRange(normalized);
    return [range, normalized];
  }

  hasFocus(): boolean {
    return (
      document.activeElement === this.root ||
      (document.activeElement != null &&
        contains(this.root, document.activeElement))
    );
  }

  normalizedToRange(range: NormalizedRange) {
    const positions: [Node, number][] = [
      [range.start.node, range.start.offset],
    ];
    if (!range.native.collapsed) {
      positions.push([range.end.node, range.end.offset]);
    }
    const indexes = positions.map((position) => {
      const [node, offset] = position;
      const blot = this.scroll.find(node, true);
      // @ts-expect-error Fix me later
      const index = blot.offset(this.scroll);
      if (offset === 0) {
        return index;
      }
      if (blot instanceof LeafBlot) {
        return index + blot.index(node, offset);
      }
      // @ts-expect-error Fix me later
      return index + blot.length();
    });
    const end = Math.min(Math.max(...indexes), this.scroll.length() - 1);
    const start = Math.min(end, ...indexes);
    return new Range(start, end - start);
  }

  normalizeNative(nativeRange: NativeRange) {
    if (
      !contains(this.root, nativeRange.startContainer) ||
      (!nativeRange.collapsed && !contains(this.root, nativeRange.endContainer))
    ) {
      return null;
    }
    const range = {
      start: {
        node: nativeRange.startContainer,
        offset: nativeRange.startOffset,
      },
      end: { node: nativeRange.endContainer, offset: nativeRange.endOffset },
      native: nativeRange,
    };
    [range.start, range.end].forEach((position) => {
      let { node, offset } = position;
      while (!(node instanceof Text) && node.childNodes.length > 0) {
        if (node.childNodes.length > offset) {
          node = node.childNodes[offset];
          offset = 0;
        } else if (node.childNodes.length === offset) {
          // @ts-expect-error Fix me later
          node = node.lastChild;
          if (node instanceof Text) {
            offset = node.data.length;
          } else if (node.childNodes.length > 0) {
            // Container case
            offset = node.childNodes.length;
          } else {
            // Embed case
            offset = node.childNodes.length + 1;
          }
        } else {
          break;
        }
      }
      position.node = node;
      position.offset = offset;
    });
    return range;
  }

  rangeToNative(range: Range): [Node | null, number, Node | null, number] {
    const scrollLength = this.scroll.length();

    const getPosition = (
      index: number,
      inclusive: boolean,
    ): [Node | null, number] => {
      index = Math.min(scrollLength - 1, index);
      const [leaf, leafOffset] = this.scroll.leaf(index);
      return leaf ? leaf.position(leafOffset, inclusive) : [null, -1];
    };
    return [
      ...getPosition(range.index, false),
      ...getPosition(range.index + range.length, true),
    ];
  }

  setNativeRange(
    startNode: Node | null,
    startOffset?: number,
    endNode = startNode,
    endOffset = startOffset,
    force = false,
  ) {
    debug.info('setNativeRange', startNode, startOffset, endNode, endOffset);
    if (
      startNode != null &&
      (this.root.parentNode == null ||
        startNode.parentNode == null ||
        // @ts-expect-error Fix me later
        endNode.parentNode == null)
    ) {
      return;
    }
    const selection = document.getSelection();
    if (selection == null) return;
    if (startNode != null) {
      if (!this.hasFocus()) this.root.focus({ preventScroll: true });
      const { native } = this.getNativeRange() || {};
      if (
        native == null ||
        force ||
        startNode !== native.startContainer ||
        startOffset !== native.startOffset ||
        endNode !== native.endContainer ||
        endOffset !== native.endOffset
      ) {
        if (startNode instanceof Element && startNode.tagName === 'BR') {
          // @ts-expect-error Fix me later
          startOffset = Array.from(startNode.parentNode.childNodes).indexOf(
            startNode,
          );
          startNode = startNode.parentNode;
        }
        if (endNode instanceof Element && endNode.tagName === 'BR') {
          // @ts-expect-error Fix me later
          endOffset = Array.from(endNode.parentNode.childNodes).indexOf(
            endNode,
          );
          endNode = endNode.parentNode;
        }
        const range = document.createRange();
        // @ts-expect-error Fix me later
        range.setStart(startNode, startOffset);
        // @ts-expect-error Fix me later
        range.setEnd(endNode, endOffset);
        selection.removeAllRanges();
        selection.addRange(range);
      }
    } else {
      selection.removeAllRanges();
      this.root.blur();
    }
  }

  setRange(range: Range | null, force: boolean, source?: EmitterSource): void;
  setRange(range: Range | null, source?: EmitterSource): void;
  setRange(
    range: Range | null,
    force: boolean | EmitterSource = false,
    source: EmitterSource = Emitter.sources.API,
  ): void {
    if (typeof force === 'string') {
      source = force;
      force = false;
    }
    debug.info('setRange', range);
    if (range != null) {
      const args = this.rangeToNative(range);
      this.setNativeRange(...args, force);
    } else {
      this.setNativeRange(null);
    }
    this.update(source);
  }

  update(source: EmitterSource = Emitter.sources.USER) {
    const oldRange = this.lastRange;
    const [lastRange, nativeRange] = this.getRange();
    this.lastRange = lastRange;
    this.lastNative = nativeRange;
    if (this.lastRange != null) {
      this.savedRange = this.lastRange;
    }
Download .txt
gitextract_t0yo5mmz/

├── .eslintignore
├── .github/
│   ├── CODE_OF_CONDUCT.md
│   ├── CONTRIBUTING.md
│   ├── DEVELOPMENT.md
│   ├── ISSUE_TEMPLATE.md
│   ├── release.yml
│   └── workflows/
│       ├── _test.yml
│       ├── changelog.yml
│       ├── label.yml
│       ├── main.yml
│       ├── pull-request.yml
│       └── release.yml
├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── package.json
├── packages/
│   ├── quill/
│   │   ├── .eslintrc.json
│   │   ├── .gitignore
│   │   ├── LICENSE
│   │   ├── README.md
│   │   ├── babel.config.cjs
│   │   ├── package.json
│   │   ├── playwright.config.ts
│   │   ├── scripts/
│   │   │   ├── babel-svg-inline-import.cjs
│   │   │   └── build
│   │   ├── src/
│   │   │   ├── assets/
│   │   │   │   ├── base.styl
│   │   │   │   ├── bubble/
│   │   │   │   │   ├── toolbar.styl
│   │   │   │   │   └── tooltip.styl
│   │   │   │   ├── bubble.styl
│   │   │   │   ├── core.styl
│   │   │   │   ├── snow/
│   │   │   │   │   ├── toolbar.styl
│   │   │   │   │   └── tooltip.styl
│   │   │   │   └── snow.styl
│   │   │   ├── blots/
│   │   │   │   ├── block.ts
│   │   │   │   ├── break.ts
│   │   │   │   ├── container.ts
│   │   │   │   ├── cursor.ts
│   │   │   │   ├── embed.ts
│   │   │   │   ├── inline.ts
│   │   │   │   ├── scroll.ts
│   │   │   │   └── text.ts
│   │   │   ├── core/
│   │   │   │   ├── composition.ts
│   │   │   │   ├── editor.ts
│   │   │   │   ├── emitter.ts
│   │   │   │   ├── instances.ts
│   │   │   │   ├── logger.ts
│   │   │   │   ├── module.ts
│   │   │   │   ├── quill.ts
│   │   │   │   ├── selection.ts
│   │   │   │   ├── theme.ts
│   │   │   │   └── utils/
│   │   │   │       ├── createRegistryWithFormats.ts
│   │   │   │       └── scrollRectIntoView.ts
│   │   │   ├── core.ts
│   │   │   ├── formats/
│   │   │   │   ├── align.ts
│   │   │   │   ├── background.ts
│   │   │   │   ├── blockquote.ts
│   │   │   │   ├── bold.ts
│   │   │   │   ├── code.ts
│   │   │   │   ├── color.ts
│   │   │   │   ├── direction.ts
│   │   │   │   ├── font.ts
│   │   │   │   ├── formula.ts
│   │   │   │   ├── header.ts
│   │   │   │   ├── image.ts
│   │   │   │   ├── indent.ts
│   │   │   │   ├── italic.ts
│   │   │   │   ├── link.ts
│   │   │   │   ├── list.ts
│   │   │   │   ├── script.ts
│   │   │   │   ├── size.ts
│   │   │   │   ├── strike.ts
│   │   │   │   ├── table.ts
│   │   │   │   ├── underline.ts
│   │   │   │   └── video.ts
│   │   │   ├── modules/
│   │   │   │   ├── clipboard.ts
│   │   │   │   ├── history.ts
│   │   │   │   ├── input.ts
│   │   │   │   ├── keyboard.ts
│   │   │   │   ├── normalizeExternalHTML/
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── normalizers/
│   │   │   │   │       ├── googleDocs.ts
│   │   │   │   │       └── msWord.ts
│   │   │   │   ├── syntax.ts
│   │   │   │   ├── table.ts
│   │   │   │   ├── tableEmbed.ts
│   │   │   │   ├── toolbar.ts
│   │   │   │   ├── uiNode.ts
│   │   │   │   └── uploader.ts
│   │   │   ├── quill.ts
│   │   │   ├── themes/
│   │   │   │   ├── base.ts
│   │   │   │   ├── bubble.ts
│   │   │   │   └── snow.ts
│   │   │   ├── types.d.ts
│   │   │   └── ui/
│   │   │       ├── color-picker.ts
│   │   │       ├── icon-picker.ts
│   │   │       ├── icons.ts
│   │   │       ├── picker.ts
│   │   │       └── tooltip.ts
│   │   ├── test/
│   │   │   ├── e2e/
│   │   │   │   ├── __dev_server__/
│   │   │   │   │   ├── index.html
│   │   │   │   │   └── webpack.config.cjs
│   │   │   │   ├── fixtures/
│   │   │   │   │   ├── Clipboard.ts
│   │   │   │   │   ├── Composition.ts
│   │   │   │   │   ├── index.ts
│   │   │   │   │   └── utils/
│   │   │   │   │       └── Locker.ts
│   │   │   │   ├── full.spec.ts
│   │   │   │   ├── history.spec.ts
│   │   │   │   ├── list.spec.ts
│   │   │   │   ├── pageobjects/
│   │   │   │   │   └── EditorPage.ts
│   │   │   │   ├── replaceSelection.spec.ts
│   │   │   │   └── utils/
│   │   │   │       └── index.ts
│   │   │   ├── fuzz/
│   │   │   │   ├── __helpers__/
│   │   │   │   │   └── utils.ts
│   │   │   │   ├── editor.spec.ts
│   │   │   │   ├── tableEmbed.spec.ts
│   │   │   │   └── vitest.config.ts
│   │   │   ├── types/
│   │   │   │   └── quill.test-d.ts
│   │   │   └── unit/
│   │   │       ├── __helpers__/
│   │   │       │   ├── cleanup.ts
│   │   │       │   ├── expect.ts
│   │   │       │   ├── factory.ts
│   │   │       │   ├── utils.ts
│   │   │       │   └── vitest.d.ts
│   │   │       ├── blots/
│   │   │       │   ├── block-embed.spec.ts
│   │   │       │   ├── block.spec.ts
│   │   │       │   ├── inline.spec.ts
│   │   │       │   └── scroll.spec.ts
│   │   │       ├── core/
│   │   │       │   ├── composition.spec.ts
│   │   │       │   ├── editor.spec.ts
│   │   │       │   ├── emitter.spec.ts
│   │   │       │   ├── quill.spec.ts
│   │   │       │   ├── selection.spec.ts
│   │   │       │   └── utils/
│   │   │       │       └── createRegistryWithFormats.spec.ts
│   │   │       ├── formats/
│   │   │       │   ├── align.spec.ts
│   │   │       │   ├── bold.spec.ts
│   │   │       │   ├── code.spec.ts
│   │   │       │   ├── color.spec.ts
│   │   │       │   ├── header.spec.ts
│   │   │       │   ├── indent.spec.ts
│   │   │       │   ├── link.spec.ts
│   │   │       │   ├── list.spec.ts
│   │   │       │   ├── script.spec.ts
│   │   │       │   └── table.spec.ts
│   │   │       ├── modules/
│   │   │       │   ├── clipboard.spec.ts
│   │   │       │   ├── history.spec.ts
│   │   │       │   ├── keyboard.spec.ts
│   │   │       │   ├── normalizeExternalHTML/
│   │   │       │   │   └── normalizers/
│   │   │       │   │       ├── googleDocs.spec.ts
│   │   │       │   │       └── msWord.spec.ts
│   │   │       │   ├── syntax.spec.ts
│   │   │       │   ├── table.spec.ts
│   │   │       │   ├── tableEmbed.spec.ts
│   │   │       │   ├── toolbar.spec.ts
│   │   │       │   └── uiNode.spec.ts
│   │   │       ├── theme/
│   │   │       │   └── base/
│   │   │       │       └── tooltip.spec.ts
│   │   │       ├── ui/
│   │   │       │   └── picker.spec.ts
│   │   │       └── vitest.config.ts
│   │   ├── tsconfig.json
│   │   ├── webpack.common.cjs
│   │   └── webpack.config.cjs
│   └── website/
│       ├── .eslintrc.json
│       ├── .gitignore
│       ├── README.md
│       ├── content/
│       │   ├── blog/
│       │   │   ├── a-new-delta.mdx
│       │   │   ├── an-official-cdn-for-quill.mdx
│       │   │   ├── announcing-quill-1-0.mdx
│       │   │   ├── are-we-there-yet-to-1-0.mdx
│       │   │   ├── quill-1-0-beta-release.mdx
│       │   │   ├── quill-1-0-release-candidate-released.mdx
│       │   │   ├── quill-v0-19-no-more-iframes.mdx
│       │   │   ├── the-road-to-1-0.mdx
│       │   │   ├── the-state-of-quill-and-2-0.mdx
│       │   │   └── upgrading-to-rich-text-deltas.mdx
│       │   └── docs/
│       │       ├── api.mdx
│       │       ├── configuration.mdx
│       │       ├── customization/
│       │       │   ├── registries.mdx
│       │       │   └── themes.mdx
│       │       ├── customization.mdx
│       │       ├── delta.mdx
│       │       ├── formats.mdx
│       │       ├── guides/
│       │       │   ├── building-a-custom-module.mdx
│       │       │   ├── cloning-medium-with-parchment.js
│       │       │   ├── cloning-medium-with-parchment.mdx
│       │       │   └── designing-the-delta-format.mdx
│       │       ├── installation.mdx
│       │       ├── modules/
│       │       │   ├── clipboard.mdx
│       │       │   ├── history.mdx
│       │       │   ├── keyboard.mdx
│       │       │   ├── syntax.mdx
│       │       │   └── toolbar.mdx
│       │       ├── modules.mdx
│       │       ├── quickstart.mdx
│       │       ├── upgrading-to-2-0.mdx
│       │       └── why-quill.mdx
│       ├── env.js
│       ├── next.config.mjs
│       ├── package.json
│       ├── public/
│       │   ├── CNAME
│       │   └── robots.txt
│       └── src/
│           ├── components/
│           │   ├── ActiveLink.jsx
│           │   ├── ClickOutsideHandler.jsx
│           │   ├── Editor.jsx
│           │   ├── GitHub.jsx
│           │   ├── GitHub.module.scss
│           │   ├── Header.jsx
│           │   ├── Header.module.scss
│           │   ├── Heading.jsx
│           │   ├── Hint.jsx
│           │   ├── Hint.module.scss
│           │   ├── Layout.jsx
│           │   ├── Link.jsx
│           │   ├── MDX.jsx
│           │   ├── NoSSR.jsx
│           │   ├── OpenSource.jsx
│           │   ├── OpenSource.module.scss
│           │   ├── PlaygroundLayout.jsx
│           │   ├── PlaygroundLayout.module.scss
│           │   ├── PostLayout.jsx
│           │   ├── PostLayout.module.scss
│           │   ├── SEO.jsx
│           │   ├── Sandpack.jsx
│           │   └── Sandpack.module.scss
│           ├── data/
│           │   ├── api.tsx
│           │   ├── docs.tsx
│           │   └── playground.tsx
│           ├── pages/
│           │   ├── 404.jsx
│           │   ├── _app.jsx
│           │   ├── _document.jsx
│           │   ├── base.css
│           │   ├── docs/
│           │   │   └── [...id].jsx
│           │   ├── docs.jsx
│           │   ├── index.jsx
│           │   ├── playground/
│           │   │   ├── [...id].jsx
│           │   │   └── [...id].module.scss
│           │   ├── playground.jsx
│           │   ├── standalone/
│           │   │   ├── bubble.mdx
│           │   │   ├── full.mdx
│           │   │   ├── snow.mdx
│           │   │   └── stress.mdx
│           │   ├── styles.scss
│           │   └── variables.scss
│           ├── playground/
│           │   ├── custom-formats/
│           │   │   ├── index.css
│           │   │   ├── index.html
│           │   │   ├── index.js
│           │   │   └── playground.json
│           │   ├── form/
│           │   │   ├── index.css
│           │   │   ├── index.html
│           │   │   ├── index.js
│           │   │   └── playground.json
│           │   ├── react/
│           │   │   ├── App.js
│           │   │   ├── Editor.js
│           │   │   ├── playground.json
│           │   │   └── styles.css
│           │   └── snow/
│           │       ├── index.html
│           │       ├── index.js
│           │       └── playground.json
│           └── utils/
│               ├── flattenData.js
│               ├── replaceCDN.js
│               └── slug.js
├── scripts/
│   ├── changelog.mjs
│   ├── release.js
│   └── utils/
│       └── configGit.mjs
└── tsconfig.json
Download .txt
SYMBOL INDEX (631 symbols across 84 files)

FILE: packages/quill/scripts/babel-svg-inline-import.cjs
  class BabelSVGInlineImport (line 6) | class BabelSVGInlineImport {
    method constructor (line 7) | constructor() {

FILE: packages/quill/src/blots/block.ts
  constant NEWLINE_LENGTH (line 14) | const NEWLINE_LENGTH = 1;
  class Block (line 16) | class Block extends BlockBlot {
    method delta (line 19) | delta(): Delta {
    method deleteAt (line 26) | deleteAt(index: number, length: number) {
    method formatAt (line 31) | formatAt(index: number, length: number, name: string, value: unknown) {
    method insertAt (line 48) | insertAt(index: number, value: string, def?: unknown) {
    method insertBefore (line 76) | insertBefore(blot: Blot, ref?: Blot | null) {
    method length (line 85) | length() {
    method moveChildren (line 92) | moveChildren(target: Parent, ref?: Blot | null) {
    method optimize (line 97) | optimize(context: { [key: string]: any }) {
    method path (line 102) | path(index: number) {
    method removeChild (line 106) | removeChild(child: Blot) {
    method split (line 111) | split(index: number, force: boolean | undefined = false): Blot | null {
  class BlockEmbed (line 131) | class BlockEmbed extends EmbedBlot {
    method attach (line 135) | attach() {
    method delta (line 140) | delta() {
    method format (line 147) | format(name: string, value: unknown) {
    method formatAt (line 155) | formatAt(index: number, length: number, name: string, value: unknown) {
    method insertAt (line 159) | insertAt(index: number, value: string, def?: unknown) {
  function blockDelta (line 183) | function blockDelta(blot: BlockBlot, filter = true) {
  function bubbleFormats (line 195) | function bubbleFormats(

FILE: packages/quill/src/blots/break.ts
  class Break (line 3) | class Break extends EmbedBlot {
    method value (line 4) | static value() {
    method optimize (line 8) | optimize() {
    method length (line 14) | length() {
    method value (line 18) | value() {

FILE: packages/quill/src/blots/container.ts
  class Container (line 3) | class Container extends ContainerBlot {}

FILE: packages/quill/src/blots/cursor.ts
  class Cursor (line 7) | class Cursor extends EmbedBlot {
    method value (line 13) | static value() {
    method constructor (line 21) | constructor(scroll: ScrollBlot, domNode: HTMLElement, selection: Selec...
    method detach (line 29) | detach() {
    method format (line 34) | format(name: string, value: unknown) {
    method index (line 56) | index(node: Node, offset: number) {
    method length (line 61) | length() {
    method position (line 65) | position(): [Text, number] {
    method remove (line 69) | remove() {
    method restore (line 75) | restore(): EmbedContextRange | null {
    method update (line 153) | update(mutations: MutationRecord[], context: Record<string, unknown>) {
    method optimize (line 176) | optimize(context?: unknown) {
    method value (line 193) | value() {

FILE: packages/quill/src/blots/embed.ts
  constant GUARD_TEXT (line 5) | const GUARD_TEXT = '\uFEFF';
  type EmbedContextRange (line 7) | interface EmbedContextRange {
  class Embed (line 14) | class Embed extends EmbedBlot {
    method constructor (line 19) | constructor(scroll: ScrollBlot, node: Node) {
    method index (line 33) | index(node: Node, offset: number) {
    method restore (line 39) | restore(node: Text): EmbedContextRange | null {
    method update (line 79) | update(mutations: MutationRecord[], context: Record<string, unknown>) {

FILE: packages/quill/src/blots/inline.ts
  class Inline (line 6) | class Inline extends InlineBlot {
    method compare (line 21) | static compare(self: string, other: string) {
    method formatAt (line 36) | formatAt(index: number, length: number, name: string, value: unknown) {
    method optimize (line 50) | optimize(context: { [key: string]: any }) {

FILE: packages/quill/src/blots/scroll.ts
  type RenderBlock (line 10) | type RenderBlock =
  function isLine (line 19) | function isLine(blot: unknown): blot is Block | BlockEmbed {
  type UpdatableEmbed (line 23) | interface UpdatableEmbed {
  function isUpdatable (line 27) | function isUpdatable(blot: Blot): blot is Blot & UpdatableEmbed {
  class Scroll (line 31) | class Scroll extends ScrollBlot {
    method constructor (line 41) | constructor(
    method batchStart (line 54) | batchStart() {
    method batchEnd (line 60) | batchEnd() {
    method emitMount (line 67) | emitMount(blot: Blot) {
    method emitUnmount (line 71) | emitUnmount(blot: Blot) {
    method emitEmbedUpdate (line 75) | emitEmbedUpdate(blot: Blot, change: unknown) {
    method deleteAt (line 79) | deleteAt(index: number, length: number) {
    method enable (line 98) | enable(enabled = true) {
    method formatAt (line 102) | formatAt(index: number, length: number, format: string, value: unknown) {
    method insertAt (line 107) | insertAt(index: number, value: string, def?: unknown) {
    method insertBefore (line 127) | insertBefore(blot: Blot, ref?: Blot | null) {
    method insertContents (line 139) | insertContents(index: number, delta: Delta) {
    method isEnabled (line 212) | isEnabled() {
    method leaf (line 216) | leaf(index: number): [LeafBlot | null, number] {
    method line (line 226) | line(index: number): [Block | BlockEmbed | null, number] {
    method lines (line 234) | lines(index = 0, length = Number.MAX_VALUE): (Block | BlockEmbed)[] {
    method optimize (line 264) | optimize(mutations = [], context = {}) {
    method path (line 272) | path(index: number) {
    method remove (line 276) | remove() {
    method update (line 282) | update(mutations?: MutationRecord[] | EmitterSource): void {
    method updateEmbedAt (line 309) | updateEmbedAt(index: number, key: string, change: unknown) {
    method handleDragStart (line 318) | protected handleDragStart(event: DragEvent) {
    method deltaToRenderBlocks (line 322) | private deltaToRenderBlocks(delta: Delta) {
    method createBlock (line 379) | private createBlock(attributes: AttributeMap, refBlot?: Blot) {
  function insertInlineContents (line 408) | function insertInlineContents(

FILE: packages/quill/src/blots/text.ts
  class Text (line 3) | class Text extends TextBlot {}
  function escapeText (line 14) | function escapeText(text: string) {

FILE: packages/quill/src/core/composition.ts
  class Composition (line 5) | class Composition {
    method constructor (line 8) | constructor(
    method setupListeners (line 15) | private setupListeners() {
    method handleCompositionStart (line 34) | private handleCompositionStart(event: CompositionEvent) {
    method handleCompositionEnd (line 48) | private handleCompositionEnd(event: CompositionEvent) {

FILE: packages/quill/src/core/editor.ts
  constant ASCII (line 12) | const ASCII = /^[ -~]*$/;
  type SelectionInfo (line 14) | type SelectionInfo = {
  class Editor (line 19) | class Editor {
    method constructor (line 23) | constructor(scroll: Scroll) {
    method applyDelta (line 28) | applyDelta(delta: Delta): Delta {
    method deleteText (line 125) | deleteText(index: number, length: number): Delta {
    method formatLine (line 130) | formatLine(
    method formatText (line 146) | formatText(
    method getContents (line 158) | getContents(index: number, length: number): Delta {
    method getDelta (line 162) | getDelta(): Delta {
    method getFormat (line 168) | getFormat(index: number, length = 0): Record<string, unknown> {
    method getHTML (line 198) | getHTML(index: number, length: number): string {
    method getText (line 211) | getText(index: number, length: number): string {
    method insertContents (line 218) | insertContents(index: number, contents: Delta): Delta {
    method insertEmbed (line 225) | insertEmbed(index: number, embed: string, value: unknown): Delta {
    method insertText (line 230) | insertText(
    method isBlank (line 245) | isBlank(): boolean {
    method removeFormat (line 255) | removeFormat(index: number, length: number): Delta {
    method update (line 273) | update(
  type ListItem (line 319) | interface ListItem {
  function convertListHTML (line 326) | function convertListHTML(
  function convertHTML (line 363) | function convertHTML(
  function combineFormats (line 413) | function combineFormats(
  function getListType (line 439) | function getListType(type: string | undefined) {
  function normalizeDelta (line 451) | function normalizeDelta(delta: Delta) {
  function shiftRange (line 461) | function shiftRange({ index, length }: Range, amount: number) {
  function splitOpLines (line 465) | function splitOpLines(ops: Op[]) {

FILE: packages/quill/src/core/emitter.ts
  constant EVENTS (line 6) | const EVENTS = ['selectionchange', 'mousedown', 'mouseup', 'click'];
  class Emitter (line 19) | class Emitter extends EventEmitter<string> {
    method constructor (line 44) | constructor() {
    method emit (line 50) | emit(...args: unknown[]): boolean {
    method handleDOM (line 56) | handleDOM(event: Event, ...args: unknown[]) {
    method listenDOM (line 64) | listenDOM(eventName: string, node: Node, handler: EventListener) {
  type EmitterSource (line 72) | type EmitterSource =

FILE: packages/quill/src/core/logger.ts
  type DebugLevel (line 2) | type DebugLevel = (typeof levels)[number];
  function debug (line 5) | function debug(method: DebugLevel, ...args: unknown[]) {
  function namespace (line 13) | function namespace(

FILE: packages/quill/src/core/module.ts
  method constructor (line 6) | constructor(

FILE: packages/quill/src/core/quill.ts
  type QuillOptions (line 39) | interface QuillOptions {
  type ExpandedQuillOptions (line 69) | interface ExpandedQuillOptions
  class Quill (line 79) | class Quill {
    method debug (line 104) | static debug(limit: DebugLevel | boolean) {
    method find (line 111) | static find(node: Node, bubble = false) {
    method import (line 120) | static import(name: string) {
    method register (line 143) | static register(...args: any[]): void {
    method constructor (line 198) | constructor(container: HTMLElement | string, options: QuillOptions = {...
    method addContainer (line 287) | addContainer(
    method blur (line 300) | blur() {
    method deleteText (line 306) | deleteText(
    method disable (line 324) | disable() {
    method editReadOnly (line 328) | editReadOnly<T>(modifier: () => T): T {
    method enable (line 335) | enable(enabled = true) {
    method focus (line 340) | focus(options: { preventScroll?: boolean } = {}) {
    method format (line 347) | format(
    method formatLine (line 390) | formatLine(
    method formatText (line 437) | formatText(
    method getBounds (line 465) | getBounds(index: number | Range, length = 0): Bounds | null {
    method getContents (line 484) | getContents(index = 0, length = this.getLength() - index) {
    method getFormat (line 493) | getFormat(
    method getIndex (line 503) | getIndex(blot: Parchment.Blot) {
    method getLength (line 507) | getLength() {
    method getLeaf (line 511) | getLeaf(index: number) {
    method getLine (line 515) | getLine(index: number) {
    method getLines (line 521) | getLines(
    method getModule (line 531) | getModule(name: string) {
    method getSelection (line 537) | getSelection(focus = false): Range | null {
    method getSemanticHTML (line 545) | getSemanticHTML(index: Range | number = 0, length?: number) {
    method getText (line 556) | getText(index: Range | number = 0, length?: number): string {
    method hasFocus (line 565) | hasFocus() {
    method insertEmbed (line 569) | insertEmbed(
    method insertText (line 599) | insertText(
    method isEnabled (line 621) | isEnabled() {
    method off (line 625) | off(...args: Parameters<(typeof Emitter)['prototype']['off']>) {
    method on (line 656) | on(...args: Parameters<(typeof Emitter)['prototype']['on']>): Emitter {
    method once (line 660) | once(...args: Parameters<(typeof Emitter)['prototype']['once']>) {
    method removeFormat (line 664) | removeFormat(index: number, length: number, source?: EmitterSource): D...
    method scrollRectIntoView (line 676) | scrollRectIntoView(rect: Rect, options: ScrollRectIntoViewOptions = {}) {
    method scrollIntoView (line 683) | scrollIntoView() {
    method scrollSelectionIntoView (line 694) | scrollSelectionIntoView(options: ScrollRectIntoViewOptions = {}) {
    method setContents (line 702) | setContents(
    method setSelection (line 725) | setSelection(
    method setText (line 743) | setText(text: string, source: EmitterSource = Emitter.sources.API) {
    method update (line 748) | update(source: EmitterSource = Emitter.sources.USER) {
    method updateContents (line 755) | updateContents(
  function resolveSelector (line 771) | function resolveSelector(selector: string | HTMLElement | null | undefin...
  function expandModuleConfig (line 777) | function expandModuleConfig(config: Record<string, unknown> | undefined) {
  function omitUndefinedValuesFromOptions (line 787) | function omitUndefinedValuesFromOptions(obj: QuillOptions) {
  function expandConfig (line 793) | function expandConfig(
  function modify (line 881) | function modify(
  type NormalizedIndexLength (line 919) | type NormalizedIndexLength = [
  function overload (line 956) | function overload(
  function shiftRange (line 1016) | function shiftRange(

FILE: packages/quill/src/core/selection.ts
  type NativeRange (line 11) | type NativeRange = AbstractRange;
  type NormalizedRange (line 13) | interface NormalizedRange {
  type Bounds (line 22) | interface Bounds {
  class Range (line 31) | class Range {
    method constructor (line 32) | constructor(
  class Selection (line 38) | class Selection {
    method constructor (line 50) | constructor(scroll: Scroll, emitter: Emitter) {
    method handleComposition (line 113) | handleComposition() {
    method handleDragging (line 134) | handleDragging() {
    method focus (line 144) | focus() {
    method format (line 150) | format(format: string, value: unknown) {
    method getBounds (line 178) | getBounds(index: number, length = 0) {
    method getNativeRange (line 241) | getNativeRange(): NormalizedRange | null {
    method getRange (line 251) | getRange(): [Range, NormalizedRange] | [null, null] {
    method hasFocus (line 264) | hasFocus(): boolean {
    method normalizedToRange (line 272) | normalizedToRange(range: NormalizedRange) {
    method normalizeNative (line 298) | normalizeNative(nativeRange: NativeRange) {
    method rangeToNative (line 341) | rangeToNative(range: Range): [Node | null, number, Node | null, number] {
    method setNativeRange (line 358) | setNativeRange(
    method setRange (line 418) | setRange(
    method update (line 437) | update(source: EmitterSource = Emitter.sources.USER) {
  function contains (line 476) | function contains(parent: Node, descendant: Node) {

FILE: packages/quill/src/core/theme.ts
  type ThemeOptions (line 8) | interface ThemeOptions {
  class Theme (line 14) | class Theme {
    method constructor (line 25) | constructor(
    method init (line 30) | init() {
    method addModule (line 43) | addModule(name: string) {
  type ThemeConstructor (line 54) | interface ThemeConstructor {

FILE: packages/quill/src/core/utils/createRegistryWithFormats.ts
  constant MAX_REGISTER_ITERATIONS (line 3) | const MAX_REGISTER_ITERATIONS = 100;
  constant CORE_FORMATS (line 4) | const CORE_FORMATS = ['block', 'break', 'cursor', 'inline', 'scroll', 't...

FILE: packages/quill/src/core/utils/scrollRectIntoView.ts
  type Rect (line 1) | type Rect = {
  type ScrollOffsetRecord (line 60) | interface ScrollOffsetRecord {
  type ScrollRectIntoViewOptions (line 66) | interface ScrollRectIntoViewOptions {

FILE: packages/quill/src/formats/blockquote.ts
  class Blockquote (line 3) | class Blockquote extends Block {

FILE: packages/quill/src/formats/bold.ts
  class Bold (line 3) | class Bold extends Inline {
    method create (line 7) | static create() {
    method formats (line 11) | static formats() {
    method optimize (line 15) | optimize(context: { [key: string]: any }) {

FILE: packages/quill/src/formats/code.ts
  class CodeBlockContainer (line 9) | class CodeBlockContainer extends Container {
    method create (line 10) | static create(value: string) {
    method code (line 16) | code(index: number, length: number) {
    method html (line 26) | html(index: number, length: number) {
  class CodeBlock (line 33) | class CodeBlock extends Block {
    method register (line 36) | static register() {
  class Code (line 41) | class Code extends Inline {}

FILE: packages/quill/src/formats/color.ts
  class ColorAttributor (line 3) | class ColorAttributor extends StyleAttributor {
    method value (line 4) | value(domNode: HTMLElement) {

FILE: packages/quill/src/formats/font.ts
  class FontStyleAttributor (line 10) | class FontStyleAttributor extends StyleAttributor {
    method value (line 11) | value(node: HTMLElement) {

FILE: packages/quill/src/formats/formula.ts
  class Formula (line 3) | class Formula extends Embed {
    method create (line 8) | static create(value: string) {
    method value (line 25) | static value(domNode: Element) {
    method html (line 29) | html() {

FILE: packages/quill/src/formats/header.ts
  class Header (line 3) | class Header extends Block {
    method formats (line 7) | static formats(domNode: Element) {

FILE: packages/quill/src/formats/image.ts
  constant ATTRIBUTES (line 4) | const ATTRIBUTES = ['alt', 'height', 'width'];
  class Image (line 6) | class Image extends EmbedBlot {
    method create (line 10) | static create(value: string) {
    method formats (line 18) | static formats(domNode: Element) {
    method match (line 30) | static match(url: string) {
    method sanitize (line 34) | static sanitize(url: string) {
    method value (line 38) | static value(domNode: Element) {
    method format (line 44) | format(name: string, value: string) {

FILE: packages/quill/src/formats/indent.ts
  class IndentAttributor (line 3) | class IndentAttributor extends ClassAttributor {
    method add (line 4) | add(node: HTMLElement, value: string | number) {
    method canAdd (line 19) | canAdd(node: HTMLElement, value: string) {
    method value (line 23) | value(node: HTMLElement) {

FILE: packages/quill/src/formats/italic.ts
  class Italic (line 3) | class Italic extends Bold {

FILE: packages/quill/src/formats/link.ts
  class Link (line 3) | class Link extends Inline {
    method create (line 9) | static create(value: string) {
    method formats (line 17) | static formats(domNode: HTMLElement) {
    method sanitize (line 21) | static sanitize(url: string) {
    method format (line 25) | format(name: string, value: unknown) {
  function sanitize (line 35) | function sanitize(url: string, protocols: string[]) {

FILE: packages/quill/src/formats/list.ts
  class ListContainer (line 6) | class ListContainer extends Container {}
  class ListItem (line 10) | class ListItem extends Block {
    method create (line 11) | static create(value: string) {
    method formats (line 17) | static formats(domNode: HTMLElement) {
    method register (line 21) | static register() {
    method constructor (line 25) | constructor(scroll: Scroll, domNode: HTMLElement) {
    method format (line 44) | format(name: string, value: string) {

FILE: packages/quill/src/formats/script.ts
  class Script (line 3) | class Script extends Inline {
    method create (line 7) | static create(value: 'super' | 'sub' | (string & {})) {
    method formats (line 17) | static formats(domNode: HTMLElement) {

FILE: packages/quill/src/formats/strike.ts
  class Strike (line 3) | class Strike extends Bold {

FILE: packages/quill/src/formats/table.ts
  class TableCell (line 5) | class TableCell extends Block {
    method create (line 9) | static create(value: string) {
    method formats (line 19) | static formats(domNode: HTMLElement) {
    method cellOffset (line 28) | cellOffset() {
    method format (line 35) | format(name: string, value: string) {
    method row (line 43) | row(): TableRow {
    method rowOffset (line 47) | rowOffset() {
    method table (line 54) | table() {
  class TableRow (line 59) | class TableRow extends Container {
    method checkMerge (line 66) | checkMerge() {
    method optimize (line 86) | optimize(context: { [key: string]: any }) {
    method rowOffset (line 107) | rowOffset() {
    method table (line 114) | table() {
  class TableBody (line 119) | class TableBody extends Container {
  class TableContainer (line 126) | class TableContainer extends Container {
    method balanceCells (line 132) | balanceCells() {
    method cells (line 151) | cells(column: number) {
    method deleteColumn (line 155) | deleteColumn(index: number) {
    method insertColumn (line 167) | insertColumn(index: number) {
    method insertRow (line 180) | insertRow(index: number) {
    method rows (line 194) | rows() {
  function tableId (line 210) | function tableId() {

FILE: packages/quill/src/formats/underline.ts
  class Underline (line 3) | class Underline extends Inline {

FILE: packages/quill/src/formats/video.ts
  constant ATTRIBUTES (line 4) | const ATTRIBUTES = ['height', 'width'];
  class Video (line 6) | class Video extends BlockEmbed {
    method create (line 11) | static create(value: string) {
    method formats (line 19) | static formats(domNode: Element) {
    method sanitize (line 31) | static sanitize(url: string) {
    method value (line 35) | static value(domNode: Element) {
    method format (line 41) | format(name: string, value: string) {
    method html (line 53) | html() {

FILE: packages/quill/src/modules/clipboard.ts
  type Selector (line 29) | type Selector = string | Node['TEXT_NODE'] | Node['ELEMENT_NODE'];
  type Matcher (line 30) | type Matcher = (node: Node, delta: Delta, scroll: ScrollBlot) => Delta;
  constant CLIPBOARD_CONFIG (line 32) | const CLIPBOARD_CONFIG: [Selector, Matcher][] = [
  constant ATTRIBUTE_ATTRIBUTORS (line 50) | const ATTRIBUTE_ATTRIBUTORS = [AlignAttribute, DirectionAttribute].reduce(
  constant STYLE_ATTRIBUTORS (line 58) | const STYLE_ATTRIBUTORS = [
  type ClipboardOptions (line 70) | interface ClipboardOptions {
  class Clipboard (line 74) | class Clipboard extends Module<ClipboardOptions> {
    method constructor (line 81) | constructor(quill: Quill, options: Partial<ClipboardOptions>) {
    method addMatcher (line 96) | addMatcher(selector: Selector, matcher: Matcher) {
    method convert (line 100) | convert(
    method normalizeHTML (line 123) | protected normalizeHTML(doc: Document) {
    method convertHTML (line 127) | protected convertHTML(html: string) {
    method dangerouslyPasteHTML (line 151) | dangerouslyPasteHTML(
    method onCaptureCopy (line 171) | onCaptureCopy(e: ClipboardEvent, isCut = false) {
    method normalizeURIList (line 187) | private normalizeURIList(urlList: string) {
    method onCapturePaste (line 197) | onCapturePaste(e: ClipboardEvent) {
    method onCopy (line 229) | onCopy(range: Range) {
    method onPaste (line 235) | onPaste(range: Range, { text, html }: { text?: string; html?: string }) {
    method prepareMatching (line 252) | prepareMatching(container: Element, nodeMatches: WeakMap<Node, Matcher...
  function applyFormat (line 280) | function applyFormat(
  function deltaEndsWith (line 300) | function deltaEndsWith(delta: Delta, text: string) {
  function isLine (line 314) | function isLine(node: Node, scroll: ScrollBlot) {
  function isBetweenInlineElements (line 358) | function isBetweenInlineElements(node: HTMLElement, scroll: ScrollBlot) {
  function isPre (line 368) | function isPre(node: Node | null) {
  function traverse (line 381) | function traverse(
  function createMatchAlias (line 420) | function createMatchAlias(format: string) {
  function matchAttributor (line 426) | function matchAttributor(node: HTMLElement, delta: Delta, scroll: Scroll...
  function matchBlot (line 457) | function matchBlot(node: Node, delta: Delta, scroll: ScrollBlot) {
  function matchBreak (line 492) | function matchBreak(node: Node, delta: Delta) {
  function matchCodeBlock (line 499) | function matchCodeBlock(node: Node, delta: Delta, scroll: ScrollBlot) {
  function matchIgnore (line 508) | function matchIgnore() {
  function matchIndent (line 512) | function matchIndent(node: Node, delta: Delta, scroll: ScrollBlot) {
  function matchList (line 541) | function matchList(node: Node, delta: Delta, scroll: ScrollBlot) {
  function matchNewline (line 553) | function matchNewline(node: Node, delta: Delta, scroll: ScrollBlot) {
  function matchStyles (line 579) | function matchStyles(node: HTMLElement, delta: Delta, scroll: ScrollBlot) {
  function matchTable (line 610) | function matchTable(
  function matchText (line 627) | function matchText(node: HTMLElement, delta: Delta, scroll: ScrollBlot) {

FILE: packages/quill/src/modules/history.ts
  type HistoryOptions (line 8) | interface HistoryOptions {
  type StackItem (line 14) | interface StackItem {
  type Stack (line 19) | interface Stack {
  class History (line 24) | class History extends Module<HistoryOptions> {
    method constructor (line 36) | constructor(quill: Quill, options: Partial<HistoryOptions>) {
    method change (line 85) | change(source: 'undo' | 'redo', dest: 'redo' | 'undo') {
    method clear (line 103) | clear() {
    method cutoff (line 107) | cutoff() {
    method record (line 111) | record(changeDelta: Delta, oldDelta: Delta) {
    method redo (line 138) | redo() {
    method transform (line 142) | transform(delta: Delta) {
    method undo (line 147) | undo() {
    method restoreSelection (line 151) | protected restoreSelection(stackItem: StackItem) {
  function transformStack (line 161) | function transformStack(stack: StackItem[], delta: Delta) {
  function endsWithNewlineChange (line 176) | function endsWithNewlineChange(scroll: Scroll, delta: Delta) {
  function getLastChangeIndex (line 190) | function getLastChangeIndex(scroll: Scroll, delta: Delta) {
  function transformRange (line 201) | function transformRange(range: Range | null, delta: Delta) {

FILE: packages/quill/src/modules/input.ts
  constant INSERT_TYPES (line 7) | const INSERT_TYPES = ['insertText', 'insertReplacementText'];
  class Input (line 9) | class Input extends Module {
    method constructor (line 10) | constructor(quill: Quill, options: Record<string, never>) {
    method deleteRange (line 26) | private deleteRange(range: Range) {
    method replaceText (line 30) | private replaceText(range: Range, text = '') {
    method handleBeforeInput (line 49) | private handleBeforeInput(event: InputEvent) {
    method handleCompositionStart (line 78) | private handleCompositionStart() {
  function getPlainTextFromInputEvent (line 86) | function getPlainTextFromInputEvent(event: InputEvent) {

FILE: packages/quill/src/modules/keyboard.ts
  constant SHORTKEY (line 13) | const SHORTKEY = /Mac/i.test(navigator.platform) ? 'metaKey' : 'ctrlKey';
  type Context (line 15) | interface Context {
  type BindingObject (line 26) | interface BindingObject
  type Binding (line 46) | type Binding = BindingObject | string | number;
  type NormalizedBinding (line 48) | interface NormalizedBinding extends Omit<BindingObject, 'key' | 'shortKe...
  type KeyboardOptions (line 52) | interface KeyboardOptions {
  type KeyboardOptions (line 56) | interface KeyboardOptions {
  class Keyboard (line 60) | class Keyboard extends Module<KeyboardOptions> {
    method match (line 63) | static match(evt: KeyboardEvent, binding: BindingObject) {
    method constructor (line 76) | constructor(quill: Quill, options: Partial<KeyboardOptions>) {
    method addBinding (line 140) | addBinding(
    method listen (line 173) | listen() {
    method handleBackspace (line 268) | handleBackspace(range: Range, context: Context) {
    method handleDelete (line 303) | handleDelete(range: Range, context: Context) {
    method handleDeleteRange (line 329) | handleDeleteRange(range: Range) {
    method handleEnter (line 334) | handleEnter(range: Range, context: Context) {
  method handler (line 366) | handler(range, context) {
  method handler (line 377) | handler(range, context) {
  method handler (line 392) | handler(range, context) {
  method handler (line 407) | handler(range) {
  method handler (line 413) | handler(range, context) {
  method handler (line 431) | handler() {
  method handler (line 440) | handler(range, context) {
  method handler (line 457) | handler(range) {
  method handler (line 480) | handler(range, context) {
  method handler (line 498) | handler() {}
  method handler (line 505) | handler() {}
  method handler (line 511) | handler(range) {
  method handler (line 540) | handler(range, context) {
  method handler (line 560) | handler(range, context) {
  method handler (line 601) | handler(range) {
  function makeCodeBlockHandler (line 639) | function makeCodeBlockHandler(indent: boolean): BindingObject {
  function makeEmbedArrowHandler (line 683) | function makeEmbedArrowHandler(
  function makeFormatHandler (line 727) | function makeFormatHandler(format: string): BindingObject {
  function makeTableArrowHandler (line 737) | function makeTableArrowHandler(up: boolean): BindingObject {
  function normalize (line 786) | function normalize(binding: Binding): BindingObject | null {
  function deleteRange (line 802) | function deleteRange({ quill, range }: { quill: Quill; range: Range }) {
  function tableSide (line 817) | function tableSide(_table: unknown, row: Blot, cell: Blot, offset: numbe...

FILE: packages/quill/src/modules/normalizeExternalHTML/index.ts
  constant NORMALIZERS (line 4) | const NORMALIZERS = [msWord, googleDocs];

FILE: packages/quill/src/modules/normalizeExternalHTML/normalizers/googleDocs.ts
  function normalize (line 30) | function normalize(doc: Document) {

FILE: packages/quill/src/modules/normalizeExternalHTML/normalizers/msWord.ts
  function normalize (line 90) | function normalize(doc: Document) {

FILE: packages/quill/src/modules/syntax.ts
  class CodeToken (line 17) | class CodeToken extends Inline {
    method formats (line 18) | static formats(node: Element, scroll: ScrollBlot) {
    method constructor (line 30) | constructor(scroll: ScrollBlot, domNode: Node, value: unknown) {
    method format (line 36) | format(format: string, value: unknown) {
    method optimize (line 47) | optimize(...args: unknown[]) {
  class SyntaxCodeBlock (line 58) | class SyntaxCodeBlock extends CodeBlock {
    method create (line 59) | static create(value: unknown) {
    method formats (line 67) | static formats(domNode: Node) {
    method register (line 72) | static register() {} // Syntax module will register
    method format (line 74) | format(name: string, value: unknown) {
    method replaceWith (line 83) | replaceWith(name: string | Blot, value?: any) {
  class SyntaxCodeBlockContainer (line 89) | class SyntaxCodeBlockContainer extends CodeBlockContainer {
    method attach (line 93) | attach() {
    method format (line 100) | format(name: string, value: unknown) {
    method formatAt (line 110) | formatAt(index: number, length: number, name: string, value: unknown) {
    method highlight (line 117) | highlight(
    method html (line 156) | html(index: number, length: number) {
    method optimize (line 167) | optimize(context: Record<string, any>) {
  type SyntaxOptions (line 188) | interface SyntaxOptions {
  class Syntax (line 204) | class Syntax extends Module<SyntaxOptions> {
    method register (line 207) | static register() {
    method constructor (line 215) | constructor(quill: Quill, options: Partial<SyntaxOptions>) {
    method initListener (line 235) | initListener() {
    method initTimer (line 260) | initTimer() {
    method highlight (line 273) | highlight(blot: SyntaxCodeBlockContainer | null = null, force = false) {
    method highlightBlot (line 290) | highlightBlot(text: string, language = 'plain') {

FILE: packages/quill/src/modules/table.ts
  class Table (line 12) | class Table extends Module {
    method register (line 13) | static register() {
    method constructor (line 20) | constructor(...args: ConstructorParameters<typeof Module>) {
    method balanceTables (line 25) | balanceTables() {
    method deleteColumn (line 31) | deleteColumn() {
    method deleteRow (line 39) | deleteRow() {
    method deleteTable (line 46) | deleteTable() {
    method getTable (line 57) | getTable(
    method insertColumn (line 71) | insertColumn(offset: number) {
    method insertColumnLeft (line 90) | insertColumnLeft() {
    method insertColumnRight (line 94) | insertColumnRight() {
    method insertRow (line 98) | insertRow(offset: number) {
    method insertRowAbove (line 117) | insertRowAbove() {
    method insertRowBelow (line 121) | insertRowBelow() {
    method insertTable (line 125) | insertTable(rows: number, columns: number) {
    method listenBalanceCells (line 137) | listenBalanceCells() {

FILE: packages/quill/src/modules/tableEmbed.ts
  type CellData (line 5) | type CellData = {
  type TableRowColumnOp (line 10) | type TableRowColumnOp = Omit<Op, 'insert'> & {
  type TableData (line 14) | interface TableData {
  method compose (line 119) | compose(a: TableData, b: TableData, keepNull?: boolean) {
  method transform (line 155) | transform(a: TableData, b: TableData, priority: boolean) {
  method invert (line 211) | invert(change: TableData, base: TableData) {
  class TableEmbed (line 257) | class TableEmbed extends Module {
    method register (line 258) | static register() {

FILE: packages/quill/src/modules/toolbar.ts
  type Handler (line 10) | type Handler = (this: Toolbar, value: any) => void;
  type ToolbarConfig (line 12) | type ToolbarConfig = Array<
  type ToolbarProps (line 15) | interface ToolbarProps {
  class Toolbar (line 23) | class Toolbar extends Module<ToolbarProps> {
    method constructor (line 30) | constructor(quill: Quill, options: Partial<ToolbarProps>) {
    method addHandler (line 70) | addHandler(format: string, handler: Handler) {
    method attach (line 74) | attach(input: HTMLElement) {
    method update (line 139) | update(range: Range | null) {
  function addButton (line 187) | function addButton(container: HTMLElement, format: string, value?: strin...
  function addControls (line 201) | function addControls(
  function addSelect (line 231) | function addSelect(
  method clean (line 253) | clean() {
  method direction (line 268) | direction(value) {
  method indent (line 277) | indent(value) {
  method link (line 289) | link(value) {
  method list (line 295) | list(value) {

FILE: packages/quill/src/modules/uiNode.ts
  constant TTL_FOR_VALID_SELECTION_CHANGE (line 8) | const TTL_FOR_VALID_SELECTION_CHANGE = 100;
  class UINode (line 30) | class UINode extends Module {
    method constructor (line 34) | constructor(quill: Quill, options: Record<string, never>) {
    method handleArrowKeys (line 41) | private handleArrowKeys() {
    method handleNavigationShortcuts (line 69) | private handleNavigationShortcuts() {
    method ensureListeningToSelectionChange (line 83) | private ensureListeningToSelectionChange() {
    method handleSelectionChange (line 102) | private handleSelectionChange() {

FILE: packages/quill/src/modules/uploader.ts
  type UploaderOptions (line 7) | interface UploaderOptions {
  class Uploader (line 12) | class Uploader extends Module<UploaderOptions> {
    method constructor (line 15) | constructor(quill: Quill, options: Partial<UploaderOptions>) {
    method upload (line 41) | upload(range: Range, files: FileList | File[]) {
  method handler (line 57) | handler(range: Range, files: File[]) {

FILE: packages/quill/src/themes/base.ts
  constant ALIGNS (line 17) | const ALIGNS = [false, 'center', 'right', 'justify'];
  constant COLORS (line 19) | const COLORS = [
  constant FONTS (line 57) | const FONTS = [false, 'serif', 'monospace'];
  constant HEADERS (line 59) | const HEADERS = ['1', '2', '3', false];
  constant SIZES (line 61) | const SIZES = ['small', false, 'large', 'huge'];
  class BaseTheme (line 63) | class BaseTheme extends Theme {
    method constructor (line 67) | constructor(quill: Quill, options: ThemeOptions) {
    method addModule (line 102) | addModule(name: string) {
    method buildButtons (line 111) | buildButtons(
    method buildPickers (line 140) | buildPickers(
  method formula (line 192) | formula() {
  method image (line 195) | image() {
  method video (line 216) | video() {
  class BaseTooltip (line 224) | class BaseTooltip extends Tooltip {
    method constructor (line 228) | constructor(quill: Quill, boundsContainer?: HTMLElement) {
    method listen (line 234) | listen() {
    method cancel (line 247) | cancel() {
    method edit (line 252) | edit(mode = 'link', preview: string | null = null) {
    method restoreFocus (line 274) | restoreFocus() {
    method save (line 278) | save() {
  function extractVideoUrl (line 329) | function extractVideoUrl(url: string) {
  function fillSelect (line 347) | function fillSelect(

FILE: packages/quill/src/themes/bubble.ts
  constant TOOLBAR_CONFIG (line 12) | const TOOLBAR_CONFIG: ToolbarConfig = [
  class BubbleTooltip (line 17) | class BubbleTooltip extends BaseTooltip {
    method constructor (line 26) | constructor(quill: Quill, bounds?: HTMLElement) {
    method listen (line 70) | listen() {
    method cancel (line 91) | cancel() {
    method position (line 95) | position(reference: Bounds) {
  class BubbleTheme (line 108) | class BubbleTheme extends BaseTheme {
    method constructor (line 111) | constructor(quill: Quill, options: ThemeOptions) {
    method extendToolbar (line 122) | extendToolbar(toolbar: Toolbar) {
  method link (line 136) | link(value: string) {

FILE: packages/quill/src/themes/snow.ts
  constant TOOLBAR_CONFIG (line 13) | const TOOLBAR_CONFIG: ToolbarConfig = [
  class SnowTooltip (line 20) | class SnowTooltip extends BaseTooltip {
    method listen (line 30) | listen() {
    method show (line 88) | show() {
  class SnowTheme (line 94) | class SnowTheme extends BaseTheme {
    method constructor (line 95) | constructor(quill: Quill, options: ThemeOptions) {
    method extendToolbar (line 106) | extendToolbar(toolbar: Toolbar) {
  method link (line 128) | link(value: string) {

FILE: packages/quill/src/ui/color-picker.ts
  class ColorPicker (line 3) | class ColorPicker extends Picker {
    method constructor (line 4) | constructor(select: HTMLSelectElement, label: string) {
    method buildItem (line 15) | buildItem(option: HTMLOptionElement) {
    method selectItem (line 21) | selectItem(item: HTMLElement | null, trigger?: boolean) {

FILE: packages/quill/src/ui/icon-picker.ts
  class IconPicker (line 3) | class IconPicker extends Picker {
    method constructor (line 6) | constructor(select: HTMLSelectElement, icons: Record<string, string>) {
    method selectItem (line 18) | selectItem(target: HTMLElement | null, trigger?: boolean) {

FILE: packages/quill/src/ui/picker.ts
  function toggleAriaAttribute (line 5) | function toggleAriaAttribute(element: HTMLElement, attribute: string) {
  class Picker (line 12) | class Picker {
    method constructor (line 17) | constructor(select: HTMLSelectElement) {
    method togglePicker (line 43) | togglePicker() {
    method buildItem (line 51) | buildItem(option: HTMLOptionElement) {
    method buildLabel (line 84) | buildLabel() {
    method buildOptions (line 96) | buildOptions() {
    method buildPicker (line 123) | buildPicker() {
    method escape (line 132) | escape() {
    method close (line 140) | close() {
    method selectItem (line 147) | selectItem(item: HTMLElement | null, trigger = false) {
    method update (line 177) | update() {

FILE: packages/quill/src/ui/tooltip.ts
  class Tooltip (line 9) | class Tooltip {
    method constructor (line 14) | constructor(quill: Quill, boundsContainer?: HTMLElement) {
    method hide (line 28) | hide() {
    method position (line 32) | position(reference: Bounds) {
    method show (line 60) | show() {

FILE: packages/quill/test/e2e/fixtures/Clipboard.ts
  class Clipboard (line 4) | class Clipboard {
    method constructor (line 5) | constructor(private page: Page) {}
    method copy (line 7) | async copy() {
    method cut (line 11) | async cut() {
    method paste (line 15) | async paste() {
    method writeText (line 19) | async writeText(value: string) {
    method writeHTML (line 45) | async writeHTML(value: string) {
    method readText (line 49) | async readText() {
    method readHTML (line 53) | async readHTML() {
    method read (line 58) | private async read(type: string) {
    method write (line 75) | private async write(data: string, type: string) {

FILE: packages/quill/test/e2e/fixtures/Composition.ts
  method constructor (line 13) | constructor(protected page: Page) {}
  method withKeyboardEvents (line 15) | protected async withKeyboardEvents(
  class ChromiumCompositionSession (line 27) | class ChromiumCompositionSession extends CompositionSession {
    method constructor (line 28) | constructor(
    method update (line 35) | async update(key: string) {
    method commit (line 47) | async commit(committedText: string) {
  class Composition (line 56) | class Composition {
    method constructor (line 57) | constructor(
    method start (line 62) | async start() {

FILE: packages/quill/test/e2e/fixtures/index.ts
  constant CHAPTER (line 34) | const CHAPTER = 'Chapter 1. Loomings.';

FILE: packages/quill/test/e2e/fixtures/utils/Locker.ts
  constant PREFIX (line 12) | const PREFIX = 'playwright_locker_';
  class Locker (line 14) | class Locker {
    method clearAll (line 15) | public static clearAll() {
    method constructor (line 19) | constructor(private key: string) {}
    method filePath (line 21) | private get filePath() {
    method lock (line 25) | async lock() {
    method release (line 34) | async release() {

FILE: packages/quill/test/e2e/pageobjects/EditorPage.ts
  type Op (line 4) | interface Op {
  class EditorPage (line 57) | class EditorPage {
    method constructor (line 58) | constructor(protected readonly page: Page) {}
    method root (line 60) | get root() {
    method open (line 64) | async open() {
    method html (line 69) | async html(content: string, title = '') {
    method getSelection (line 78) | getSelection() {
    method setSelection (line 87) | async setSelection(
    method typeWordWithIME (line 98) | async typeWordWithIME(composition: Composition, composedWord: string) {
    method cutoffHistory (line 105) | async cutoffHistory() {
    method updateContents (line 112) | async updateContents(delta: Op[], source: 'api' | 'user' = 'api') {
    method setContents (line 122) | async setContents(delta: Op[]) {
    method getContents (line 129) | getContents(): Promise<Op[]> {
    method moveCursorTo (line 140) | async moveCursorTo(query: string) {
    method moveCursorAfterText (line 163) | moveCursorAfterText(text: string) {
    method moveCursorBeforeText (line 167) | moveCursorBeforeText(text: string) {
    method selectText (line 171) | async selectText(start: string, end?: string) {
    method waitForText (line 199) | private async waitForText(text: string) {

FILE: packages/quill/test/e2e/utils/index.ts
  constant SHORTKEY (line 2) | const SHORTKEY = isMac ? 'Meta' : 'Control';
  function getSelectionInTextNode (line 4) | function getSelectionInTextNode() {

FILE: packages/quill/test/fuzz/__helpers__/utils.ts
  function randomInt (line 1) | function randomInt(max: number) {
  function choose (line 5) | function choose<T>(choices: T[]): T {
  function runFuzz (line 9) | function runFuzz(testCase: () => void) {

FILE: packages/quill/test/fuzz/editor.spec.ts
  type AttributeDef (line 10) | type AttributeDef = { name: string; values: (number | string | boolean)[...
  constant BLOCK_EMBED_NAME (line 11) | const BLOCK_EMBED_NAME = 'video';
  constant INLINE_EMBED_NAME (line 12) | const INLINE_EMBED_NAME = 'image';
  type SingleInsertValue (line 84) | type SingleInsertValue =

FILE: packages/quill/test/types/quill.test-d.ts
  class MyBlot (line 16) | class MyBlot extends LeafBlot {}

FILE: packages/quill/test/unit/__helpers__/expect.ts
  method toEqualHTML (line 26) | toEqualHTML(received, expected, options: { ignoreAttrs?: string[] } = {}) {

FILE: packages/quill/test/unit/__helpers__/vitest.d.ts
  type CustomMatchers (line 3) | interface CustomMatchers<R = unknown> {
  type Assertion (line 8) | interface Assertion<T = any> extends CustomMatchers<T> {}
  type AsymmetricMatchersContaining (line 9) | interface AsymmetricMatchersContaining extends CustomMatchers {}

FILE: packages/quill/test/unit/core/editor.spec.ts
  class MyBlot (line 874) | class MyBlot extends Block {
    method formatAt (line 878) | formatAt(index: number, length: number, name: string, value: string) {

FILE: packages/quill/test/unit/core/quill.spec.ts
  class Counter (line 47) | class Counter {}
  class MyCounterBlot (line 55) | class MyCounterBlot extends LeafBlot {
  class ABlot (line 66) | class ABlot extends LeafBlot {
  class AModule (line 70) | class AModule {}

FILE: packages/quill/test/unit/core/utils/createRegistryWithFormats.spec.ts
  class RequiredContainer (line 28) | class RequiredContainer extends Container {
  class Child (line 31) | class Child extends Inline {
  class InfiniteBlot (line 51) | class InfiniteBlot extends Inline {

FILE: packages/quill/test/unit/modules/syntax.spec.ts
  constant HIGHLIGHT_INTERVAL (line 10) | const HIGHLIGHT_INTERVAL = 10;

FILE: packages/quill/test/unit/theme/base/tooltip.spec.ts
  class Tooltip (line 7) | class Tooltip extends BaseTooltip {

FILE: packages/website/next.config.mjs
  method webpack (line 54) | webpack(config) {

FILE: packages/website/src/components/ClickOutsideHandler.jsx
  constant TOUCH_EVENT (line 3) | const TOUCH_EVENT = { react: 'onTouchStart', native: 'touchstart' };
  constant MOUSE_EVENT (line 4) | const MOUSE_EVENT = { react: 'onMouseDown', native: 'mousedown' };

FILE: packages/website/src/components/Heading.jsx
  constant EXPERIMENTAL_FLAG (line 4) | const EXPERIMENTAL_FLAG = ' #experimental';

FILE: packages/website/src/components/MDX.jsx
  function MDX (line 72) | function MDX({ mdxSource, data }) {

FILE: packages/website/src/pages/_app.jsx
  function MyApp (line 9) | function MyApp({ Component, pageProps }) {

FILE: packages/website/src/pages/_document.jsx
  function Document (line 5) | function Document() {

FILE: packages/website/src/pages/docs/[...id].jsx
  function getStaticPaths (line 10) | async function getStaticPaths() {
  function getStaticProps (line 17) | async function getStaticProps({ params }) {
  function Doc (line 44) | function Doc({ mdxSource, filePath, permalink, data }) {

FILE: packages/website/src/pages/index.jsx
  function loadFonts (line 132) | function loadFonts() {

FILE: packages/website/src/pages/playground/[...id].jsx
  function getStaticPaths (line 19) | async function getStaticPaths() {
  function getStaticProps (line 26) | async function getStaticProps({ params }) {
  function Playground (line 47) | function Playground({ pack, permalink, title }) {

FILE: packages/website/src/utils/flattenData.js
  function flattenData (line 1) | function flattenData(root) {

FILE: scripts/release.js
  function main (line 33) | async function main() {

FILE: scripts/utils/configGit.mjs
  function configGit (line 3) | async function configGit() {
Condensed preview — 260 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,049K chars).
[
  {
    "path": ".eslintignore",
    "chars": 15,
    "preview": "dist/\nscripts/\n"
  },
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "chars": 3227,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "chars": 4130,
    "preview": "# Contributing\n\nThe best way to contribute is to help others in the Quill community. This includes:\n\n- Reporting new [bu"
  },
  {
    "path": ".github/DEVELOPMENT.md",
    "chars": 1987,
    "preview": "# Development\n\nThis repo is a monorepo powered by npm's official [workspace feature](https://docs.npmjs.com/cli/v10/usin"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "chars": 609,
    "preview": "Please describe the a concise description and fill out the details below. It will help others efficiently understand you"
  },
  {
    "path": ".github/release.yml",
    "chars": 326,
    "preview": "changelog:\n  exclude:\n    authors:\n      - quill-bot\n  categories:\n    - title: Bug Fixes 🛠\n      labels:\n        - chan"
  },
  {
    "path": ".github/workflows/_test.yml",
    "chars": 1532,
    "preview": "name: Tests\non:\n  workflow_call:\njobs:\n  e2e:\n    name: E2E Tests\n    timeout-minutes: 60\n    runs-on: ubuntu-latest\n   "
  },
  {
    "path": ".github/workflows/changelog.yml",
    "chars": 447,
    "preview": "name: Generate Changelog\n\non:\n  release:\n    types: [published, created]\n  workflow_dispatch: {}\n\njobs:\n  changelog:\n   "
  },
  {
    "path": ".github/workflows/label.yml",
    "chars": 510,
    "preview": "name: Pull Requests\n\non:\n  pull_request:\n    types: [opened, labeled, unlabeled, synchronize]\n\njobs:\n  label:\n    runs-o"
  },
  {
    "path": ".github/workflows/main.yml",
    "chars": 161,
    "preview": "name: Main\n\non:\n  push:\n    branches: [main]\n\nconcurrency:\n  group: main-build\n  cancel-in-progress: true\n\njobs:\n  test:"
  },
  {
    "path": ".github/workflows/pull-request.yml",
    "chars": 117,
    "preview": "name: Pull Requests\n\non:\n  pull_request:\n    branches: [main]\n\njobs:\n  test:\n    uses: ./.github/workflows/_test.yml\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 1200,
    "preview": "name: Release\n\non:\n  workflow_dispatch:\n    inputs:\n      version:\n        description: 'npm version. Examples: \"2.0.0\","
  },
  {
    "path": ".gitignore",
    "chars": 115,
    "preview": ".*\n!.eslintrc.json\n!.eslintignore\n!.npmignore\n!.gitignore\n!.github\n\nnode_modules\n\ntest-results/\nplaywright-report/\n"
  },
  {
    "path": ".npmignore",
    "chars": 56,
    "preview": "*.ts\n!*.d.ts\n.*\n.github\n.vscode\ndocs\ntest\ntsconfig.json\n"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 51741,
    "preview": "# v2.0.2 (2024-05-13)\n\n<!-- Release notes generated using configuration in .github/release.yml at v2.0.2 -->\n\n## What's "
  },
  {
    "path": "LICENSE",
    "chars": 1561,
    "preview": "Copyright (c) 2017-2024, Slab\nCopyright (c) 2014, Jason Chen\nCopyright (c) 2013, salesforce.com\nAll rights reserved.\n\nRe"
  },
  {
    "path": "README.md",
    "chars": 3493,
    "preview": "<h1 align=\"center\">\n  <a href=\"https://quilljs.com/\" title=\"Quill\">Quill Rich Text Editor</a>\n</h1>\n<p align=\"center\">\n "
  },
  {
    "path": "package.json",
    "chars": 1127,
    "preview": "{\n  \"name\": \"quill-monorepo\",\n  \"version\": \"2.0.3\",\n  \"description\": \"Quill development environment\",\n  \"private\": true,"
  },
  {
    "path": "packages/quill/.eslintrc.json",
    "chars": 1109,
    "preview": "{\n  \"extends\": [\n    \"eslint:recommended\",\n    \"plugin:prettier/recommended\",\n    \"plugin:import/recommended\",\n    \"plug"
  },
  {
    "path": "packages/quill/.gitignore",
    "chars": 5,
    "preview": "dist\n"
  },
  {
    "path": "packages/quill/LICENSE",
    "chars": 1561,
    "preview": "Copyright (c) 2017-2024, Slab\nCopyright (c) 2014, Jason Chen\nCopyright (c) 2013, salesforce.com\nAll rights reserved.\n\nRe"
  },
  {
    "path": "packages/quill/README.md",
    "chars": 44,
    "preview": "# Quill\n\nThis is the main package of Quill.\n"
  },
  {
    "path": "packages/quill/babel.config.cjs",
    "chars": 280,
    "preview": "const pkg = require('./package.json');\n\nmodule.exports = {\n  presets: [\n    ['@babel/preset-env', { modules: false }],\n "
  },
  {
    "path": "packages/quill/package.json",
    "chars": 2947,
    "preview": "{\n  \"name\": \"quill\",\n  \"version\": \"2.0.3\",\n  \"description\": \"Your powerful, rich text editor\",\n  \"author\": \"Jason Chen <"
  },
  {
    "path": "packages/quill/playwright.config.ts",
    "chars": 1100,
    "preview": "import { defineConfig, devices } from '@playwright/test';\n\nconst port = 9001;\n\nexport default defineConfig({\n  testDir: "
  },
  {
    "path": "packages/quill/scripts/babel-svg-inline-import.cjs",
    "chars": 1321,
    "preview": "const fs = require('fs');\nconst { dirname, resolve } = require('path');\nconst { optimize } = require('svgo');\n\nmodule.ex"
  },
  {
    "path": "packages/quill/scripts/build",
    "chars": 520,
    "preview": "#!/usr/bin/env bash\n\nset -e\n\nDIST=dist\n\nTMPDIR=$(mktemp -d 2>/dev/null || mktemp -d -t 'mytmpdir')\nnpx tsc --declaration"
  },
  {
    "path": "packages/quill/src/assets/base.styl",
    "chars": 7325,
    "preview": "// Styles shared between snow and bubble\n\ncontrolHeight = 24px\ninputPaddingWidth = 5px\ninputPaddingHeight = 3px\n\ncolorIt"
  },
  {
    "path": "packages/quill/src/assets/bubble/toolbar.styl",
    "chars": 275,
    "preview": "arrowWidth = 6px\n\n.ql-bubble\n  .ql-toolbar\n    .ql-formats\n      margin: 8px 12px 8px 0px\n    .ql-formats:first-child\n  "
  },
  {
    "path": "packages/quill/src/assets/bubble/tooltip.styl",
    "chars": 1142,
    "preview": "arrowWidth = 6px\n\n.ql-bubble\n  .ql-tooltip\n    background-color: backgroundColor\n    border-radius: 25px\n    color: text"
  },
  {
    "path": "packages/quill/src/assets/bubble.styl",
    "chars": 1053,
    "preview": "themeName = 'bubble'\nactiveColor = #fff\nborderColor = #777\nbackgroundColor = #444\ninactiveColor = #ccc\nshadowColor = #dd"
  },
  {
    "path": "packages/quill/src/assets/core.styl",
    "chars": 4957,
    "preview": "// Styles necessary for Quill\n\nLIST_STYLE = decimal lower-alpha lower-roman\nLIST_STYLE_WIDTH = 1.2em\nLIST_STYLE_MARGIN ="
  },
  {
    "path": "packages/quill/src/assets/snow/toolbar.styl",
    "chars": 644,
    "preview": ".ql-toolbar.ql-snow\n  border: 1px solid borderColor\n  box-sizing: border-box\n  font-family: 'Helvetica Neue', 'Helvetica"
  },
  {
    "path": "packages/quill/src/assets/snow/tooltip.styl",
    "chars": 1328,
    "preview": "tooltipMargin = 8px\n\n.ql-snow\n  .ql-tooltip\n    background-color: #fff\n    border: 1px solid borderColor\n    box-shadow:"
  },
  {
    "path": "packages/quill/src/assets/snow.styl",
    "chars": 283,
    "preview": "themeName = 'snow'\nactiveColor = #06c\nborderColor = #ccc\nbackgroundColor = #fff\ninactiveColor = #444\nshadowColor = #ddd\n"
  },
  {
    "path": "packages/quill/src/blots/block.ts",
    "chars": 5771,
    "preview": "import {\n  AttributorStore,\n  BlockBlot,\n  EmbedBlot,\n  LeafBlot,\n  Scope,\n} from 'parchment';\nimport type { Blot, Paren"
  },
  {
    "path": "packages/quill/src/blots/break.ts",
    "chars": 335,
    "preview": "import { EmbedBlot } from 'parchment';\n\nclass Break extends EmbedBlot {\n  static value() {\n    return undefined;\n  }\n\n  "
  },
  {
    "path": "packages/quill/src/blots/container.ts",
    "chars": 112,
    "preview": "import { ContainerBlot } from 'parchment';\n\nclass Container extends ContainerBlot {}\n\nexport default Container;\n"
  },
  {
    "path": "packages/quill/src/blots/cursor.ts",
    "chars": 6200,
    "preview": "import { EmbedBlot, Scope } from 'parchment';\nimport type { Parent, ScrollBlot } from 'parchment';\nimport type Selection"
  },
  {
    "path": "packages/quill/src/blots/embed.ts",
    "chars": 2785,
    "preview": "import type { ScrollBlot } from 'parchment';\nimport { EmbedBlot } from 'parchment';\nimport TextBlot from './text.js';\n\nc"
  },
  {
    "path": "packages/quill/src/blots/inline.ts",
    "chars": 1733,
    "preview": "import { EmbedBlot, InlineBlot, Scope } from 'parchment';\nimport type { BlotConstructor } from 'parchment';\nimport Break"
  },
  {
    "path": "packages/quill/src/blots/scroll.ts",
    "chars": 13063,
    "preview": "import { ContainerBlot, LeafBlot, Scope, ScrollBlot } from 'parchment';\nimport type { Blot, Parent, EmbedBlot, ParentBlo"
  },
  {
    "path": "packages/quill/src/blots/text.ts",
    "chars": 367,
    "preview": "import { TextBlot } from 'parchment';\n\nclass Text extends TextBlot {}\n\n// https://lodash.com/docs#escape\nconst entityMap"
  },
  {
    "path": "packages/quill/src/core/composition.ts",
    "chars": 1564,
    "preview": "import Embed from '../blots/embed.js';\nimport type Scroll from '../blots/scroll.js';\nimport Emitter from './emitter.js';"
  },
  {
    "path": "packages/quill/src/core/editor.ts",
    "chars": 15796,
    "preview": "import { cloneDeep, isEqual, merge } from 'lodash-es';\nimport { LeafBlot, EmbedBlot, Scope, ParentBlot } from 'parchment"
  },
  {
    "path": "packages/quill/src/core/emitter.ts",
    "chars": 2182,
    "preview": "import { EventEmitter } from 'eventemitter3';\nimport instances from './instances.js';\nimport logger from './logger.js';\n"
  },
  {
    "path": "packages/quill/src/core/instances.ts",
    "chars": 81,
    "preview": "import type Quill from '../core.js';\n\nexport default new WeakMap<Node, Quill>();\n"
  },
  {
    "path": "packages/quill/src/core/logger.ts",
    "chars": 784,
    "preview": "const levels = ['error', 'warn', 'log', 'info'] as const;\nexport type DebugLevel = (typeof levels)[number];\nlet level: D"
  },
  {
    "path": "packages/quill/src/core/module.ts",
    "chars": 219,
    "preview": "import type Quill from './quill.js';\n\nabstract class Module<T extends {} = {}> {\n  static DEFAULTS = {};\n\n  constructor("
  },
  {
    "path": "packages/quill/src/core/quill.ts",
    "chars": 29923,
    "preview": "import { merge } from 'lodash-es';\nimport * as Parchment from 'parchment';\nimport type { Op } from 'quill-delta';\nimport"
  },
  {
    "path": "packages/quill/src/core/selection.ts",
    "chars": 14990,
    "preview": "import { LeafBlot, Scope } from 'parchment';\nimport { cloneDeep, isEqual } from 'lodash-es';\nimport Emitter from './emit"
  },
  {
    "path": "packages/quill/src/core/theme.ts",
    "chars": 1433,
    "preview": "import type Quill from '../core.js';\nimport type Clipboard from '../modules/clipboard.js';\nimport type History from '../"
  },
  {
    "path": "packages/quill/src/core/utils/createRegistryWithFormats.ts",
    "chars": 1142,
    "preview": "import { Registry } from 'parchment';\n\nconst MAX_REGISTER_ITERATIONS = 100;\nconst CORE_FORMATS = ['block', 'break', 'cur"
  },
  {
    "path": "packages/quill/src/core/utils/scrollRectIntoView.ts",
    "chars": 4552,
    "preview": "export type Rect = {\n  top: number;\n  right: number;\n  bottom: number;\n  left: number;\n};\n\nconst getParentElement = (ele"
  },
  {
    "path": "packages/quill/src/core.ts",
    "chars": 1541,
    "preview": "import Quill, { Parchment, Range } from './core/quill.js';\nimport type {\n  Bounds,\n  DebugLevel,\n  EmitterSource,\n  Expa"
  },
  {
    "path": "packages/quill/src/formats/align.ts",
    "chars": 427,
    "preview": "import { Attributor, ClassAttributor, Scope, StyleAttributor } from 'parchment';\n\nconst config = {\n  scope: Scope.BLOCK,"
  },
  {
    "path": "packages/quill/src/formats/background.ts",
    "chars": 348,
    "preview": "import { ClassAttributor, Scope } from 'parchment';\nimport { ColorAttributor } from './color.js';\n\nconst BackgroundClass"
  },
  {
    "path": "packages/quill/src/formats/blockquote.ts",
    "chars": 170,
    "preview": "import Block from '../blots/block.js';\n\nclass Blockquote extends Block {\n  static blotName = 'blockquote';\n  static tagN"
  },
  {
    "path": "packages/quill/src/formats/bold.ts",
    "chars": 446,
    "preview": "import Inline from '../blots/inline.js';\n\nclass Bold extends Inline {\n  static blotName = 'bold';\n  static tagName = ['S"
  },
  {
    "path": "packages/quill/src/formats/code.ts",
    "chars": 1703,
    "preview": "import Block from '../blots/block.js';\nimport Break from '../blots/break.js';\nimport Cursor from '../blots/cursor.js';\ni"
  },
  {
    "path": "packages/quill/src/formats/color.ts",
    "chars": 707,
    "preview": "import { ClassAttributor, Scope, StyleAttributor } from 'parchment';\n\nclass ColorAttributor extends StyleAttributor {\n  "
  },
  {
    "path": "packages/quill/src/formats/direction.ts",
    "chars": 441,
    "preview": "import { Attributor, ClassAttributor, Scope, StyleAttributor } from 'parchment';\n\nconst config = {\n  scope: Scope.BLOCK,"
  },
  {
    "path": "packages/quill/src/formats/font.ts",
    "chars": 465,
    "preview": "import { ClassAttributor, Scope, StyleAttributor } from 'parchment';\n\nconst config = {\n  scope: Scope.INLINE,\n  whitelis"
  },
  {
    "path": "packages/quill/src/formats/formula.ts",
    "chars": 815,
    "preview": "import Embed from '../blots/embed.js';\n\nclass Formula extends Embed {\n  static blotName = 'formula';\n  static className "
  },
  {
    "path": "packages/quill/src/formats/header.ts",
    "chars": 278,
    "preview": "import Block from '../blots/block.js';\n\nclass Header extends Block {\n  static blotName = 'header';\n  static tagName = ['"
  },
  {
    "path": "packages/quill/src/formats/image.ts",
    "chars": 1349,
    "preview": "import { EmbedBlot } from 'parchment';\nimport { sanitize } from './link.js';\n\nconst ATTRIBUTES = ['alt', 'height', 'widt"
  },
  {
    "path": "packages/quill/src/formats/indent.ts",
    "chars": 987,
    "preview": "import { ClassAttributor, Scope } from 'parchment';\n\nclass IndentAttributor extends ClassAttributor {\n  add(node: HTMLEl"
  },
  {
    "path": "packages/quill/src/formats/italic.ts",
    "chars": 147,
    "preview": "import Bold from './bold.js';\n\nclass Italic extends Bold {\n  static blotName = 'italic';\n  static tagName = ['EM', 'I'];"
  },
  {
    "path": "packages/quill/src/formats/link.ts",
    "chars": 1221,
    "preview": "import Inline from '../blots/inline.js';\n\nclass Link extends Inline {\n  static blotName = 'link';\n  static tagName = 'A'"
  },
  {
    "path": "packages/quill/src/formats/list.ts",
    "chars": 1679,
    "preview": "import Block from '../blots/block.js';\nimport Container from '../blots/container.js';\nimport type Scroll from '../blots/"
  },
  {
    "path": "packages/quill/src/formats/script.ts",
    "chars": 582,
    "preview": "import Inline from '../blots/inline.js';\n\nclass Script extends Inline {\n  static blotName = 'script';\n  static tagName ="
  },
  {
    "path": "packages/quill/src/formats/size.ts",
    "chars": 358,
    "preview": "import { ClassAttributor, Scope, StyleAttributor } from 'parchment';\n\nconst SizeClass = new ClassAttributor('size', 'ql-"
  },
  {
    "path": "packages/quill/src/formats/strike.ts",
    "chars": 151,
    "preview": "import Bold from './bold.js';\n\nclass Strike extends Bold {\n  static blotName = 'strike';\n  static tagName = ['S', 'STRIK"
  },
  {
    "path": "packages/quill/src/formats/table.ts",
    "chars": 5695,
    "preview": "import type { LinkedList } from 'parchment';\nimport Block from '../blots/block.js';\nimport Container from '../blots/cont"
  },
  {
    "path": "packages/quill/src/formats/underline.ts",
    "chars": 161,
    "preview": "import Inline from '../blots/inline.js';\n\nclass Underline extends Inline {\n  static blotName = 'underline';\n  static tag"
  },
  {
    "path": "packages/quill/src/formats/video.ts",
    "chars": 1373,
    "preview": "import { BlockEmbed } from '../blots/block.js';\nimport Link from './link.js';\n\nconst ATTRIBUTES = ['height', 'width'];\n\n"
  },
  {
    "path": "packages/quill/src/modules/clipboard.ts",
    "chars": 19077,
    "preview": "import type { ScrollBlot } from 'parchment';\nimport {\n  Attributor,\n  BlockBlot,\n  ClassAttributor,\n  EmbedBlot,\n  Scope"
  },
  {
    "path": "packages/quill/src/modules/history.ts",
    "chars": 5774,
    "preview": "import { Scope } from 'parchment';\nimport type Delta from 'quill-delta';\nimport Module from '../core/module.js';\nimport "
  },
  {
    "path": "packages/quill/src/modules/input.ts",
    "chars": 2933,
    "preview": "import Delta from 'quill-delta';\nimport Module from '../core/module.js';\nimport Quill from '../core/quill.js';\nimport ty"
  },
  {
    "path": "packages/quill/src/modules/keyboard.ts",
    "chars": 25766,
    "preview": "import { cloneDeep, isEqual } from 'lodash-es';\nimport Delta, { AttributeMap } from 'quill-delta';\nimport { EmbedBlot, S"
  },
  {
    "path": "packages/quill/src/modules/normalizeExternalHTML/index.ts",
    "chars": 341,
    "preview": "import googleDocs from './normalizers/googleDocs.js';\nimport msWord from './normalizers/msWord.js';\n\nconst NORMALIZERS ="
  },
  {
    "path": "packages/quill/src/modules/normalizeExternalHTML/normalizers/googleDocs.ts",
    "chars": 1058,
    "preview": "const normalWeightRegexp = /font-weight:\\s*normal/;\nconst blockTagNames = ['P', 'OL', 'UL'];\n\nconst isBlockElement = (el"
  },
  {
    "path": "packages/quill/src/modules/normalizeExternalHTML/normalizers/msWord.ts",
    "chars": 3144,
    "preview": "const ignoreRegexp = /\\bmso-list:[^;]*ignore/i;\nconst idRegexp = /\\bmso-list:[^;]*\\bl(\\d+)/i;\nconst indentRegexp = /\\bms"
  },
  {
    "path": "packages/quill/src/modules/syntax.ts",
    "chars": 10803,
    "preview": "import Delta from 'quill-delta';\nimport { ClassAttributor, Scope } from 'parchment';\nimport type { Blot, ScrollBlot } fr"
  },
  {
    "path": "packages/quill/src/modules/table.ts",
    "chars": 4148,
    "preview": "import Delta from 'quill-delta';\nimport Quill from '../core/quill.js';\nimport Module from '../core/module.js';\nimport {\n"
  },
  {
    "path": "packages/quill/src/modules/tableEmbed.ts",
    "chars": 7155,
    "preview": "import Delta, { OpIterator } from 'quill-delta';\nimport type { Op, AttributeMap } from 'quill-delta';\nimport Module from"
  },
  {
    "path": "packages/quill/src/modules/toolbar.ts",
    "chars": 10291,
    "preview": "import Delta from 'quill-delta';\nimport { EmbedBlot, Scope } from 'parchment';\nimport Quill from '../core/quill.js';\nimp"
  },
  {
    "path": "packages/quill/src/modules/uiNode.ts",
    "chars": 3337,
    "preview": "import { ParentBlot } from 'parchment';\nimport Module from '../core/module.js';\nimport Quill from '../core/quill.js';\n\nc"
  },
  {
    "path": "packages/quill/src/modules/uploader.ts",
    "chars": 2683,
    "preview": "import Delta from 'quill-delta';\nimport type Quill from '../core/quill.js';\nimport Emitter from '../core/emitter.js';\nim"
  },
  {
    "path": "packages/quill/src/quill.ts",
    "chars": 3485,
    "preview": "import Quill from './core.js';\nimport type {\n  Bounds,\n  DebugLevel,\n  EmitterSource,\n  ExpandedQuillOptions,\n  QuillOpt"
  },
  {
    "path": "packages/quill/src/themes/base.ts",
    "chars": 10185,
    "preview": "import { merge } from 'lodash-es';\nimport type Quill from '../core/quill.js';\nimport Emitter from '../core/emitter.js';\n"
  },
  {
    "path": "packages/quill/src/themes/bubble.ts",
    "chars": 4660,
    "preview": "import { merge } from 'lodash-es';\nimport Emitter from '../core/emitter.js';\nimport BaseTheme, { BaseTooltip } from './b"
  },
  {
    "path": "packages/quill/src/themes/snow.ts",
    "chars": 4837,
    "preview": "import { merge } from 'lodash-es';\nimport Emitter from '../core/emitter.js';\nimport BaseTheme, { BaseTooltip } from './b"
  },
  {
    "path": "packages/quill/src/types.d.ts",
    "chars": 128,
    "preview": "declare module '*.svg' {\n  const content: string;\n  export default content;\n}\n\ndeclare const QUILL_VERSION: string | und"
  },
  {
    "path": "packages/quill/src/ui/color-picker.ts",
    "chars": 1034,
    "preview": "import Picker from './picker.js';\n\nclass ColorPicker extends Picker {\n  constructor(select: HTMLSelectElement, label: st"
  },
  {
    "path": "packages/quill/src/ui/icon-picker.ts",
    "chars": 858,
    "preview": "import Picker from './picker.js';\n\nclass IconPicker extends Picker {\n  defaultItem: HTMLElement | null;\n\n  constructor(s"
  },
  {
    "path": "packages/quill/src/ui/icons.ts",
    "chars": 2843,
    "preview": "import alignLeftIcon from '../assets/icons/align-left.svg';\nimport alignCenterIcon from '../assets/icons/align-center.sv"
  },
  {
    "path": "packages/quill/src/ui/picker.ts",
    "chars": 5736,
    "preview": "import DropdownIcon from '../assets/icons/dropdown.svg';\n\nlet optionsCounter = 0;\n\nfunction toggleAriaAttribute(element:"
  },
  {
    "path": "packages/quill/src/ui/tooltip.ts",
    "chars": 2158,
    "preview": "import type Quill from '../core.js';\nimport type { Bounds } from '../core/selection.js';\n\nconst isScrollable = (el: Elem"
  },
  {
    "path": "packages/quill/test/e2e/__dev_server__/index.html",
    "chars": 2612,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"utf-8\" />\n  <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edg"
  },
  {
    "path": "packages/quill/test/e2e/__dev_server__/webpack.config.cjs",
    "chars": 765,
    "preview": "/*eslint-env node*/\n\nconst path = require('path');\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\nconst commo"
  },
  {
    "path": "packages/quill/test/e2e/fixtures/Clipboard.ts",
    "chars": 2568,
    "preview": "import type { Page } from '@playwright/test';\nimport { SHORTKEY } from '../utils/index.js';\n\nclass Clipboard {\n  constru"
  },
  {
    "path": "packages/quill/test/e2e/fixtures/Composition.ts",
    "chars": 1776,
    "preview": "import type {\n  CDPSession,\n  Page,\n  PlaywrightWorkerOptions,\n} from '@playwright/test';\n\nabstract class CompositionSes"
  },
  {
    "path": "packages/quill/test/e2e/fixtures/index.ts",
    "chars": 1235,
    "preview": "import { test as base } from '@playwright/test';\nimport EditorPage from '../pageobjects/EditorPage.js';\nimport Compositi"
  },
  {
    "path": "packages/quill/test/e2e/fixtures/utils/Locker.ts",
    "chars": 791,
    "preview": "import { unlink, writeFile } from 'fs/promises';\nimport { unlinkSync } from 'fs';\nimport { tmpdir } from 'os';\nimport { "
  },
  {
    "path": "packages/quill/test/e2e/full.spec.ts",
    "chars": 6432,
    "preview": "import { expect } from '@playwright/test';\nimport { getSelectionInTextNode, SHORTKEY } from './utils/index.js';\nimport {"
  },
  {
    "path": "packages/quill/test/e2e/history.spec.ts",
    "chars": 5473,
    "preview": "import { expect } from '@playwright/test';\nimport type { Page } from '@playwright/test';\nimport { test } from './fixture"
  },
  {
    "path": "packages/quill/test/e2e/list.spec.ts",
    "chars": 6067,
    "preview": "import { expect } from '@playwright/test';\nimport { test } from './fixtures/index.js';\nimport { isMac, sleep } from './u"
  },
  {
    "path": "packages/quill/test/e2e/pageobjects/EditorPage.ts",
    "chars": 5914,
    "preview": "import type { Page } from '@playwright/test';\nimport type Composition from '../fixtures/Composition.js';\n\ninterface Op {"
  },
  {
    "path": "packages/quill/test/e2e/replaceSelection.spec.ts",
    "chars": 3131,
    "preview": "import { expect } from '@playwright/test';\nimport { test } from './fixtures/index.js';\n\ntest.describe('replace selection"
  },
  {
    "path": "packages/quill/test/e2e/utils/index.ts",
    "chars": 582,
    "preview": "export const isMac = process.platform === 'darwin';\nexport const SHORTKEY = isMac ? 'Meta' : 'Control';\n\nexport function"
  },
  {
    "path": "packages/quill/test/fuzz/__helpers__/utils.ts",
    "chars": 338,
    "preview": "export function randomInt(max: number) {\n  return Math.floor(Math.random() * max);\n}\n\nexport function choose<T>(choices:"
  },
  {
    "path": "packages/quill/test/fuzz/editor.spec.ts",
    "chars": 7927,
    "preview": "import type { Op } from 'quill-delta';\nimport Delta, { AttributeMap } from 'quill-delta';\nimport { choose, randomInt, ru"
  },
  {
    "path": "packages/quill/test/fuzz/tableEmbed.spec.ts",
    "chars": 5021,
    "preview": "import type { AttributeMap } from 'quill-delta';\nimport Delta from 'quill-delta';\nimport TableEmbed from '../../src/modu"
  },
  {
    "path": "packages/quill/test/fuzz/vitest.config.ts",
    "chars": 258,
    "preview": "import { defineConfig } from 'vitest/config';\n\nexport default defineConfig({\n  resolve: {\n    extensions: ['.ts', '.js']"
  },
  {
    "path": "packages/quill/test/types/quill.test-d.ts",
    "chars": 5319,
    "preview": "import { assertType, expectTypeOf } from 'vitest';\nimport Quill, { Delta } from '../../src/quill.js';\nimport type { Emit"
  },
  {
    "path": "packages/quill/test/unit/__helpers__/cleanup.ts",
    "chars": 93,
    "preview": "import { beforeEach } from 'vitest';\n\nbeforeEach(() => {\n  document.body.innerHTML = '';\n});\n"
  },
  {
    "path": "packages/quill/test/unit/__helpers__/expect.ts",
    "chars": 1758,
    "preview": "import { expect } from 'vitest';\nimport { normalizeHTML } from './utils.js';\n\nconst sortAttributes = (element: HTMLEleme"
  },
  {
    "path": "packages/quill/test/unit/__helpers__/factory.ts",
    "chars": 1359,
    "preview": "import { Registry } from 'parchment';\nimport type { Attributor } from 'parchment';\n\nimport Block from '../../../src/blot"
  },
  {
    "path": "packages/quill/test/unit/__helpers__/utils.ts",
    "chars": 254,
    "preview": "export const sleep = (ms: number) =>\n  new Promise<void>((r) => {\n    setTimeout(() => {\n      r();\n    }, ms);\n  });\n\ne"
  },
  {
    "path": "packages/quill/test/unit/__helpers__/vitest.d.ts",
    "chars": 340,
    "preview": "import type { Assertion, AsymmetricMatchersContaining } from 'vitest';\n\ninterface CustomMatchers<R = unknown> {\n  toEqua"
  },
  {
    "path": "packages/quill/test/unit/blots/block-embed.spec.ts",
    "chars": 5296,
    "preview": "import { describe, expect, test } from 'vitest';\nimport {\n  createScroll as baseCreateScroll,\n  createRegistry,\n} from '"
  },
  {
    "path": "packages/quill/test/unit/blots/block.spec.ts",
    "chars": 3128,
    "preview": "import { describe, expect, test } from 'vitest';\nimport {\n  createScroll as baseCreateScroll,\n  createRegistry,\n} from '"
  },
  {
    "path": "packages/quill/test/unit/blots/inline.spec.ts",
    "chars": 1278,
    "preview": "import { describe, expect, test } from 'vitest';\nimport { createRegistry, createScroll } from '../__helpers__/factory.js"
  },
  {
    "path": "packages/quill/test/unit/blots/scroll.spec.ts",
    "chars": 3838,
    "preview": "import { describe, expect, test, vitest } from 'vitest';\nimport Emitter from '../../../src/core/emitter.js';\nimport Sele"
  },
  {
    "path": "packages/quill/test/unit/core/composition.spec.ts",
    "chars": 957,
    "preview": "import Emitter from '../../../src/core/emitter.js';\nimport Composition from '../../../src/core/composition.js';\nimport S"
  },
  {
    "path": "packages/quill/test/unit/core/editor.spec.ts",
    "chars": 47918,
    "preview": "import Delta from 'quill-delta';\nimport Editor from '../../../src/core/editor.js';\nimport Block from '../../../src/blots"
  },
  {
    "path": "packages/quill/test/unit/core/emitter.spec.ts",
    "chars": 937,
    "preview": "import { describe, expect, test } from 'vitest';\nimport Emitter from '../../../src/core/emitter.js';\nimport Quill from '"
  },
  {
    "path": "packages/quill/test/unit/core/quill.spec.ts",
    "chars": 42996,
    "preview": "import '../../../src/quill.js';\nimport Delta from 'quill-delta';\nimport { LeafBlot, Registry } from 'parchment';\nimport "
  },
  {
    "path": "packages/quill/test/unit/core/selection.spec.ts",
    "chars": 22073,
    "preview": "import Selection, { Range } from '../../../src/core/selection.js';\nimport Cursor from '../../../src/blots/cursor.js';\nim"
  },
  {
    "path": "packages/quill/test/unit/core/utils/createRegistryWithFormats.spec.ts",
    "chars": 2517,
    "preview": "import '../../../../src/quill.js';\nimport { describe, expect, test, vitest } from 'vitest';\nimport createRegistryWithFor"
  },
  {
    "path": "packages/quill/test/unit/formats/align.spec.ts",
    "chars": 1751,
    "preview": "import Delta from 'quill-delta';\nimport Editor from '../../../src/core/editor.js';\nimport { describe, test, expect } fro"
  },
  {
    "path": "packages/quill/test/unit/formats/bold.spec.ts",
    "chars": 758,
    "preview": "import { describe, expect, test } from 'vitest';\nimport {\n  createRegistry,\n  createScroll as baseCreateScroll,\n} from '"
  },
  {
    "path": "packages/quill/test/unit/formats/code.spec.ts",
    "chars": 10990,
    "preview": "import Delta from 'quill-delta';\nimport Editor from '../../../src/core/editor.js';\nimport {\n  createScroll as baseCreate"
  },
  {
    "path": "packages/quill/test/unit/formats/color.spec.ts",
    "chars": 1859,
    "preview": "import Delta from 'quill-delta';\nimport Editor from '../../../src/core/editor.js';\nimport {\n  createScroll as baseCreate"
  },
  {
    "path": "packages/quill/test/unit/formats/header.spec.ts",
    "chars": 1488,
    "preview": "import Delta from 'quill-delta';\nimport {\n  createScroll as baseCreateScroll,\n  createRegistry,\n} from '../__helpers__/f"
  },
  {
    "path": "packages/quill/test/unit/formats/indent.spec.ts",
    "chars": 1714,
    "preview": "import Delta from 'quill-delta';\nimport {\n  createScroll as baseCreateScroll,\n  createRegistry,\n} from '../__helpers__/f"
  },
  {
    "path": "packages/quill/test/unit/formats/link.spec.ts",
    "chars": 2879,
    "preview": "import Delta from 'quill-delta';\nimport {\n  createScroll as baseCreateScroll,\n  createRegistry,\n} from '../__helpers__/f"
  },
  {
    "path": "packages/quill/test/unit/formats/list.spec.ts",
    "chars": 10212,
    "preview": "import Delta from 'quill-delta';\nimport {\n  createScroll as baseCreateScroll,\n  createRegistry,\n} from '../__helpers__/f"
  },
  {
    "path": "packages/quill/test/unit/formats/script.spec.ts",
    "chars": 1205,
    "preview": "import Editor from '../../../src/core/editor.js';\nimport Script from '../../../src/formats/script.js';\nimport {\n  create"
  },
  {
    "path": "packages/quill/test/unit/formats/table.spec.ts",
    "chars": 8837,
    "preview": "import Delta from 'quill-delta';\nimport Editor from '../../../src/core/editor.js';\nimport {\n  createScroll as baseCreate"
  },
  {
    "path": "packages/quill/test/unit/modules/clipboard.spec.ts",
    "chars": 23540,
    "preview": "import Delta from 'quill-delta';\nimport { describe, expect, test, vitest } from 'vitest';\nimport Quill from '../../../sr"
  },
  {
    "path": "packages/quill/test/unit/modules/history.spec.ts",
    "chars": 9911,
    "preview": "import Delta from 'quill-delta';\nimport { describe, expect, test, vitest } from 'vitest';\nimport Quill from '../../../sr"
  },
  {
    "path": "packages/quill/test/unit/modules/keyboard.spec.ts",
    "chars": 2461,
    "preview": "import { describe, expect, test } from 'vitest';\nimport Keyboard, {\n  SHORTKEY,\n  normalize,\n} from '../../../src/module"
  },
  {
    "path": "packages/quill/test/unit/modules/normalizeExternalHTML/normalizers/googleDocs.spec.ts",
    "chars": 864,
    "preview": "import { describe, expect, test } from 'vitest';\nimport normalize from '../../../../../src/modules/normalizeExternalHTML"
  },
  {
    "path": "packages/quill/test/unit/modules/normalizeExternalHTML/normalizers/msWord.spec.ts",
    "chars": 1621,
    "preview": "import { describe, expect, test } from 'vitest';\nimport normalize from '../../../../../src/modules/normalizeExternalHTML"
  },
  {
    "path": "packages/quill/test/unit/modules/syntax.spec.ts",
    "chars": 11615,
    "preview": "import hljs from 'highlight.js';\nimport Delta from 'quill-delta';\nimport { afterAll, beforeAll, describe, expect, test }"
  },
  {
    "path": "packages/quill/test/unit/modules/table.spec.ts",
    "chars": 5860,
    "preview": "import Delta from 'quill-delta';\nimport Quill from '../../../src/core.js';\nimport { describe, expect, test } from 'vites"
  },
  {
    "path": "packages/quill/test/unit/modules/tableEmbed.spec.ts",
    "chars": 15927,
    "preview": "import Delta from 'quill-delta';\nimport { tableHandler } from '../../../src/modules/tableEmbed.js';\nimport { afterEach, "
  },
  {
    "path": "packages/quill/test/unit/modules/toolbar.spec.ts",
    "chars": 9889,
    "preview": "import { describe, expect, test } from 'vitest';\nimport Quill from '../../../src/core/quill.js';\nimport Toolbar, { addCo"
  },
  {
    "path": "packages/quill/test/unit/modules/uiNode.spec.ts",
    "chars": 1426,
    "preview": "import '../../../src/quill.js';\nimport { describe, expect, test } from 'vitest';\nimport UINode, {\n  TTL_FOR_VALID_SELECT"
  },
  {
    "path": "packages/quill/test/unit/theme/base/tooltip.spec.ts",
    "chars": 3848,
    "preview": "import { describe, expect, test } from 'vitest';\nimport Quill from '../../../../src/core.js';\nimport Video from '../../."
  },
  {
    "path": "packages/quill/test/unit/ui/picker.spec.ts",
    "chars": 6197,
    "preview": "import { describe, expect, test } from 'vitest';\nimport Picker from '../../../src/ui/picker.js';\n\ndescribe('Picker', () "
  },
  {
    "path": "packages/quill/test/unit/vitest.config.ts",
    "chars": 577,
    "preview": "import { defineConfig } from 'vitest/config';\nimport { resolve } from 'path';\n\nexport default defineConfig({\n  resolve: "
  },
  {
    "path": "packages/quill/tsconfig.json",
    "chars": 465,
    "preview": "{\n  \"ts-node\": {\n    \"compilerOptions\": {\n      \"esModuleInterop\": true\n    }\n  },\n  \"compilerOptions\": {\n    \"outDir\": "
  },
  {
    "path": "packages/quill/webpack.common.cjs",
    "chars": 1398,
    "preview": "/*eslint-env node*/\n\nconst { resolve } = require('path');\nconst MiniCssExtractPlugin = require('mini-css-extract-plugin'"
  },
  {
    "path": "packages/quill/webpack.config.cjs",
    "chars": 1139,
    "preview": "/*eslint-env node*/\n\nconst { BannerPlugin, DefinePlugin } = require('webpack');\nconst common = require('./webpack.common"
  },
  {
    "path": "packages/website/.eslintrc.json",
    "chars": 24,
    "preview": "{\n  \"extends\": \"next\"\n}\n"
  },
  {
    "path": "packages/website/.gitignore",
    "chars": 36,
    "preview": "node_modules\n\n.next\nout\ncertificates"
  },
  {
    "path": "packages/website/README.md",
    "chars": 69,
    "preview": "# Quill Official Website\n\n[https://quilljs.com](https://quilljs.com)\n"
  },
  {
    "path": "packages/website/content/blog/a-new-delta.mdx",
    "chars": 3439,
    "preview": "---\ntitle: A New Delta\ndate: 2014-09-29\n---\n\nPart of providing a complete API in Quill is providing events for when and "
  },
  {
    "path": "packages/website/content/blog/an-official-cdn-for-quill.mdx",
    "chars": 481,
    "preview": "---\ntitle: An Offical CDN for Quill\ndate: 2014-08-12\n---\n\nQuill now has an offical Content Distribution Network so you c"
  },
  {
    "path": "packages/website/content/blog/announcing-quill-1-0.mdx",
    "chars": 4509,
    "preview": "---\ntitle: Announcing Quill 1.0\ndate: 2016-09-06\n---\n\nQuill 1.0 has arrived. It was just two years ago that Quill made i"
  },
  {
    "path": "packages/website/content/blog/are-we-there-yet-to-1-0.mdx",
    "chars": 1993,
    "preview": "---\ntitle: Are We There Yet (to 1.0)?\ndate: 2016-03-14\n---\n\nWhen Quill laid out its [1.0 roadmap](/blog/the-road-to-1-0/"
  },
  {
    "path": "packages/website/content/blog/quill-1-0-beta-release.mdx",
    "chars": 1154,
    "preview": "---\ntitle: Quill 1.0 Beta Release\ndate: 2016-05-03\n---\n\nToday Quill is ready for its first beta preview of 1.0. This is "
  },
  {
    "path": "packages/website/content/blog/quill-1-0-release-candidate-released.mdx",
    "chars": 3927,
    "preview": "---\ntitle: Quill 1.0 Release Candidate... Released!\ndate: 2016-08-18\n---\n\nToday Quill enters its highly anticipated 1.0 "
  },
  {
    "path": "packages/website/content/blog/quill-v0-19-no-more-iframes.mdx",
    "chars": 3099,
    "preview": "---\ntitle: Quill v0.19 - No More Iframes\ndate: 2014-11-06\n---\n\nCustomizability is core to Quill's ethos and the new [v0."
  },
  {
    "path": "packages/website/content/blog/the-road-to-1-0.mdx",
    "chars": 4588,
    "preview": "---\ntitle: The Road to 1.0\ndate: 2015-09-15\n---\n\nQuill launched with the ambitious goal of becoming _the_ rich text edit"
  },
  {
    "path": "packages/website/content/blog/the-state-of-quill-and-2-0.mdx",
    "chars": 585,
    "preview": "---\ntitle: The State of Quill and 2.0\ndate: 2017-09-21\nexternal: https://medium.com/@jhchen/the-state-of-quill-and-2-0-f"
  },
  {
    "path": "packages/website/content/blog/upgrading-to-rich-text-deltas.mdx",
    "chars": 3528,
    "preview": "---\ntitle: Upgrading to Rich Text Deltas\ndate: 2014-10-19\n---\n\nThe new rich text type is now live and being used in Quil"
  },
  {
    "path": "packages/website/content/docs/api.mdx",
    "chars": 26177,
    "preview": "---\ntitle: API\n---\n\n<div className=\"table-of-contents\">\n  {data.api.map(({ hashes, title }) => (\n    <nav key={title}>\n "
  },
  {
    "path": "packages/website/content/docs/configuration.mdx",
    "chars": 3954,
    "preview": "---\ntitle: Configuration\n---\n\nQuill allows several ways to customize it to suit your needs. This section is dedicated to"
  },
  {
    "path": "packages/website/content/docs/customization/registries.mdx",
    "chars": 2468,
    "preview": "---\ntitle: Registries\n---\n\nRegistries allow multiple editors with different formats to coexist on the same page.\n\nIf you"
  },
  {
    "path": "packages/website/content/docs/customization/themes.mdx",
    "chars": 2031,
    "preview": "---\ntitle: Themes\n---\n\nThemes allow you to easily make your editor look good with minimal effort. Quill features two off"
  },
  {
    "path": "packages/website/content/docs/customization.mdx",
    "chars": 8303,
    "preview": "---\ntitle: Customization\n---\n\nQuill was designed with customization and extension in mind. This is achieved by implement"
  },
  {
    "path": "packages/website/content/docs/delta.mdx",
    "chars": 6217,
    "preview": "---\ntitle: Delta\n---\n\nDeltas are a simple, yet expressive format that can be used to describe Quill's contents and chang"
  },
  {
    "path": "packages/website/content/docs/formats.mdx",
    "chars": 3316,
    "preview": "---\ntitle: Formats\n---\n\nQuill supports a number of formats, both in UI controls and API calls.\n\nBy default, all formats "
  },
  {
    "path": "packages/website/content/docs/guides/building-a-custom-module.mdx",
    "chars": 6601,
    "preview": "---\ntitle: Building a Custom Module\n---\n\nQuill's core strength as an editor is its rich API and powerful customization c"
  },
  {
    "path": "packages/website/content/docs/guides/cloning-medium-with-parchment.js",
    "chars": 5609,
    "preview": "export const externalResources = [\n  'https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css',\n  'h"
  },
  {
    "path": "packages/website/content/docs/guides/cloning-medium-with-parchment.mdx",
    "chars": 27699,
    "preview": "---\ntitle: Cloning Medium with Parchment\n---\n\nTo provide a consistent editing experience, you need both consistent data "
  },
  {
    "path": "packages/website/content/docs/guides/designing-the-delta-format.mdx",
    "chars": 14999,
    "preview": "---\ntitle: Designing the Delta Format\n---\n\nRich text editors lack a specification to express its own contents. Until rec"
  },
  {
    "path": "packages/website/content/docs/installation.mdx",
    "chars": 3482,
    "preview": "---\ntitle: Installation\n---\n\nQuill comes ready to use in several convenient forms.\n\n## CDN\n\nA globally distributed and a"
  },
  {
    "path": "packages/website/content/docs/modules/clipboard.mdx",
    "chars": 2993,
    "preview": "---\ntitle: Clipboard Module\n---\n\nThe Clipboard handles copy, cut and paste between Quill and external applications. A se"
  },
  {
    "path": "packages/website/content/docs/modules/history.mdx",
    "chars": 1916,
    "preview": "---\ntitle: History Module\n---\n\nThe History module is responsible for handling undo and redo for Quill. It can be configu"
  },
  {
    "path": "packages/website/content/docs/modules/keyboard.mdx",
    "chars": 7461,
    "preview": "---\ntitle: Keyboard Module\n---\n\nThe Keyboard module enables custom behavior for keyboard events in particular contexts. "
  },
  {
    "path": "packages/website/content/docs/modules/syntax.mdx",
    "chars": 1817,
    "preview": "---\ntitle: Syntax Highlighter Module\n---\n\nThe Syntax Module enhances the Code Block format by automatically detecting an"
  },
  {
    "path": "packages/website/content/docs/modules/toolbar.mdx",
    "chars": 7502,
    "preview": "---\ntitle: Toolbar Module\n---\n\nThe Toolbar module allow users to easily format Quill's contents.\n\n<div className=\"quill-"
  },
  {
    "path": "packages/website/content/docs/modules.mdx",
    "chars": 1818,
    "preview": "---\ntitle: Modules\n---\n\nModules allow Quill's behavior and functionality to be customized. Several officially supported "
  },
  {
    "path": "packages/website/content/docs/quickstart.mdx",
    "chars": 1084,
    "preview": "---\ntitle: Quickstart\n---\n\nThe best way to get started is to try a simple example. Quill is initialized with a DOM eleme"
  },
  {
    "path": "packages/website/content/docs/upgrading-to-2-0.mdx",
    "chars": 3283,
    "preview": "---\ntitle: Upgrading to 2.0\n---\n\nQuill has been significantly modernized. Leveraging the latest browser-supported APIs, "
  },
  {
    "path": "packages/website/content/docs/why-quill.mdx",
    "chars": 3741,
    "preview": "---\ntitle: Why Quill\n---\n\nContent creation has been at the core to the web since its beginning. The `<textarea>` provide"
  },
  {
    "path": "packages/website/env.js",
    "chars": 869,
    "preview": "const { version, homepage } = require('./package.json');\n\nconst cdn = process.env.NEXT_PUBLIC_LOCAL_QUILL\n  ? `http://lo"
  },
  {
    "path": "packages/website/next.config.mjs",
    "chars": 2354,
    "preview": "import withMDX from '@next/mdx';\nimport env from './env.js';\n\n/** @type {import('next').NextConfig} */\nexport default wi"
  },
  {
    "path": "packages/website/package.json",
    "chars": 1221,
    "preview": "{\n  \"name\": \"website\",\n  \"version\": \"2.0.3\",\n  \"description\": \"Quill official website\",\n  \"private\": true,\n  \"homepage\":"
  },
  {
    "path": "packages/website/public/CNAME",
    "chars": 11,
    "preview": "quilljs.com"
  },
  {
    "path": "packages/website/public/robots.txt",
    "chars": 23,
    "preview": "User-agent: *\nAllow: /\n"
  },
  {
    "path": "packages/website/src/components/ActiveLink.jsx",
    "chars": 1178,
    "preview": "import { useRouter } from 'next/router';\nimport Link from 'next/link';\nimport React, { useState, useEffect, useCallback "
  },
  {
    "path": "packages/website/src/components/ClickOutsideHandler.jsx",
    "chars": 1419,
    "preview": "import { useEffect, useRef } from 'react';\n\nconst TOUCH_EVENT = { react: 'onTouchStart', native: 'touchstart' };\nconst M"
  },
  {
    "path": "packages/website/src/components/Editor.jsx",
    "chars": 1026,
    "preview": "import { useLayoutEffect, useRef } from 'react';\nimport { withoutSSR } from './NoSSR';\n\nconst Editor = ({\n  children,\n  "
  },
  {
    "path": "packages/website/src/components/GitHub.jsx",
    "chars": 1227,
    "preview": "import classNames from 'classnames';\nimport { useEffect, useState } from 'react';\nimport OctocatIcon from '../svg/octoca"
  }
]

// ... and 60 more files (download for full content)

About this extraction

This page contains the full source code of the slab/quill GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 260 files (968.9 KB), approximately 262.8k tokens, and a symbol index with 631 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!